Created
December 1, 2017 14:23
-
-
Save LowerDeez/780920e718b02bdf28d2c08fcd82aa4f to your computer and use it in GitHub Desktop.
Django. Nested Inlines
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Title</title> | |
<script src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script> | |
</head> | |
<body> | |
<form action="{% url 'corn:create-view' %}" method="post" id="create_form" name="create_form"> | |
{% csrf_token %} | |
<h1>Культура:</h1> | |
{{ form.as_p }} | |
<hr> | |
<hr> {{ formset.management_form }} {{ formset.non_form_errors }} | |
<div id="items-form-container"> | |
{% for child_form in formset.forms %} | |
<div id="item-{{ forloop.counter0 }}"> | |
{{ child_form.id }} | |
<h2>Показатель</h2> | |
{{ child_form.as_p }} {% if child_form.nested %} {{ child_form.nested.management_form }} {{ child_form.nested.non_form_errors }} | |
<div id="nested-items-form-container"> | |
{% for nested_form in child_form.nested.forms %} | |
<div id="item-{{ forloop.counter0 }}" data-index="{{nested_form.id.auto_id}}"> | |
{{ nested_form.id }} | |
<h3>Процентное соотношение</h3> | |
{{ nested_form.as_p }} | |
</div> | |
{% endfor %} | |
</div> | |
<button class="add_index_nested_form">Add index nested form</button> {% endif %} | |
<hr> | |
<hr> | |
</div> | |
{% endfor %} | |
</div> | |
<button class="add_index_form">Add index form</button> | |
<br> | |
<br> | |
<input type="submit" value="Save"> | |
</form> | |
<!--Empty child form for Index model--> | |
<div id="empty_child_form" style="display: none"> | |
<div id="item-__prefix__"> | |
<h2>Показатель</h2> | |
{{ formset.empty_form.as_p }} {% if formset.empty_form.nested %} {{ formset.empty_form.nested.management_form }} {{ formset.empty_form.nested.non_form_errors }} | |
<!--TODO: Add indexes for nested-items-form-container for diffrent child form--> | |
<div id="nested-items-form-container"> | |
{% for nested_form in formset.empty_form.nested.forms %} | |
<div id="nested-item-{{ forloop.counter0 }}" data-index="{{nested_form.id.auto_id}}"> | |
{{ nested_form.id }} | |
<hr> | |
<h3>Процентное соотношение</h3> | |
{{ nested_form.as_p }} | |
</div> | |
{% endfor %} | |
</div> | |
<button class="add_index_nested_form">Add index nested form</button> {% endif %} | |
<hr> | |
<hr> | |
</div> | |
</div> | |
<!--Empty nested form for PercentWeight model for child forms--> | |
<div id="empty_nested_child_form" style="display: none"> | |
<div id="item-__prefix__"> | |
<h3>Процентное соотношение</h3> | |
{{ formset.empty_form.nested.empty_form.as_p }} | |
</div> | |
</div> | |
<script> | |
$(document).ready(function() { | |
$('.add_index_form').click(function(ev) { | |
ev.preventDefault(); | |
var count = $('#items-form-container').children().length; | |
var tmplMarkup = $('#empty_child_form').html(); | |
var compiledTmpl = tmplMarkup.replace(/__prefix__/g, count); | |
$('#items-form-container').append(compiledTmpl); | |
// update form count | |
$('#id_indexes-TOTAL_FORMS').attr('value', count + 1); | |
// some animate to scroll to view our new form | |
$('html, body').animate({ | |
scrollTop: $(this).offset().top - 400 | |
}, 800); | |
}); | |
}); | |
$(document).ready(function() { | |
$(document).on('click', '.add_index_nested_form', function(ev) { | |
ev.preventDefault(); | |
var nested_conteiner = $(this).siblings('#nested-items-form-container') | |
console.log(nested_conteiner) | |
var count = nested_conteiner.children().length; | |
var id = nested_conteiner.children().first().data('index'); | |
var index = parseInt(id.match(/indexes-([0-9]{1,})/)[1]); | |
var tmplMarkup = $('#empty_nested_child_form').html(); | |
var compiledTmpl = tmplMarkup | |
.replace(/item-__prefix__/g, "item-" + count) | |
.replace(/percents-__prefix__/g, "percents-" + count) | |
.replace(/indexes-__prefix__/g, "indexes-" + index); | |
nested_conteiner.append(compiledTmpl); | |
// update form count | |
$('#id_percent_weight-indexes-' + index + '-percents-TOTAL_FORMS').attr('value', count + 1); | |
// some animate to scroll to view our new form | |
$('html, body').animate({ | |
scrollTop: $(this).offset().top - 400 | |
}, 800); | |
}); | |
}); | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from django import forms | |
from django.forms.models import BaseInlineFormSet, inlineformset_factory | |
from .models.culter import Culture | |
from .models.index import Index, PercentWeight | |
class CultureForm(forms.ModelForm): | |
class Meta: | |
model = Culture | |
exclude = ['slug'] | |
class PercentWeightForm(forms.ModelForm): | |
increase = forms.TypedChoiceField( | |
coerce=lambda x: x == 'True', | |
choices=((False, '-'), (True, '+')), | |
) | |
class Meta: | |
model = PercentWeight | |
fields = '__all__' | |
PercentWeightFormSet = inlineformset_factory( | |
Index, | |
PercentWeight, | |
form=PercentWeightForm, | |
fields='__all__', | |
extra=2 | |
) | |
class BaseChildrenFormset(BaseInlineFormSet): | |
def add_fields(self, form, index): | |
super().add_fields(form, index) | |
# save the formset in the 'nested' property | |
form.nested = PercentWeightFormSet( | |
instance=form.instance, | |
data=form.data if form.is_bound else None, | |
files=form.files if form.is_bound else None, | |
prefix='percent_weight-%s-%s' % ( | |
form.prefix, | |
PercentWeightFormSet.get_default_prefix()) | |
) | |
def is_valid(self): | |
result = super().is_valid() | |
if self.is_bound: | |
for form in self.forms: | |
if hasattr(form, 'nested'): | |
result = result and form.nested.is_valid() | |
return result | |
def save(self, commit=True): | |
result = super().save(commit=commit) | |
for form in self.forms: | |
if hasattr(form, 'nested'): | |
if not self._should_delete_form(form): | |
form.nested.save(commit=commit) | |
return result | |
IndexFormSet = inlineformset_factory( | |
Culture, | |
Index, | |
formset=BaseChildrenFormset, | |
extra=1, | |
exclude=['slug'] | |
) | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from decimal import Decimal | |
from django.db import models | |
from django.urls import reverse | |
from django.utils.translation import ugettext_lazy as _ | |
from stem.core.models.sluggable import SluggableModel | |
class Culture(SluggableModel): | |
""" | |
Culture model | |
Attributes: | |
title(str): name of culture | |
marge(decimal): marge value of culture | |
basic_price(decimal): basic price of value | |
""" | |
title = models.CharField( | |
_("Название культуры"), | |
max_length=250, | |
) | |
basic_price = models.DecimalField( | |
_("Базовая цена"), | |
max_digits=10, | |
decimal_places=2 | |
) | |
marge = models.DecimalField( | |
_("Маржа"), | |
max_digits=10, | |
decimal_places=2 | |
) | |
class Meta: | |
verbose_name = _("Культура") | |
verbose_name_plural = _("Культуры") | |
def __str__(self) -> str: | |
return self.title | |
def get_absolute_url(self): | |
return reverse('corn:update-view', kwargs={'slug': self.slug}) | |
class Index(SluggableModel): | |
""" | |
Index model | |
Attributes: | |
culter(foreign key): culture | |
title(str): name of culture's index | |
description(str): description of culture's index | |
""" | |
culture = models.ForeignKey( | |
Culture, | |
verbose_name=_("Культура"), | |
on_delete=models.CASCADE, | |
related_name='indexes' | |
) | |
title = models.CharField( | |
_("Название"), | |
max_length=250, | |
help_text=_("Название показателя (например, белок, влага, натура).") | |
) | |
description = models.TextField( | |
_("Описание"), | |
blank=True, null=True, | |
) | |
class Meta: | |
verbose_name = _("Показатель") | |
verbose_name_plural = _("Показатели") | |
def __str__(self): | |
return self.title | |
class PercentWeight(BaseModel): | |
""" | |
PercentWeight model | |
Attributes: | |
index(foreign key): index | |
percent(float): percent value | |
increase(boolean): influence on price (if True - increase, else - decrease) | |
amount(decimal): additional price, which defines by percent value | |
""" | |
index = models.ForeignKey( | |
Index, | |
verbose_name=_("Показатель"), | |
on_delete=models.CASCADE, | |
related_name='percents' | |
) | |
percent = models.FloatField( | |
_("Процентное соотношение"), | |
) | |
increase = models.BooleanField( | |
_("Влияние (+/-) на цену"), | |
) | |
amount = models.DecimalField( | |
_("Сумма"), | |
max_digits=10, | |
decimal_places=2 | |
) | |
class Meta: | |
verbose_name = _("Процентное соотношение") | |
verbose_name_plural = _("Процентные соотношения") | |
def __str__(self): | |
return str(self.percent) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from django.db import transaction | |
from django.urls import reverse_lazy | |
from django.http import HttpResponse | |
from stem.core.views.base import CreateView, UpdateView, SLUG_REGEX, FormView | |
from ..forms import CultureForm, IndexFormSet | |
from ..models.culter import Culture | |
class CultureBaseEditView(FormView): | |
def form_valid(self, form, formset): | |
# saving images formset | |
with transaction.atomic(): | |
self.object = form.save() | |
formset.instance = self.object | |
formset.save() | |
return super().form_valid(form) | |
def form_invalid(self, form, formset): | |
return self.render_to_response(self.get_context_data(form=form, formset=formset)) | |
class CultureCreateView(CultureBaseEditView, CreateView): | |
name = 'create-view' | |
url_prefix = 'create/' | |
template_name = 'create.html' | |
model = Culture | |
form_class = CultureForm | |
def get_context_data(self, **kwargs): | |
context = super().get_context_data(**kwargs) | |
context['formset'] = IndexFormSet() | |
return context | |
def post(self, request, *args, **kwargs): | |
self.object = None | |
form = self.get_form(self.form_class) | |
formset = IndexFormSet(request.POST) | |
if form.is_valid() and formset.is_valid(): | |
print(form.cleaned_data) | |
print(formset.cleaned_data) | |
return self.form_valid(form, formset) | |
return self.form_invalid(form, formset) | |
class CultureUpdateView(CultureBaseEditView, UpdateView): | |
name = 'update-view' | |
url_prefix = 'update/' | |
url_regex_list = [SLUG_REGEX] | |
template_name = 'create.html' | |
model = Culture | |
form_class = CultureForm | |
def get_context_data(self, **kwargs): | |
context = super().get_context_data(**kwargs) | |
context['formset'] = IndexFormSet(instance=self.object) | |
return context | |
def post(self, request, *args, **kwargs): | |
self.object = self.get_object() | |
form = self.get_form(self.form_class) | |
formset = IndexFormSet(self.request.POST, self.request.FILES, instance=self.object) | |
if form.is_valid() and formset.is_valid(): | |
return self.form_valid(form, formset) | |
return self.form_invalid(form, formset) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment