I am trying to move all business-logic-related validations to models, instead of leaving them in forms. But here I have a tricky situation, for which I like to consult with the SO community.
In my SignupForm (a model form), I have the following field-specific validation to make sure the input email does not exist already.
def clean_email(self):
email = self.cleaned_data['email']
if ExtendedUser.objects.filter(email=email).exists():
raise ValidationError('This email address already exists.')
return email
If I were to move this validation to the models, according to the official doc, I would put it in clean() of the corresponding model, ExtendedUser. But the doc also mentions the following:
Any ValidationError exceptions raised by Model.clean() will be stored
in a special key error dictionary key, NON_FIELD_ERRORS, that is used
for errors that are tied to the entire model instead of to a specific
field
That means, with clean(), I cannot associate the errors raised from it with specific fields. I was wondering if models offer something similar to forms' clean_<fieldname>(). If not, where would you put this validation logic and why?
You could convert your clean method into a validator and include it when you declare the field.
Another option is to subclass the model field and override its clean method.
However there is no direct equivalent of defining clean_<field name> methods as you can do for forms. You can't even assign errors to individual fields, as you can do for forms
As stated in the comment I believe you should handle this validation at the modelform level. If you still feel like it would be better to do it closer to the model, and since they can't be changed, I would advise a change directly at the db level:
ALTER TABLE auth_user ADD UNIQUE (email)
Which is the poor way to add the unique=True constraint to the User model without monkey patching auth.
As requested, I think that a good way to go about customizing different forms should be done by inheriting from a base modelform. A good example of this is found in django-registration. The only difference is that instead of the parent form inheriting from forms.Form you would make it a modelForm:
class MyBaseModelForm(ModelForm):
class Meta:
model = MyModel
You could then inherit from it and make different forms from this base model:
class OtherFormWithCustomClean(MyBaseModelForm):
def clean_email(self):
email = self.cleaned_data['email']
if ExtendedUser.objects.filter(email=email).exists():
raise ValidationError('This email address already exists.')
return email
Related
I have a model
class SomeModel(models.Model):
emails = ArrayField(models.EmailField(), default=list)
And let's say I have the following Serializer of the model:
class SomeModelSerializer(serializers.ModelSerializer):
class Meta:
model = SomeModel
fields = ['emails']
The email field is not blank-able, i.e: It's required to set a value for it when submitting a Form of the model, or when making changes to its Admin page.
My understanding is that DRF relies as well on Django's internal machinery to validate whether emails is missing on the Serializer data or not. But the thing is that I can't find where (and when) this happens.
I've found that DRF is not calling the Model's clean() method anymore (link). But what baffles me is that changing the blank value on the field seems to have a direct impact on the Serializer. I have switched to blank=True, and then the Serializer would allow it to be saved without that field... Then I switched back to blank=False, and the Serializer would fail if emails is not present.
So do you have any idea of when and where DRF checks for a field's blank value?
Thanks!
As far as I know, it simply doesn't. Those are only used across forms and the django admin interface.
I always specify those things on the serializer level, by setting the appropiate arguments for my fields (doc), in this case it would be allow_blank.
I am building REST APIs with django, and the only case where the blank property on the model field catches me, is when fiddling around on the admin page.
However, there appears to be a package that could be of interest to you:
django-seriously.
I haven't used it, but it appears to call full_clean() on every save().
Of course, this has the disadvantage that you will probably loose DRFs nice error messages.
I have a simple Model that stores the user that created it with a ForeignKey. The model has a corresponding ModelSerializer and ModelViewSet.
The problem is that when the user submits a POST to create a new record, the user should be set by the backend. I tried overriding perform_create on the ModelViewSet to set the user, but it actually still fails during the validation step (which makes sense). It comes back saying the user field is required.
I'm thinking about overriding the user field on the ModelSerializer to be optional, but I feel like there's probably a cleaner and more efficient way to do this. Any ideas?
I came across this answer while looking for a way to update values before the control goes to the validator.
This might be useful for someone else - here's how I finally did it (DRF 3) without rewriting the whole validator.
class MyModelSerializer(serializers.ModelSerializer):
def to_internal_value(self, data):
data['user'] = '<Set Value Here>'
return super(MyModelSerializer, self).to_internal_value(data)
For those who're curious, I used this to round decimal values to precision defined in the model so that the validator doesn't throw errors.
You can make the user field as read_only.
This will ensure that the field is used when serializing a representation, but is not used when creating or updating an instance during deserialization.
In your serializers, you can do something like:
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
extra_kwargs = {
'user' : {'read_only' : True} # define the 'user' field as 'read-only'
}
You can then override the perform_create() and set the user as per your requirements.
Old topic but it could be useful for someone.
If you want to alter your data before validation of serializer:
serializer.initial_data["your_key"] = "your data"
serializer.is_valid(raise_exception=True)
I've overloaded admin form for a model by adding an extra-field
class MyModelAdminForm(forms.ModelForm):
password = forms.CharField(widget=forms.PasswordInput(), required=False)
class Meta:
model = MyModel
The password field isn't exists in model and I don't want it to be stored automatically.
I want to retrieve the value of this form field in the pre_save method :
#receiver(pre_save, sender=Member)
def my_pre_save_method(sender, **kwargs):
...
Actually I don't find a way to retrieve it.
Is this possible ? And How ?
Thanks
I don't full understand what you're asking here.
Anyhow, your question is far too general to answer in full. As an overview you only 'pass' clean form data in a view i.e. when you create an instance of that object. In your case this would be 'Member'.
I would suggest you start learning Django with the tutorials. They really do help, honest.
pre_save is called from model's save and it works on form level. The field password is not a model field so this will not be available on the instance of model and hence you can't access it in pre_save.
So, you can only retrieve is in the view using cleaned_data, and then use it in some way you want.
I am using a django DateField in my model.
class CalWeek(models.Model):
start_monday = models.DateField(verbose_name="Start monday")
I have a custom validation method that is specific to my modelField: I want to make sure that it is actually a Monday. I currently have no reason to use a custom ModelForm in the admin--the one Django generates is just fine. Creating a custom form class just so i can utilize the clean_start_monday(self)1 sugar that django Form classes provide seems like a lot of work just to add some field validation. I realize I can override the model's clean method and raise a ValidationError there. However, this is not ideal: these errors get attributed as non-field errors and end up at the top of the page, not next to the problematic user input--not an ideal UX.
Is there an easy way to validate a specific model field and have your error message show up next to the field in the admin, without having to use a custom form class?
You can look into Django Validators.
https://docs.djangoproject.com/en/dev/ref/validators/
You would put the validator before the class, then set the validator in the Field.
def validate_monday(date):
if date.weekday() != 0:
raise ValidationError("Please select a Monday.")
class CalWeek(models.Model):
start_date = models.DateField(validators=[validate_monday])
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.