Last active
October 24, 2017 08:07
-
-
Save LowerDeez/cc1c9a5c15de0febd9c4507160f21d20 to your computer and use it in GitHub Desktop.
Django. UpdateView and CreateView with inline formsets (and with django-extra-views)
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
{% 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 %} |
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.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(" "), | |
) | |
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) |
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
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')) |
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
Template should work with both variants of views, but it is not for sure! |
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
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) |
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
# 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