Django class based UpdateView with inline formsets - django

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??

Related

Get username or user as initial value on forms in django

I would like to get initial value on the following form but i get error : name 'user' is not defined. I don't know how to get user.username.
class InvoiceForm(ModelForm):
date = forms.DateField(widget=DateInput)
company = forms.CharField(initial={"company": user.username})
class Meta:
model = Invoice
fields = "__all__"
and the view to create the invoice is :
class InvoiceCreate(CreateView):
form_class = InvoiceForm
model = Invoice
template_name = "sales/invoice_form.html"
def get_success_url(self, request):
return reverse_lazy('invoice_details', kwargs={'pk' : self.object.pk})
def get(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
formset = InvoiceItemFormSet()
products = list(Product.objects.values())
return self.render_to_response(
self.get_context_data(form=form,formset=formset, products=products))
def post(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
formset = InvoiceItemFormSet(self.request.POST)
if (form.is_valid() and formset.is_valid()):
return self.form_valid(form, formset)
else:
return self.form_invalid(form, formset)
def form_valid(self, form, formset):
self.object = form.save()
formset.instance = self.object
formset.save()
try:
addmore = self.request.GET["addmore"]
if addmore == "True":
return redirect("update_invoice", pk=self.object.id)
except Exception as e:
pass
return HttpResponseRedirect(self.get_success_url())
def form_invalid(self, form, formset):
return self.render_to_response(self.get_context_data(form=form, formset=formset))
I would like to get initial value on the following form but i get error : name 'user' is not defined. I don't know how to get user.username.
You had that error because there is no user at that point in the class level definition:
company = forms.CharField(initial={"company": user.username})
change to:
company = forms.CharField()
then is views.py add this method to InvoiceCreate class:
def get_initial(self):
return {"company": self.request.user}

Django inline formset validation

I have the following models:
class CaseForm(ModelForm):
class Meta:
model = Case
fields = '__all__'
class ClientForm(ModelForm):
class Meta:
model = Client
fields = '_all__'
CaseClientFormset = inlineformset_factory(Case, Client, form=ClientForm,
extra=0, max_num=2, min_num=1,
validate_max=True,
validate_min=True)
When I fill in the top part of the form (caseform) it saves correctly. When I fill in the caseform and a clientform it saves correctly.
If I fill in the caseform but partially fill in the clientform no validation appears to take place, and a case is saved and the client information goes missing and is never saved.
class CaseCreateView(LoginRequiredMixin, AdviserExistenceMixin,
CreateView):
model = Case
form_class = CaseForm
def form_valid(self, form):
context = self.get_context_data()
clients = context['clients']
self.object = form.save()
if clients.is_valid():
clients.instance = self.object
clients.save()
return super(CaseCreateView, self).form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.request.POST:
context['clients'] = CaseClientFormset(self.request.POST)
else:
context['clients'] = CaseClientFormset()
context['navbar'] = str(self.model.__name__).lower()
return context
The other issue I have is that despite specifying min_num=1 and validate_min=True, I appear to be able to save a case without a clientform being filled in.
Any help would be appreciated.
Fixed replacing def form_valid with:
def form_valid(self, form):
context = self.get_context_data()
clients = context['clients']
if clients.is_valid():
self.object = form.save()
clients.instance = self.object
clients.save()
return super(CaseUpdateView, self).form_valid(form)
else:
return super(CaseUpdateView, self).form_invalid(form)

How do I prepopulate an inline formset that is purely ImageFields?

I have two models: Profile and CredentialImage.
I have implemented inlineformsets to allow the Profile to upload up to 5 maximum images(CredentialImage).
Currently, the images save to the database but they will not pre-populate when I revisit the update form, thus allowing the user to upload an unlimited amount of photos 5 at a time. Ideally, the same images saved to the database would pre-populate on the form as to limit the Profile to own and edit only 5 photos at a time.
From my understanding, I need to pass in the object to the page to pre-populate information, and I believe I'm doing just that by defining get_object.
Here are the two models:
class Profile(models.Model):
...
def get_absolute_url(self):
return reverse("profile:profile_detail",
kwargs={"username": self.user})
class CredentialImage(models.Model):
profile = models.ForeignKey(Profile, default=None,
related_name='credentialimage')
image = models.ImageField(upload_to=credential_photo_upload_loc)
The modelforms + initialization of the inlineformset_factory:
from django.forms.models import inlineformset_factory
class ProfileUpdateForm(ModelForm):
class Meta:
model = Profile
fields = [
"introduction",
"biography",
]
class CredentialImageForm(ModelForm):
image = ImageField()
class Meta:
model = CredentialImage
fields = ['image', ]
CredentialImageFormSet = inlineformset_factory(Profile,
CredentialImage, fields=('image', ), extra=4, max_num=4)
A class-based UpdateView for updating a Profile:
class ProfileUpdateView(LoginRequiredMixin, UpdateView):
form_class = ProfileUpdateForm
template_name = 'profile/profile_edit.html'
def get_context_data(self, **kwargs):
context = super(ProfileUpdateView, self).get_context_data(**kwargs)
if self.request.POST:
context['credential_image'] = CredentialImageFormSet(self.request.POST, self.request.FILES)
else:
context['credential_image'] = CredentialImageFormSet()
return context
def get_object(self, *args, **kwargs):
user_profile = self.kwargs.get('username')
obj = get_object_or_404(Profile, user__username=user_profile)
return obj
def form_valid(self, form):
data = self.get_context_data()
formset = data['credential_image']
if formset.is_valid():
self.object = form.save()
formset.instance = self.object
formset.save()
return redirect(self.object.get_absolute_url())
instance = form.save(commit=False)
instance.user = self.request.user
return super(ProfileUpdateView, self).form_valid(form)
You have to supply an instance in your get_context_data method.
def get_context_data(self, **kwargs):
context = super(ProfileUpdateView, self).get_context_data(**kwargs)
if self.request.POST:
context['credential_image'] = CredentialImageFormSet(self.request.POST, self.request.FILES, instance=self.object)
else:
context['credential_image'] = CredentialImageFormSet(instance=self.object)
return context

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 CreateView and UpdateView with multiple inline formsets

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)