Django class-based CreateView and UpdateView with multiple inline formsets - django

I have been trying to do Django class-based CreateView and UpdateView with multiple inline formsets
CreateView works fine but UpdateView is not working properly, If anyone tried UpdateView with multiple inline formsets, anyone tried pls share updateview code snippet.
# models.py
from django.db import models
class Recipe(models.Model):
title = models.CharField(max_length=255)
description = models.TextField()
class Ingredient(models.Model):
recipe = models.ForeignKey(Recipe)
description = models.CharField(max_length=255)
class Instruction(models.Model):
recipe = models.ForeignKey(Recipe)
number = models.PositiveSmallIntegerField()
description = models.TextField()
# forms.py
from django.forms import ModelForm
from django.forms.models import inlineformset_factory
from .models import Recipe, Ingredient, Instruction
class RecipeForm(ModelForm):
class Meta:
model = Recipe
IngredientFormSet = inlineformset_factory(Recipe, Ingredient, extra=0)
InstructionFormSet = inlineformset_factory(Recipe, Instruction, extra=0)
# views.py
from django.http import HttpResponseRedirect
from django.views.generic.edit import CreateView, UpdateView
from django.shortcuts import get_object_or_404
from .forms import IngredientFormSet, InstructionFormSet, RecipeForm
from .models import Recipe
class RecipeCreateView(CreateView):
template_name = 'recipe_add.html'
model = Recipe
form_class = RecipeForm
success_url = '/account/dashboard/'
def get(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
ingredient_form = IngredientFormSet()
instruction_form = InstructionFormSet()
return self.render_to_response(
self.get_context_data(form=form,
ingredient_form=ingredient_form,
instruction_form=instruction_form))
def post(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
ingredient_form = IngredientFormSet(self.request.POST)
instruction_form = InstructionFormSet(self.request.POST)
if (form.is_valid() and ingredient_form.is_valid() and
instruction_form.is_valid()):
return self.form_valid(form, ingredient_form, instruction_form)
else:
return self.form_invalid(form, ingredient_form, instruction_form)
def form_valid(self, form, ingredient_form, instruction_form):
self.object = form.save()
ingredient_form.instance = self.object
ingredient_form.save()
instruction_form.instance = self.object
instruction_form.save()
return HttpResponseRedirect(self.get_success_url())
def form_invalid(self, form, ingredient_form, instruction_form):
return self.render_to_response(
self.get_context_data(form=form,
ingredient_form=ingredient_form,
instruction_form=instruction_form))
class RecipeUpdateView(UpdateView):
template_name = 'recipe_add.html'
model = Recipe
form_class = RecipeForm
def get_success_url(self):
self.success_url = '/account/dashboard/'
return self.success_url
def get_context_data(self, **kwargs):
context = super(RecipeUpdateView, self).get_context_data(**kwargs)
if self.request.POST:
context['form'] = RecipeForm(self.request.POST, instance=self.object)
context['ingredient_form'] = IngredientFormSet(self.request.POST, instance=self.object)
context['instruction_form'] = InstructionFormSet(self.request.POST, instance=self.object)
else:
context['form'] = RecipeForm(instance=self.object)
context['ingredient_form'] = IngredientFormSet(instance=self.object)
context['instruction_form'] = InstructionFormSet(instance=self.object)
return context
def post(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
ingredient_form = IngredientFormSet(self.request.POST)
instruction_form = InstructionFormSet(self.request.POST)
if (form.is_valid() and ingredient_form.is_valid() and
instruction_form.is_valid()):
return self.form_valid(form, ingredient_form, instruction_form)
else:
return self.form_invalid(form, ingredient_form, instruction_form)
def form_valid(self, form, ingredient_form, instruction_form):
self.object = form.save()
ingredient_form.instance = self.object
ingredient_form.save()
instruction_form.instance = self.object
instruction_form.save()
return HttpResponseRedirect(self.get_success_url())
def form_invalid(self, form, ingredient_form, instruction_form):
return self.render_to_response(
self.get_context_data(form=form,
ingredient_form=ingredient_form,
instruction_form=instruction_form))
Thanks in advance.

I don't think that the regular form of the updateview has to be added to the context because it is there anyways. A working Updateview with inlineformsets could be achieved less complicated. I based this on this Question
class RecipeUpdateView(UpdateView):
model = Recipe
form_class = RecipeUpdateForm
success_url = "/foo/"
def get_success_url(self):
self.success_url = '/account/dashboard/'
return self.success_url
def get_object(self):
return #your object
def get_context_data(self, **kwargs):
context = super(RecipeUpdateView, self).get_context_data(**kwargs)
if self.request.POST:
context['ingredient_form'] = IngredientFormSet(self.request.POST, instance=self.object)
context['instruction_form'] = InstructionFormSet(self.request.POST, instance=self.object)
else:
context['ingredient_form'] = IngredientFormSet(instance=self.object)
context['instruction_form'] = InstructionFormSet(instance=self.object)
return context
def form_valid(self, form):
context = self.get_context_data()
ingredient_form = context['ingredient_form']
instruction_form = context['instruction_form']
if ingredient_form.is_valid() and instruction_form.is_valid():
self.object = form.save()
ingredient_form.instance = self.object
ingredient_form.save()
instruction_form.instance = self.object
instruction_form.save()
return self.render_to_response(self.get_context_data(form=form))

My guess is that you can't do
self.object = None
on overwritten post method in a UpdateView. So, try
self.object = self.get_object()
instead, once you already have an object instance in this case.

I'm not sure if you've found an answer, but I have a working version of UpdateView documented in my answer found here:
UpdateView with inline formsets trying to save duplicate records?

So I recognize the models form this post. To get UpdateView working properly you are going to have to do at least two, maybe three things:
Update the self.object = self.get_object() -- after that, your ability to dynamically add should work.
To get the dynamic deletes updating properly, you will need to alter the template with form.DELETE (in two places, the ingredients and the instructions).
{{ form.description }}
{% if form.instance.pk %}{{ form.DELETE }}{% endif %}
Not sure it was necessary but I added can_delete to the factory too.
IngredientFormSet = inlineformset_factory(Recipe, Ingredient, fields=('description',), extra=3, can_delete=True)
InstructionFormSet = inlineformset_factory(Recipe, Instruction, fields=('number', 'description',), extra=1, can_delete=True)

Related

why django inlineformset_factory data not saving in class-based updateview?

inlineformset_factory fields is rendering in my html template. Others fields data saving except inlineformset_factory fields. here is my code:
models.py
class HeaderImage(models.Model):
header_image = models.ImageField()
post = models.ForeignKey(Post, on_delete=models.CASCADE)
froms.py
BlogImageFormSet = inlineformset_factory(Post, # parent form
HeaderImage, # inline-form
fields=['header_image'] ,can_delete=False, extra=1)
#views.py
class BlogUpdateView(PermissionRequiredMixin,UpdateView):
raise_exception = True
permission_required = "blog.change_post"
model = Post
template_name = "blog_update_post.html"
form_class = BlogPost
def get_success_url(self):
self.success_url = 'http://127.0.0.1:8000/blog'
return self.success_url
def get_context_data(self, **kwargs):
context = super(BlogUpdateView, self).get_context_data(**kwargs)
if self.request.POST:
context['form'] = BlogPost(self.request.POST, instance=self.object)
context['ingredient_form'] = BlogImageFormSet(self.request.POST, instance=self.object)
else:
context['form'] = BlogPost(instance=self.object)
context['ingredient_form'] =BlogImageFormSet(instance=self.object)
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form_class = self.get_form_class()
form = self.get_form(form_class)
ingredient_form = BlogImageFormSet(self.request.POST)
if (form.is_valid() and ingredient_form.is_valid()):
return self.form_valid(form, ingredient_form)
else:
return self.form_invalid(form, ingredient_form)
def form_valid(self, form, ingredient_form):
self.object = form.save()
ingredient_form.instance = self.object
ingredient_form.save()
return HttpResponseRedirect(self.get_success_url())
def form_invalid(self, form, ingredient_form):
return self.render_to_response(
self.get_context_data(form=form,
ingredient_form=ingredient_form,
))
#html
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{form.media}}
{{form.as_p}}
{{ingredient_form}}
<button class="btn btn-info">Update</button>
</form>
I am not understanding why data is not saving for inlineformset_factory field. see the picture for better understand
Can you try this?
class BlogUpdateView(PermissionRequiredMixin,UpdateView):
...
def get_context_data(self, **kwargs):
context = super(BlogUpdateView, self).get_context_data(**kwargs)
if self.request.POST:
context['form'] = BlogPost(self.request.POST, instance=self.object)
# this line below changed
context['ingredient_form'] = BlogImageFormSet(self.request.POST, self.request.FILES, instance=self.object)
else:
context['form'] = BlogPost(instance=self.object)
context['ingredient_form'] =BlogImageFormSet(instance=self.object)
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form_class = self.get_form_class()
form = self.get_form(form_class)
# this line below changed
ingredient_form = BlogImageFormSet(self.request.POST, self.request.FILES)
if (form.is_valid() and ingredient_form.is_valid()):
return self.form_valid(form, ingredient_form)
else:
return self.form_invalid(form, ingredient_form)
...
by data you mean the file you are uploading is not getting saved. The image field in your model needs a location to save the file
header_image = models.ImageField(upload_to='some_directory/')

Pagination in DetailView with context? [django]

I made comment and comments in Article(DetailView)
How can I make paginations this comments in DetailView.
I have tried many options but without succes...
VIEWS:
class ArticleDetailView(FormMixin, HitCountDetailView):
template_name = 'news/article_detail.html'
model = Article
count_hit = True
form_class = CommentForm
def get_success_url(self):
pk = self.kwargs["pk"]
slug = self.kwargs['slug']
return reverse_lazy('news:article_detail', kwargs={'pk': pk, 'slug': slug})
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['comments'] = ArticleComment.objects.filter(article_id=self.object.id).all()
context['comments_number'] = ArticleComment.objects.filter(article_id=self.object.id).count()
context['form'] = CommentForm()
return context
def form_valid(self, form):
form.instance.article = self.object
form.instance.author = self.request.user
form.save()
messages.success(request=self.request, message="KOMENTARZ ZOSTAƁ DODANY POPRAWNIE")
return super().form_valid(form)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
You can try like this.
from django.core.paginator import Paginator
qs = ArticleComment.objects.filter(article_id=self.object.id).all()
page = self.request.GET.get('page')
context['comments'] = Paginator(qs, 10).get_page(page)
return context

how can i filter comments by user and by article with get_context_data Django?

i want to filter the comments by user and by post. would you like to tell me how can i filter the comment using get_context_data. i am getting this error with that code 'NewsDetailView' object has no attribute 'get_object_or_404' how can i solve this issue?
models.py
class Comment(models.Model):
article = models.ForeignKey(Article, on_delete=models.CASCADE)
commentator = models.ForeignKey(User, on_delete=models.CASCADE)
content = models.TextField(max_length=200)
created_on = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.article.title
views.py
class NewsDetailView(LoginRequiredMixin, DetailView):
model = Article
form_class = CommentForm
template_name = 'news/news_detail.html'
def get(self, request, *args, **kwargs):
self.object = self.get_object_or_404(User, username=self.kwargs.get('username'))
self.object = self.get_object_or_404(Article, pk=self.kwargs.get('pk'))
return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['comments'] = self.object
context['form'] = CommentForm()
return context
def post(self, request, *args, **kwargs):
if request.method == 'POST':
form = CommentForm(request.POST)
form.instance.article = Article.objects.get(
pk=self.kwargs.get('pk'))
form.instance.commentator = self.request.user
form.save()
return redirect('news:news-detail', pk=self.kwargs.get('pk'))
else:
return redirect('news:news-detail', pk=self.kwargs.get('pk'))
The NewsDetailView indeed has no get_object_or_404, so using self.get_object_or_404 does not make any sense. Furthermore it is not necessary at all.
You can obtain the Comments that belong to a user with the username with self.object.comment_set.filter(commentator__username=username)
You can further use the FormMixin [Django-doc] to avoid some boilerplate code to construct the form and redirect to the success url:
from django.urls import reverse
from django.views.generic.edit import FormMixin
class NewsDetailView(LoginRequiredMixin, FormMixin, DetailView):
model = Article
form_class = CommentForm
template_name = 'news/news_detail.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['comments'] = self.object.comment_set.filter(
commentator__username=self.kwargs['username']
)
return context
def post(self, request, *args, **kwargs):
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
form.instance.article_id = self.kwargs['pk']
form.instance.commentator = self.request.user
form.save()
return super().form_valid(form)
def form_invalid(self, form):
self.object = self.get_object()
return super().form_invalid(form)
def get_success_url(self):
return reverse('news:news-detail', kwargs={'pk': self.kwargs['pk']})

UpdateView gives blank form in django inline formset

MY CreateView worked fine but when I try to update this form data, it gives blank form. No data was shown. Here is my views.py:
#require_authenticated_permission(
'member.add_person')
class PersonCreate( FormsetMixin, CreateView):
template_name = 'member/person_form.html'
model = Person
form_class = MemberForm
formset_class = PersonFormSet
#require_authenticated_permission(
'member.change_person')
class PersonUpdate( FormsetMixin, UpdateView):
template_name = 'member/person_form.html'
model = Person
form_class = MemberForm
formset_class = PersonFormSet
Here is my formset:
class MemberForm(ModelForm):
class Meta:
model = Person
exclude = ('user',)
def __init__(self, *args, **kwargs):
super(MemberForm, self).__init__(*args, **kwargs)
self.fields['name'].widget.attrs['placeholder'] = 'Your full name'
self.fields['tele_land'].label = 'Land phone'
self.fields['tele_cell'].label = 'Cell phone'
self.fields['passing_year'].label = 'Passing year'
self.fields['passing_year'].help_text = 'According to your session year'
def save(self, request, commit=True):
person = super().save(commit=False)
if not person.pk:
person.user = get_user(request)
if commit:
person.save()
self.save_m2m()
return person
class ChildrenForm(ModelForm):
class Meta:
model = Children
fields = '__all__'
PersonFormSet = inlineformset_factory(Person, Children, extra=0, min_num=1, fields=('child_name', 'child_birth_date','blood_group' ))
Url:
url(r'^person/create/$', views.PersonCreate.as_view(), name='person-create'),
url(r'^person/(?P<slug>[\w\-]+)/update/$', views.PersonUpdate.as_view(), name='person-update'),
My formset mixin:
class FormsetMixin(object):
object = None
def get(self, request, *args, **kwargs):
if getattr(self, 'is_update_view', False):
self.object = self.get_object()
form_class = self.get_form_class()
form = self.get_form(form_class)
formset_class = self.get_formset_class()
formset = self.get_formset(formset_class)
return self.render_to_response(self.get_context_data(form=form, formset=formset))
def post(self, request, *args, **kwargs):
if getattr(self, 'is_update_view', False):
self.object = self.get_object()
form_class = self.get_form_class()
form = self.get_form(form_class)
formset_class = self.get_formset_class()
formset = self.get_formset(formset_class)
if form.is_valid() and formset.is_valid():
return self.form_valid(form, formset)
else:
return self.form_invalid(form, formset)
def get_formset_class(self):
return self.formset_class
def get_formset(self, formset_class):
return formset_class(**self.get_formset_kwargs())
def get_formset_kwargs(self):
kwargs = {
'instance': self.object
}
if self.request.method in ('POST', 'PUT'):
kwargs.update({
'data': self.request.POST,
'files': self.request.FILES,
})
return kwargs
def form_valid(self, form, formset):
self.object = form.save(self.request)
formset.instance = self.object
formset.save()
return redirect(self.object.get_absolute_url())
def form_invalid(self, form, formset):
return self.render_to_response(self.get_context_data(form=form, formset=formset))
Why my UpdateView gives blank form?
Proably because getattr(self, 'is_update_view', False) always is false so you're not loading any self.object.
I have for similar situations used ModelFormSetView from django-extra-views. It's easy to use if you already understand Django class based views and will save you a lot of code to write and maintain yourself.
BTW you should implement get_queryset and prefetch the Children.
Use in UpdateView: is_update_view = True. It will be like
class PersonUpdate( FormsetMixin, UpdateView):
template_name = 'member/person_form.html'
model = Person
is_update_view = True
form_class = MemberForm
formset_class = PersonFormSet

