Skip to content

Instantly share code, notes, and snippets.

@LowerDeez
Last active October 24, 2017 08:07
Show Gist options
  • Save LowerDeez/cc1c9a5c15de0febd9c4507160f21d20 to your computer and use it in GitHub Desktop.
Save LowerDeez/cc1c9a5c15de0febd9c4507160f21d20 to your computer and use it in GitHub Desktop.
Django. UpdateView and CreateView with inline formsets (and with django-extra-views)
{% extends 'base.html' %}
{% load static %}
{% load widget_tweaks %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col-md-10 col-md-offset-1">
<h2>
{% if action == 'update' %}
{% trans "Update article" %}
{% else %}
{% trans "Create new article" %}
{% endif %}
</h2>
<form method="post" class="form" name="create-form" novalidate enctype="multipart/form-data">
{%csrf_token%}
{% include 'partial/partial_form_fields.html' %} <!--For main form-->
{% for field in form.visible_fields %}
<div class="form-group">
{% for error in field.errors %}
<div class="alert alert-danger" role="alert">
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
<span class="sr-only">{% trans "Error:" %}</span>
{{ error }}
</div>
{% endfor %}
<label for="{{field.id_for_label}}">{{ field.label }}:</label>
{{ field|add_class:'form-control'}}
{% if field.help_text %}
<span class="help-block">{{ field.help_text|safe }}</span>
{% endif %}
</div>
{% endfor %}
<table class="table-responsive table">
{{ inlines.management_form }}
{% for form in inlines.forms %}
{{ form.id }} <!--IMPORTANT-->
{% if forloop.first %}
<thead>
<tr>
{% for field in form.visible_fields %}
<th>{{ field.label|capfirst }}</th>
{% endfor %}
</tr>
</thead>
{% endif %}
<tr class="formset_row">
{% for field in form.visible_fields %}
<td>
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field|add_class:'form-control' }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<div class="form-group">
<button type="submit" class="btn btn-success">
<span class="glyphicon glyphicon-ok"></span> Ok
</button>
<a href="{% url 'articles:home' %}" class="btn btn-default">
{% trans "Cancel" %}
</a>
</div>
</form>
</div>
</div>
{% endblock %}
{% block javascript %}
<script src="{% static 'articles/js/article.js' %}"></script>
<script src="{% static 'articles/js/jquery.formset.js' %}"></script>
<script type="text/javascript">
$('.formset_row').formset({
addText: 'Add new Image',
deleteText: "Remove",
prefix: 'images',
addCssClass: 'btn btn-primary',
deleteCssClass: 'btn btn-danger'
});
$('.formset_row > td > a:not(.btn)').css('display', 'none');
$('.formset_row > td > input[type=checkbox]').css('display', 'none');
$('.formset_row > td > label').css('display', 'none');
$('.formset_row > td > a').each(function(){
if (this.href !== "javascript:void(0)"){
$(this).after(function(){
return '<img style="height: 250px; width: auto;" src="' + this.href + '"/>'
})
}
})
</script>
{% endblock %}
from django.forms import inlineformset_factory
class ArticleForm(TranslatableModelForm):
title = TranslatedField()
short_description = TranslatedField(
label=_('Short description'),
# widget=forms.Textarea(attrs={'placeholder': _('Optional field')}),
required=False,
help_text=_("You can use html tags for short description. Max length: 1000 characters."))
content = TranslatedField(
label=_('Content'),
form_class=forms.CharField,
widget=TinyMCE,
required=True)
categories = TreeNodeMultipleChoiceField(
label=_("Category"),
queryset=Category.objects.all(),
required=False,
help_text=_('You can select few categories')
#level_indicator=mark_safe("&nbsp;&nbsp;&nbsp;&nbsp;"),
)
class Meta:
model = Article
fields = ['title',
'categories',
'short_description',
'content',
'status']
class ArticleImageForm(forms.ModelForm):
class Meta:
model = ArticleImage
fields = ['image']
labels = {
'image': 'Choose new Image'
}
ArticleImageFormset = inlineformset_factory(Article, ArticleImage, form=ArticleImageForm, extra=1)
def test_can_create_new_article(self):
"""
Test article creating
:return:
"""
self.client.login(username="test12345", password="supersecret123")
create_url = reverse('articles:create')
response = self.client.get(create_url)
self.assertEqual(response.status_code, 200)
data = {
'title': 'A new title',
'content': 'asd',
'status': Article.DRAFT,
'categories': [self.cat.pk, ],
# 'csrf_token': response.context_data['csrf_token'],
}
# management form information, needed because of the formset for images
management_form = response.context_data['article_line_image_form'].management_form # use context_data w jinja2
for i in 'TOTAL_FORMS', 'INITIAL_FORMS', 'MIN_NUM_FORMS', 'MAX_NUM_FORMS':
data['%s-%s' % (management_form.prefix, i)] = management_form[i].value()
for i in range(response.context_data['article_line_image_form'].total_form_count()):
# get form index 'i'
current_form = response.context_data['article_line_image_form'].forms[i]
# retrieve all the fields
for field_name in current_form.fields:
value = current_form[field_name].value()
data['%s-%s' % (current_form.prefix, field_name)] = value if value is not None else ''
response = self.client.post(create_url, data=data)
self.assertIsNotNone(Article.objects.get(translations__title='A new title'))
Template should work with both variants of views, but it is not for sure!
class ArticleBaseFormsetView(FormView):
def form_valid(self, form, article_line_image_form):
# saving images formset
with transaction.atomic():
self.object = form.save()
article_line_image_form.instance = self.object
article_line_image_form.save()
return super().form_valid(form)
def form_invalid(self, form, article_line_image_form):
return self.render_to_response(
self.get_context_data(form=form, article_line_image_form=article_line_image_form))
class ArticleCreateView(LoginRequiredMixin, ArticleBaseFormsetView, SuccessMessageMixin, TranslatableCreateView):
"""
Article create view
"""
model = Article
form_class = ArticleForm
# template_name = 'articles/create.html'
template_name = 'articles/jinja2/create.jinja.html'
view_url_name = 'articles:create'
def get_success_message(self, cleaned_data):
status = cleaned_data['status']
if status == Article.DRAFT:
return _('Your article was successfully created and added to drafts!')
elif status == Article.PUBLISHED:
return _('Your article was successfully created!')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['action'] = 'create'
context['inlines'] = ArticleImageFormset()
return context
def post(self, request, *args, **kwargs):
self.object = None
form = self.get_form(self.form_class)
article_line_image_form = ArticleImageFormset(request.POST, request.FILES)
if form.is_valid() and article_line_image_form.is_valid():
return self.form_valid(form, article_line_image_form)
return self.form_invalid(form, article_line_image_form)
class ArticleUpdateView(LoginRequiredMixin, ArticleBaseFormsetView, SuccessMessageMixin,
IsOwnerMixin, TranslatableSlugMixin, TranslatableUpdateView):
"""
Update view for Article
"""
form_class = ArticleForm
model = Article
# template_name = 'articles/create.html'
template_name = 'articles/jinja2/create.jinja.html'
view_url_name = 'articles:update'
def get_success_message(self, cleaned_data):
return _('Your article ({}) was updated!').format(cleaned_data['title'])
def get_context_data(self, **kwargs):
context = super(ArticleUpdateView, self).get_context_data(**kwargs)
context['action'] = 'update'
context['inlines'] = ArticleImageFormset(instance=self.object)
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form(self.form_class)
article_line_image_form = ArticleImageFormset(self.request.POST, self.request.FILES, instance=self.object)
if form.is_valid() and article_line_image_form.is_valid():
return self.form_valid(form, article_line_image_form)
return self.form_invalid(form, article_line_image_form)
# With using django-extra-views
from extra_views import InlineFormSet, CreateWithInlinesView, UpdateWithInlinesView
"""
################# Create and Update View with Django Extra Views package ###################
"""
class ArticleImageInlineFormSet(InlineFormSet):
model = ArticleImage
form_class = ArticleImageForm
extra = 1
can_delete = True
class ArticleCreateView2(LoginRequiredMixin, TranslatableModelFormMixin, CreateWithInlinesView):
model = Article
form_class = ArticleForm
inlines = [ArticleImageInlineFormSet]
template_name = 'articles/jinja2/create-DEV.jinja.html'
view_url_name = 'articles:create'
def get_success_url(self):
return self.object.get_absolute_url()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['article_action'] = 'create'
return context
class ArticleUpdateView2(LoginRequiredMixin, IsOwnerMixin, TranslatableModelFormMixin, UpdateWithInlinesView):
model = Article
form_class = ArticleForm
inlines = [ArticleImageInlineFormSet]
template_name = 'articles/jinja2/create-DEV.jinja.html'
view_url_name = 'articles:update'
slug_field = 'translations__slug'
def get_success_url(self):
return self.object.get_absolute_url()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['article_action'] = 'update'
return context
"""
#########################################################################################
"""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment