Enhance is_valid() with an exception - python

I have a django charField that is checked via the is_valid() method. The user is supposed to enter a valid logical expression in this field, so I wrote a parsing method that raises an exception if the expression is not correct.
How can I enhance the is_valid() method to cover this exception and display an error message to the user that his query was wrong?
I read this article (https://docs.djangoproject.com/en/dev/ref/forms/validation/#cleaning-a-specific-field-attribute) but still have no idea how to do that.
try:
job = Job(user=request.user) # set the current user
form = JobForm(request.POST, instance=job)
if form.is_valid():
form.save()
job.execute()
messages.success(request, u'A new job with the query "{query}" was created.'.format(query=job.query))
return HttpResponseRedirect(reverse('job-index'))
return self.render_to_response({'job_form': form, 'is_new': True})
except ParseError:
return self.render_to_response({'job_form': form, 'is_new': True})
The try...except-Block should be done within the is_valid() method, that is my intention. Someone got any hints?

You've provided an answer to the question yourself - you create your own form (or model form) and perform custom validation on that form's field using its clean_'fieldname'() method. So for example, say your model is:
class Job(models.Model):
expression_field = models.CharField(...)
...
you create a forms.py:
class JobForm(forms.ModelForm):
pass
class Meta:
model = Job
def clean_expression_field(self):
# You perform your custom validation on this field in here,
# raising any problems
value = self.cleaned_data['expression_field']
if value is 'really_bad':
raise forms.ValidationError("bad bad bad")
return value
then make use of it in your views.py as you already are in your example. Now if the value the user enters doesn't meet your criteria, an exception will be automatically raised

Related

Why form._errors is working in generic.UpdateView but not in generic.CreateView?

When I use this in my mixins
form._errors[forms.forms.NON_FIELD_ERRORS]=ErrorList(["age must be positive"])
in my UpdateView it works and the message is popped out in red. But in my CreateView nothing is popping out
How can I achieve this?
Here's my mixin.py:
class AgePositiveMixin(object):
def form_valid(self, form):
print(form.instance.age)
if form.instance.age>0:
return super(AgePositiveMixin, self).form_valid(form)
else:
form._errors[forms.forms.NON_FIELD_ERRORS]=ErrorList(["Age must be positive"])
# raise forms.ValidationError('Age must be positive')
return self.form_invalid(form)
A CreateView doesn't have an instance because an object doesn't exist yet when the form is constructed. Instead of form.instance, you can do this:
instance = form.save(commit=False)
# perform validation here
instance.save()
return super(AgePositiveMixin, self).form_valid(form)
Is the age supposed to be positive everywhere (in the Django admin, user-facing forms)? If so a PositiveSmallIntegerField will do that validation for you.
In general, I like to keep validations as close to the database as possible. If that PositiveSmallIntegerField doesn't fit your use-case, I'd recommend using a validator. With validators, Django will perform the validation for you in the admin, model forms, and anywhere else. It's great for consistency.
Edit: one more way to perform validation is to do it in a form class:
class UserForm(forms.ModelForm):
class Meta:
model = User
def clean_age(self):
age = self.cleaned_data['age']
if not age > 0:
raise forms.ValidationError("Age must be positive")
return age
Check out Django's Form and field validation docs for more information on this.

django custom form clean() raising error from clean_field()

I have created a custom form and need to override both of the clean_field() method and clean() method. Here is my code:
class MyForm(forms.Form):
username=forms.RegexField(regex=r'^1[34578]\d{9}$')
code = forms.RegexField(regex=r'^\d{4}$')
def clean_username(self):
u = User.objects.filter(username=username)
if u:
raise forms.ValidationError('username already exist')
return username
def clean(self):
cleaned_data = super(MyForm, self).clean()
# How can I raise the field error here?
If I save this form twice, and the username will be already exist in the second time, the clean_username method will raise an error, however, the clean() method still run without interruption.
So my question is, how can I stop calling clean() when error already raise by cleaned_xxx, if that is not possible, then how can I raised the error again which raised by clean_xxxx() in clean() method?
In your clean method, you can check whether username is in the cleaned_data dictionary.
def clean(self):
cleaned_data = super(MyForm, self).clean()
if 'username' in cleaned_data:
# username was valid, safe to continue
...
else:
# raise an exception if you really want to
You probably don't need the else statement. The user will see the error from the clean_username method so you don't need to create another one.

how to ensure that only my custom clean_field is called when a form is submitted?

I have a django form like this :
class PatientForm(forms.Form):
patient_id = forms.IntegerField()
patient_national_code = forms.CharField()
and I have a custom clean_ method for this form:
def clean_patient_national_code(self):
patient_national_code = self.cleaned_data['patient_national_code']
if not patient_national_code:
raise forms.ValidationError("My Error!")
return patient_national_code
but when I try to submit a form that its national_code field is empty(and should return "MY Error!"), it returns "This field is required." error. I think that it is the error that django's default validator returns, what should I do to get "My Error!" instead of django's default error?
Add required=False in the field definition, in that case django will not perform the check and will call your clean method.
However, note than when this field is not in the form data, accessing it as self.cleaned_data['patient_national_code'] may result in KeyError.

KeyError from customized clean() method in BaseModelFormset

I have read over the Forms and Formset Django documentation about 100x. To make this very clear, this is probably the first time I've ever used super() or tried to overload/inherit from another class (big deal for me.)
What's happening? I am making a django-model-formset in a view and I am passing it to a template. The model that the formset is inheriting from happens to be a ManyToMany relationship. I want these relationships to be unique, so that if my user is creating a form and they accidentally choose the same Object for the ManyToMany, I want it to fail validation.
I believe I have written this custom "BaseModelFormSet" properly (via the documentation) but I am getting a KeyError. It's telling me that it cannot find cleaned_data['tech'] and I am getting the KeyError on the word 'tech' on the line where I commented below.
The Model:
class Tech_Onsite(models.Model):
tech = models.ForeignKey(User)
ticket = models.ForeignKey(Ticket)
in_time = models.DateTimeField(blank=False)
out_time = models.DateTimeField(blank=False)
def total_time(self):
return self.out_time - self.in_time
The customized BaseModelFormSet:
from django.forms.models import BaseModelFormSet
from django.core.exceptions import ValidationError
class BaseTechOnsiteFormset(BaseModelFormSet):
def clean(self):
""" Checks to make sure there are unique techs present """
super(BaseTechOnsiteFormset, self).clean()
if any(self.errors):
# Don't bother validating enless the rest of the form is valid
return
techs_present = []
for form in self.forms:
tech = form.cleaned_data['tech'] ## KeyError: 'tech' <-
if tech in techs_present:
raise ValidationError("You cannot input multiple times for the same technician. Please make sure you did not select the same technician twice.")
techs_present.append(tech)
The View: (Summary)
## I am instantiating my view with POST data:
tech_onsite_form = tech_onsite_formset(request.POST, request.FILES)
## I am receiving an error when the script reaches:
if tech_onsite_form.is_valid():
## blah blah blah..
Isn't the clean method missing a return statement ? If I remember correctly it should always return the cleaned_data. Also the super call returns the cleaned_data so you should assign it there.
def clean(self):
cleaned_data = super(BaseTechOnsiteFormset, self).clean()
# use cleaned_data from here to validate your form
return cleaned_data
See: the django docs for more information
I used the Django shell to call the forms manually. I found that I was executing the clean() method on all of the forms returned from the view. There were 2 filled out with data, and 2 blank. When my clean() method was iterating through them all, it returned a KeyError when it got to the first blank one.
I fixed my issue by using a try-statement and passing on KeyErrors.

How do I raise a ValidationError (or do something similar) in views.py of my Django?

I'm using Django forms. I'm validating in the model layer:
def clean_title(self):
title = self.cleaned_data['title']
if len(title) < 5:
raise forms.ValidationError("Headline must be more than 5 characters.")
return title
However, there are some things that I need to validate in the views.py . For example...was the last time the user posted something more than a minute ago?
That kind of stuff requires request.user, which the models layer cannot get. So, I must validate in the views.py. How do I do something in the views.py to do the exact thing as this?
raise forms.ValidationError("Headline must be more than 5 characters.")
I think gruszczy's answer is a good one, but if you're after generic validation involving variables that you think are only available in the view, here's an alternative: pass in the vars as arguments to the form and deal with them in the form's main clean() method.
The difference/advantage here is that your view stays simpler and all things related to the form content being acceptable happen in the form.
eg:
# IN YOUR VIEW
# pass request.user as a keyword argument to the form
myform = MyForm(user=request.user)
# IN YOUR forms.py
# at the top:
from myapp.foo.bar import ok_to_post # some abstracted utility you write to rate-limit posting
# and in your particular Form definition
class MyForm(forms.Form)
... your fields here ...
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user') # cache the user object you pass in
super(MyForm, self).__init__(*args, **kwargs) # and carry on to init the form
def clean(self):
# test the rate limit by passing in the cached user object
if not ok_to_post(self.user): # use your throttling utility here
raise forms.ValidationError("You cannot post more than once every x minutes")
return self.cleaned_data # never forget this! ;o)
Note that raising a generic ValidationError in the clean() method will put the error into myform.non_field_errors so you'll have to make sure that your template contains {{form.non_field_errors}} if you're manually displaying your form
You don't use ValidationError in views, as those exceptions as for forms. Rather, you should redirect the user to some other url, that will explain to him, that he cannot post again that soon. This is the proper way to handle this stuff. ValidationError should be raised inside a Form instance, when input data doesn't validate. This is not the case.
You can use messages in views:
from django.contrib import messages
messages.error(request, "Error!")
Documentation: https://docs.djangoproject.com/es/1.9/ref/contrib/messages/

Categories