forked from CGM_Public/pretix_original
Order change manager: Allow to add multiple products
This commit is contained in:
@@ -196,10 +196,6 @@ class OtherOperationsForm(forms.Form):
|
|||||||
|
|
||||||
|
|
||||||
class OrderPositionAddForm(forms.Form):
|
class OrderPositionAddForm(forms.Form):
|
||||||
do = forms.BooleanField(
|
|
||||||
label=_('Add a new product to the order'),
|
|
||||||
required=False
|
|
||||||
)
|
|
||||||
itemvar = forms.ChoiceField(
|
itemvar = forms.ChoiceField(
|
||||||
label=_('Product')
|
label=_('Product')
|
||||||
)
|
)
|
||||||
@@ -291,6 +287,28 @@ class OrderPositionAddForm(forms.Form):
|
|||||||
change_decimal_field(self.fields['price'], order.event.currency)
|
change_decimal_field(self.fields['price'], order.event.currency)
|
||||||
|
|
||||||
|
|
||||||
|
class OrderPositionAddFormset(forms.BaseFormSet):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.order = kwargs.pop('order', None)
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def _construct_form(self, i, **kwargs):
|
||||||
|
kwargs['order'] = self.order
|
||||||
|
return super()._construct_form(i, **kwargs)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def empty_form(self):
|
||||||
|
form = self.form(
|
||||||
|
auto_id=self.auto_id,
|
||||||
|
prefix=self.add_prefix('__prefix__'),
|
||||||
|
empty_permitted=True,
|
||||||
|
use_required_attribute=False,
|
||||||
|
order=self.order,
|
||||||
|
)
|
||||||
|
self.add_fields(form, None)
|
||||||
|
return form
|
||||||
|
|
||||||
|
|
||||||
class OrderPositionChangeForm(forms.Form):
|
class OrderPositionChangeForm(forms.Form):
|
||||||
itemvar = forms.ChoiceField(
|
itemvar = forms.ChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{% extends "pretixcontrol/event/base.html" %}
|
{% extends "pretixcontrol/event/base.html" %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load bootstrap3 %}
|
{% load bootstrap3 %}
|
||||||
|
{% load formset_tags %}
|
||||||
{% load money %}
|
{% load money %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% blocktrans trimmed with code=order.code %}
|
{% blocktrans trimmed with code=order.code %}
|
||||||
@@ -64,10 +65,10 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% if position.addon_to %}
|
{% if position.addon_to %}
|
||||||
– <em>
|
– <em>
|
||||||
{% blocktrans trimmed with posid=position.addon_to.positionid %}
|
{% blocktrans trimmed with posid=position.addon_to.positionid %}
|
||||||
Add-On to position #{{ posid }}
|
Add-On to position #{{ posid }}
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</em>
|
</em>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
@@ -173,33 +174,87 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<div class="panel panel-default items">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h3 class="panel-title">
|
<div class="formset" data-formset data-formset-prefix="{{ add_formset.prefix }}">
|
||||||
{% trans "Add product" %}
|
{{ add_formset.management_form }}
|
||||||
</h3>
|
{% bootstrap_formset_errors add_formset %}
|
||||||
</div>
|
<div data-formset-body>
|
||||||
<div class="panel-body">
|
{% for add_form in add_formset %}
|
||||||
<div class="form-horizontal">
|
<div class="panel panel-default items" data-formset-form>
|
||||||
{% bootstrap_form_errors add_form %}
|
<div class="panel-heading">
|
||||||
{% if add_form.custom_error %}
|
<h3 class="panel-title">
|
||||||
<div class="alert alert-danger">
|
<button type="button" class="btn btn-danger btn-xs pull-right flip"
|
||||||
{{ add_form.custom_error }}
|
data-formset-delete-button>
|
||||||
|
<i class="fa fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
{% trans "Add product" %}
|
||||||
|
<div class="sr-only">
|
||||||
|
{{ add_form.id }}
|
||||||
|
{% bootstrap_field add_form.DELETE form_group_class="" layout="inline" %}
|
||||||
|
</div>
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
<div class="panel-body">
|
||||||
{% bootstrap_field add_form.do layout="control" %}
|
<div class="form-horizontal">
|
||||||
{% bootstrap_field add_form.itemvar layout="control" %}
|
{% bootstrap_form_errors add_form %}
|
||||||
{% bootstrap_field add_form.price addon_after=request.event.currency layout="control" %}
|
{% if add_form.custom_error %}
|
||||||
{% if add_form.addon_to %}
|
<div class="alert alert-danger">
|
||||||
{% bootstrap_field add_form.addon_to layout="control" %}
|
{{ add_form.custom_error }}
|
||||||
{% endif %}
|
</div>
|
||||||
{% if add_form.subevent %}
|
{% endif %}
|
||||||
{% bootstrap_field add_form.subevent layout="control" %}
|
{% bootstrap_field add_form.itemvar layout="control" %}
|
||||||
{% endif %}
|
{% bootstrap_field add_form.price addon_after=request.event.currency layout="control" %}
|
||||||
{% bootstrap_field add_form.seat layout="control" %}
|
{% if add_form.addon_to %}
|
||||||
</div>
|
{% bootstrap_field add_form.addon_to layout="control" %}
|
||||||
|
{% endif %}
|
||||||
|
{% if add_form.subevent %}
|
||||||
|
{% bootstrap_field add_form.subevent layout="control" %}
|
||||||
|
{% endif %}
|
||||||
|
{% bootstrap_field add_form.seat layout="control" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
<script type="form-template" data-formset-empty-form>
|
||||||
|
{% escapescript %}
|
||||||
|
<div class="panel panel-default items" data-formset-form>
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h3 class="panel-title">
|
||||||
|
<button type="button" class="btn btn-danger btn-xs pull-right flip"
|
||||||
|
data-formset-delete-button>
|
||||||
|
<i class="fa fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
{% trans "Add product" %}
|
||||||
|
<div class="sr-only">
|
||||||
|
{{ add_formset.empty_form.id }}
|
||||||
|
{% bootstrap_field add_formset.empty_form.DELETE form_group_class="" layout="inline" %}
|
||||||
|
</div>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<div class="form-horizontal">
|
||||||
|
{% bootstrap_field add_formset.empty_form.itemvar layout="control" %}
|
||||||
|
{% bootstrap_field add_formset.empty_form.price addon_after=request.event.currency layout="control" %}
|
||||||
|
{% if add_formset.empty_form.addon_to %}
|
||||||
|
{% bootstrap_field add_formset.empty_form.addon_to layout="control" %}
|
||||||
|
{% endif %}
|
||||||
|
{% if add_formset.empty_form.subevent %}
|
||||||
|
{% bootstrap_field add_formset.empty_form.subevent layout="control" %}
|
||||||
|
{% endif %}
|
||||||
|
{% bootstrap_field add_formset.empty_form.seat layout="control" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endescapescript %}
|
||||||
|
</script>
|
||||||
|
<p>
|
||||||
|
<button type="button" class="btn btn-default" data-formset-add>
|
||||||
|
<i class="fa fa-plus"></i> {% trans "Add product" %}</button>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel panel-default items">
|
<div class="panel panel-default items">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<h3 class="panel-title">
|
<h3 class="panel-title">
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from django.db import transaction
|
|||||||
from django.db.models import (
|
from django.db.models import (
|
||||||
Count, IntegerField, OuterRef, Prefetch, ProtectedError, Q, Subquery, Sum,
|
Count, IntegerField, OuterRef, Prefetch, ProtectedError, Q, Subquery, Sum,
|
||||||
)
|
)
|
||||||
|
from django.forms import formset_factory
|
||||||
from django.http import (
|
from django.http import (
|
||||||
FileResponse, Http404, HttpResponseNotAllowed, JsonResponse,
|
FileResponse, Http404, HttpResponseNotAllowed, JsonResponse,
|
||||||
)
|
)
|
||||||
@@ -70,8 +71,8 @@ from pretix.control.forms.filter import (
|
|||||||
from pretix.control.forms.orders import (
|
from pretix.control.forms.orders import (
|
||||||
CancelForm, CommentForm, ConfirmPaymentForm, ExporterForm, ExtendForm,
|
CancelForm, CommentForm, ConfirmPaymentForm, ExporterForm, ExtendForm,
|
||||||
MarkPaidForm, OrderContactForm, OrderLocaleForm, OrderMailForm,
|
MarkPaidForm, OrderContactForm, OrderLocaleForm, OrderMailForm,
|
||||||
OrderPositionAddForm, OrderPositionChangeForm, OrderRefundForm,
|
OrderPositionAddForm, OrderPositionAddFormset, OrderPositionChangeForm,
|
||||||
OtherOperationsForm,
|
OrderRefundForm, OtherOperationsForm,
|
||||||
)
|
)
|
||||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||||
from pretix.control.views import PaginationMixin
|
from pretix.control.views import PaginationMixin
|
||||||
@@ -1188,9 +1189,16 @@ class OrderChange(OrderView):
|
|||||||
data=self.request.POST if self.request.method == "POST" else None)
|
data=self.request.POST if self.request.method == "POST" else None)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def add_form(self):
|
def add_formset(self):
|
||||||
return OrderPositionAddForm(prefix='add', order=self.order,
|
ff = formset_factory(
|
||||||
data=self.request.POST if self.request.method == "POST" else None)
|
OrderPositionAddForm, formset=OrderPositionAddFormset,
|
||||||
|
can_order=False, can_delete=True, extra=0
|
||||||
|
)
|
||||||
|
return ff(
|
||||||
|
prefix='add',
|
||||||
|
order=self.order,
|
||||||
|
data=self.request.POST if self.request.method == "POST" else None
|
||||||
|
)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def positions(self):
|
def positions(self):
|
||||||
@@ -1208,7 +1216,7 @@ class OrderChange(OrderView):
|
|||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
ctx = super().get_context_data(**kwargs)
|
ctx = super().get_context_data(**kwargs)
|
||||||
ctx['positions'] = self.positions
|
ctx['positions'] = self.positions
|
||||||
ctx['add_form'] = self.add_form
|
ctx['add_formset'] = self.add_formset
|
||||||
ctx['other_form'] = self.other_form
|
ctx['other_form'] = self.other_form
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
@@ -1221,16 +1229,17 @@ class OrderChange(OrderView):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def _process_add(self, ocm):
|
def _process_add(self, ocm):
|
||||||
if 'add-do' not in self.request.POST:
|
if not self.add_formset.is_valid():
|
||||||
return True
|
|
||||||
if not self.add_form.is_valid():
|
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
if self.add_form.cleaned_data['do']:
|
for f in self.add_formset.forms:
|
||||||
if '-' in self.add_form.cleaned_data['itemvar']:
|
if f in self.add_formset.deleted_forms or not f.has_changed():
|
||||||
itemid, varid = self.add_form.cleaned_data['itemvar'].split('-')
|
continue
|
||||||
|
|
||||||
|
if '-' in f.cleaned_data['itemvar']:
|
||||||
|
itemid, varid = f.cleaned_data['itemvar'].split('-')
|
||||||
else:
|
else:
|
||||||
itemid, varid = self.add_form.cleaned_data['itemvar'], None
|
itemid, varid = f.cleaned_data['itemvar'], None
|
||||||
|
|
||||||
item = Item.objects.get(pk=itemid, event=self.request.event)
|
item = Item.objects.get(pk=itemid, event=self.request.event)
|
||||||
if varid:
|
if varid:
|
||||||
@@ -1239,12 +1248,12 @@ class OrderChange(OrderView):
|
|||||||
variation = None
|
variation = None
|
||||||
try:
|
try:
|
||||||
ocm.add_position(item, variation,
|
ocm.add_position(item, variation,
|
||||||
self.add_form.cleaned_data['price'],
|
f.cleaned_data['price'],
|
||||||
self.add_form.cleaned_data.get('addon_to'),
|
f.cleaned_data.get('addon_to'),
|
||||||
self.add_form.cleaned_data.get('subevent'),
|
f.cleaned_data.get('subevent'),
|
||||||
self.add_form.cleaned_data.get('seat'))
|
f.cleaned_data.get('seat'))
|
||||||
except OrderError as e:
|
except OrderError as e:
|
||||||
self.add_form.custom_error = str(e)
|
f.custom_error = str(e)
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
@@ -1154,9 +1154,12 @@ class OrderChangeTests(SoupTest):
|
|||||||
self.client.post('/control/event/{}/{}/orders/{}/change'.format(
|
self.client.post('/control/event/{}/{}/orders/{}/change'.format(
|
||||||
self.event.organizer.slug, self.event.slug, self.order.code
|
self.event.organizer.slug, self.event.slug, self.order.code
|
||||||
), {
|
), {
|
||||||
|
'add-TOTAL_FORMS': '0',
|
||||||
|
'add-INITIAL_FORMS': '0',
|
||||||
|
'add-MIN_NUM_FORMS': '0',
|
||||||
|
'add-MAX_NUM_FORMS': '100',
|
||||||
'op-{}-itemvar'.format(self.op1.pk): str(self.shirt.pk),
|
'op-{}-itemvar'.format(self.op1.pk): str(self.shirt.pk),
|
||||||
'op-{}-price'.format(self.op1.pk): str('12.00'),
|
'op-{}-price'.format(self.op1.pk): str('12.00'),
|
||||||
'add-itemvar': str(self.ticket.pk),
|
|
||||||
})
|
})
|
||||||
self.op1.refresh_from_db()
|
self.op1.refresh_from_db()
|
||||||
self.order.refresh_from_db()
|
self.order.refresh_from_db()
|
||||||
@@ -1183,9 +1186,11 @@ class OrderChangeTests(SoupTest):
|
|||||||
self.client.post('/control/event/{}/{}/orders/{}/change'.format(
|
self.client.post('/control/event/{}/{}/orders/{}/change'.format(
|
||||||
self.event.organizer.slug, self.event.slug, self.order.code
|
self.event.organizer.slug, self.event.slug, self.order.code
|
||||||
), {
|
), {
|
||||||
|
'add-TOTAL_FORMS': '0',
|
||||||
|
'add-INITIAL_FORMS': '0',
|
||||||
|
'add-MIN_NUM_FORMS': '0',
|
||||||
|
'add-MAX_NUM_FORMS': '100',
|
||||||
'op-{}-subevent'.format(self.op1.pk): str(se2.pk),
|
'op-{}-subevent'.format(self.op1.pk): str(se2.pk),
|
||||||
'add-itemvar': str(self.ticket.pk),
|
|
||||||
'add-subevent': str(se1.pk),
|
|
||||||
})
|
})
|
||||||
self.op1.refresh_from_db()
|
self.op1.refresh_from_db()
|
||||||
self.op2.refresh_from_db()
|
self.op2.refresh_from_db()
|
||||||
@@ -1197,12 +1202,15 @@ class OrderChangeTests(SoupTest):
|
|||||||
self.client.post('/control/event/{}/{}/orders/{}/change'.format(
|
self.client.post('/control/event/{}/{}/orders/{}/change'.format(
|
||||||
self.event.organizer.slug, self.event.slug, self.order.code
|
self.event.organizer.slug, self.event.slug, self.order.code
|
||||||
), {
|
), {
|
||||||
|
'add-TOTAL_FORMS': '0',
|
||||||
|
'add-INITIAL_FORMS': '0',
|
||||||
|
'add-MIN_NUM_FORMS': '0',
|
||||||
|
'add-MAX_NUM_FORMS': '100',
|
||||||
'op-{}-operation'.format(self.op1.pk): 'price',
|
'op-{}-operation'.format(self.op1.pk): 'price',
|
||||||
'op-{}-itemvar'.format(self.op1.pk): str(self.ticket.pk),
|
'op-{}-itemvar'.format(self.op1.pk): str(self.ticket.pk),
|
||||||
'op-{}-price'.format(self.op1.pk): '24.00',
|
'op-{}-price'.format(self.op1.pk): '24.00',
|
||||||
'op-{}-operation'.format(self.op2.pk): '',
|
'op-{}-operation'.format(self.op2.pk): '',
|
||||||
'op-{}-itemvar'.format(self.op2.pk): str(self.ticket.pk),
|
'op-{}-itemvar'.format(self.op2.pk): str(self.ticket.pk),
|
||||||
'add-itemvar': str(self.ticket.pk),
|
|
||||||
})
|
})
|
||||||
self.op1.refresh_from_db()
|
self.op1.refresh_from_db()
|
||||||
self.order.refresh_from_db()
|
self.order.refresh_from_db()
|
||||||
@@ -1214,8 +1222,11 @@ class OrderChangeTests(SoupTest):
|
|||||||
self.client.post('/control/event/{}/{}/orders/{}/change'.format(
|
self.client.post('/control/event/{}/{}/orders/{}/change'.format(
|
||||||
self.event.organizer.slug, self.event.slug, self.order.code
|
self.event.organizer.slug, self.event.slug, self.order.code
|
||||||
), {
|
), {
|
||||||
|
'add-TOTAL_FORMS': '0',
|
||||||
|
'add-INITIAL_FORMS': '0',
|
||||||
|
'add-MIN_NUM_FORMS': '0',
|
||||||
|
'add-MAX_NUM_FORMS': '100',
|
||||||
'op-{}-operation_cancel'.format(self.op1.pk): 'on',
|
'op-{}-operation_cancel'.format(self.op1.pk): 'on',
|
||||||
'add-itemvar': str(self.ticket.pk),
|
|
||||||
})
|
})
|
||||||
self.order.refresh_from_db()
|
self.order.refresh_from_db()
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
@@ -1226,9 +1237,13 @@ class OrderChangeTests(SoupTest):
|
|||||||
self.client.post('/control/event/{}/{}/orders/{}/change'.format(
|
self.client.post('/control/event/{}/{}/orders/{}/change'.format(
|
||||||
self.event.organizer.slug, self.event.slug, self.order.code
|
self.event.organizer.slug, self.event.slug, self.order.code
|
||||||
), {
|
), {
|
||||||
'add-itemvar': str(self.shirt.pk),
|
'add-TOTAL_FORMS': '1',
|
||||||
'add-do': 'on',
|
'add-INITIAL_FORMS': '0',
|
||||||
'add-price': '14.00',
|
'add-MIN_NUM_FORMS': '0',
|
||||||
|
'add-MAX_NUM_FORMS': '100',
|
||||||
|
'add-0-itemvar': str(self.shirt.pk),
|
||||||
|
'add-0-do': 'on',
|
||||||
|
'add-0-price': '14.00',
|
||||||
})
|
})
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
assert self.order.positions.count() == 3
|
assert self.order.positions.count() == 3
|
||||||
@@ -1251,8 +1266,11 @@ class OrderChangeTests(SoupTest):
|
|||||||
self.client.post('/control/event/{}/{}/orders/{}/change'.format(
|
self.client.post('/control/event/{}/{}/orders/{}/change'.format(
|
||||||
self.event.organizer.slug, self.event.slug, self.order.code
|
self.event.organizer.slug, self.event.slug, self.order.code
|
||||||
), {
|
), {
|
||||||
|
'add-TOTAL_FORMS': '0',
|
||||||
|
'add-INITIAL_FORMS': '0',
|
||||||
|
'add-MIN_NUM_FORMS': '0',
|
||||||
|
'add-MAX_NUM_FORMS': '100',
|
||||||
'other-recalculate_taxes': 'on',
|
'other-recalculate_taxes': 'on',
|
||||||
'add-itemvar': str(self.ticket.pk),
|
|
||||||
'op-{}-operation'.format(self.op1.pk): '',
|
'op-{}-operation'.format(self.op1.pk): '',
|
||||||
'op-{}-operation'.format(self.op2.pk): '',
|
'op-{}-operation'.format(self.op2.pk): '',
|
||||||
'op-{}-itemvar'.format(self.op2.pk): str(self.ticket.pk),
|
'op-{}-itemvar'.format(self.op2.pk): str(self.ticket.pk),
|
||||||
|
|||||||
Reference in New Issue
Block a user