Django class based UpdateView with inline formsets

I have a model PSG which has a relation to another model SubPSG. PSGs can have several SubPSGs. I'm using inline formsets to create SubPSGs as I create the PSGs. The CreateView works okay but the UpdateView is not working as expected. The UpdateView is as follows:
class PSGUpdate(GroupRequiredMixin, UpdateView):
model = PSG
form_class = PSGForm
template_name = "management/psg_update_form.html"
success_url = reverse_lazy('dashboard')
group_required = [u"admin", "manager"]
def get_context_data(self, **kwargs):
context = super(PSGUpdate, self).get_context_data(**kwargs)
subpsgs = SubPSG.objects.filter(psg=self.object).values()
sub_formset = inlineformset_factory(PSG, SubPSG, fields=('name',), can_delete=True,
extra=len(subpsgs), formset=BaseSubPSGFormset,)
context['formset'] = sub_formset(instance=self.object, initial=subpsgs, prefix='psg')
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
subpsgs = SubPSG.objects.filter(psg=self.object).values()
psg_form = PSGForm(request.POST, instance=self.object)
sub_formset = inlineformset_factory(PSG, SubPSG, fields=('name',), can_delete=True, extra=len(subpsgs), formset=BaseSubPSGFormset)
formset = sub_formset(self.request.POST, instance=self.object, prefix='psg')
if all([psg_form.is_valid(), formset.is_valid()]):
return self.form_valid(psg_form, formset)
else:
return self.form_invalid(psg_form, formset)
def form_valid(self, form, formset):
if all([formset.is_valid() and form.is_valid()]):
self.object = form.save()
instances = formset.save(commit=False)
for form in instances:
form.psg = self.object
form.save()
return HttpResponseRedirect(self.get_success_url())
else:
return super(PSGUpdate, self).form_valid(form)
When I try updating a SubPSG formset, a new object is created instead of the edit taking place. Moreover, duplicates of the other formsets are also created.
What I'm I doing wrong??