Skip to content

Instantly share code, notes, and snippets.

@LowerDeez
Created December 1, 2017 14:23
Show Gist options
  • Save LowerDeez/780920e718b02bdf28d2c08fcd82aa4f to your computer and use it in GitHub Desktop.
Save LowerDeez/780920e718b02bdf28d2c08fcd82aa4f to your computer and use it in GitHub Desktop.
Django. Nested Inlines
<!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>
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']
)
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)
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