I'm using django 1.5.4
Here's a minimal example of the issue I'm facing.
The model:
#models.py
from django.db import models
class SampleModel(models.Model):
spam = models.BooleanField()
The form:
#forms.py
from django.forms import ModelForm
from .models import SampleModel
class SampleModelForm(ModelForm):
class Meta:
model = SampleModel
fields = ('spam', )
From the django shell:
>>> data = {} #intentionally blank
>>> form = SampleModelForm(data)
>>> is_valid = form.is_valid() #is_valid is True
>>> form.save() # model instance is created with "spam" set to False by default.
Am I validating the form incorrectly? form.is_valid validates fields of other types correctly.
The docs indicate that all fields are required by default but is_valid returns Truewithout the boolean field key being present.
I need to ensure that the boolean field is present in the input data.
As of now, I'm manually checking if the field is present and is of type bool. Do you think I should override form.is_valid and add this check so that it can be reused for other models too?
It turns out (from code inspection; the docs don't say) that model BooleanFields have blank=True set automatically in their __init__ method, thus making the automatically created model form field not required. This makes sense upon consideration (False counts as blank, so BooleanFields need it to be true) but it's not obvious when you just read the docs.
If you want it to be required to be True, the usual form field overrides apply - declare the field yourself or set its required attribute to be True somewhere before validating (I usually use the form's __init__ method). If you want it to allow True or False but not Python None, it's harder.
Specifically, the standard widget for a BooleanField is a checkbox. Browsers do not submit anything for unchecked checkboxes, so the checkbox widget treats the absence of the field from the submit as False. There's no way to distinguish the user not selecting the checkbox from cases in which the input really is bad. You could use a different widget (say, a RadioSelect) to at least make it possible for the browser to submit something for False, but you still have the problem that the BooleanField's to_python method converts its value to a boolean before validating, so you'd have to subclass and override.
Fortunately, False is not considered empty by the validation code, so if you do that you'll be able to set required=True and don't need custom cleaning methods.
Related
I have standard Django models with ForeignKey.
Django docs:
"ForeignKey is represented by django.forms.ModelChoiceField, which is a ChoiceField whose choices are a model QuerySet."
and
"If the model field has choices set, then the form field’s widget will be set to Select, with choices coming from the model field’s choices."
Now I have dropdown menu with choices.
I don't want dropdown menu where user can see options. I want CharField(textfield or similar) where user type, but still
that must be one of the options from the database for that field. He must type a valid entry.
I tried:
class TransakcijeForm(forms.ModelForm):
model = models.Transakcije
fields = .....
labels = .....
widgets ={'subscriber':forms.TextInput()}
but I receive the message:
"Select a valid choice. That choice is not one of the available choices."
(entry is correct and it works with dropdown menu)
This is my first question here and I'm sorry if I miss the form.
The reason you are getting that error is because your form is still treating the subscriber field as a ModelChoiceField because you are only overriding what widget is rendered to html. You need to change the actual field type of your field. You can define your form like this:
from django.core.exceptions import ValidationError
class TransakcijeForm(forms.ModelForm):
subscriber = forms.CharField()
class Meta:
model = models.Transakcije
fields = ....
labels = ....
def clean_subscriber(self):
subscriber_id = self.cleaned_data['subscriber']
try:
# adjust this line to appropriately get the model object that you need
subscriber = SubscriberModel.objects.get(id=subscriber_id)
return subscriber
except:
raise ValidationError('Subscriber does not exist')
The line subscriber = forms.CharField() will change the form to treat the field as a CharField rather than a ModelChoiceField. Doing this will cause the form to return the subscriber field value as a string, so you will need to get the appropriate model object based on the value of the field. That is what the clean_subscriber(self) function is for. It needs to be named like clean_<field name>(). That function will take the string that is returned by the form, try and find the correct model object and return it if an object is found. If it finds no matching objects it will raise a ValidationError so the form doesn't submit with a bad value.
Currently, if a field is required, this can be enforced via the blank = False argument, such as:
models.py
address1 = models.CharField(max_length=255,null=False,blank=False)
However, the validation is performed prior to the POST action, yielding something like this when trying to submit the form containing an empty field:
I would prefer the validation to be done during the post step, like this:
models.py
address1 = models.CharField(max_length=255,null=False,blank=true)
forms.py
class AddressForm(forms.ModelForm):
def __init__(self,*args,**kwargs):
super(AddressForm,self).__init__(*args,**kwargs)
self.fields['address1'].required = True
And this yields the following result when trying to submit the form containing an empty field:
But the problem with this, (as far as I can tell) is that I need to explicitly state the required attribute for each field on a case-by-case basis.
Is there any way that I can associate blank=False as being representative of the required=True attribute, suppressing the first form validation (above), in favour of the second?
ModelForm runs form validation, then model validation:
There are two main steps involved in validating a ModelForm:
Validating the form
Validating the model instance
So you have to manually add the extra form validation that you want before the inherited model validations.
However, default ModelForm field for blank field is already required:
If the model field has blank=True, then required is set to False on
the form field. Otherwise, required=True
You can change the error message. If you use this additional validations a lot, you can use a Mixin:
class BlankToRequiredMixin(object):
def set_required(self):
model = self._meta.model
for field_name,form_field in self.fields.iteritems():
if not model._meta.get_field(field_name).blank:
form_field.error_messages={'required': 'This field is required'} # to make it required in addtion to non-blank set .required=True
Then, to set required=True for all fields that are non-blank in the model:
class AddressForm(forms.ModelForm,BlankToRequiredMixin):
def __init__(self,*args,**kwargs):
super(AddressForm,self).__init__(*args,**kwargs)
self.set_required()
In a similar way you can add other validations to the form fields, based on the model validation attributes. For the appearance, change the widget and set the field widget in the mixin.
Django ChoiceField "Validates that the given value exists in the list of choices."
I want a ChoiceField (so I can input choices in the view) but I don't want Django to check if the choice is in the list of choices. It's complicated to explain why but this is what I need. How would this be achieved?
You could create a custom ChoiceField and override to skip validation:
class ChoiceFieldNoValidation(ChoiceField):
def validate(self, value):
pass
I'd like to know your use case, because I really can't think of any reason why you would need this.
Edit: to test, make a form:
class TestForm(forms.Form):
choice = ChoiceFieldNoValidation(choices=[('one', 'One'), ('two', 'Two')])
Provide "invalid" data, and see if the form is still valid:
form = TestForm({'choice': 'not-a-valid-choice'})
form.is_valid() # True
Best way to do this from the looks of it is create a forms.Charfield and use a forms.Select widget. Here is an example:
from django import forms
class PurchaserChoiceForm(forms.ModelForm):
floor = forms.CharField(required=False, widget=forms.Select(choices=[]))
class Meta:
model = PurchaserChoice
fields = ['model', ]
For some reason overwriting the validator alone did not do the trick for me.
As another option, you could write your own validator
from django.core.exceptions import ValidationError
def validate_all_choices(value):
# here have your custom logic
pass
and then in your form
class MyForm(forms.Form):
my_field = forms.ChoiceField(validators=[validate_all_choices])
Edit: another option could be defining the field as a CharField but then render it manually in the template as a select with your choices. This way, it can accept everything without needing a custom validator
In an my model, I've the following
--- models.py ---
class A(models.Model):
my_Bs = models.ManyToManyField('B', through='AlinksB')
...
class B(models.Model):
...
class AlinksB(models.Model):
my_A = models.ForeignKey(A)
my_B = models.models.ForeignKey(B)
order = models.IntegerField()
So is the corresponding admin (A admin view has an inline to link B instances, and I prepared the required to custom this inline's formset and forms):
--- admin.py ---
class AlinksBInlineForm(forms.ModelForm):
class Meta:
model = AlinksB
class AlinksBInlineFormset(forms.models.BaseInlineFormSet): # there also is a BaseModelFormset
form = AlinksBInlineForm
class AlinksBInline(admin.TabularInline):
formset = AlinksBInlineFormset
model = AlinksB
class AAdmin(admin.ModelAdmin):
form = AForm
inlines = (AlinksBInline,)
...
class BAdmin(admin.ModelAdmin):
...
Now to custom the forms validation, nothing difficult: just override the "clean" method of the form object. If you want many different forms in the formset, I think you just have to change some manually in the "init" method of the formset. But what about programatically validating all the forms when we clean the formset, and that only under some conditions.
In my case: how to automatically set the "order" field (in the inline of A admin view) with an autoincrement if all the orders (inline rows to remove excluded) are empty ?!
I just spent a lot of time Googling about trying to perform automatic form cleaning during a formset validation in Django Framework. After a few days a couldn't figure a solution so I started looking right into Django's source code to see how work fields, widgets, forms and formsets.
Here is what I understood:
-All the data POSTed by the user when he submits the formset it stored in the "data" attribute of the formset. This attribute is very ugly and cannot be directly used.
- The form is just a wrapper for fields (it calls all the fields' clean methods and fill error buffers, and only a few more)
-The form fields have a widget. This widget allow getting back the field's raw value from the "data" attribute of the formset
form.add_prefix('field name') # returns the 'field prefix', the key of formset.data used to retrieve the field's raw value
form.fields['field name'].widget.value_from_datadict(form.data, form.files, 'field prefix') # returns the raw value
-The form fields also have a method to transform the raw value into a right python value (in my case: order is an integer, or None if the field has been left empty)
form.fields['field name'].to_python(raw_value) # returns a value with the right type
-You can change the value of one of the fields from the formset with the following code
form.data.__setitem__('field prefix', value) # code to update an iterable knowing the key to change
-Once you have modified the fields value, you can call the "full_clean" method of the forms to retry cleaning them (this will remove the previous errors).
-Once you have validated again the forms, you can retry validating the formset with its "full_clean" method too. But take care to avoid infinite loops
-The forms clean data can only be used has a read-only data, to add more error messages in the form or the formset
An other solution would be to manually change the "form.clean_data" attribute, and clean the formset.errors and all the form.errors
Hope it could help somebody in the same situation as me !
Ricola3D
Am I thinking about this all wrong, or am I missing something that's really obvious?
Python's style guide say's less code is better (and I don't think that's subjective... It's a fact), so consider this.
To use forms for all validation means that to write a model field subclass with custom validation, you'd have to:
Subclass models.Field
Subclass forms.Field
Add custom validation to your forms.Field subclass
Set your custom form field as the default form field for the custom model field
Always use a django model form if you want to perform validation
With all validation in the model you'd just have to:
Subclass models.Field
Add custom validation to your models.Field subclass
Now you could use your model field in an API that bypasses all use of web forms, and you'd still have validation at lowest level. If you used web forms, the validation would propagate upwards.
Is there a way to do this without having to write the Django team and wait for them to fix this error?
This sort of thing is already possible in Django's development version:
There are three steps in validating a
model, and all three are called by a
model’s full_clean() method. Most of
the time, this method will be called
automatically by a ModelForm. You should only need to
call full_clean() if you plan to
handle validation errors yourself.
See the docs.
e.g. (as part of your model class):
def clean(self):
from django.core.exceptions import ValidationError
# Don't allow draft entries to have a pub_date.
if self.status == 'draft' and self.pub_date is not None:
raise ValidationError('Draft entries may not have a publication date.')
# Set the pub_date for published items if it hasn't been set already.
if self.status == 'published' and self.pub_date is None:
self.pub_date = datetime.datetime.now()
The ModelForm's validation process will call into your model's clean method, per this:
As part of its validation process,
ModelForm will call the clean() method
of each field on your model that has a
corresponding field on your form. If
you have excluded any model fields,
validation will not be run on those
fields. Also,
your model's clean() method will be
called before any uniqueness checks
are made. See Validating objects for
more information on the model's
clean() hook.
It is already being fixed, and will be in Django 1.2.