ModelForm with ImageFields, not clearing properly if validation error - django

First the code.
The ModelForm (im1 and im2 are models.ImageField):
class TestForm(forms.ModelForm):
checkme = forms.BooleanField(required=True)
class Meta:
model = UserProfile
fields = ('im1', 'im2')
The view:
def test(request):
profile = request.user.get_profile()
form = TestForm(instance=profile)
if request.method == "POST":
form = TestForm(request.POST, request.FILES, instance=profile)
if form.is_valid():
form.save()
return render(request, 'test.html', {'form':form})
The template:
<html>
<head>
<title>Test</title>
</head>
<body>
<form method="post" enctype="multipart/form-data">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="submit" />
</form>
</body>
</html>
The problems:
If im1 contains a valid image, and I check the clear checkbox next to it but don't check checkme and submit, the form comes back with an error saying that checkme is required. Although the form returns with the error, it appears as if im1 has been cleared. In reality it has not because if I reload the form im1 shows back up with its file and clear checkbox.
My question is how can I fix this? Is it something I am doing or is this something to do with django?

Django is acting exactly as it should.
If the request is a POST request, then your form is bound to the data from request.POST and request.FILES. instance=profile is simply telling the form what particular object to save to if all validation passes. Even if your form isn't valid, it's still bound to the data with the cleared image, and that's what you're passing to render().
Firstly, you shouldn't be creating the first bound form if the request method is POST:
def test(request):
profile = request.user.get_profile()
if request.method == "POST":
form = TestForm(request.POST, request.FILES, instance=profile)
if form.is_valid():
form.save()
else:
form = TestForm(instance=profile)
return render(request, 'test.html', {'form':form})
Secondly, why do you want your user to do the same exact action twice if they did indeed want to delete an image but simply missed another checkbox?
If you really need Django to act this way, I would do one of two things. Either create a bound form from an instance of UserProfile and pass both the non-valid form and the newly created form to the template and use the non-valid form for displaying the errors and the other one for displaying the rest of the form:
def test(request):
profile = request.user.get_profile()
if request.method == "POST":
errors_form = TestForm(request.POST, request.FILES, instance=profile)
if errors_form.is_valid():
errors_form.save()
form = errors_form
else:
form = TestForm(instance=profile)
return render(request, 'test.html', {'form':form, 'errors_form': errors_form})
else:
form = TestForm(instance=profile)
return render(request, 'test.html', {'form':form})
OR I'd do the same thing but save the errors from the non-valid form to the newly created form so you don't end up with renders() all over the place:
def test(request):
profile = request.user.get_profile()
if request.method == "POST":
errors_form = TestForm(request.POST, request.FILES, instance=profile)
if errors_form.is_valid():
errors_form.save()
form = errors_form
else:
form = TestForm(instance=profile)
#this is left up to you to implement, but you'd do something like
#form.errors = errors_form.errors
#and also iterate through every form attribute.errors and assign it to
#errors_form attribute.errors etc...
else:
form = TestForm(instance=profile)
return render(request, 'test.html', {'form':form})
Both aren't very elegant solutions and I'm not positive the second one will even work as expected without some more hacks since I'm not completely familiar with the Django Forms implementation.
I don't see that doing this is worth it. As I stated before, you're just creating more work for your user...

Related

Django checkbox do not return nothing

I mading a project and i need to get checkbox values in sequence, but django do not return anything when that checkbox are unchecked.
how can i do that return False instead of nothing?
forms.py
class myFormExemple(forms.Form):
checkbox = forms.BooleanField(required=False)
views.py
def MyViewExemple(request):
if request.method == 'POST':
print(request.POST.getlist('checkbox'))
context = {
'form': myFormExemple
}
return render(request, "cadastro/myHTMLTemplate.html", context)
and myHTMLTemplate.html:
<form method="post" id='form'>
{% csrf_token %}
{{form.checkbox}}
<button type="submit">save</button>
</form>
If the checkbox is not checked, the POST request will indeed not contain the name of that checkbox, that is how the HTML form works.
You can however validate and clean the data with your form, and transform this into a boolean with:
def MyViewExemple(request):
if request.method == 'POST':
form = myFormExemple(request.POST)
if form.is_valid():
print(form.cleaned_data['checkbox'])
context = {
'form': myFormExemple()
}
return render(request, "cadastro/myHTMLTemplate.html", context)
We thus construct a form with request.POST as data source, and then if the form is valid, we can obtain a boolean with form.cleaned_data['checkbox'].
Django's forms are thus not only used to render a form, but also to process data of a form, validate that data, and convert the data to another type.

