Django rest framework serializer: required_fields - python

Is it possible to set required fields as a list inside serializer?
I don't want to override each fields with their type in the each line like this:
name = serializers.CharField(required=True)
description = serializers.CharField(required=True)
date_start = serializers.DateTimeField(required=True)
date_end = serializers.DateTimeField(required=True)
I just want to enumerate the names of fields
class CampaignStepFirstSerializer(serializers.ModelSerializer):
class Meta:
model = Campaign
fields = (
'name',
'description',
'date_start',
'date_end',
)
required_fields = fields

If the model field was instantiated with null=True or blank=True, or if the model field has a default value, the Serializer will automatically set the corresponding field as required=False. (See the source of rest_framework.utils.field_mapping.get_field_kwargs).
If you need to override this behavior, you can do this:
class CampaignStepFirstSerializer(serializers.ModelSerializer):
class Meta:
model = Campaign
fields = (
'name',
'description',
'date_start',
'date_end',
)
# 1st option. If some fields are required
extra_kwargs = {
'name': {'required': True},
'description': {'required': True},
'date_start': {'required': True},
'date_end': {'required': True},
}
# 2nd option. If all the fields are required:
extra_kwargs = {i:{'required': True} for i in fields}

There is no such option in DRF. The closest you can get with Meta is extra_kwargs (assuming you're using serializers.ModelSerializer), with mentioning the field names separately with values being a dict with {'required': True}. But that would be more work than explicitly mentioning required=True while initializing the fields.
You can get what you want with a tiny bit of extension to the get_fields method of serializers.Serializers and any sublclass (e.g. serializers.ModelSerializer):
class CampaignStepFirstSerializer(serializers.ModelSerializer):
def get_fields(self):
fields = super().get_fields()
try:
required_fields = self.Meta.required_fields
except AttributeError:
return fields
if not isinstance(required_fields, (list, tuple)):
raise TypeError(
'The value of `Meta.required_fields` option must be a list or tuple.'
)
for field_name in required_fields:
try:
field = fields[field_name]
except KeyError:
continue
if (
not field.read_only and
field.default is serializers.empty
):
field.required = True
fields[field_name] = field
return fields
class Meta:
model = Campaign
fields = (
'name',
'description',
'date_start',
'date_end',
)
required_fields = fields
As shown, in the Meta class of the serializer class, you can define the required_fields option and those fields will be made as required if they are not read_only and doesn't have a default.
One caveat of this is that, if you have some field defined explicitly on serializer with required=False, and also mentioned the field in Meta.required_fields, the __repr__ will show the required=False (for example, when you'll be checking <serializer_instance>.fields). serializers.Field.__repr__ is defined such that the initial arguments used in the creation of a field are shown as-is. The constructor (Field.__new__) keeps a _kwargs attribute to preserve the initial arguments.
This applies to all the explicitly declared fields (the metaclass serializers.SerailizerMetaclass sets _declared_fields attribute on the serializer class) so using read_only_fields/write_only_fields/extra_kwargs Meta options also don't impact the representation.
If you want, you can override the __repr__ of the field to change this but I don't think you should do so as that will break consistency with the rest of the design.

Related

How can i bind two serializers into separate one serializer django rest framework?

info: One is User and the Second one is User Data like phone number,city etc
Now i can add these two serializer into third serializer and show all data in third one
Problem: now i want to create new Serializer Where i can combine both serializer attributes. but i don't understand how can i achieve to bind two serializer into different one and show all data?
serializers.py
class ContactSerializer(serializers.ModelSerializer):
class Meta:
model = Contact
fields = ['email', 'phone']
class LocationSerializer(serializers.ModelSerializer):
class Meta:
model = Location
fields = ['city']
Want to achieve
class CombineSerializer(serializers.ModelSerializer):
class Meta:
fields = ['email', 'phone', 'city']
You can simply create an object of a LocationSerializer in your contact serializer. This approach will create a nested object of location serializer.
class ContactSerializer(serializers.ModelSerializer):
location = LocationSerializer() #if the contact serializer have multiple city then just add `many=True` as an argument.
class Meta:
model = Contact
fields = ['email', 'phone', 'location']
While listing the object will look like this -
{
'email':'some#email.com',
'phone':'00009999888',
'location':{
'city':'some_city'
}
}
However, if you want to have both objects as a nested object than in that case you can simply call the serializer in the CombineSerializer class. But here instead of using serializers.ModelSerializer you will have to use serializers.Serializer as no model is associated with it.
This is how you can achieve it -
class CombineSerializer(serializers.Serializer):
contact = ContactSerializer()
location = LocationSerializer()
In this case while listing the object will look like this -
{
'contact' : {
'email' : 'some#email.com',
'phone' : '00999887666'
},
'location' : {
'city' : 'Some City'
}
}

How do you create a deferred field with a SerializerMethodField?

I want to create a deferred method field in a model serializer using drf-flexfields.
I am using Django Rest Framework and drf-flexfields. I want to create a method field in my model serializer to make a complex query. Because retrieving this field will incur extra database lookups, I want this to be a deferred field, i.e. it will only be retrieved if the client specifically asks for it.
The DRF-Flexfields documentation seems to infer that a field can be deferred by only listing it in "expanded_fields" and not in the normal "fields" list, but gives no further explanation or example. https://github.com/rsinger86/drf-flex-fields#deferred-fields
I have tried creating a simple SerializerMethodField to test this:
class Phase(models.Model):
name = models.CharField(max_length=100)
assigned = models.ManyToManyField(settings.AUTH_USER_MODEL,
blank=True,
related_name="phases_assigned")
class PhaseSerializer(FlexFieldsModelSerializer):
assignable_users = serializers.SerializerMethodField("get_assignable_users")
expandable_fields = {
'assignable_users': (UserSerializer, {'source': 'assignable_users', 'many': True}),
}
class Meta:
model = Phase
fields = ['name', 'assigned']
def get_assignable_users(self, phase):
return {'test': 'this is a deferred field. It should only shows up when /?expand=assigned_users is given in '
'the api get request url'}
I get the following error :
"The field 'assignable_users' was declared on serializer PhaseSerializer, but has not been included in the 'fields' option."
the desired result would be that a call to the api at /phase/ will return just the default fields specified in the meta "fields" list. "assignable_users" will only get returned if the client specifically asks for it with /phase/?expand=assignable_users.
What is the correct way to go about accomplishing this?
No matter it is a SerializerMethodField, you should add your assignable_users field in Meta fields:
class PhaseSerializer(FlexFieldsModelSerializer):
...
class Meta:
model = Phase
fields = ['name', 'assigned', 'assignable_users']
# _____________________________^
Check the docs for more information.
If you declare a field in the serializer, you must include it in the fields option, as the error says. But if you do that, the field will be shown by default.
So, if you want a deferred field (on demand field), you can declare it in the model as a property:
class MyModel(models.Model):
my_field1 = ...
my_field2 = ...
my_field3 = ...
...
#property
def deferred_field(self):
return 'extra database lookups'
Then, in your serializer, you include it in expandable_fields as serializers.StringRelatedField:
class MySerializer(FlexFieldsModelSerializer):
class Meta:
model = MyModel
fields = ['my_field1', 'my_field2']
expandable_fields = {
'my_field3': (serializers.StringRelatedField),
'deferred_field': (serializers.StringRelatedField),
}
class PhaseSerializer(FlexFieldsModelSerializer):
...
expandable_fields = {
'assignable_users': (serializers.SerializerMethodField, {'read_only': True}),
'assignable_users_m2m': (UserSerializer, {'source': 'assigned', 'many': True}),
}
class Meta:
model = Phase
fields = ['name', 'assigned']
def get_assignable_users(self, phase):
return {'test': '1111'}

django-filter filter on annotated field

class EventViewSet(viewsets.ModelViewSet):
queryset = Event.objects.all()
serializer_class = EventSerializer
def get_queryset(self):
return super().get_queryset().annotate(
is_active=ExpressionWrapper(
Q(start_date__lt=timezone.now()) & Q(end_date__gt=timezone.now()),
output_field=BooleanField()
),
)
search_fields = [
'name',
'short_desc',
'desc',
]
filterset_fields = [
'is_active',
]
I have this ViewSet that I want to filter on an annotated field, normally you can simply just filter on the annotation in django querysets, however the above combined with this serializer:
class EventSerializer(serializers.ModelSerializer):
is_active = serializers.SerializerMethodField()
#staticmethod
def get_is_active(obj):
return obj.is_active
class Meta:
model = Event
fields = [
'timestamp',
'id',
'name',
'short_desc',
'desc',
'start_date',
'end_date',
'is_active',
]
I haven't looked deep into the source code but I'd assume it would do a simple qs.filter for the fields in filterset_fields but I'm getting this beautiful error that fails to explain much(at least to me):
'Meta.fields' contains fields that are not defined on this FilterSet: is_active
Apparently you simply don't add the declared filters on the Meta.fields list. Literally inside the docs and I found out about it by reading the code.
Also, when adding an annotated field to the declared_fields AKA the filterset class body, add a label or django-filters can't produce field label properly and just goes with "invalid name" instead of you know, the field name. Who knows why.

unique_together of two field messes with read_only_fields

I have this code for rating lessons, user and lesson should be added automatically from request authorization and URL:
#views.py
class RatingViewSet(
mixins.ListModelMixin,
mixins.CreateModelMixin,
viewsets.GenericViewSet
):
permission_classes = [permissions.IsAuthenticated]
serializer_class = RatingSerializer
def perform_create(self, serializer):
lessonInstance = Lesson.objects.get(id = self.kwargs['lessonID'])
serializer.save(user=self.request.user, lesson = lessonInstance)
def get_queryset(self):
lessonID = self.kwargs['lessonID']
return Rating.objects.filter(user=self.request.user, lesson=lessonID)
#serializers.py
class RatingSerializer(serializers.ModelSerializer):
class Meta:
model = Rating
fields = ('id', 'lesson','user', 'difficulty')
read_only_fields = ('id', 'user','lesson')
#models.py
class Rating(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
lesson = models.ForeignKey('lessons.Lesson')
difficulty = models.IntegerField()
class meta:
unique_together('user','lesson')
I want to have max 1 rating per user/lesson, hence unique_together('user','lesson'). But there is a problem: as long as that constraint is in the code, requests without user or lesson fields get denied with field required error, even though they are read_only.
(If I migrate with unique_together('user','lesson'), then delete that line it works, but as soon as it's there I get errors.)
I want to keep that bit of code there so I don't accidentally remove the unique_together constraint on later migrations.
This is a special-case that requires a different approach. Here's what django-rest-framework documentation (see the Note) says about this case:
The right way to deal with this is to specify the field explicitly on
the serializer, providing both the read_only=True and default=…
keyword arguments.
In your case, you need to explicitly define the user and lesson fields on your RatingSerializer, like this:
class RatingSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault()) # gets the user from request
lesson = serializers.PrimaryKeyRelatedField(read_only=True, default=None) # or any other appropriate value
class Meta:
model = Rating
fields = ('id', 'lesson','user', 'difficulty')
Good luck!
If a field is read_only=True then the validated_data will ignore data of it => Cause error required field, read more at doc
I also met this issue in a similar context, then tried #iulian's answer above but with no luck!
This combo read_only + default behavior is not supported anymore, check this
I resolved this issue by 2 solutions:
My model:
class Friendship(TimeStampedModel):
"""Model present Friendship request"""
from_user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='friendship_from_user')
to_user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='friendship_to_user')
class Meta:
unique_together = ('from_user', 'to_user')
Solution 1. Write your own CurrentUserDefault class to get the user id then set to default attribute data of serializer(Ref from #51940976)
class CurrentUserDefault(object):
def set_context(self, serializer_field):
self.user_id = serializer_field.context['request'].user.id
def __call__(self):
return self.user_id
class FriendshipSerializer(serializers.ModelSerializer):
from_user_id = serializers.HiddenField(default=CurrentUserDefault())
class Meta:
model = Friendship
fields = ('id', 'from_user', 'from_user_id', 'to_user', 'status')
extra_kwargs = {
'from_user': {'read_only': True},
}
Solution 2. Override the create method of serializer to set data for user id(Ref from this)
class FriendshipSerializer(serializers.ModelSerializer):
class Meta:
model = Friendship
fields = ('id', 'from_user', 'to_user', 'status')
extra_kwargs = {
'from_user': {'read_only': True},
}
def create(self, validated_data):
"""Override create to provide a user via request.user by default.
This is require since the read_only `user` filed is not included by default anymore since
https://github.com/encode/django-rest-framework/pull/5886.
"""
if 'user' not in validated_data:
validated_data['from_user'] = self.context['request'].user
return super(FriendshipSerializer, self).create(validated_data)
I hope this helps!

Django REST Framework nested resource key "id" unaccessible

So I have the following Structure:
A ClientFile belongs to an Owner (class name = Contact).
I'm trying to create a Clientfile using the API. The request contains the following data:
{
name: "Hello!"
owner: {
id: 1,
first_name: "Charlie",
last_name: "Watson"
}
}
I created the serializer according to my structure. Hoping that this API call would create a clientfile with the name "Hello!" and Contact id 1 as the owner:
class ContactSerializer(serializers.ModelSerializer):
class Meta:
model = Contact
fields = (
'id',
'first_name',
'last_name',
)
class ClientfileSerializer(serializers.ModelSerializer):
owner = ContactSerializer(read_only=False)
class Meta():
model = Clientfile
fields = (
'id',
'name',
'owner',
)
def create(self, validated_data):
owner = Contact.objects.get(pk=validated_data['owner']['id'])
I do get into the create method. However, the only field I need (['owner']['id']) is not accessible. If I do print ['owner']['first_name'] it does return 'Charlie'. But the ID for some reasons doesn't seem to be accessible...
Any reasons why this can be happening? Am i missing something? (I'm new to Django)
SOLUTION: Just found out that the reason why ID didn't show in the first place was because I had to declare it in the fields like so: Hope this helps.
class ContactSerializer(serializers.ModelSerializer):
id = serializers.IntegerField() # ← Here
class Meta:
model = Contact
fields = (
'id',
'first_name',
'last_name',
)
In Django REST Framework AutoField fields (those that are automatically generated) are defaulted to read-only. From the docs:
read_only
Set this to True to ensure that the field is used when
serializing a representation, but is not used when creating or
updating an instance during deserialization.
Defaults to False
You can see this by inspecting your serializer by printing the representation in your shell:
serializer = ClientfileSerializer()
print repr(serializer)
You can override this by setting read_only=False against the id field in the extra_kwargs:
class ContactSerializer(serializers.ModelSerializer):
class Meta:
model = Contact
fields = (
'id',
'first_name',
'last_name',
)
extra_kwargs = {'id': {'read_only': False}}
class ClientfileSerializer(serializers.ModelSerializer):
owner = ContactSerializer(read_only=False)
class Meta():
model = Clientfile
fields = (
'id',
'name',
'owner',
)
extra_kwargs = {'id': {'read_only': False}}
Alright so I found a different approach that works.
I added an IntegerField serializer for the owner relation. I also had to set the owner relation to read_only=True.
This is the json I am sending via POST:
{
name: "Hello!"
owner_id: 1
}
This is my serializer:
class ClientfileSerializer(serializers.ModelSerializer):
owner_id = serializers.IntegerField()
owner = ContactSerializer(read_only=True)
class Meta():
model = Clientfile
fields = (
'id',
'owner_id',
'owner',
)
It seems less cool than the first way, but it does the job.
Plus I don't want to create a new owner, but just select one that is already in the database. So maybe it's more semantic to only have the ID and not the full set of information posted via Json.
You can try something like this:
class YourModelSerializer(serializers.ModelSerializer):
class Meta:
model = YourModel
fields = ('id', 'field1', 'field2')
def to_internal_value(self, data):
"""
Dict of native values <- Dict of primitive datatypes.
Add instance key to values if `id` present in primitive dict
:param data:
"""
obj = super(YourModelSerializer, self).to_internal_value(data)
instance_id = data.get('id', None)
if instance_id:
obj['instance'] = YourModel.objects.get(id=instance_id)
return obj
Then in serializer validated data you should have "instance" key if request.data has "id" key.
Also You can add just "id" instead of full instance/object.
The top voted answer does solve the issue but it raises a new one as mentioned in the comments we can no longer create a new record as it will thrown ac exception saying is required. We can set id to required=False then id will be available in validated_data and it wont be required to set it manually
id = serializers.IntegerField(required=False) <- Like this
class Meta:
model = Details
fields = ('id', 'product_name', 'description', 'specification', 'make_model',
'brand', 'quantity',)

Categories

Resources