I have DB structure like this:
common_org:
id,
code,
title
special_org:
id,
code,
address
Okay. For this I've created models:
class CommonOrg(models.Model):
code = models.CharField()
title = models.CharField()
class SpecialOrg(models.Model):
code = models.CharField(null=True)
address= models.CharField()
Now I want to output SpecialOrg as usual, but if I have CommonOrg.code == SpecialOrg.code, then attach CommonOrg to SpecialOrg like this:
{
"id": 1,
"code": "XYZ",
"address": "ADDRESS",
"common_org": {
"id": 2,
"code": "XYZ",
"title": "TITLE"
}
}
Now I have solution with serializers.RelatedField:
class CommonOrgField(serializers.RelatedField):
def to_representation(self, value):
class _CommonOrgSerializer(serializers.ModelSerializer):
class Meta:
model = CommonOrg
fields = '__all__'
representation = None
try:
common_org = CommonOrg.objects.get(code=value)
representation = _CommonOrgSerializer(common_org).data
except CommonOrg.DoesNotExist:
pass
return representation
class SpecialOrgSerializer(serializers.ModelSerializer):
class Meta:
model = SpecialOrg
fields = '__all__'
common_org = CommonOrgField(read_only=True, source='code')
But it looks ugly for me.
So the question is: what is the right approach to implement it in DRF? Database is not mine and I cannot to alter it.
In most cases where I am to add a read only field to a serializer, when the field is not related to the current model at database level, I'd use a serializer method field. You could use serializer method field like this in your case:
class SpecialOrgSerializer(serializers.ModelSerializer):
common_org = serializers.SerializerMethodField()
class Meta:
model = SpecialOrg
fields = '__all__'
def get_common_org(self, obj):
try:
common_org = CommonOrg.objects.get(code=value)
except CommonOrg.DoesNotExist:
return None
return _CommonOrgSerializer(common_org).data
Related
I have to pass a request body as shown below.
{
"user_id": 1,
"skill": [
{
"id":1
},
{
"id":2
},
{
"id":3
}
]
}
Models looks like this:
class UserSkill(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
skill = models.ForeignKey(Skills, on_delete=models.CASCADE)
class Meta:
db_table = "user_skill"
Serializer looks like :
class UserSkillSerializer(serializers.ModelSerializer):
class Meta:
model = UserSkill
fields = ['user', 'skill']
Viewset is done like:
class UserSkillViewSet(viewsets.ModelViewSet):
queryset = UserSkill.objects.all()
serializer_class = UserSkillSerializer
I have to pass the above mentioned request body to this api and for each element in "skill", I have to create an object for the model "UserSkill".
Some one please help me with the changes I have to make to get this api working.
Thanks
class UserSkillSerializer(serializers.ModelSerializer):
class Meta:
model = UserSkill
fields = ['user', 'skill']
def create(self, validated_data):
user_id = validated_data.get('user_id')
skill_ids = validated_data.get('skill')
skill = [item.get("id") for item in skill_ids]
super().create({"user": user_id, "skill": skill})
Try it.
change your skill field in UserSkill model to ManyToManyField. like:
class UserSkill(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
skill = models.ManyToManyField(Skills)
class Meta:
db_table = "user_skill"
and you need to pass a request body like this.
{
"user": 1,
"skill": [1, 2, 3]
}
without any change in serializer and view classes your requirements applicated.
if you want to use ForeignKey you need to some implementation in your view and you must overwrite create function in ModelViewSet.
I use ModelSerializer to return serialized data of a model:
class CategorySerializer(serializers.ModelSerializer):
category_count = serializers.SerializerMethodField('_category_count')
def _category_count(self, obj):
category_obj = Category.objects.get(id=obj.id)
questions_count = Question.objects.filter(category=category_obj).count()
return questions_count
class Meta:
model = Category
fields = ["id", "category", "category_count"]
What I get write now is here:
[{
"category": "orzecznictwo lekarskie",
"category_count": 0,
"id": 9
},
{
"category": "zdrowie publiczne",
"category_count": 1,
"id": 10
}
]
I want to remove objects with category_count == 0 from serialization
[
{
"category": "zdrowie publiczne",
"category_count": 1,
"id": 10
}
]
Thank you for your help.
EDIT:
class Question(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE)
question = models.TextField(blank=True, null=True)
(...)
class Category(models.Model):
category = models.TextField()
category_slug = models.CharField(max_length=50)
You're generating the field inside the serializer and then want to discard the object. That's not how things work: serializers merely change representation of data. Which data is displayed is determined in views.
class CategoryListView(generics.ListAPIView):
def get_queryset(self):
return models.Category.objects.annotate(
question_count=Count("question_set")
).exclude(question_count=0)
And in the serializer, you don't have to create the count any more and add the 'question_count' to Meta.fields.
Recommended reading.
Edit:
Forgot that DRF will check the field on the model and fail, so you need to declare the field:
class CategoryWithQuestionCount(serializers.ModelSerializer):
question_count = serializers.IntegerField(read_only=True)
class Meta:
model = models.Category
fields = ("pk", "category", "question_count")
I'm not sure about removing it in the ModelSerializer. But you can try overriding get_queryset() or changing the queryset in your View:
queryset = Class.objects.exclude(dzial_liczba_pytan=0)
My models:
class Category(models.Model):
name = models.Charfield()..
code = models.Charfield()...
class Movie(models.Model):
name = models.Charfield()..
categories = models.ManyToManyField(Category)
My serializer
class MovieSerializer(serializers.ModelSerialier):
class Meta:
model= Movie
fields = ['name',
'categories',
]
When i get a Movie by id it returns:
[
{"name": "My Movie",
"categories": [1, 2, 3],
}
]
I want my response to be :
....
"categories": ["Fun", "Adventure", ...]
What I've tried
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['name']
def to_representation(self, instance):
return instance.name
It gives me the expected result, but i dont think it is a clean way to do it
Option 1
You can use your models __str__ method for this. Just use a StringRelatedField:
class MovieSerializer(serializers.ModelSerialier):
categories = serializers.StringRelatedField(many=True)
class Meta:
model= Movie
fields = ['name', 'categories']
and add the __str__ method to your category model:
class Category(models.Model):
name = models.Charfield()
code = models.Charfield()
def __str__(self):
return self.name
Option 2
Use a custom RelatedField, like described in the docs:
class CategoryListingField(serializers.RelatedField):
def to_representation(self, value):
return value.name
class MovieSerializer(serializers.ModelSerialier):
categories = CategoryListingField(many=True)
class Meta:
model= Movie
fields = ['name', 'categories']
This is close to what you did, just a little bit cleaner, as a RelatedField is designed to accomplish exactly that. Using a custom serializer is not the way to go here, since you dont want to serialize the object itself, but only a specific field from it. Per default, this is the pk (or id), but by specifying your own related field, you can change it to whatever you want.
Add depth = 1 to your serializer.
This will give you something like:
[
{
"name": "My Movie",
"categories": [
{ "id": 1, "name": "Fun", "code": "category_code" }
]
}
]
then you can extract whatever you want from this object.
Hi I want to deserialize only using 1 field. However, I want to serialize it as an object depending on the model.
Suppose I have:
#models.py
class Product(models.Model):
name = models.CharField()
amount = models.IntegerField()
description = models.TextField()
class Query(models.Model):
name = models.CharField()
product = models.ForeignKey(Product)
...
#serializers.py
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = '__all__'
class QuerySerializer(serializers.ModelSerializer):
product = ProductSerializer()
class Meta:
model = Query
fields = '__all__'
I want to POST/deserialize something like this on the QuerySerializer:
{
"name": "Query 1",
"product": "Banana",
...
}
and I want something like this in return for serializer:
{
"name": "Query 1",
"product": {
"name": "Banana",
"amount": 1,
"description": "Banana description"
}
...
}
I know a way is overriding to_internal_value but I do not like it since it messes up with ValidationErrrors.
I also get this as a result:
{'product': {'non_field_errors':
['Invalid data. Expected a dictionary, but got str.']}}
First of all, make the name field of Product as unique to avoid unnecessary complications.
class Product(models.Model):
name = models.CharField(unique=True)
amount = models.IntegerField()
description = models.TextField()
and change your serializer as,
class QuerySerializer(serializers.ModelSerializer):
product = serializers.CharField(write_only=True)
class Meta:
model = Query
fields = '__all__'
def create(self, validated_data):
product_name = validated_data.pop('product')
product_instance = Product.objects.get(name=product_name)
return Query.objects.create(product=product_instance, **validated_data)
def to_representation(self, instance):
rep = super().to_representation(instance)
rep['product'] = ProductSerializer(instance.product).data
return rep
Reference: DRF: Simple foreign key assignment with nested serializers?
I'm trying to serialize foreikey to another name.
For example:
class User(models.Model):
user_name = models.CharField(..)
class Blog(models.Model):
user = models.ForeignKey(User, ...)
title = models.CharField(...)
class BlogSerializer(serializers.ModelSerializer):
class Meta:
model = Blog
fields = '__all__'
when use BlogSerializer(blog_instance).data can get the data:
{
"id": 1,
"user": 1,
"user_info": {"id": 1, "user_name": "admin"},
"title": "hello world"
}
Just create another serializer for User and add it as any field you want in the BlogSerializer. For example:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username')
class BlogSerializer(serializers.ModelSerializer):
user_info = UserSerializer(source='user')
class Meta:
model = Blog
fields = ('id', 'user', 'user_info', 'title')
Though I don't see much need of user with just the id since user_info already includes the id and other info. But if that is what you want then this is how to implement it
I think you just want a different representation for the user, you can make this trick to change that:
class BlogSerializer(serializers.ModelSerializer):
class Meta:
model = Blog
fields = '__all__'
def to_representation(self, instance):
ret = super().to_representation(instance)
# here you override the 'user' field, you can either use a dictionary
ret['user'] = {"id": instance.user.id, "user_name": instance.user.user_name}
# or better yet create an actual UserSerializer and get that representation, eg.
# ret['user'] = UserSerializer(context=self.context).to_representation(instance.user)
return ret