Django UpdateView creates duplicated formsets - django

I'm having problems when updating the entries of my products, discounts and inventory in the database, the inventories and discounts have the product id as a foreign key and I have defined two inline formsets to enter all the data in one single form
ProductMeta=forms.inlineformset_factory(Product,Inventory,InventoryForm,extra=0,can_delete=False)
DiscountMeta=forms.inlineformset_factory(Product,ProductDiscount,DiscountForm,extra=0,can_delete=False)
Something strange happens, both formsets work normally when creating using extra=1 in the formsets, but in the UpdateView I must use extra=0, otherwise the inventory and discount forms get duplicated, and both forms disappear from the creation view
views.py
class UpdateProduct(UpdateView):
model=Product
form_class =ProductForm
template_name = 'new_product.html'
title = "UPDATE PRODUCT"
def get_context_data(self, **kwargs):
context = super(UpdateProduct, self).get_context_data(**kwargs)
if self.request.method == 'POST':
context['product_meta_formset'] = ProductMeta(self.request.POST,instance=self.object)
context['discount_meta_formset'] = DiscountMeta(self.request.POST,instance=self.object)
else:
context['product_meta_formset'] = ProductMeta(instance=self.object)
context['discount_meta_formset'] = DiscountMeta(instance=self.object)
return context
def form_valid(self, form):
context=self.get_context_data()
productMeta=context['product_meta_formset']
discountMeta=context['discount_meta_formset']
if productMeta.is_valid() and discountMeta.is_valid():
self.object=form.save()
productMeta.instance=self.object
productMeta.save()
discountMeta.instance=self.object
discountMeta.save()
if not productMeta.is_valid():
print("productMeta invalid")
if not discountMeta.is_valid() :
print("discountMeta invalid")
return redirect(reverse("products:listProducts"))
For some reason Django considers that my productMeta and discountMeta are invalid since both prints are executed and obviously the changes are not saved in the database, for now I fix the inlineformset extra=0 problem using other inlineformsets, but I would like to know how to solve the invalid formset issue and correctly save the information.
NOTE: The formsets read the data from database and places them in the respective html input

Related

How to link two forms (wagtail form and django form) with a foreign key?

