Django form and field validation does not work? - django

When using the Django built-in Form classes the validation routine does not seem to work.
The form consists simply of firstname and lastname. Firstname is required, and for testing purposes I check if lastname is Smith and raise an exception. When I violate those requirements nothing happens, i.e. no exception is being raised - after submitting the form the defined action (POST to union/VIP_best/) is simply triggered without any validation. The form is called at union/contact/, directed from urls.py to views.ContactView.as_view()
Here is my setup so far:
views.py
from union.forms import ContactForm
class ContactView(generic.edit.FormView):
template_name = 'union/contact.html'
form_class = ContactForm
forms.py
from django import forms
from django.core.exceptions import ValidationError
class ContactForm(forms.Form):
firstname = forms.CharField(label='Vorname', max_length=20, required=True, error_messages={'required': 'Please enter first name!'})
lastname = forms.CharField(label='Nachname', max_length=20)
def clean_lastname(self):
data = self.cleaned_data['lastname']
if data != "Smith":
raise forms.ValidationError("Your last name is not Smith.")
else:
raise forms.ValidationError("Your last name is Smith.")
return data
templates/union/contact.html
<form action="/union/VIP_best/" method="post">
{% csrf_token %}
{{ form.as_table }}
<tr>
<td><input type="submit" value="Submit"></td>
</tr>
</form>
What am i missing so that the .clean() or .is_valid is being triggered?
Do I have to explicitly call Field.clean() and is_valid()? If so, where?
The tutorials and Django documentation do not seem to mention anything the like.
Django 1.7, Python 3.4.2

Your form should be submitting to itself. Meaning, if the form is on /union/contact/, you should be POSTing to /union/contact/. Currently, you have it submitting to a different view/url.
The problem is arising because your form processing is happening in the FormView, not at your success_url(). You need to POST to the view that is actually responsible for the validation of the form (in this case, your ContactView at /union/contact/).
As a side note, it would probably be better to modify the action of the form to use {% url 'your_form_url_name' %}, as opposed to hard-coding the url into the template.

Related

Forms, Model, and updating with current user

I think this works, but I came across a couple of things before getting it to work that I want to understand better, so the question. It also looks like other people do this a variety of ways looking at other answers on stack overflow. What I am trying to avoid is having the user to have to select his username from the pulldown when creating a new search-profile. The search profile model is:
class Search_Profile(models.Model):
author_of_profile = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE,blank=True)
keyword_string = models.CharField(max_length=200)
other_stuff = models.CharField(max_length=200)
The form I ended up with was:
class Search_Profile_Form(ModelForm):
class Meta:
model = Search_Profile
fields = [ 'keyword_string', 'other_stuff']
Where I deliberately left out 'author_of_profile' so that it wouldn't be shown or need to be validated. I tried hiding it, but then the form would not save to the model because a field hadn't been entered. If I was o.k. with a pulldown I guess I could have left it in.
I didn't have any issues with the HTML while testing for completeness:
<form action="" method="POST">
{% csrf_token %}
{{ form.author_of_profile}}
{{ form.keyword_string }}
{{ form.other_stuff }}
<input type="submit" value="Save and Return to Home Page">
</form>
And the View is where I ended up treating the form and the model separated, saving the form first, then updating the model, which is where I think there might be some other way people do it.
def New_Profile(request):
if request.method=='POST':
form = Search_Profile_Form(request.POST)
if form.is_valid():
post=form.save(commit=False)
# here is where I thought I could update the author of profile field somehow with USER
# but If I include the author_of_profile field in the form it seems I can't.
post.save()
#So instead I tried to update the author_of profile directly in the model
current_profile=Search_Profile.objects.last()
current_profile.author_of_profile=request.user
current_profile.save()
return(redirect('home'))
else:
form=Search_Profile_Form()
return render(request, 'mainapp/New_Profile.html', {'form': form})
So a few questions:
For the Foreign Key in author_of_profile field, is it better to use blank=True, or null=True
I ended up using request.user rather than importing from django.contrib.auth.models import User is there any difference?
My real question though, is the above a reasonable way to get form data and update the database with that data and the user? Or am I missing some other way that is more build in?
post=form.save()
current_profile.author_of_profile=request.user
post.save()
return(redirect('home'))
try something like this. save the form to db then change the author again. save(commit=False) will not save the date to db immediately.

Enforce form field validation & display error without render?