Django: form.is_valid() error, but only after "if request.method == 'POST'"?

When I print(formexam) BEFORE the if request.method == 'POST', it shows the appropriately filled out form items (the exam was created earlier, now I'm just updating it to make changes as desired).
However, when I print(formexam) AFTER if request.method == 'POST', it shows the form fields (at least several of them, I didn't look at every single one) to be empty. What makes that happen?
(In my example code below, I am printing the errors instead of the form.)
Also, a very very similar views.py function (changeExam, bottom of this post) works just fine.
Thank you!
views.py
def updateExam(request, pk):
exam = Exam.objects.get(id=pk)
formod = form_od.ODForm(instance=exam.od)
formos = form_os.OSForm(instance=exam.os)
formexam = ExamForm(instance=exam)
print(f'The errors are: {formexam.errors}')
if request.method == 'POST':
formexam = ExamForm(request.POST, instance=exam)
print(f'The errors are: {formexam.errors}')
formod = form_od.ODForm(request.POST, instance=exam.od)
formos = form_os.OSForm(request.POST, instance=exam.os)
if formod.is_valid() and formos.is_valid():
print("these are valid")
if formexam.is_valid():
print("so is this one.")
#save exam
instance = formexam.save(commit=False)
instance.save()
#save OD
instance = formod.save(commit=False)
instance.save()
#save OS
instance = formos.save(commit=False)
instance.save()
else:
print("Exam form not valid.")
#save OD
instance = formod.save(commit=False)
instance.save()
#save OS
instance = formos.save(commit=False)
instance.save()
else:
print("No forms are valid.")
context = {
'formod': formod,
'formos': formos,
'formexam': formexam,
'exam': exam,
}
return render(request, 'accounts/form_exam_update.html', context)
Results of hitting submit button:
The errors are:
The errors are: <ul class="errorlist"><li>doctor<ul class="errorlist">.<li>This field is required.</li></ul></li><li>examtype<ul class="errorlist"><li>This field is required.</li></ul></li></ul>
these are valid
Exam form not valid.
However, this very similar one works:
views.py
def changeExam(request, pk):
exam = Exam.objects.get(id=pk)
form = ExamForm(instance=exam)
if request.method == 'POST':
form = ExamForm(request.POST, instance=exam)
if form.is_valid():
print(form)
form.save()
next = request.POST.get('next', '/')
return HttpResponseRedirect(next)
context = {
'form': form,
}
return render(request, 'accounts/form_exam.html', context)def
Figured it out!
On changeExam, the resultant HTML page (form_exam.html) has <form> tags with all of the fields within the <form> tags. Therefore, no error.
On updateExam, it's a larger page (form_exam_update.html) using Django template tags to include things in different sections, and I realized a few of the fields (e.g. doctor, examtype) were outside of the <form> tags.
I updated the HTML code so that all corresponding fields were within the <form> tags, and it now works as desired!

Unique=True in Form not throwing any error

I'm entering a duplicate value (already saved in another instance of the same model) in my form to test the unique=True attribute. form.is_valid() returns 'False', as expected, but I don't receive any prompt in the template. Shouldn't I get prompted something like "obj with this value already exists"? The page simply reloads... What am I missing?
forms.py
def update_route(request, pk):
instance = Route.objects.get(id=pk)
if request.method == "POST":
form = RouteForm(request.POST)
if form.is_valid():
data = form.cleaned_data
instance.name = data['name']
instance.priority = data['priority']
instance.url = data['url']
return redirect('campaigns:routes_list')
form = RouteForm(instance=instance)
context= {
'form': form,
}
return render(request, "campaigns/route_form.html", context)
models.py
class Route(models.Model):
name = models.CharField(max_length=48)
priority = models.SmallIntegerField(choices=PRIORITY_LEVEL, default=0, unique=True)
url = models.URLField()
Template
<form method="post" action="">
{% csrf_token %}
{{form.as_p}}
<input type="submit" value="Submit">
</form>
Your update_route() view handles the condition in which the submitted form is valid (form.is_valid()), but not the condition in which the form is invalid.
The errors you are looking for are stored in the form object that you created with RouteForm(request.POST). The errors are generated when the is_valid() method is called.
This form object needs to be added to the context dict and rerendered to the user for the errors to surface. But your code currently overwrites that object with form = RouteForm(instance=instance), so the POST data and the related errors disappear.
One solution could be to handle it in the conditional statement:
if form.is_valid():
...
else:
context = {'form': form}
return render(request, "campaigns/route_form.html", context)
Another solution could be to create a conditional statement for GET requests, for example:
elif request.method == 'GET':
form = RouteForm(instance=instance)

Django templated does not display inputs to be filled

I need a call a form on an HTML template where the user posts data which saves to model
The code is running without any errors
But the html page display only title and button
No text input fields
I have a form which is to be displayed on a html page so the user can input data and it saves the data into the model.I am not getting any errors while executing th code but the template does not display the form it just shows the title and submit button
def boqmodel1(request):
form = boqform(request.POST)
if form.is_valid():
obj=form.save(commit=False)
obj.save()
context = {'form': form}
return render(request, 'create.html', context)
else:
context = {'error': 'The post has been successfully created.
Please enter boq'}
return render(request, 'create.html', context)
MyTemplate
<form action="" method="POST">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Create boq"/>
</form>
MY Url
urlpatterns = [
url(r'^create/', views.boqmodel1, name='boqmodel1'),
path('', views.boq, name='boq'),
]
First of all, your first request, without submitting form is GET. When you submit a form you send POST.
The form is not displaying, because your form is not valid in the first place. Your function should look like this:
def boqmodel1(request):
context = {}
if request.method == "GET":
form = boqform()
context["form"] = form
# if you post a form do all the saving
if request.method == "POST":
form = boqform(request.POST)
context = {'form': form}
if form.is_valid():
obj=form.save()
return render(request, 'create.html', context)
else:
context["errors"] = form.errors
return render(request, 'create.html', context)
If method is GET init your form and pass it to your context, so you can display it on frontend.
If method is POST, init your form with your data from frontend (request.POST), check if the form is valid. If it is valid - save it. If it is not valid, return your errors and display them as you wish.

Django filtering based on data

I'm using django to develop a web app. Right now I am trying to make a page that will initially render a blank form and all data objects of a certain model, and then once the user submits the form, will then filter the objects displayed to show those that have the desired attributes.
My form class is as follows
class AreaForm(forms.Form):
seating = forms.IntegerField(label='Seating', default=False)
whiteboards = forms.BooleanField(label='Whiteboards', default=False)
outlets = forms.IntegerField(label='Outlets', default=False)
tables = forms.IntegerField(label='Tables', default=False)
And the view for this page thus far is
def search(request):
if request.method == 'GET':
form = NameForm(request.POST)
if form.is_valid():
# do filtering logic here somehow
return render(request, 'SpotMe/search.html', {'form': form}) # ????
else:
return render(request, 'SpotMe/search.html', {}) # ????
I'm as of yet unsure how to implement the templates page. Am I headed in completely the wrong direction?
To show the form empty and do some logic when user post data, you need to pass the form to the template and it'll render empty if there is not post data.
view.py
def search(request):
form = AreaForm(request.POST or None)
if request.method == 'POST':
if form.is_valid():
# do filtering logic here somehow
return render(request, 'SpotMe/search.html', {'form': form})
search.html
.....
{{ form.as_p }}
.....
Based on your reply to my question below your original post, this is an easy solution that will show all data object of a model, and then you can use an input and submit it from a template to filter the results on the same page.
Note: Substitute MODEL for your actually model name. You don't need a form if you are looking to filter results (it is an extra, unnecessary step).
def search(request):
if request.method == 'GET':
if request.GET.get('seating_no'):
seating_no = request.GET.get('seating_no')
queryset = MODEL.objects.filter(seating=seating_no)
else:
queryset = MODEL.objects.all()
return render(request, 'SpotMe/search.html', {'queryset': queryset})
and in your SpotMe/search.html you can have a <form><input name="seating_no" /></form> and submit button that will lead to the same URL, and make the input name(s) into whatever you want to capture via request.GET.get()