I'm slowly learning how to work with rest framework and I'm stuck in one part I don't really understand(my english isn't great either). I have this api point: building for which I show some data on api/building/ but I want a certain field to appear only on api/building/1 (1=pk number) and I cannot figure this out how.
Here is my serializer code so far:
class FloorSerializer(serializers.ModelSerializer):
class Meta:
model = Floor
fields = ('number',
'title')
class BuildingSerializer(serializers.HyperlinkedModelSerializer):
location = serializers.CharField(source='location_address')
avg_temperature = serializers.SerializerMethodField('_get_avg_temperature')
avg_humidity = serializers.SerializerMethodField('_get_avg_humidity')
occupancy_level = serializers.SerializerMethodField('_get_occupancy_level')
floors = FloorSerializer(many=True, read_only=True)
class Meta:
model = Building
fields = ('pk',
'title',
'image_url',
'location',
'campus_name',
'avg_temperature',
'avg_humidity',
'occupancy_level',
'floors')
def _get_avg_temperature(self, obj):
# magia filtrului per buildingu asta.
temp = SensorData.objects.filter(sensor__room__floor__building__pk=obj.pk).filter(sensor__type='TP')\
.aggregate(Avg('value'))
return temp
def _get_avg_humidity(self, obj):
# magia filtrului per buildingu asta.
hum = SensorData.objects.filter(sensor__room__floor__building__pk=obj.pk).filter(sensor__type='HU')\
.aggregate(Avg('value'))
return hum
def _get_occupancy_level(self, obj):
ocup = randint(45, 65)
return ocup
the field in question is floors. I want to show it only on api/building/pk level and while I read the documentation it is not quite clear to me.
Here is a great answer demonstrating what you should do:
https://stackoverflow.com/a/22755648/2402929
In summary, you should create a serializer that will contain all your methods and fields you want in the list route (/api/building/), then extend that serializer, adding the additional fields you want in the detail routes (/api/building/:pk)
Example:
class BuildingSerializer(serializers.HyperlinkedModelSerializer):
# additional methods for fields that will be
# inherited in the DetailBuildingSerializer
class Meta:
model = Building
fields = ('pk',
'title',
'image_url',
'location',
'campus_name',
'avg_temperature',
'avg_humidity',
'occupancy_level',
)
class DetailBuildingSerializer(BuildingSerializer):
class Meta:
model = Building
fields = ('pk',
'title',
'image_url',
'location',
'campus_name',
'avg_temperature',
'avg_humidity',
'occupancy_level',
'floors'
)
Later on, separate serializers in your viewset (assuming you are using viewsets):
class BuildingViewset(viewsets.ModelViewSet):
def get_serializer_class(self):
if self.action == 'list':
return serializers.BuildingSerializer
if self.action == 'retrieve':
return serializers.BuildingDetailSerializer
Related
I'm using Django 3.0 and I have a serializer using django-rest-framework. Let's say that for example I have a Forum object. Each forum has an owner that is a user.
In my GET /forums/ endpoint, I'd like to just have the owner_id. However, in my GET /forums/<forum_id>/ endpoint I'd like to return the entire embedded object.
Is there any way to have one serializer support both of these scenarios? If not, I would hate to have to make two serializers just to support this.
class ForumSerializer(serializers.ModelSerializer, compact=True):
if self.compact is False:
owner = UserSerializer(source='owner', read_only=True)
else:
owner_id = serializers.UUIDField(source='owner_id')
...
How can I achieve this compact thing?
class Meta:
fields = [...]
read_only_fields = ['owner', 'owner_id']
You can add a SerializerMethodField like this:
class ForumSerializer(serializers.ModelSerializer):
owner = serializer.SerializerMethodField()
def get_owner(self, obj):
if self.context['is_compact'] == True:
return obj.owner.pk
else:
return UserSerializer(obj.owner).data
class Meta:
model = YourModel
fields = '__all__'
# Usage in view
serializer = ForumSerializer(context={'is_compact':True})
I am passing is_compact value through serializer's extra context.
create two serializer classes
class ForumSerializerId(ModelSerializer):
class Meta:
model = Forum
fields = ['forum_id']
class ForumSerializerDetail(ModelSerializer):
class Meta:
model = Forum
on your view.py
forums(request):
forum_list = Forum.objects.all()
forum_serializer = ForumSerializerId(forum_list,many=True)
return Response({"form":forum_serializer.data})
forum_detail(request,pk):
forum = get_object_or_404(Forum,pk)
forum_serializer = ForumSerializerDetail(forum)
return Response({"form":forum_serializer.data})
I want the complete related model on GET and use the id's on CREATE, UPDATE and DELETE.
I try to use to_representation.
So i want to create an array of dicts called users which should show the complete users.
But i get the error "unhashable type: 'ReturnDict'" when i add the dict in the object, it works fine if i would do it for a single user by writing to the array directly.
class CompanySerializer(serializers.ModelSerializer):
#users = UserSerializer(many=True)
created_at = serializers.DateTimeField()
updated_at = serializers.DateTimeField()
class Meta:
model = Company
fields = ['id', 'name', 'street', 'city', 'postal_code', 'state', 'company_form', 'users', 'created_at', 'updated_at']
def to_representation(self, instance):
representation = super(CompanySerializer, self).to_representation(instance)
representation['company_form'] = CompanyFormSerializer(instance.company_form).data
representation['users'] = []
for entry in instance.users.all():
user = {UserSerializer(entry).data}
representation['users'].extend(user)
return representation
There is no need to do this manually, you can add serializers to your serializer, like:
class CompanySerializer(serializers.ModelSerializer):
users = UserSerializer(read_only=True, many=True)
company_form = CompanyFormSerializer()
created_at = serializers.DateTimeField()
updated_at = serializers.DateTimeField()
class Meta:
model = Company
fields = ['id', 'name', 'street', 'city', 'postal_code', 'state', 'company_form', 'users', 'created_at', 'updated_at']
For more information, see the Dealing with nested objects section of the Django REST Framework documentation.
Your to_representation model was wrong on two parts:
you wrapped the result of the .data in a set, but as you found out, a dictionary can not be placed in a dictionary, since a set is mutable; and
you should use .append(..) instead of .extend(..) here.
def to_representation(self, instance):
representation = super(CompanySerializer, self).to_representation(instance)
representation['company_form'] = CompanyFormSerializer(instance.company_form).data
representation['users'] = []
for entry in instance.users.all():
user = UserSerializer(entry).data
representation['users'].append(user)
return representation
But that being said, it is in my opinion, bad software design to aim to do this yourself. Django has a lot of tooling to handle relationships properly through URIs, etc.
Roughly said, I have the following schema in ORM:
class Page(models.Model):
title = models.CharField(max_length=255, null=False, blank=False)
#property
def content(self):
return [Video.objects.all()[0], Text.objects.all()[0], Video.objects.all()[1]]
and I have the following set of classes to support serialization for detailed view:
class ContentSerializer(serializers.ListSerializer):
class Meta:
model = ???
fields = '???'
class PageDetailSerializer(serializers.ModelSerializer):
content = ContentSerializer(many=True)
class Meta:
model = Page
fields = ('title', 'content', )
So I'm looking for a way to serialize that Page.content property - which is:
a list;
will contain heterogeneous data (combination of, let's say Video, Audio, Text and other models.
So I need somehow patch one of builtin serializers to iterate thru the list and check type of each object. And then decide how to serialize each one. E.g. I could prepare kind of dynamically created ModelSerializer with:
obj_type = type(obj)
class ContentModelSerializer(serializers.ModelSerializer):
class Meta:
model = obj_type
fields = '__all__'
serialized_obj = ContentModelSerializer(obj)
How could I implement that?
You can simply achieve this by overriding the to_representation method of Page serializer. like this:
class PageDetailSerializer(serializers.ModelSerializer):
class Meta:
model = Page
fields = ('title', 'content', )
def to_representation(self, instance):
ctx = super(PageDetailSerializer, self).to_representation(instance)
content = instance.content # property field of page, will return list of items
serialized_content = []
for c in content:
if type(c) == Video:
serialized_content.append({... serialized data of video type ..})
elif type(c) == ...
# other conditions here..
I had googled a lot before found the solution. This article has a reference to SerializerMethodField, which let you add custom handler for a field. And the final solution, which worked for me is:
class PageDetailSerializer(serializers.ModelSerializer):
_cache_serializers = {}
content = serializers.SerializerMethodField()
class Meta:
model = Page
fields = ('title', 'content', )
def _get_content_item_serializer(self, content_item_type):
if content_item_type not in self._cache_serializers:
class ContentItemSerializer(serializers.ModelSerializer):
class Meta:
model = content_item_type
exclude = ('id', 'page', )
self._cache_serializers[content_item_type] = ContentItemSerializer
return self._cache_serializers[content_item_type]
def get_content(self, page):
return [
self._get_content_item_serializer(type(content_item))(content_item).data for content_item in page.content
]
I'm trying to create a custom serializer method that counts the number of passed and failed quizzes from my QuizResults model. A failed quiz is under .7 and a passed quiz is .7 or over.
I want to be able to look into the Users QuizResult and count the number of passed quizzes(.7 or over). I would then duplicate the method to count the failed quizzes (under .7).
So far I don't have much idea on how to do so. I want to be able to grab the percent_correct field of the model and do a calculation and add it to a field in the serializer called "quiz_passed".
Here is my QuizResult model:
class QuizResult(models.Model):
quiz = models.ForeignKey(Quiz)
user = models.ForeignKey(User, related_name='quiz_parent')
percent_correct = models.FloatField(validators=[MinValueValidator(0.0), MaxValueValidator(1.0)])
date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return 'Quiz Results for : ' + self.quiz.title
Here is my serializer:
class ProfileSerializer(serializers.HyperlinkedModelSerializer):
todo_count = serializers.IntegerField(source='todo_parent.count', read_only=True)
discussion_count = serializers.IntegerField(source='comment_parent.count', read_only=True)
quiz_passed = serializers.SerializerMethodField()
class Meta:
model = User
fields = ('todo_count', 'discussion_count', 'quiz_passed', 'username', )
def get_quiz_passed(self, obj):
return passed
Any help is appreciated.
Edit:
I extended the User model and added a model method like you suggested.
class Profile(User):
def get_quizzes_passed_count(self):
return self.quiz_parent.filter(percent_correct__gte=0.8).count()
I then added your suggestion to my ProfileSerializer.
class ProfileSerializer(serializers.HyperlinkedModelSerializer):
todo_count = serializers.IntegerField(source='todo_parent.count', read_only=True)
discussion_count = serializers.IntegerField(source='comment_parent.count', read_only=True)
num_quizzes_passed = serializers.ReadOnlyField(source="get_quizzes_passed_count")
class Meta:
model = Profile
fields = ('todo_count', 'discussion_count', 'num_quizzes_passed', 'username')
Unfortunately when I add this nothing appears in the framework once these have been added. Any suggestions? Thank you.
You can use a model method on the user model to count that user's number of passed quizzes:
class User(models.model):
# rest of your User attributes
def get_quizzes_passed_count(self):
return self.quiz_parent.filter(percent_correct__gte=0.7).count()
Then add that to your serializer using a DRF ReadOnlyField to serialize that method:
class ProfileSerializer(serializers.HyperlinkedModelSerializer):
todo_count = serializers.IntegerField(
source='todo_parent.count', read_only=True
)
discussion_count = serializers.IntegerField(
source='comment_parent.count', read_only=True
)
quiz_passed = serializers.SerializerMethodField()
num_quizzes_passed = serializers.ReadOnlyField(source="get_quizzes_passed_count")
class Meta:
model = User
fields = ('todo_count', 'discussion_count', 'quiz_passed', 'username', )
def get_quiz_passed(self, obj):
return passed
You can duplicate this for the number of failed quizzes.
I have this:
class GenericCharacterFieldMixin():
attributes = serializers.SerializerMethodField('character_attribute')
skills = serializers.SerializerMethodField('character_skill')
def character_attribute(self, obj):
character_attribute_fields = {}
character_attribute_fields['mental'] = {str(trait_item.get()): trait_item.get().current_value
for trait_item in obj.mental_attributes}
character_attribute_fields['physical'] = {str(trait_item.get()): trait_item.get().current_value
for trait_item in obj.physical_attributes}
character_attribute_fields['social'] = {str(trait_item.get()): trait_item.get().current_value
for trait_item in obj.social_attributes}
return character_attribute_fields
def character_skill(self, obj):
character_skill_fields = {}
character_skill_fields['mental'] = {str(trait_item.get()): trait_item.get().current_value
for trait_item in obj.mental_skills}
character_skill_fields['physical'] = {str(trait_item.get()): trait_item.get().current_value
for trait_item in obj.physical_skills}
character_skill_fields['social'] = {str(trait_item.get()): trait_item.get().current_value
for trait_item in obj.social_skills}
return character_skill_fields
class MageSerializer(GenericCharacterFieldMixin, serializers.ModelSerializer):
player = serializers.ReadOnlyField(source='player.username')
arcana = serializers.SerializerMethodField()
def get_arcana(self, obj):
if obj:
return {str(arcana): arcana.current_value for arcana in obj.linked_arcana.all()}
class Meta:
model = Mage
fields = ('id', 'player', 'name', 'sub_race', 'faction', 'is_published',
'power_level', 'energy_trait', 'virtue', 'vice', 'morality', 'size',
'arcana', 'attributes', 'skills')
depth = 1
GenericCharacterFieldMixin is a Mixin of Fields for Characters, that are Generic, i.e. common to all types of characters.
I'd like my Mage Serializer to have these 'mixed in' rather than c/p then between all types of character (Mage is a type of character) hopefully this will increase DRYness in my webapp.
The issue is on the model I have this:
class NWODCharacter(models.Model):
class Meta:
abstract = True
ordering = ['updated_date', 'created_date']
name = models.CharField(max_length=200)
player = models.ForeignKey('auth.User', related_name="%(class)s_by_user")
....
def save(self, *args, **kwargs):
...
attributes = GenericRelation('CharacterAttributeLink')
skills = GenericRelation('CharacterSkillLink')
Which means I get this error:
TypeError at /characters/api/mages
<django.contrib.contenttypes.fields.create_generic_related_manager.<locals>.GenericRelatedObjectManager object at 0x00000000051CBD30> is not JSON serializable
Django Rest Framework thinks I want to serialize my generic relationship.
If I rename the fields in the model (s/attributes/foos/g, s/skills/bars/g) then I get a different (less clear?) error :
ImproperlyConfigured at /characters/api/mages
Field name `attributes` is not valid for model `ModelBase`.
How do I pull those methods and fields into a mixin, without confusing DRF?
Set SerializerMetaclass:
from rest_framework import serializers
class GenericCharacterFieldMixin(metaclass=serializers.SerializerMetaclass):
# ...
This is the solution recommended by DRF's authors.
Solutions suggested in the previous answers are problematic:
user1376455's solution hacks DRF into registering the mixin's fields in _declared_fields by declaring them on the child as different fields. This hack might not work in subsequent versions of the framework.
Nikolay Fominyh's solution changes the mixin to a fully fledged serializer (note that due to this, the name GenericCharacterFieldMixin is very unfortunate for a class which is not a mixin, but a serializer!). This is problematic because it takes the full Serializer class into the multiple inheritance, see the DRF issue for examples demonstrating why this is a bad idea.
Solution is simple as changing
class GenericCharacterFieldMixin():
to
class GenericCharacterFieldMixin(serializers.Serializer):
i had same issue and my google search brought me here. i managed to solve it.
since you are including attributes and skill fields in serialiser, you need to provide serialisation method for it.
this worked for me
class MageSerializer(GenericCharacterFieldMixin, serializers.ModelSerializer):
player = serializers.ReadOnlyField(source='player.username')
arcana = serializers.SerializerMethodField()
attributes = serializers.PrimaryKeyRelatedField(many=True,
read_only= True)
skills = serializers.PrimaryKeyRelatedField(many=True,
read_only= True)
def get_arcana(self, obj):
if obj:
return {str(arcana): arcana.current_value for arcana in obj.linked_arcana.all()}
class Meta:
model = Mage
fields = ('id', 'player', 'name', 'sub_race', 'faction', 'is_published',
'power_level', 'energy_trait', 'virtue', 'vice', 'morality', 'size',
'arcana', 'attributes', 'skills')
depth = 1