I'm a django newbie so a verbose answer will be greatly appreciated. I'm enforcing a capacity limit on any newly created Bottle objects in my model, like so:
class Bottle(models.Model):
name = models.CharField(max_length=150, blank=False, default="")
brand = models.ForeignKey(Brand, on_delete=models.CASCADE, related_name="bottles")
vintage = models.IntegerField('vintage', choices=YEAR_CHOICES, default=datetime.datetime.now().year)
capacity = models.IntegerField(default=750,
validators=[MaxValueValidator(2000, message="Must be less than 2000")
,MinValueValidator(50, message="Must be more than 50")])
My BottleForm looks like so:
class BottleForm(ModelForm):
class Meta:
model = Bottle
fields = '__all__'
My view (with form validation logic based on this answer):
def index(request):
args = {}
user = request.user
object = Bottle.objects.filter(brand__business__owner_id=user.id).all(). \
values('brand__name', 'name', 'capacity', 'vintage').annotate(Count('brand')).order_by('brand__count')
args['object'] = object
if request.method == "POST":
form = BottleForm(request.POST)
if form.is_valid():
bottle = form.save(commit=False)
bottle.save()
return redirect('index')
else:
form = BottleForm()
args['form'] = form
return render(request, template_name="index.pug", context=args)
And my template (in pug format), like so:
form(class="form-horizontal")(method="post" action=".")
| {% csrf_token %}
for field in da_form
div(class="form-group")
label(class="col-lg-3 col-md-3 col-sm-3 control-label") {{field.label_tag}}
div(class="col-lg-9 col-md-9 col-sm-9")
| {{ field|add_class:"form-control" }}
input(class="btn btn-primary")(type="submit" value="submit")
After a few hours of messing with my code and browsing SO, I managed to display the error by adding {{ form.errors }} to my template, but that only shows after the page has already been reloaded and in a very ugly form: see here.
What I'd like is to utilize django's built-in popover error messages without reloading page (see example on default non-empty field), which is so much better from a UX standpoint.
That is not a Django message. That is an HTML5 validation message, which is enforced directly by your browser. Django simply outputs the input field as type number with a max attribute:
<input type="number" name="capacity" max="750">
I'm not sure if your (horrible) pug templating thing is getting in the way, or whether it's just that Django doesn't pass on these arguments when you use validators. You may need to redefine the field in the form, specifying the max and min values:
class BottleForm(ModelForm):
capacity = forms.IntegerField(initial=750, max_value=2000, min_value=250)
(Note, doing {{ field.errors }} alongside each field gives a much better display than just doing {{ form.errors }} at the top, anyway.)

how to pre-fill an HiddenInput in admin ModelForm