I'm using a django form in conjunction with a wagtail form. The django form will record some fields that will be on any form of this type: name, email and the wagtail form will record extra data defined by the form page creator specific to that instance.
I've overloaded the serve method to capture both sets of data and I can process both forms, but I'm stuck when trying to add the logic to relate the form contents to each other so that when one submission set is deleted, the other set will be as well. I think what I need is a foreign key.
The following code fails at form_submission.event_submission = a.id where I'd like to take the id from the wagtail form submission and add that as a foreign key to the django form, so that when the wagtail form portion is deleted, the other is deleted as well, and so that I can have a usable link between the two form submissions.
def serve(self, request, *args, **kwargs):
if request.method == 'POST':
form = EventSignupForm(request.POST)
wagtail_form = self.get_form(request.POST, request.FILES, page=self, user=request.user)
if form.is_valid() and wagtail_form.is_valid():
a = self.process_form_submission(wagtail_form)
form_submission = form.save(commit=False)
form_submission.event_submission = a.id
form_submission.save()
return self.render_landing_page(request, form_submission, *args, **kwargs)
else:
form = EventSignupForm()
wagtail_form = self.get_form(page=self, user=request.user)
context = self.get_context(request)
context['form'] = form
context['wagtail_form'] = wagtail_form
return TemplateResponse(
request,
self.get_template(request),
context
)
The form submission class and django model form looks like this. I think the ForeignKey I have in the Model isn't right, but I don't know. Any help?
class EventFormSubmission(AbstractFormSubmission):
cancellation_id = models.CharField(max_length=7)
class EventSignup(models.Model):
"""
A model to contain signup info for an event: name, email.
"""
event_submission = models.ForeignKey(EventFormSubmission, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
email = models.EmailField()
I solved this by adding the extra fields I wanted (name, email) to the EventFormSubmission and then using a regular django form (not a ModelForm) to collect these pieces of information.

Django conditional field display on form

I am trying to make a simple form, that conditionally shows the website input field based on the value of another database field (that is not on the form) status. For the sake of this process the status field is not editable by the user, just by the admin. Both fields are in the same table: profile.
After working at this for a while I copped-out and just did the conditional hiding and showing on the template. But, I realise this is the unsophisticated method, and would like to improve it.
What I tried so far in forms.py:
class WebsiteForm(forms.ModelForm):
class Meta:
model = Profile
fields = (
'e-mail',
'website',
)
if Profile.status == 'personal' :
exclude = ('website',)
This method in forms.py works effectively, in that I can conditionally show and hide the field if I use test comparitors in the if statement like:
if 1 == 1:
or
if 1 != 1:
But, I cannot get an effective test using the field Profile.status, the value in the field seems to be unavailable at the point the if test in forms.py is performed.
If I use print(Profile.status) I get the following output in the terminal: user__profile__status__isnull, so I think this means that I am at least testing the correct field in the database. Although I am also noting that this output only shows at initialisation of runserver, not when the form page is accessed.
One final point, the user is authenticated and editing their own record.
Any help very much appreciated.
After a lot of trial and even more error, and some wide-ranging searching, I found the answer via the documentation at https://ccbv.co.uk/.
Essentially the path I decided to take was to use a different form for the respective fields that I wanted to use (I'm sure there are other solutions out there that add or subtract fields from the views). This involved changing the form_class with get_form_class:
# views.py
class telephone_view(UpdateView):
template_name = 'account/telephone.html'
#no need to define "form_class" here
#form_class = TelephoneForm
success_url = '/accounts/telephone/'
def get_form_class(self):
if self.request.user.profile.status == 'managed':
messages.success(self.request, _('you got the managed form'))
return TelephoneFormExtended
else:
messages.success(self.request, _('you got the other form'))
return TelephoneFormLight
def get_object(self, queryset=None):
return Profile.get_or_create_for_user(self.request.user)
def form_valid(self, form):
messages.success(self.request, _('Your telephone setting was updated'))
return super(telephone_view, self).form_valid(form)
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(telephone_view, self).dispatch(*args, **kwargs)
After working it out for myself I also found this answer which does the same thing:
Updateview with dynamic form_class

Different ways to save form values to the database

I have started learning Django recently using a Udemy course. While going through the course instructor asked to save values from a Form to database.
After searching on the internet I figured out how to put form values into database and everything is working fine. Below is my views.py and forms.py files.
forms.py
class FormName(forms.Form):
fname = forms.CharField( label="First Name")
lname = forms.CharField(label="Last name:")
email = forms.EmailField()
verify_email = forms.EmailField(label='Verify Email:')
def clean(self):
all_clean_data = super().clean()
email = all_clean_data['email']
vmail = all_clean_data['verify_email']
if email != vmail:
raise forms.ValidationError("Check the emails")
views.py
def signup(request):
form = forms.FormName()
if request.method == 'POST':
form = forms.FormName(request.POST)
if form.is_valid():
post = User()
post.fname=request.POST.get('fname')
post.lname=request.POST.get('lname')
post.email=request.POST.get('email')
post.save()
return render(request,'third_app/greet.html')
else:
return render(request,'third_app/oops.html',{'form':form})
return render(request, 'third_app/signup.html',{'form':form})
Now coming to question, the instructor is using Meta class to store the form values to the database. Below are his forms.py and views.py files. I am curious about what the difference is between my method and the instructor's.
forms.py
class FormName(forms.ModelForm):
class Meta():
model = User
fields = 'all'
views.py
def signup(request):
form = forms.FormName()
if request.method == 'POST':
form = forms.FormName(request.POST)
if form.is_valid():
form.save(commit=True)
return render(request,'third_app/greet.html')
else:
return render(request,'third_app/oops.html',{'form':form})
return render(request, 'third_app/signup.html',{'form':form})
Thanks.
The Django docs explain this very well. It's what is known as a ModelForm:
If you’re building a database-driven app, chances are you’ll have forms that map closely to Django models. For instance, you might have a BlogComment model, and you want to create a form that lets people submit comments. In this case, it would be redundant to define the field types in your form, because you’ve already defined the fields in your model.
For this reason, Django provides a helper class that lets you create a Form class from a Django model.
So, to answer your question, your method uses a regular form (forms.Form) where you define the form fields, perform validation and then save each field individually in your view. When using form.ModelForm, field validation and saving is taken care of for you. Seeing as you have already defined what your fields are, the ModelForm uses this to perform the validation. The save() method conveniently saves each field to the database.

Django formset - validate input based on user cookie?

I have a Django form (TestForm) that contains a single field, quantity. I also have a Django formset (TestFormset) that contains multiple instances of my TestForm.
I want to write a custom clean() method for my TestFormset that validates that the sum of the quantities specified within my multiple TestForms is equal to a number, max_quantity, stored in a session variable.
I know that I am able to perform this validation within views.py (for example, after my formset is validated and cleaned, I could manually sum up the 'quantity' variables in my TestForms and check to ensure that they are equal to request.session['max_quantity'], throwing an error if any problems are found).
But ideally I'd love to move all my form validation logic into the clean() method of forms.py. However, I can't figure out how to pass an external value into my Formset that is not linked to one of its individual forms.
Is this possible to do?
forms.py
from django.forms import BaseFormSet
class TestForm(forms.Form):
quantity = forms.IntegerField()
class BaseTestFormset(BaseFormset):
def clean(self):
if any(self.errors):
# Don't bother validating the formset unless each form is valid on its own
return
quantity = 0
for form in self.forms:
quantity += form.cleaned_data['quantity']
# IF QUANTITY IS NOT EQUAL TO MAX_QUANTITY, THROW AN ERROR...
# ...BUT HOW DO WE GET THE MAX_QUANTITY INTO THIS FUNCTION?
views.py
from .forms import TestForm, BaseTestFormset
def serve_form(request):
TestFormSet = formset_factory(TestForm, formset=BaseTestFormset)
if request.method == 'POST':
formset = TestFormSet(request.POST)
# This method should check to ensure that the sum of quantities within our formsets does not exceed max_quantity
if formset.is_valid():
# Proceed to take action
else:
# Sample initial data
formset = TestFormSet(initial=[{'quantity': 5}, {'quantity': 7}])
# I CAN PASS MAX_QUANTITY INTO THE TEMPLATE... BUT HOW DO I GET IT INTO THE FORMSET VALIDATION METHOD?
return render(request, 'template.html', {'formset': formset, 'max_quantity': request.session['max_quantity']}
As with forms, if you want access to something in a method you need to pass it in somewhere. You can do that in the initialiser if you like:
class BaseTestFormset(forms.BaseFormSet):
def __init__(self, *args, **kwargs):
self.max_quantity = kwargs.pop('max_quantity', None)
super(BaseTestFormset, self).__init__(*args, **kwargs)
def clean(self):
...
if quantity > self.max_quantity:
...
and in the view:
if request.method == 'POST':
formset = TestFormSet(request.POST, max_quantity=request.session['max_quantity'])

Saving inlineformset in Django class-based views (CBV)

So I'm in the process of working on a web application that has implemented security questions into it's registration process. Because of the way my models are setup and the fact that I am trying to use Django's Class based views (CBV), I've had a bit of problems getting this all to integrate cleanly. Here are what my models look like:
Model.py
class AcctSecurityQuestions(models.Model):
class Meta:
db_table = 'security_questions'
id = models.AutoField(primary_key=True)
question = models.CharField(max_length = 250, null=False)
def __unicode__(self):
return u'%s' % self.question
class AcctUser(AbstractBaseUser, PermissionsMixin):
...
user_questions = models.ManyToManyField(AcctSecurityQuestions, through='SecurityQuestionsInter')
...
class SecurityQuestionsInter(models.Model):
class Meta:
db_table = 'security_questions_inter'
acct_user = models.ForeignKey(AcctUser)
security_questions = models.ForeignKey(AcctSecurityQuestions, verbose_name="Security Question")
answer = models.CharField(max_length=128, null=False)
Here is what my current view looks like:
View.py
class AcctRegistration(CreateView):
template_name = 'registration/registration_form.html'
disallowed_url_name = 'registration_disallowed'
model = AcctUser
backend_path = 'registration.backends.default.DefaultBackend'
form_class = AcctRegistrationForm
success_url = 'registration_complete'
def form_valid(self, form):
context = self.get_context_data()
securityquestion_form = context['formset']
if securityquestion_form.is_valid():
self.object = form.save()
securityquestion_form.instance = self.object
securityquestion_form.save()
return HttpResponseRedirect(self.get_success_url())
else:
return self.render_to_response(self.get_context_data(form=form))
def get_context_data(self, **kwargs):
ctx = super(AcctRegistration, self).get_context_data(**kwargs)
if self.request.POST:
ctx['formset'] = SecurityQuestionsInLineFormSet(self.request.POST, instance=self.object)
ctx['formset'].full_clean()
else:
ctx['formset'] = SecurityQuestionsInLineFormSet(instance=self.object)
return ctx
And for giggles and completeness here is what my form looks like:
Forms.py
class AcctRegistrationForm(ModelForm):
password1 = CharField(widget=PasswordInput(attrs=attrs_dict, render_value=False),
label="Password")
password2 = CharField(widget=PasswordInput(attrs=attrs_dict, render_value=False),
label="Password (again)")
class Meta:
model = AcctUser
...
def clean(self):
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
if self.cleaned_data['password1'] != self.cleaned_data['password2']:
raise ValidationError(_("The two password fields didn't match."))
return self.cleaned_data
SecurityQuestionsInLineFormSet = inlineformset_factory(AcctUser,
SecurityQuestionsInter,
extra=2,
max_num=2,
can_delete=False
)
This post helped me a lot, however in the most recent comments of the chosen answer, its mentioned that formset data should be integrated into the form in the overidden get and post methods:
django class-based views with inline model-form or formset
If I am overiding the get and post how would I add in my data from my formset? And what would I call to loop over the formset data?
Inline formsets are handy when you already have the user object in the database. Then, when you initialize, it'll automatically preload the right security questions, etc. But for creation, a normal model formset is probably best, and one that doesn't include the field on the through table that ties back to the user. Then you can create the user and manually set the user field on the created through table.
Here's how I would do this using a just a model formset:
forms.py:
SecurityQuestionsFormSet = modelformset_factory(SecurityQuestionsInter,
fields=('security_questions', 'answer'),
extra=2,
max_num=2,
can_delete=False,
)
views.py:
class AcctRegistration(CreateView):
# class data like form name as usual
def form_valid(self):
# override the ModelFormMixin definition so you don't save twice
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))
def get(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
formset = SecurityQuestionsFormSet(queryset=SecurityQuestionsInter.objects.none())
return self.render_to_response(self.get_context_data(form=form, formset=formset))
def post(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
formset = SecurityQuestionsFormSet(request.POST)
form_valid = form.is_valid()
formset_valid = formset.is_valid()
if form_valid and formset_valid:
self.object = form.save()
security_questions = formset.save(commit=False)
for security_question in security_questions:
security_question.acct_user = self.object
security_question.save()
formset.save_m2m()
return self.form_valid()
else:
return self.form_invalid(form, formset)
Regarding some questions in the comments about why this works the way it does:
I don't quite understand why we needed the queryset
The queryset defines the initial editable scope of objects for the formset. It's the set of instances to be bound to each form within the queryset, similar to the instance parameter of an individual form. Then, if the size of the queryset doesn't exceed the max_num parameter, it'll add extra unbound forms up to max_num or the specified number of extras. Specifying the empty queryset means we've said that we don't want to edit any of the model instances, we just want to create new data.
If you inspect the HTML of the unsubmitted form for the version that uses the default queryset, you'll see hidden inputs giving the IDs of the intermediary rows - plus you'll see the chosen question and answer displayed in the non-hidden inputs.
It's arguably confusing that forms default to being unbound (unless you specify an instance) while formsets default to being bound to the entire table (unless you specify otherwise). It certainly threw me off for a while, as the comments show. But formsets are inherently plural in ways that a single form aren't, so there's that.
Limiting the queryset is one of the things that inline formsets do.
or how the formset knew it was related until we set the acct_user for the formset. Why didn't we use the instance parameter
The formset actually never knows that it's related. Eventually the SecurityQuestionsInter objects do, once we set that model field.
Basically, the HTML form passes in the values of all its fields in the POST data - the two passwords, plus the IDs of two security question selections and the user's answers, plus maybe anything else that wasn't relevant to this question. Each of the Python objects we create (form and formset) can tell based on the field ids and the formset prefix (default values work fine here, with multiple formsets in one page it gets more complicated) which parts of the POST data are its responsibility. form handles the passwords but knows nothing about the security questions. formset handles the two security questions, but knows nothing about the passwords (or, by implication, the user). Internally, formset creates two forms, each of which handles one question/answer pair - again, they rely on numbering in the ids to tell what parts of the POST data they handle.
It's the view that ties the two together. None of the forms know about how they relate, but the view does.
Inline formsets have various special behavior for tracking such a relation, and after some more code review I think there is a way to use them here without needing to save the user before validating the security Q/A pairs - they do build an internal queryset that filters to the instance, but it doesn't look like they actually need to evaluate that queryset for validation. The main part that's throwing me off from just saying you can use them instead and just pass in an uncommitted user object (i.e. the return value of form.save(commit=False)) as the instance argument, or None if the user form is not valid is that I'm not 100% sure it would do the right thing in the second case. It might be worth testing if you find that approach clearer - set up your inline formset as you initially had it, initialize the formset in get with no arguments, then leave the final saving behavior in form_valid after all:
def form_valid(self, form, formset):
# commit the uncommitted version set in post
self.object.save()
form.save_m2m()
formset.save()
return HttpResponseRedirect(self.get_success_url())
def post(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
if form.is_valid():
self.object = form.save(commit=False)
# passing in None as the instance if the user form is not valid
formset = SecurityQuestionsInLineFormSet(request.POST, instance=self.object)
if form.is_valid() and formset.is_valid():
return self.form_valid(form, formset)
else:
return self.form_invalid(form, formset)
If that works as desired when the form is not valid, I may have talked myself into that version being better. Behind the scenes it's just doing what the non-inline version does, but more of the processing is hidden. It also more closely parallels the implementation of the various generic mixins in the first place - although you could move the saving behavior into form_valid with the non-inline version too.