mirror of
https://github.com/pretix/pretix.git
synced 2026-05-08 15:44:02 +00:00
tixlcontrol: Quota UI
This commit is contained in:
@@ -11,7 +11,7 @@ class VersionedBaseModelForm(BaseModelForm):
|
|||||||
if self.instance.pk is not None and isinstance(self.instance, Versionable):
|
if self.instance.pk is not None and isinstance(self.instance, Versionable):
|
||||||
if self.has_changed():
|
if self.has_changed():
|
||||||
self.instance = self.instance.clone()
|
self.instance = self.instance.clone()
|
||||||
super().save(commit)
|
return super().save(commit)
|
||||||
|
|
||||||
|
|
||||||
class VersionedModelForm(six.with_metaclass(ModelFormMetaclass, VersionedBaseModelForm)):
|
class VersionedModelForm(six.with_metaclass(ModelFormMetaclass, VersionedBaseModelForm)):
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
from itertools import product
|
from itertools import product
|
||||||
|
import copy
|
||||||
|
import uuid
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@@ -6,11 +8,54 @@ from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, Permis
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.template.defaultfilters import date as _date
|
from django.template.defaultfilters import date as _date
|
||||||
from django.core.validators import RegexValidator
|
from django.core.validators import RegexValidator
|
||||||
from versions.models import Versionable, VersionedForeignKey, VersionedManyToManyField
|
import six
|
||||||
|
from versions.models import Versionable as BaseVersionable
|
||||||
|
from versions.models import VersionedForeignKey, VersionedManyToManyField, get_utc_now
|
||||||
|
|
||||||
from tixlbase.types import VariationDict
|
from tixlbase.types import VariationDict
|
||||||
|
|
||||||
|
|
||||||
|
class Versionable(BaseVersionable):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
def clone_shallow(self, forced_version_date=None):
|
||||||
|
"""
|
||||||
|
This behaves like clone(), but misses all the Many2Many-relation-handling. This is
|
||||||
|
a performance optimization for cases in which we have to handle the Many2Many relations
|
||||||
|
by handy anyways.
|
||||||
|
"""
|
||||||
|
if not self.pk:
|
||||||
|
raise ValueError('Instance must be saved before it can be cloned')
|
||||||
|
|
||||||
|
if self.version_end_date:
|
||||||
|
raise ValueError('This is a historical item and can not be cloned.')
|
||||||
|
|
||||||
|
if forced_version_date:
|
||||||
|
if not self.version_start_date <= forced_version_date <= get_utc_now():
|
||||||
|
raise ValueError('The clone date must be between the version start date and now.')
|
||||||
|
else:
|
||||||
|
forced_version_date = get_utc_now()
|
||||||
|
|
||||||
|
earlier_version = self
|
||||||
|
|
||||||
|
later_version = copy.copy(earlier_version)
|
||||||
|
later_version.version_end_date = None
|
||||||
|
later_version.version_start_date = forced_version_date
|
||||||
|
|
||||||
|
# set earlier_version's ID to a new UUID so the clone (later_version) can
|
||||||
|
# get the old one -- this allows 'head' to always have the original
|
||||||
|
# id allowing us to get at all historic foreign key relationships
|
||||||
|
earlier_version.id = six.u(str(uuid.uuid4()))
|
||||||
|
earlier_version.version_end_date = forced_version_date
|
||||||
|
earlier_version.save()
|
||||||
|
|
||||||
|
later_version.save()
|
||||||
|
|
||||||
|
return later_version
|
||||||
|
|
||||||
|
|
||||||
class UserManager(BaseUserManager):
|
class UserManager(BaseUserManager):
|
||||||
"""
|
"""
|
||||||
This is the user manager for our custom user model. See the User
|
This is the user manager for our custom user model. See the User
|
||||||
@@ -614,18 +659,18 @@ class Item(Versionable):
|
|||||||
if use_cache and hasattr(self, '_get_all_variations_cache'):
|
if use_cache and hasattr(self, '_get_all_variations_cache'):
|
||||||
return self._get_all_variations_cache
|
return self._get_all_variations_cache
|
||||||
|
|
||||||
all_variations = self.variations.current.all().prefetch_related("values")
|
all_variations = self.variations.all().prefetch_related("values")
|
||||||
all_properties = self.properties.current.all().prefetch_related("values")
|
all_properties = self.properties.all().prefetch_related("values")
|
||||||
variations_cache = {}
|
variations_cache = {}
|
||||||
for var in all_variations:
|
for var in all_variations:
|
||||||
key = []
|
key = []
|
||||||
for v in var.values.current.all():
|
for v in var.values.all():
|
||||||
key.append((v.prop_id, v.identity))
|
key.append((v.prop_id, v.identity))
|
||||||
key = tuple(sorted(key))
|
key = tuple(sorted(key))
|
||||||
variations_cache[key] = var
|
variations_cache[key] = var
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
for comb in product(*[prop.values.current.all() for prop in all_properties]):
|
for comb in product(*[prop.values.all() for prop in all_properties]):
|
||||||
if len(comb) == 0:
|
if len(comb) == 0:
|
||||||
result.append(VariationDict())
|
result.append(VariationDict())
|
||||||
continue
|
continue
|
||||||
@@ -772,8 +817,8 @@ class Quota(Versionable):
|
|||||||
and no more than 100 of them will be VIP tickets (but 450 normal and 50
|
and no more than 100 of them will be VIP tickets (but 450 normal and 50
|
||||||
VIP tickets will be fine).
|
VIP tickets will be fine).
|
||||||
|
|
||||||
As always, a quota can not only be tied to an item, but also to a specific
|
As always, a quota can not only be tied to an item, but also to specific
|
||||||
variation. We follow the general rule here: If there are no variations
|
variations. We follow the general rule here: If there are no variations
|
||||||
speficied, the quota applies to all of them, and if there are variations
|
speficied, the quota applies to all of them, and if there are variations
|
||||||
specified, the quota applies to those.
|
specified, the quota applies to those.
|
||||||
|
|
||||||
@@ -797,10 +842,12 @@ class Quota(Versionable):
|
|||||||
items = VersionedManyToManyField(
|
items = VersionedManyToManyField(
|
||||||
Item,
|
Item,
|
||||||
verbose_name=_("Item"),
|
verbose_name=_("Item"),
|
||||||
|
related_name="quotas",
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
variations = VariationsField(
|
variations = VariationsField(
|
||||||
ItemVariation,
|
ItemVariation,
|
||||||
|
related_name="quotas",
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name=_("Variations")
|
verbose_name=_("Variations")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,14 +5,23 @@ $(function () {
|
|||||||
reorderMode: 'animate'
|
reorderMode: 'animate'
|
||||||
});
|
});
|
||||||
$(document).on("click", ".variations .variations-select-all", function (e) {
|
$(document).on("click", ".variations .variations-select-all", function (e) {
|
||||||
$(this).parent().parent().find("input[type=checkbox]").prop("checked", true);
|
$(this).parent().parent().find("input[type=checkbox]").prop("checked", true).change();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
$(document).on("click", ".variations .variations-select-none", function (e) {
|
$(document).on("click", ".variations .variations-select-none", function (e) {
|
||||||
$(this).parent().parent().find("input[type=checkbox]").prop("checked", false);
|
$(this).parent().parent().find("input[type=checkbox]").prop("checked", false).change();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
if ($(".items-on-quota").length) {
|
||||||
|
$(".items-on-quota .panel").each(function () {
|
||||||
|
var $panel = $(this);
|
||||||
|
$panel.toggleClass("panel-success", $panel.find("input:checked").length > 0);
|
||||||
|
$(this).find("input").change(function () {
|
||||||
|
$panel.toggleClass("panel-success", $panel.find("input:checked").length > 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
$('.collapse').collapse();
|
$('.collapse').collapse();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ td > .form-group > .checkbox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.panel .form-group:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.container ul.nav-pills {
|
.container ul.nav-pills {
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
}
|
}
|
||||||
@@ -56,6 +60,13 @@ td > .form-group > .checkbox {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table-quotas td ul {
|
||||||
|
list-style: none;
|
||||||
|
margin-left: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: @screen-sm-min) {
|
@media (min-width: @screen-sm-min) {
|
||||||
.variation-matrix > tbody > tr > td {
|
.variation-matrix > tbody > tr > td {
|
||||||
line-height: 34px;
|
line-height: 34px;
|
||||||
|
|||||||
@@ -8,6 +8,14 @@
|
|||||||
<li {% if "event.item.variations" == url_name %}class="active"{% endif %}><a href="{% url 'control:event.item.variations' organizer=request.event.organizer.slug event=request.event.slug item=item.identity %}">{% trans "Variations" %}</a></li>
|
<li {% if "event.item.variations" == url_name %}class="active"{% endif %}><a href="{% url 'control:event.item.variations' organizer=request.event.organizer.slug event=request.event.slug item=item.identity %}">{% trans "Variations" %}</a></li>
|
||||||
<li {% if "event.item.restrictions" == url_name %}class="active"{% endif %}><a href="{% url 'control:event.item.restrictions' organizer=request.event.organizer.slug event=request.event.slug item=item.identity %}">{% trans "Restrictions" %}</a></li>
|
<li {% if "event.item.restrictions" == url_name %}class="active"{% endif %}><a href="{% url 'control:event.item.restrictions' organizer=request.event.organizer.slug event=request.event.slug item=item.identity %}">{% trans "Restrictions" %}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
{% if item.identity and not item.quotas.exists %}
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
Please note, that your item will <strong>not</strong> be available for sale until you added your item
|
||||||
|
to an existing or newly created quota.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% block inside %}
|
{% block inside %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<h4 class="panel-title">
|
<h4 class="panel-title">
|
||||||
<a data-toggle="collapse" data-parent="#accordion"
|
<a data-toggle="collapse" data-parent="#accordion"
|
||||||
href="#collapse{{ f.prefix }}">
|
href="#collapse{{ f.prefix }}">
|
||||||
Test
|
{{ set.title }}
|
||||||
</a>
|
</a>
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<ul class="nav nav-pills">
|
<ul class="nav nav-pills">
|
||||||
<li {% if "event.items" == url_name %}class="active"{% endif %}><a href="{% url 'control:event.items' organizer=request.event.organizer.slug event=request.event.slug %}">{% trans "Items" %}</a></li>
|
<li {% if "event.items" == url_name %}class="active"{% endif %}><a href="{% url 'control:event.items' organizer=request.event.organizer.slug event=request.event.slug %}">{% trans "Items" %}</a></li>
|
||||||
|
<li {% if "event.items.quotas" in url_name %}class="active"{% endif %}><a href="{% url 'control:event.items.quotas' organizer=request.event.organizer.slug event=request.event.slug %}">{% trans "Quotas" %}</a></li>
|
||||||
<li {% if "event.items.categories" in url_name %}class="active"{% endif %}><a href="{% url 'control:event.items.categories' organizer=request.event.organizer.slug event=request.event.slug %}">{% trans "Categories" %}</a></li>
|
<li {% if "event.items.categories" in url_name %}class="active"{% endif %}><a href="{% url 'control:event.items.categories' organizer=request.event.organizer.slug event=request.event.slug %}">{% trans "Categories" %}</a></li>
|
||||||
<li {% if "event.items.properties" in url_name %}class="active"{% endif %}><a href="{% url 'control:event.items.properties' organizer=request.event.organizer.slug event=request.event.slug %}">{% trans "Properties" %}</a></li>
|
<li {% if "event.items.properties" in url_name %}class="active"{% endif %}><a href="{% url 'control:event.items.properties' organizer=request.event.organizer.slug event=request.event.slug %}">{% trans "Properties" %}</a></li>
|
||||||
<li {% if "event.items.questions" in url_name %}class="active"{% endif %}><a href="{% url 'control:event.items.questions' organizer=request.event.organizer.slug event=request.event.slug %}">{% trans "Questions" %}</a></li>
|
<li {% if "event.items.questions" in url_name %}class="active"{% endif %}><a href="{% url 'control:event.items.questions' organizer=request.event.organizer.slug event=request.event.slug %}">{% trans "Questions" %}</a></li>
|
||||||
<li {% if "event.items.quotas" in url_name %}class="active"{% endif %}><a href="{% url 'control:event.items.quotas' organizer=request.event.organizer.slug event=request.event.slug %}">{% trans "Quotas" %}</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
{% block inside %}
|
{% block inside %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -15,6 +15,35 @@
|
|||||||
<legend>{% trans "General information" %}</legend>
|
<legend>{% trans "General information" %}</legend>
|
||||||
{% bootstrap_field form.name layout="horizontal" %}
|
{% bootstrap_field form.name layout="horizontal" %}
|
||||||
{% bootstrap_field form.size layout="horizontal" %}
|
{% bootstrap_field form.size layout="horizontal" %}
|
||||||
|
<legend>{% trans "Items" %}</legend>
|
||||||
|
<p>
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
Please select the items or item variations this quota should be applied to. If you apply two
|
||||||
|
quotas to the same items, it will only be available if <strong>both</strong> quotas have capacity
|
||||||
|
left.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</p>
|
||||||
|
<div class="panel-group items-on-quota">
|
||||||
|
{% for item in items %}
|
||||||
|
<div class="panel panel-default" data-formset-form>
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h4 class="panel-title">
|
||||||
|
<a data-toggle="collapse" data-parent="#accordion"
|
||||||
|
href="#collapse{{ item.identity }}">
|
||||||
|
{{ item.name }}
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div id="collapse{{ item.identity }}" class="panel-collapse collapse in">
|
||||||
|
<div class="panel-body">
|
||||||
|
<div class="form-horizontal">
|
||||||
|
{% bootstrap_field item.field layout="horizontal" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<div class="form-group submit-group">
|
<div class="form-group submit-group">
|
||||||
<button type="submit" class="btn btn-primary btn-save">
|
<button type="submit" class="btn btn-primary btn-save">
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<p>
|
<p>
|
||||||
<a href="{% url "control:event.items.quotas.add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new quota" %}</a>
|
<a href="{% url "control:event.items.quotas.add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new quota" %}</a>
|
||||||
</p>
|
</p>
|
||||||
<table class="table table-hover">
|
<table class="table table-hover table-quotas">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans "Quota name" %}</th>
|
<th>{% trans "Quota name" %}</th>
|
||||||
@@ -33,7 +33,15 @@
|
|||||||
{% for q in quotas %}
|
{% for q in quotas %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong><a href="{% url "control:event.items.quotas.edit" organizer=request.event.organizer.slug event=request.event.slug quota=q.identity %}">{{ q.name }}</a></strong></td>
|
<td><strong><a href="{% url "control:event.items.quotas.edit" organizer=request.event.organizer.slug event=request.event.slug quota=q.identity %}">{{ q.name }}</a></strong></td>
|
||||||
<td></td>
|
<td>
|
||||||
|
<ul>
|
||||||
|
{% for item in q.items.all %}
|
||||||
|
<li><a href="
|
||||||
|
{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=item.identity %}"
|
||||||
|
>{{ item.name }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
<td>{{ q.size }}</td>
|
<td>{{ q.size }}</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td class="text-right"><a href="{% url "control:event.items.quotas.delete" organizer=request.event.organizer.slug event=request.event.slug quota=q.identity %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a></td>
|
<td class="text-right"><a href="{% url "control:event.items.quotas.delete" organizer=request.event.organizer.slug event=request.event.slug quota=q.identity %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a></td>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from django.forms.widgets import flatatt
|
|||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from tixlbase.forms import VersionedModelForm
|
from tixlbase.forms import VersionedModelForm
|
||||||
|
|
||||||
from tixlbase.models import ItemVariation, PropertyValue, Item
|
from tixlbase.models import ItemVariation, PropertyValue, Item
|
||||||
@@ -301,7 +301,7 @@ class VariationsField(forms.ModelMultipleChoiceField):
|
|||||||
if self.required and not value:
|
if self.required and not value:
|
||||||
raise ValidationError(self.error_messages['required'], code='required')
|
raise ValidationError(self.error_messages['required'], code='required')
|
||||||
elif not self.required and not value:
|
elif not self.required and not value:
|
||||||
return self.queryset.none()
|
return []
|
||||||
if not isinstance(value, (list, tuple)):
|
if not isinstance(value, (list, tuple)):
|
||||||
raise ValidationError(self.error_messages['list'], code='list')
|
raise ValidationError(self.error_messages['list'], code='list')
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
from itertools import product
|
from itertools import product
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
from django.forms import BooleanField, ModelForm
|
||||||
|
from django.utils.functional import cached_property
|
||||||
|
|
||||||
from django.views.generic import ListView
|
from django.views.generic import ListView
|
||||||
from django.views.generic.edit import CreateView, UpdateView, DeleteView
|
from django.views.generic.edit import CreateView, UpdateView, DeleteView
|
||||||
@@ -9,13 +11,14 @@ from django.core.urlresolvers import resolve, reverse
|
|||||||
from django.http import HttpResponseRedirect, HttpResponseForbidden
|
from django.http import HttpResponseRedirect, HttpResponseForbidden
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.forms.models import inlineformset_factory
|
from django.forms.models import inlineformset_factory
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from tixlbase.forms import VersionedModelForm
|
from tixlbase.forms import VersionedModelForm
|
||||||
|
|
||||||
from tixlbase.models import (
|
from tixlbase.models import (
|
||||||
Item, ItemCategory, Property, ItemVariation, PropertyValue, Question, Quota
|
Item, ItemCategory, Property, ItemVariation, PropertyValue, Question, Quota,
|
||||||
)
|
Versionable)
|
||||||
from tixlcontrol.permissions import EventPermissionRequiredMixin, event_permission_required
|
from tixlcontrol.permissions import EventPermissionRequiredMixin, event_permission_required
|
||||||
from tixlcontrol.views.forms import TolerantFormsetModelForm
|
from tixlcontrol.views.forms import TolerantFormsetModelForm, VariationsField
|
||||||
from tixlcontrol.signals import restriction_formset
|
from tixlcontrol.signals import restriction_formset
|
||||||
|
|
||||||
|
|
||||||
@@ -431,10 +434,45 @@ class QuotaList(ListView):
|
|||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return Quota.objects.current.filter(
|
return Quota.objects.current.filter(
|
||||||
event=self.request.event
|
event=self.request.event
|
||||||
)
|
).prefetch_related("items")
|
||||||
|
|
||||||
|
|
||||||
class QuotaForm(VersionedModelForm):
|
class QuotaForm(ModelForm):
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
items = kwargs['items']
|
||||||
|
del kwargs['items']
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
if hasattr(self, 'instance'):
|
||||||
|
active_items = set(self.instance.items.all())
|
||||||
|
active_variations = set(self.instance.variations.all())
|
||||||
|
else:
|
||||||
|
active_items = set()
|
||||||
|
active_variations = set()
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
if len(item.properties.all()) > 0:
|
||||||
|
self.fields['item_%s' % item.identity] = VariationsField(
|
||||||
|
item, label=_("Activate for"),
|
||||||
|
required=False,
|
||||||
|
initial=active_variations
|
||||||
|
)
|
||||||
|
self.fields['item_%s' % item.identity].set_item(item)
|
||||||
|
else:
|
||||||
|
self.fields['item_%s' % item.identity] = BooleanField(
|
||||||
|
label=_("Activate"),
|
||||||
|
required=False,
|
||||||
|
initial=(item in active_items)
|
||||||
|
)
|
||||||
|
|
||||||
|
def save(self, commit=True):
|
||||||
|
if self.instance.pk is not None and isinstance(self.instance, Versionable):
|
||||||
|
if self.has_changed():
|
||||||
|
self.instance = self.instance.clone_shallow()
|
||||||
|
# TODO: order_cache, lock_cache are emptied by that but you'll have
|
||||||
|
# to rebuild them anyway
|
||||||
|
return super().save(commit)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Quota
|
model = Quota
|
||||||
@@ -445,7 +483,53 @@ class QuotaForm(VersionedModelForm):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class QuotaCreate(EventPermissionRequiredMixin, CreateView):
|
class QuotaEditorMixin:
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def items(self) -> "List[Item]":
|
||||||
|
return list(self.request.event.items.all().prefetch_related("properties", "variations"))
|
||||||
|
|
||||||
|
def get_form(self, form_class):
|
||||||
|
if not hasattr(self, '_form'):
|
||||||
|
kwargs = self.get_form_kwargs()
|
||||||
|
kwargs['items'] = self.items
|
||||||
|
self._form = form_class(**kwargs)
|
||||||
|
return self._form
|
||||||
|
|
||||||
|
def get_context_data(self, *args, **kwargs) -> dict:
|
||||||
|
context = super().get_context_data(*args, **kwargs)
|
||||||
|
context['items'] = self.items
|
||||||
|
for item in context['items']:
|
||||||
|
item.field = self.get_form(QuotaForm)['item_%s' % item.identity]
|
||||||
|
return context
|
||||||
|
|
||||||
|
@transaction.atomic()
|
||||||
|
def form_valid(self, form):
|
||||||
|
res = super().form_valid(form)
|
||||||
|
# The following commented-out checks are not necessary as both self.object.items
|
||||||
|
# and self.object.variations can be expected empty due to the performance
|
||||||
|
# optimization of tixlbase.models.Versionable.clone_shallow()
|
||||||
|
# items = self.object.items.all()
|
||||||
|
# variations = self.object.variations.all()
|
||||||
|
for item in self.items:
|
||||||
|
field = form.fields['item_%s' % item.identity]
|
||||||
|
data = form.cleaned_data['item_%s' % item.identity]
|
||||||
|
if isinstance(field, VariationsField):
|
||||||
|
self.object.variations.add(data)
|
||||||
|
# for v in data:
|
||||||
|
# if v not in variations:
|
||||||
|
# self.object.variations.add(v)
|
||||||
|
# for v in variations:
|
||||||
|
# if v not in data:
|
||||||
|
# self.object.variations.remove(v)
|
||||||
|
if data: # and item not in items:
|
||||||
|
self.object.items.add(item)
|
||||||
|
# elif not data and item in items:
|
||||||
|
# self.object.items.remove(item)
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
class QuotaCreate(EventPermissionRequiredMixin, QuotaEditorMixin, CreateView):
|
||||||
model = Quota
|
model = Quota
|
||||||
form_class = QuotaForm
|
form_class = QuotaForm
|
||||||
template_name = 'tixlcontrol/items/quota.html'
|
template_name = 'tixlcontrol/items/quota.html'
|
||||||
@@ -463,7 +547,7 @@ class QuotaCreate(EventPermissionRequiredMixin, CreateView):
|
|||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
class QuotaUpdate(EventPermissionRequiredMixin, UpdateView):
|
class QuotaUpdate(EventPermissionRequiredMixin, QuotaEditorMixin, UpdateView):
|
||||||
model = Quota
|
model = Quota
|
||||||
form_class = QuotaForm
|
form_class = QuotaForm
|
||||||
template_name = 'tixlcontrol/items/quota.html'
|
template_name = 'tixlcontrol/items/quota.html'
|
||||||
|
|||||||
Reference in New Issue
Block a user