Django Rest Framework group fields - django

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.

Related

Different Field types of model class

I'm trying to provide for users possibility to store in SomeModel a list of ExtraParameters of any number or kind (it may be something small like IntegerField, BooleanField or quite large like TextField).
I tried to implement ExtraParameter abstract model class, that will keep ForeignKey to SomeModel, and also its child classes with only one parameter like:
class ExtraParameter(models.Model):
somemodel = models.ForeignKey(SomeModel, ...)
class Meta:
abstract = True
class IntegerExtraParameter(ExtraParameter):
value = models.IntegerField()
I believe it takes multiple small classes like this one so it could be migrated to multiple database tables of different fields.
Am I right? Please provide better solution. Maybe other way to decorate ExtraParameter is possible?
The problem with this approach is while implementing template it is not so easy to get all the stored parameters of all kind by doing somemodel.extraparameters.all(), rather I need to call every child class explicitly and build set from it. But also I've seen a solution with finding all subclasses of any class inside app's config, so it would help.
Jakub
I think the answer you are looking for is covered in great depth in here:
https://stackoverflow.com/a/7934577/
Personally I like the way Django and JsonField works together.
https://docs.djangoproject.com/en/3.1/ref/models/fields/#django.db.models.JSONField
JsonField would give you vast flexibility with dynamic metadata.
In this example I'm simply attaching a JsonField to those models that are in need of dynamic meta data. extra_information could then hold data about what kind of values is in relation with the parent model.
class ExtraInformationModel(models.Model):
# Abstract model that adds json field for additional meta data
extra_information = JSONField()
class Meta:
abstract = True
class SomeModel(ExtraInformationModel):
# extra_information field comes from the abstract model
...usual model stuff..
Making queries with JsonFields is as easy as using Django orm usually is.
https://docs.djangoproject.com/en/3.1/topics/db/queries/#querying-jsonfield
(example from Django documentation)
Dog.objects.create(name='Rufus', data={
'breed': 'labrador',
'owner': {
'name': 'Bob',
'other_pets': [{
'name': 'Fishy',
}],
},
})
Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': None})
Dog.objects.filter(data__breed='collie')

Django Rest Framework model serializer with out unique together validation

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

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.

How can I update two models in one serializer in Django Rest Framework?

I have a database schema that has each object of a certain type being stored across two separate tables (one row in each table, different data in each, with a foreign key from one to the other.)
Unfortunately, Django Rest Framework tends to assume that there is a one to one correspondence between serializers and models, which is not true of my case. How should I be approaching this? It seems like the serializer should return the representation of the object which will be the actual HTTP response of the ajax requests, so using two serializers doesn't seem right. I've looked at extending BaseSerializer (which is how I currently plan to implement this if I don't find better solutions), but certain methods take in an instance, which should contain all the data needed to serialize the object, whereas I have two instances relevant.
Any advice would be super appreciated! Thank you.
Writable nested representations section might help you.
You have 2 models ModelA and ModelB. Create your first model's serializer
class ModelASerializer(serializers.ModelSerializer):
class Meta:
model = ModelA
fields = ('fields',..) #
Then in other model's serializer add the first serializer and override the required methods (like create, update). Something like this:
class ModelBSerializer(serializers.ModelSerializer):
# add the serializer for the foreignkey model
model_a = ModelASerializer()
class Meta:
model = ModelB
fields = ('fields',..) #
def create(self, validated_data):
modela_data = validated_data.pop('model_a')
model_b = ModelB.objects.create(**validated_data)
ModelA.objects.create(model_b=model_b, **modela_data)
return model_b
# override update too ..

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)