Django Rest Framework model serializer with out unique together validation - django

I have a model with some fields and a unique together:
....
class Meta(object):
unique_together = ('device_identifier', 'device_platform',)
Obviously, in this way, about Django rest framework serializer, I obtain an error when I try to make a PUT with the same device_identifier and device_platform (if already exist an entry with this data).
{
"non_field_errors": [
"The fields device_identifier, device_platform must make a unique set."
]
}
Is possible to disable this validation in my model serializer?
Because I need to manage this situation during save model step (for me, in serializer validation this is not an error)

Django REST framework applies the UniqueTogetherValidator on the serializer. You can remove this by override the validators field in the serializer definition.
class ExampleSerializer(serializers.ModelSerializer):
class Meta:
validators = []
Note that this also removes the other unique-check validators that are applied on the model, which might not be the best idea. To avoid that, just override the get_unique_together_validators method on the serializer, to ensure only unique-together check is removed.
class ExampleSerializer(serializers.ModelSerializer):
def get_unique_together_validators(self):
"""Overriding method to disable unique together checks"""
return []

You need to remove the validator from the serializer's list.
Although not exactly the same, the steps are explained here

Related

Using inline in ModelForm in Django admin to validate many-to-many inline field (by overriding the clean() method)

I would like to validate a Many-to-many field in Django admin by overriding the clean method.
This thread gives a way to do that by creating a ModelForm and then doing the clean there. However, my problem is the many-to-many field is an inline i.e. instead of the widget where you have to select multiple elements, I have a tabular inline.
I would like to find out if anyone knows how to add the inlines in the ModelForm so that I can do the clean and validation. I've seen people talk about inlineformset_factory but it's always been as it relates to views.py and not the admin (and I can't figure out how I'd even go about overriding the clean method of that).
I've added some of my code below:
class ProductVariantForm(ModelForm):
class Meta:
model = ProductVariant
fields = [ 'name',
'price',
]
# I then want to be able to add something like
# inlines = [OptionValueInline,]
# for the inline many-to-many field.
def clean(self):
# Check if list of option_values is ok.
class ProductVariantAdmin(admin.ModelAdmin):
form = ProductVariantForm
Adding an inline is a feature of the Admin itself. See this doc for more info about inlines. Afaik, you can't add an inline to just a plain form (or a ModelForm).
To check the validity of the data in an inline, you could use the form property of the InlineModelAdmin class. This way you can access the clean method of the inline form directly.
To elaborate, it is split this way because the inlines are a separate form in Django's terms, concerning different data and running separate queries. They are all submitted in one HTTP request, but that is all they have in common. So it doesn't really make sense to use the main ModelForm for the inline data.
My solution to the problem is based on this post.
class ProductVariantOptionValueInlineFormSet(BaseInlineFormSet):
def clean(self):
super().clean()
data = self.cleaned_data
# do whatever validation on data here
class ProductVariantOptionValueInline(admin.TabularInline):
model = ProductVariant.option_values.through
formset = ProductVariantOptionValueInlineFormSet
class ProductVariantAdmin(admin.ModelAdmin):
inlines = [
ProductVariantOptionValueInline,
]
exclude = ('option_values',)

Django Rest Framework group fields

I'm exposing an REST api for legacy application.
I have a Company model class that defines the following fields:
address_street (required)
address_street_number (required)
shipping_address_street (optional)
shipping_address_street_number (optional)
billing_address_street (optional)
... you got the point
I would like to group all address fields into an Adress serializer in order to have a cleaner structure.
Some thing like:
{
"adress": {"street": "foo", "street_number": "bar"},
"shipping_address": {"street": "ham", "street_number": "spam"},
"billing_address": null,
}
So far, I can create a CompanySerializer from a rest_framework.serializers.Serializer and manually build my Company objects from this.
It is tedious, but it will work.
But how can I build a rest_framework.serializers.ModelSerializer for my Company model, changing the way fields are structured to have my model fields automatically populated by rest framework ?
DRF nested model serializers seems to only work for relations, not groups of fields.
Am I to build my models instances by hands or is there a way to decouple the representation of a model serializer to my object ?
From ModelSerializer documentation
The process of automatically determining a set of serializer fields
based on the model fields is reasonably complex
You probably should stick to the "tedious" method you mention (you will have to put in some effort if serializer representation and model fields have different structures altogether).
ModelSerializer is tightly linked to the model in question, so overriding that behaviour seems to be for little benefit when you can do the same thing using a plain Serializer and put object creation under save.
Maybe you need to override the data property/method on the Serializer subclass so that you get a dict that is fit for consumption directly by the model, that might make it less tedious
You can build custom serialiser fields with SerializerMethodField:
from rest_framework.fields import SerializerMethodField
class AdressSerializer(ModelSerializer):
adress = SerializerMethodField()
shipping_address = SerializerMethodField()
def get_adress(self, instance):
return {
"street": instance.address_street,
"street_number": instance.address_street_number
}
def get_shipping_address(self, instance):
// same approach as above
If needed to populate the model from the same data representation, the best approach is to override serialiser's save method. I don't think there is an "automatic" way of doing it.

Django Rest Framework, ModelSerializers and custom fields validation

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.

DRF PrimaryRelatedField when write and NestedSerializer when read?

I am using a nested serializer. I need ProfileSerializer to return full related Project object for get requests and consider only id switching (changing current) like with relatedPrimaryField behaiviour for post/put requests on ProfileSerializer. any solutions on how to achieve this ?
class ProfileSerializer(serializers.ModelSerializer):
current = ProjectSerializer()
class Meta:
model = Profile
fields = ('function', 'current')
As Linova mentioned, the easiest way to solve this issue without using a third-party library is to declare two separate fields in your serializer. Your nested serializer current would stay the same, but you would add a new PrimaryKeyRelatedField serializer. The nested serializer should be read only, but the related field would not be read only. I normally name the related field <field>_id by convention.
In GET requests, both the nested serializer and the id field will be returned, but for PUT or POST requests only the <field>_id needs to be specified.
class ProfileSerializer(serializers.ModelSerializer):
current = ProjectSerializer(read_only=True)
current_id = serializers.PrimaryKeyRelatedField(queryset=Projects.objects.all(), source='current')
class Meta:
model = Profile
fields = ('function', 'current', 'current_id')
The most consistent way I usually advice is to mark all the nested serializer (ProjectSerializer in this case) as read_only and add the id field as read_only=False
You'll therefore have consistence between the list/retrieve and creation/updates.

Modify data before validation step with django rest framework

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)