My model has a type attribute and get_required_fields method.
get_required_fields returns a list of required fields based on self.type which I then use in full_clean method to validate the model before saved
for field in self.get_required_fields():
if getattr(self, field, None) is None:
missing_fields.append(field) # todo append verbose_name
if missing_fields:
raise ValidationError({x: "Povinné pole pre daný druh" for x in missing_fields})
The problem is that this doesn't work for DRF model serializers. I've overriden validate method this way:
def validate(self, attrs):
Income(**attrs).full_clean()
return super().validate(attrs)
But then I realized this will not work for PATCH method and probably also not for POST with missing fields
How can I make it work for both model and DRF ModelSerializer while using the same code to keep it DRY?
Related
I'm using Django 2.2 and Django REST framework.
In the serializer, I want to have few fields only for creating an instance and not for updating. Means, the user won't be able to change the value of that field once created.
Since Django does not provide any default way to do it. I am writing a mixin which when used will use the create_only_fields from the serializer Meta to remove the fields from the request data with PUT/PATCH request.
serializer_mixin.py
class CreateOnlyFieldsMixin(object):
def to_internal_value(self, data):
data = super().to_internal_value(data)
print(data)
if self.instance:
# Update request
print(data)
for x in self.Meta.create_only_fields:
if x in data:
data.pop(x)
return data
and using in the serializer like
class MySerializer(CreateOnlyFieldsMixin, serializers.ModelSerializer):
class Meta:
model = MyModel
fields = [
'id',
'name',
'value'
]
create_only_fields = ['name']
But now calling any endpoint, it gives an error
Cannot call `.is_valid()` as no `data=` keyword argument was passed when instantiating the serializer instance.
Putting CreateOnlyFieldsMixin after serializers.ModelSerializer gives no error but has no effect of the mixin.
Appending to_internal_value directly in the serializer is working as expected.
Using latest version of Django and DRF.
I have a rather complex requirement I can't find a solution for. I'll try to simplify it.
Let's say I have a model that has two fields. field_a and field_b
I have a ModelSerializer for it. I POST a request with its fields. The fields get validated with the model and then against my two serializer functions validate_field_a and validate_field_b. All is well.
Now I'd like my POST request to include a third field that is not a member of that model. let's call it field_c. I have a custom def create(self, validated_data): in my serializer which saves everything to the database.
with regards to field_c I would like to:
Custom Validate it. just like I do with the other two fields.
Require that it is mandatory for the whole request to succeed and if it's not, issue a "Field is required" error just like if I forgot to POST one of my required model fields.
Have the chance to take field_c and save it onto a totally different unrelated Model's row in the db.
I can't seem to get around that. If I add field_c to the fields meta - it throws an exception saying justifiably that field_c is not in my model. If I don't include it in fields, the validate_field_c which I really want to put there doesn't even get called.
What can I do?
You can add the custom field in your serializer as a write_only field and override the create method so that you can handle the custom field's value.
Something like this:
class MySerializer(serializers.ModelSerializer):
field_c = serializers.CharField(write_only=True)
class Meta:
model = MyModel
fields = ('field_a', 'field_b', 'field_c')
def validate_field_c(self, value):
if value is 'test':
raise ValidationError('Invalid')
return value
def create(self, validated_data, **kwargs):
field_c = validated_data.pop('field_c')
return MyModel.objects.create(**validated_data)
Don't use ModelSerializer for this - use a serializer that recreates the same fields as your model & include field_c as you would.
I understand that you want your model to do some of the work in the validation process but the design of DRF is such that it isolates these responsibilities. You can read more about it here. Basically, the serializer should be the one doing all the validation heavy-lifting.
Of course, this means that you'll have to explicitly define the validation methods in the serializer.
In your custom create() method you can create the model instance or do whatever you want in it as required.
I'm facing a validation problem.
I need to use form validation and model validation together, but django (1.10) doesn't seem to like this.
Here is a short version of my setup:
class MyModel(models.Model):
fk = models.ForeignKey('ap.Model')
foo = models.CharField(max_length=12)
def clean(self):
if self.fk.som_field != self.foo:
raise ValidationError("This did not validate")
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ('fk',)
def view(request):
instance = MyModel(foo='bar')
form = MyModelForm(data=request.POST, instance=instance)
if form.is_valid():
# process
# redirect
# display template
So I have some model field validation in the model itself.
I need this here because it is re-used in other non-form related parts of my application.
And I have some user input validation in the form.
In this case, checking that the provided fk is valid and exists.
But when the form is validated and the user provided 'fk' is not valid, the form is rejected.
But the form also calls MyModel.full_clean add more model validation.
The problem is that calling MyModel.clean() without having any data in the field fk will raise a RelatedObjectDoesNotExist exception.
How can I do this the proper way ?
I feel that MyModel.full_clean() should not be called by the form until the form itself is valid to ensure that the model is validated with at least correct field types in it.
I could embed my MyModel.clean operations in a try/except that would catch a RealtedObjectDoesNotExist, but it has a bad code smell for me.
The fact that MyModel.fk exists should be ensured by the first form layer validation.
As you have found, the model form performs the instance validation (by calling self.instance.full_clean()`) whether or not the form is valid.
You could prevent this behaviour (I might try to override the _post_clean, but I think it's simpler to take account of the behaviour in the clean method.
Rather than catching the exception, it would be simpler to check that self.fk_id is not None before accessing self.fk.
class MyModel(models.Model):
fk = models.ForeignKey('ap.Model')
foo = models.CharField(max_length=12)
def clean(self):
if self.fk_id is not None and self.fk.som_field != self.foo:
raise ValidationError("This did not validate")
Django 1.10
The documentaion says that ModelForm validates unique together if clean method is correctly overridden.
https://docs.djangoproject.com/en/1.10/topics/forms/modelforms/#overriding-the-clean-method
I have done something wrong as unique_together validation doesn't work.
>>> from wiki.models import Wiki
>>> Wiki.objects.all()
<QuerySet [<Wiki: image/1/fdfff>, <Wiki: image/1/fdffff>]>
Where image is related_model and 1 is related_id.
Could you help me understand what is wrong with this overriding?
class WikiForm(ModelForm):
class Meta:
model = Wiki
fields = ['related_model', 'related_id', 'article']
unique_together = (("related_model", "related_id"),)
def validate_related_model(self):
...
def validate_related_id(self):
...
def clean(self):
self.validate_related_model()
self.validate_related_id()
# To maintain unique_together validation,
# we must call the parent class’s clean() method.
return super(WikiForm, self).clean()
unique_together is a database-level constraint. It's supposed to be specified in the Meta class of the model, not the model form. For the validation to work as you would like, move it to the Wiki model. In your form, you probably won't even need to have those extra validation methods.
This doesn't look like it'd be the case for you, but also note that in order for the unique validation to work correctly, your model form must include all of the fields that are specified in the unique_together constraint. So, if related_model or related_id were excluded from the form, you'd need to do some extra work in order to allow the correct validation to happen:
def validate_unique(self):
# Must remove organization field from exclude in order
# for the unique_together constraint to be enforced.
exclude = self._get_validation_exclusions()
exclude.remove('organization')
try:
self.instance.validate_unique(exclude=exclude)
except ValidationError, e:
self._update_errors(e.message_dict)
In the example above I am removing organization from the form's list of excluded fields because it's part of the unique_together constraint.
I am using standard Django Models and ModelForms.
On the Model, I am overriding the clean() method to check that 2 fields in combination are valid. I am having validators[], not nulls etc defined on other fields.
In the ModelForm, I am not modifying anything. I only set the corresponding Model in the ModelForm's Meta class.
When I try to create a new object through the form, the individual fields in the ModelForm/Model are not validated when I call form.is_valid() in the View. According to the docs (https://docs.djangoproject.com/en/1.6/ref/models/instances/#validating-objects) the is_valid() method on the Form should call the Model's clean_fields() method (first).
This doesn't seem to work when you submit a form without a Model instance (or a new instance not in the db). When I'm editing an existing object, all is well. It nicely triggers invalid values in individual fields before calling the Model's clean() method.
When I remove the overridden clean() method from my Model, all is well. Individual fields are validated both when creating new objects and editing existing ones.
I have also tested this with the admin module's forms. It has exactly the same behaviour.
So the question is, why does overriding the clean() method on my Model prevent the ModelForm validating the individual fields before calling the clean() method to test additional cross-field stuff???
Note that I am not validating the ModelForm. All validation is on the Model itself.
Model:
class Survey(models.Model):
from_date = models.DateField(null=False, blank=False, validators=[...])
to_date = models.DateField(null=False, blank=False, validators=[...])
(...)
def clean(self):
errors = []
# At this point I expect self.to_date already to be validated for not null etc.
# It isn't for new Model instances, only when editing an existing one
if self.to_date < self.from_date:
errors.append(ValidationError("..."))
ModelForm:
class TestForm(ModelForm):
class Meta:
model = Survey
View (to render blank form for new Model data entry):
(...)
if request.method == "POST":
survey_form = TestForm(request.POST)
if '_save' in request.POST:
if survey_form.is_valid():
survey_form.save()
return HttpResponseRedirect(next_url)
else:
return HttpResponseRedirect(next_url)
else:
survey_form = TestForm()
context = {'form': survey_form}
(...)