I'm sure that problems similar to the one I'm going to ask about were already discussed somewhere, but I always found very tricky solutions while I think there must be a very straightforward one (you can guess I'm a totally newbie).
I have a model (Lecturer) that is connected OneToOne to the User model:
class Lecturer(models.Model):
user = models.OneToOneField(User)
... other fiels follow ...
I'd like each user to create its own Lecturer object through the admin site.
My idea is to present to the user the add_view without the user field. Then I would something like obj.user=request.user when saving the model.
In other words, I don't want to give the user the possibility of selecting a different user among the registered for its own Lecturer object.
I modified the form by overriding the get_form method and by providing a custom form:
admin.py
class LecturerAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
if is_lecturer(request.user):
kwargs['form'] = UserLecturerForm
return super(LecturerAdmin, self).get_form(request, obj, **kwargs)
class UserLecturerForm(forms.ModelForm):
class Meta:
model = Lecturer
fields = ('__all__')
widgets = {'user': forms.HiddenInput()}
I cannot just exclude the user field and give it a value at some other level (e.g. save_model or clean ...) because this raises an error at the template rendering level:
Django Version: 1.7.7
Exception Type: KeyError
Exception Value: u"Key 'user' not found in 'LecturerForm'"
Exception Location: /usr/lib/python2.7/dist-packages/django/forms/forms.py in getitem, line 147
Python Executable: /usr/bin/python
Python Version: 2.7.9
Error during template rendering
In template /usr/lib/python2.7/dist-packages/django/contrib/admin/templates/admin/includes/fieldset.html, error at line 7
[...]
7 <div class="form-row{% if line.fields|length_is:'1' and line.errors %} errors{% endif %}{% if not line.has_visible_field %} hidden{% endif %}{% for field in line %}{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% endfor %}">
I tried making the user field hidden in the form. But then the problem came of how to give it the correct request.user value, given that the form doesn't know anything about the request. If I don't fill it with a valid value, the form will not validate (and I cannot use solutions involving the save_model as suggested here)
I found solutions that involve changing the view, but I wouldn't do it in the admin context.
An alternative would be to change the validation behavior, but still validation is a method of the form and the form doesn't know request.
Any help is appreciated.

Populate a PasswordField in wtforms

Is it possible to populate a password field in wtforms in flask?
I've tried this:
capform = RECAPTCHA_Form()
capform.username.data = username
capform.password.data = password
The form is defined like:
class RECAPTCHA_Form(Form):
username = TextField('username', validators=[DataRequired()])
password = PasswordField('password', validators=[DataRequired()])
remember_me = BooleanField('Remember me.')
recaptcha = RecaptchaField()
The template looks like this:
<form method="POST" action="">
{{ form.hidden_tag() }}
{{ form.username(size=20) }}
{{ form.password(size=20) }}
{% for error in form.recaptcha.errors %}
<p>{{ error }}</p>
{% endfor %}
{{ form.recaptcha }}
<input type="submit" value="Go">
</form>
I have tried to change the PasswordField to a TextField, and then it works.
Is there some special limitation to populating PasswordFields in wtforms?
Update: After looking through the WTForms docs I found an even better solution. There is a widget arg.
from wtforms import StringField
from wtforms.widgets import PasswordInput
class MyForm(Form):
# ...
password = StringField('Password', widget=PasswordInput(hide_value=False))
As yuji-tomita-tomita pointed out, the PasswordInput class (source) has an hide_value argument, however the constructor of PasswordField (source) does not forward it to the PasswordInput. Here is a PasswordField class that initializes PasswordInput with hide_value=False:
from wtforms import widgets
from wtforms.fields.core import StringField
class PasswordField(StringField):
"""
Original source: https://github.com/wtforms/wtforms/blob/2.0.2/wtforms/fields/simple.py#L35-L42
A StringField, except renders an ``<input type="password">``.
Also, whatever value is accepted by this field is not rendered back
to the browser like normal fields.
"""
widget = widgets.PasswordInput(hide_value=False)
Something I've found with Flask, and Flask apps in general, is that the source is the documentation. Indeed, it looks like by default you cannot populate the field. You can pass an argument hide_value to prevent this behavior.
This is a good call, since if you can populate the field, you have access to the raw password... which could be dangerous.
class PasswordInput(Input):
"""
Render a password input.
For security purposes, this field will not reproduce the value on a form
submit by default. To have the value filled in, set `hide_value` to
`False`.
"""
input_type = 'password'
def __init__(self, hide_value=True):
self.hide_value = hide_value
def __call__(self, field, **kwargs):
if self.hide_value:
kwargs['value'] = ''
return super(
I believe there is an easier way to access the data of the password field, without usinghide_value. In your view, simply add in the request data as an argument to the form's constructor:
from flask import request
capform = RECAPTCHA_Form(request.form)
capform.username.data = username
capform.password.data = password
This should make the password input available for validation, and to be used in testing if desired.

How edit data from form django admin

I'm learning Django Framework, and I have a question. To help you understand I will try and explain using the example below:
Suppose that we have some table in db as is:
CREATE TABLE names (id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100));
And I have the form in Django Admin as is:
<form>
<textarea name="names"></textarea>
<input type="submit" name="sbt" value="Submit">
</form>
User entered something in the input names in the form and submitted it. Then a script catches this data and splits it into an array (str.split("\n")) and in cycle adding to table names!
And I many quetion:
How i can add form to Django Admin?
How i can catch form data and add this data to somethink table in database?
Thanks.
First of all you must create a django model.
Put this code in models.py.
class Names(models.Model):
name = models.CharField(max_length = 100)
Then you must create the admin model.
Put this code in admin.py.
class NamesAdmin(admin.ModelAdmin):
list_display = ['name']
# whatever you want in your admin panel like filter, search and ...
admin.site.register(Names, NamesAdmin)
I think it meet your request. And for split the names you can override save model method and split the names in there. But if you want to have an extra form, you can easily create a django model form.
Put the code somewhere like admin.py, views.py or forms.py
class NamesForm(forms.ModelForm)
class Meta:
model = Names
That's your model and form. So, if your want to add the form to django admin panel you must create a view for it in django admin. For do this create a view as common.
Put the code in your admin.py or views.py.
def spliter(req):
if req.method == 'POST':
form = NamesForm(req.POST)
if form.is_valid():
for name in form.cleaned_data['names'].split(' '):
Names(name = name).save()
return HttpResponseRedirect('') # wherever you want to redirect
return render(req, 'names.html', {'form': form})
return render(req, 'names.html', {'form': NamesForm()})
Be aware you must create the names.html and put the below code in you html page.
{% extends 'admin/base_site.html' %}
{% block content %}
<!-- /admin/names/spliter/ is your url in admin panel (you can change it whatever you want) -->
<form action="/admin/names/spliter/" method="post" >{% csrf_token %}
{{ form }}
<input type="submit" value="'Send'" >
</form>
{% endblock %}
This is your view and your can use it everywhere. But if you want only the admin have permission to see this page you must add this method too your NamesAdmin class.
def get_urls(self):
return patterns(
'',
(r'^spliter/$', self.admin_site.admin_view(spliter)) # spliter is your view
) + super(NamesAdmin, self).get_urls()
That's It. I hope this can help you.