I have a Model which has a restricts the number of decimal places:
lat = models.DecimalField(max_digits=8, decimal_places=5, null=True, blank=True)
From it I create a ModalForm however I would like to cap the number of decimal places if they submitted something with more than 5 places. So I do a custom clean method:
def clean_lat(self):
lat = self.cleaned_data['lat']
return round(lat, 4)
But it still raises a ValidationError that I have more decimal places then allowed. What am I doing wrong?
There are two option to solve this.
First one is to override your modelform init method like this
def __init__(self, *args, **kwargs):
super(TestForm, self).__init__(*args, **kwargs)
self.fields['lat'].decimal_places = None
self.fields['lat'].max_digits = None
This will disable the decimal places and max digit valididation by modelform. Then your clean_lat method should ensure ensure form data validation. Model will still truncate/ roundoff/ validate decimal value.
Second option is that remove max_digits=8, decimal_places=5 from model and ensure validation in your forms clean_lat method. This can create problem if object is saved without using the ModelForm.
the form.is_valid method do a very simple thing:
def full_clean(self):
"""
Cleans all of self.data and populates self._errors and
self.cleaned_data.
"""
self._errors = ErrorDict()
if not self.is_bound: # Stop further processing.
return
self.cleaned_data = {}
# If the form is permitted to be empty, and none of the form data has
# changed from the initial data, short circuit any validation.
if self.empty_permitted and not self.has_changed():
return
self._clean_fields()
self._clean_form()
self._post_clean()
I suggest you get into the source code(django/forms/forms.py), and the reason will be located quickly.
Related
Django has various numeric fields available for use in models, e.g. DecimalField and PositiveIntegerField. Although the former can be restricted to the number of decimal places stored and the overall number of characters stored, is there any way to restrict it to storing only numbers within a certain range, e.g. 0.0-5.0 ?
Failing that, is there any way to restrict a PositiveIntegerField to only store, for instance, numbers up to 50?
Update: now that Bug 6845 has been closed, this StackOverflow question may be moot. - sampablokuper
You can use Django's built-in validators—
from django.db.models import IntegerField, Model
from django.core.validators import MaxValueValidator, MinValueValidator
class CoolModelBro(Model):
limited_integer_field = IntegerField(
default=1,
validators=[
MaxValueValidator(100),
MinValueValidator(1)
]
)
Edit: When working directly with the model, make sure to call the model full_clean method before saving the model in order to trigger the validators. This is not required when using ModelForm since the forms will do that automatically.
You could also create a custom model field type - see http://docs.djangoproject.com/en/dev/howto/custom-model-fields/#howto-custom-model-fields
In this case, you could 'inherit' from the built-in IntegerField and override its validation logic.
The more I think about this, I realize how useful this would be for many Django apps. Perhaps a IntegerRangeField type could be submitted as a patch for the Django devs to consider adding to trunk.
This is working for me:
from django.db import models
class IntegerRangeField(models.IntegerField):
def __init__(self, verbose_name=None, name=None, min_value=None, max_value=None, **kwargs):
self.min_value, self.max_value = min_value, max_value
models.IntegerField.__init__(self, verbose_name, name, **kwargs)
def formfield(self, **kwargs):
defaults = {'min_value': self.min_value, 'max_value':self.max_value}
defaults.update(kwargs)
return super(IntegerRangeField, self).formfield(**defaults)
Then in your model class, you would use it like this (field being the module where you put the above code):
size = fields.IntegerRangeField(min_value=1, max_value=50)
OR for a range of negative and positive (like an oscillator range):
size = fields.IntegerRangeField(min_value=-100, max_value=100)
What would be really cool is if it could be called with the range operator like this:
size = fields.IntegerRangeField(range(1, 50))
But, that would require a lot more code since since you can specify a 'skip' parameter - range(1, 50, 2) - Interesting idea though...
from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator
size = models.IntegerField(validators=[MinValueValidator(0),
MaxValueValidator(5)])
I had this very same problem; here was my solution:
SCORE_CHOICES = zip( range(1,n), range(1,n) )
score = models.IntegerField(choices=SCORE_CHOICES, blank=True)
There are two ways to do this. One is to use form validation to never let any number over 50 be entered by a user. Form validation docs.
If there is no user involved in the process, or you're not using a form to enter data, then you'll have to override the model's save method to throw an exception or limit the data going into the field.
Here is the best solution if you want some extra flexibility and don't want to change your model field. Just add this custom validator:
#Imports
from django.core.exceptions import ValidationError
class validate_range_or_null(object):
compare = lambda self, a, b, c: a > c or a < b
clean = lambda self, x: x
message = ('Ensure this value is between %(limit_min)s and %(limit_max)s (it is %(show_value)s).')
code = 'limit_value'
def __init__(self, limit_min, limit_max):
self.limit_min = limit_min
self.limit_max = limit_max
def __call__(self, value):
cleaned = self.clean(value)
params = {'limit_min': self.limit_min, 'limit_max': self.limit_max, 'show_value': cleaned}
if value: # make it optional, remove it to make required, or make required on the model
if self.compare(cleaned, self.limit_min, self.limit_max):
raise ValidationError(self.message, code=self.code, params=params)
And it can be used as such:
class YourModel(models.Model):
....
no_dependents = models.PositiveSmallIntegerField("How many dependants?", blank=True, null=True, default=0, validators=[validate_range_or_null(1,100)])
The two parameters are max and min, and it allows nulls. You can customize the validator if you like by getting rid of the marked if statement or change your field to be blank=False, null=False in the model. That will of course require a migration.
Note: I had to add the validator because Django does not validate the range on PositiveSmallIntegerField, instead it creates a smallint (in postgres) for this field and you get a DB error if the numeric specified is out of range.
Hope this helps :) More on Validators in Django.
PS. I based my answer on BaseValidator in django.core.validators, but everything is different except for the code.
In the forms.py
Class FloatForm(forms.ModelForm):
class Meta:
model = Float
fields = ('name','country', 'city', 'point', 'year')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['point'] = forms.FloatField(max_value=100, min_value=1)
It is worth mentioning that sometimes Django validation doesn't work as Django validation is mostly an application-level validation, not validation at the database level. Also, Model validation is not run automatically on the save/create/update of the model. If you want to validate your values instantly in your code then you need to do it manually — using the override save() method:
class UserRating():
SCORE_CHOICES = (
(1, _("Terrible")),
(2, _("Poor")),
(3, _("Average")),
(4, _("Very Good")),
(5, _("Excellent")),
)
score = models.PositiveSmallIntegerField(
choices=SCORE_CHOICES, default=1,
validators=[
MaxValueValidator(5),
MinValueValidator(1)
]
)
def save(self, *args, **kwargs):
if int(self.score) < 1 or int(self.score) > 5:
raise ValidationError('Score must be located between 0 to 5')
super(UserRating, self).save(*args, **kwargs)
...
Add validator like this your model column in models.py
class Planogram(models.Model):
camera = models.ForeignKey(Camera, on_delete=models.CASCADE)
xtl = models.DecimalField(decimal_places=10, max_digits=11,validators=[MaxValueValidator(1),MinValueValidator(0)])
if you are using create function to create objects change it to constructor like below....
and call fullclean() on that object and then save..
everything will work perfectly.
planogram = Planogram(camera_id = camera,xtl=xtl,ytl=ytl,xbr=xbr,ybr=ybr,product_id=product_id)
planogram.full_clean()
planogram.save()
I have written a function that accept as input a string and do some validation tasks on it and also changes the value.
def validate(str):
# do validation. If any error, raise Validation error
# modify value of str
return str
I want to use this function as a validator for some django model field. I know how to do it. My problem is that in addition to validation I want the modified value, i.e. return value of function, to be saved in field.
The models.py module is not right place to do this as input validation is usually done in forms. But still you can do it in Model.save() method:
# models.py
def validate(str):
# do validation. If any error, raise Validation error
# modify value of str
return str
class YourModel(models.Model):
...
field_to_validate = models.CharField(max_length=100)
...
def save(self, **kwargs):
try:
self.field_to_validate = validate(self.field_to_validate)
except YourValidationError:
self.field_to_validate = ''
super(YourModel, self).save(**kwargs)
Assuming I have a blog with entries i would like to filter optionally by category or date: For the filter I use the following form
#forms.py
class MyForm(forms.Form):
categories = forms.ModelMultipleChoiceField(Category.objects.all(),
required=False)
start_date = forms.DateField(required=False)
end_date = forms.DateField(required=False)
I ve got the following view:
#views.py
blog_entries = Blog.objects.all()
cat_filter = TurnoverFilterForm(request.GET)
if cat_filter.is_valid():
categories_chosen = cat_filter.cleaned_data['categories']
start_date = cat_filter.cleaned_data['start_date']
end_date = cat_filter.cleaned_data['end_date']
blog_entries = blog_entries.cat_filter(categories_chosen).date_filter(start_date,end_date)
return render(request,'index.html',{'blog_entries':blog_entries}
Where date_filter and cat_filter are customized manager functions (which work).
The questions are:
Do I really need to make each field in form optional? Is there any optional form for those cases? (since there is a lot of code redundancy)
i ve got an ugly if-statement in my form since the form is always valid (or at least should be as category and date range is optional and the form's request type is 'get'
Is there any other elegant solution for this sort of problems? I can imagine it is really common
If you want all the fields in the form to be optional you may override the __init__ function of the form like this:
def __init__(self, *args, **kwargs):
super(forms.Form, self).__init__(*args, **kwargs)
for f in self.fields:
self.fields[f].required=False
That way you set all the fields' required field to False and avoid code redundancy to make the whole form become optional.
Like is_valid method will return True always you may remove it from your code and add to the form another function which encapsulate the remaining code in the views.py.
With this you may simplify a little bit that code. If you want something fancier think about subclass the Form class and create OptionalForm so you can make that code reusable.
I'm trying to make a form that handles the checking of a domain: the form should fail based on a variable that was set earlier in another form.
Basically, when a user wants to create a new domain, this form should fail if the entered domain exists.
When a user wants to move a domain, this form should fail if the entered domain doesn't exist.
I've tried making it dynamic overload the initbut couldn't see a way to get my passed variabele to the clean function.
I've read that this dynamic validation can be accomplished using a factory method, but maybe someone can help me on my way with this?
Here's a simplified version of the form so far:
#OrderFormStep1 presents the user with a choice: create or move domain
class OrderFormStep2(forms.Form):
domain = forms.CharField()
extension = forms.CharField()
def clean(self):
cleaned_data = self.cleaned_data
domain = cleaned_data.get("domain")
extension = cleaned_data.get("extension")
if domain and extension:
code = whoislookup(domain+extension);
#Raise error based on result from OrderFormStep1
#raise forms.ValidationError('error, domain already exists')
#raise forms.ValidationError('error, domain does not exist')
return cleaned_data
Overriding the __init__ is the way to go. In that method, you can simply set your value to an instance variable.
def __init__(self, *args, **kwargs):
self.myvalue = kwargs.pop('myvalue')
super(MyForm, self).__init__(*args, **kwargs)
Now self.myvalue is available in any form method.
Do you have a model that stores the domains? If so, you want to use a ModelForm and set unique=True on whichever field stores the actual domain in the model. As of Django 1.2, you can even do any additional validation inside the model, rather than the form.
Django has various numeric fields available for use in models, e.g. DecimalField and PositiveIntegerField. Although the former can be restricted to the number of decimal places stored and the overall number of characters stored, is there any way to restrict it to storing only numbers within a certain range, e.g. 0.0-5.0 ?
Failing that, is there any way to restrict a PositiveIntegerField to only store, for instance, numbers up to 50?
Update: now that Bug 6845 has been closed, this StackOverflow question may be moot. - sampablokuper
You can use Django's built-in validators—
from django.db.models import IntegerField, Model
from django.core.validators import MaxValueValidator, MinValueValidator
class CoolModelBro(Model):
limited_integer_field = IntegerField(
default=1,
validators=[
MaxValueValidator(100),
MinValueValidator(1)
]
)
Edit: When working directly with the model, make sure to call the model full_clean method before saving the model in order to trigger the validators. This is not required when using ModelForm since the forms will do that automatically.
You could also create a custom model field type - see http://docs.djangoproject.com/en/dev/howto/custom-model-fields/#howto-custom-model-fields
In this case, you could 'inherit' from the built-in IntegerField and override its validation logic.
The more I think about this, I realize how useful this would be for many Django apps. Perhaps a IntegerRangeField type could be submitted as a patch for the Django devs to consider adding to trunk.
This is working for me:
from django.db import models
class IntegerRangeField(models.IntegerField):
def __init__(self, verbose_name=None, name=None, min_value=None, max_value=None, **kwargs):
self.min_value, self.max_value = min_value, max_value
models.IntegerField.__init__(self, verbose_name, name, **kwargs)
def formfield(self, **kwargs):
defaults = {'min_value': self.min_value, 'max_value':self.max_value}
defaults.update(kwargs)
return super(IntegerRangeField, self).formfield(**defaults)
Then in your model class, you would use it like this (field being the module where you put the above code):
size = fields.IntegerRangeField(min_value=1, max_value=50)
OR for a range of negative and positive (like an oscillator range):
size = fields.IntegerRangeField(min_value=-100, max_value=100)
What would be really cool is if it could be called with the range operator like this:
size = fields.IntegerRangeField(range(1, 50))
But, that would require a lot more code since since you can specify a 'skip' parameter - range(1, 50, 2) - Interesting idea though...
from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator
size = models.IntegerField(validators=[MinValueValidator(0),
MaxValueValidator(5)])
I had this very same problem; here was my solution:
SCORE_CHOICES = zip( range(1,n), range(1,n) )
score = models.IntegerField(choices=SCORE_CHOICES, blank=True)
There are two ways to do this. One is to use form validation to never let any number over 50 be entered by a user. Form validation docs.
If there is no user involved in the process, or you're not using a form to enter data, then you'll have to override the model's save method to throw an exception or limit the data going into the field.
Here is the best solution if you want some extra flexibility and don't want to change your model field. Just add this custom validator:
#Imports
from django.core.exceptions import ValidationError
class validate_range_or_null(object):
compare = lambda self, a, b, c: a > c or a < b
clean = lambda self, x: x
message = ('Ensure this value is between %(limit_min)s and %(limit_max)s (it is %(show_value)s).')
code = 'limit_value'
def __init__(self, limit_min, limit_max):
self.limit_min = limit_min
self.limit_max = limit_max
def __call__(self, value):
cleaned = self.clean(value)
params = {'limit_min': self.limit_min, 'limit_max': self.limit_max, 'show_value': cleaned}
if value: # make it optional, remove it to make required, or make required on the model
if self.compare(cleaned, self.limit_min, self.limit_max):
raise ValidationError(self.message, code=self.code, params=params)
And it can be used as such:
class YourModel(models.Model):
....
no_dependents = models.PositiveSmallIntegerField("How many dependants?", blank=True, null=True, default=0, validators=[validate_range_or_null(1,100)])
The two parameters are max and min, and it allows nulls. You can customize the validator if you like by getting rid of the marked if statement or change your field to be blank=False, null=False in the model. That will of course require a migration.
Note: I had to add the validator because Django does not validate the range on PositiveSmallIntegerField, instead it creates a smallint (in postgres) for this field and you get a DB error if the numeric specified is out of range.
Hope this helps :) More on Validators in Django.
PS. I based my answer on BaseValidator in django.core.validators, but everything is different except for the code.
In the forms.py
Class FloatForm(forms.ModelForm):
class Meta:
model = Float
fields = ('name','country', 'city', 'point', 'year')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['point'] = forms.FloatField(max_value=100, min_value=1)
It is worth mentioning that sometimes Django validation doesn't work as Django validation is mostly an application-level validation, not validation at the database level. Also, Model validation is not run automatically on the save/create/update of the model. If you want to validate your values instantly in your code then you need to do it manually — using the override save() method:
class UserRating():
SCORE_CHOICES = (
(1, _("Terrible")),
(2, _("Poor")),
(3, _("Average")),
(4, _("Very Good")),
(5, _("Excellent")),
)
score = models.PositiveSmallIntegerField(
choices=SCORE_CHOICES, default=1,
validators=[
MaxValueValidator(5),
MinValueValidator(1)
]
)
def save(self, *args, **kwargs):
if int(self.score) < 1 or int(self.score) > 5:
raise ValidationError('Score must be located between 0 to 5')
super(UserRating, self).save(*args, **kwargs)
...
Add validator like this your model column in models.py
class Planogram(models.Model):
camera = models.ForeignKey(Camera, on_delete=models.CASCADE)
xtl = models.DecimalField(decimal_places=10, max_digits=11,validators=[MaxValueValidator(1),MinValueValidator(0)])
if you are using create function to create objects change it to constructor like below....
and call fullclean() on that object and then save..
everything will work perfectly.
planogram = Planogram(camera_id = camera,xtl=xtl,ytl=ytl,xbr=xbr,ybr=ybr,product_id=product_id)
planogram.full_clean()
planogram.save()