Django REST displayed filtered nested resource - django

I'm attempting to return JSON in a simple GET that includes a row from a joined table. The models:
class School(models.Model):
id = models.CharField(max_length=18,primary_key=True)
name = models.CharField(max_length=255,blank=False)
class Meta:
db_table='school'
class FKTable(models.Model):
school = models.ForeignKey(School,blank=False, db_column='school_id', on_delete=models.CASCADE)
name = models.CharField(max_length=45, blank=False)
value = models.CharField(max_length=255, blank=True, null=True)
class Meta:
db_table='fk_table'
And the serializer:
class SchoolListSerializer(serializers.ModelSerializer):
id = serializers.CharField(source='id')
name = serializers.CharField(source='name')
fkColumn = ????
class Meta:
model = models.School
fields = ('id','name','fkColumn')
The problem is I want to also filter the nested resource that I join against on the 'name' column so I don't know what to put for 'fkColumn' in the serializer. In SQL it would look something like this:
SELECT school.*, fk_table.value from school INNER JOIN fk_table on
fk_table.school_id = school.id AND fk_table.name = 'some name'
The end result JSON I want is:
{
"schools": [
{
"id": "someId",
"name": "someName",
"fkColumn": "Value"
}]}
RelatedField does not seem to work and none of the documentation seems to give a solid answer on how to do this. Any ideas?

Revised after clarification. Define an fkeySerializer:
class fkeySerializer(serializers.ModelSerializer):
...
Then link this in your main serializer:
fkey = fkeySerializer(many=True)
class Meta:
model = models.School
fields = ('id','name','fkColumn',)
You can use a MethodField serializer here. For example:
fkColumn = serializers.SerializerMethodField()
def get_fkColumn(self, obj):
return obj.fkColumn
class Meta:
model = models.School
fields = ('id','name','fkColumn',)
As an aside, perhaps there's a good reason for it, but it might be easier to rename this table to be more easily readable

Related

How to use _set in SerializerMethod for instance?

I want to calculate the average rating by using SerializerMethodField().
The error in the following code is AttributeError: 'FeedbackModel' object has no attribute 'aggregate'
I think _set is missing but I don't know where to put it..!
class FeedbackSerializer(serializers.ModelSerializer):
feedback_by_user_profile_pic = serializers.ImageField(source='feedback_by.profile_pic')
average_rating = serializers.SerializerMethodField()
def get_average_rating(self,instance):
return instance.aggregate(average_rating=Avg('rating'))['average_rating']
class Meta:
model = FeedbackModel
fields = ['feedback_text','rating','date','feedback_by_user_profile_pic','average_rating']
Feedback Model
class FeedbackModel(models.Model):
feedback_text = models.CharField(max_length=1000)
rating = models.IntegerField()
date = models.DateField(auto_now=True)
feedback_by = models.ForeignKey(UserModel,on_delete=models.CASCADE)
business_account = models.ForeignKey(BusinessAccountModel,on_delete=models.CASCADE)
class Meta:
db_table = 'feedback'
BusinessAccountModel
class BusinessAccountModel(models.Model):
business_title = models.CharField(max_length=70)
business_description = models.CharField(max_length=500)
status = models.CharField(max_length=100)
note = models.CharField(max_length=200)
user = models.OneToOneField(UserModel,on_delete=models.CASCADE)
class Meta:
db_table = 'wp_business_acc'
BusiAccSerializer
class BusiAccSerializer(serializers.ModelSerializer):
class Meta:
model = BusinessAccountModel
fields = '__all__'
I think you need to add the average_rating field in the BusiAccSerializer, not in the FeedbackSerializer.
First you have to set the related_name attribute in the FeedbackModel.
class FeedbackModel(models.Model):
...
# here I added the `related_name` attribute
business_account = models.ForeignKey(BusinessAccountModel,on_delete=models.CASCADE, related_name="feedbacks")
And then in the BusiAccSerializer,
class BusiAccSerializer(serializers.ModelSerializer):
average_rating = serializers.SerializerMethodField(read_only = True)
def get_average_rating(self, obj):
return obj.feedbacks.aggregate(average_rating = Avg('rating'))['average_rating']
class Meta:
model = BusinessAccountModel
fields = (
'business_title', 'business_description', 'status', 'note', 'user', 'average_rating',
)
First of all, you've indicated that you need an average rating for a business account, but you can not get an average rating for an account without having a concrete business account, so you need to do it in the business account serializer.
David Lu has already answered how to do it in the BusiAccSerializer, but I have something to add:
What you've trying to do is to use a serializer method field to add some aggregated data to the output. This way of solving your problem has a major drawback: when you will try to serialize a list of BusinessAccountModels, the serializer will do a separate database call for each business account and it could be slow. You better need to specify an annotated queryset in your view like this:
BusinessAccountModel.objects.all().annotate(average_rating=Avg('feedbacks__rating'))
Then you will be able to use the result of calculation as a regular field in your serializer:
class BusiAccSerializer(serializers.ModelSerializer):
...
average_rating = serializers.FloatField(read_only=True)
This way there will be no additional database queries done by the serializer.

Django Rest Framework reverse serializer

Please I need a little help. I have a model like below
class Person(models.Model):
name = models.CharField(max_length=100)
address = models.CharField(max_length=100)
class Employee(models.Model):
person = models.ForeignKey(Person, related_name='employee')
code = models.CharField()
In my EmployeeSerializer, how can I add the Person field
Something like:
class EmployeeSerializer(serializer.ModelSerializer):
person = # Something the get **Person** instance
code = serializers.IntegerField
class Meta:
model = Employee
fields = [
'id',
'person',
'code'
]
You can use depth option to get nested representation of related objects:
class EmployeeSerializer(serializer.ModelSerializer):
class Meta:
model = Employee
fields = [
'id',
'person',
'code'
]
depth = 1
If you need to customize nested object, you should use nested serializers as described here.

Serialize many-to-many relation with intermediate model in Django Rest

I tried to check another topics, but didn't found a solution...
I have a many-to-many model, that have intermediate model with another field additional_field inside.
class BoardField(models.Model):
title = models.CharField(max_length=500, default='')
class Article(models.Model):
title = models.CharField(max_length=500, default='')
fields = models.ManyToManyField(BoardField, through='ArticleField', through_fields=('article', 'board_field'))
class ArticleField(models.Model):
article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='task')
board_field = models.ForeignKey(BoardField, on_delete=models.CASCADE)
additional_field = models.CharField(max_length=200, blank=True, null=True)
I want serialize Article with structure:
[
"title":"Title",
"fields":[
{
"board_field": {
"title":"Title"
},
"additional_field":"Additional info"
}
]
]
So, I wrote serializer:
class BoardFieldSrl(serializers.ModelSerializer):
class Meta:
model = BoardField
fields = (
'title',
)
class ArticleFieldSrl(serializers.ModelSerializer):
board_field = BoardFieldSrl()
class Meta:
model = ArticleField
fields = (
'board_field',
'additional_field',
)
class ArticleListSrl(serializers.ModelSerializer):
fields = ArticleFieldSrl(many=True)
class Meta:
model = Article
fields = (
'title',
'fields',
)
But I always got an error:
Got AttributeError when attempting to get a value for field `board_field` on serializer `ArticleFieldSrl`.
The serializer field might be named incorrectly and not match any attribute or key on the `BoardField` instance.
Original exception text was: 'BoardField' object has no attribute 'board_field'.
I made another several examples, but they doesn't gave my result, that I need... My maximum - I got BoardField with levels, but without intermediate model...
Can you help me with serializer, that return structure, that I mentioned above? It must include intermediate model ArticleField and nested BoardField.
Try fields = ArticleFieldSrl(source='articlefield_set', many=True)
You didn't specified a related_name at M2M field so the default naming is applied which is 'Intermediate model name'_set and if you want to use the fields on M2M relation you have to tell the serializer where to look for.
EDIT:
Camel removed from articlefield_set, model name is always converted to lower case

django drf left join

I have this model:
class Env(models.Model):
env_name = models.CharField(max_length=100, unique=True)
is_enabled = models.CharField(max_length=1, choices=ENABLED, default='Y')
def __unicode__(self):
return unicode(self.env_name)
I also have this model ...
class Hosts(models.Model):
host_name = models.CharField(max_length=200, unique=True)
host_variables = jsonfield.JSONField()
host_env = models.ForeignKey(Env, models.DO_NOTHING, related_name='host_env')
I wish to have a serialized representation equivalent to a join.
I'm trying to get rows that contain host_name and env_name
I can't seem to find the right way to serialize it
I'm have so far ...
class HostSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Hosts
fields = ('host_name', 'ip_address', 'is_enabled','is_managed','managed_users')
I can't seem to find the right way to get the name of the env in each row of my Hosts results.
What am I missing?
A serializer only handles a single Model, so anything else you want to add has to be added explicitly.
If you just want to add the env_name, you can use the SerializerMethodField field like this:
class HostSerializer(serializers.HyperlinkedModelSerializer):
env_name = serializers.SerializerMethodField()
class Meta:
model = Hosts
fields = ('host_name', 'env_name', 'ip_address', 'is_enabled','is_managed',
'managed_users',)
def get_env_name(self, obj):
host_env = obj.host_env
if host_env:
return str(host_env.env_name)
return None
Note that you may also want to look into nested serializers, but that would produce something like:
{
'host_name': 'my host name',
'host_env': {
'env_name': 'My env name'
}
}
See http://www.django-rest-framework.org/api-guide/relations/#nested-relationships for that (not explaining that as that was not your OP, but giving it to you as a reference for a potentially better way)
You can try
class HostSerializer(serializers.HyperlinkedModelSerializer):
env_name = serializers.ReadOnlyField(source='host_env.env_name')
class Meta:
model = Hosts
fields = ('host_name', 'ip_address', 'is_enabled','is_managed','managed_users', 'env_name',)

In Django Rest Framework, how to limit number foreign key objects being serialized

I'm serialzing a Product model and its comments. Here's my simple code:
class ProductSerializer(serializers.HyperlinkedModelSerializer):
comment_set = CommentSerializer(many=True, read_only=True)
class Meta:
model = Product
fields = [
'title',
'comment_set'
]
class CommentSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Comment
fields = [
'text',
]
class Comment(models.Model):
product = models.ForeignKey(Product, null=True, blank=True, db_index=True)
class Product(models.Model):
title = models.CharField(max_length=50)
...
Problem:
If the product has many comments. For example, 500 comments. All 500 of them got serialized.
How to limit the result to a number of my own choosing, like 100 comments?
I've done some research before posting this but only found questions about filtering.
Thank you.
Define a new method on the Product model that returns a query set with a limited number of comments.
Then pass that method as the source of the CommentSerializer inside your ProductSerializer.
class Product(models.Model):
title = models.CharField(max_length=50)
def less_comments(self):
return Comment.objects.all().filter(product=self).order_by("-id")[:100]
Then in the ProductSerializer:
class ProductSerializer(serializers.HyperlinkedModelSerializer):
comment_set = CommentSerializer(many=True, read_only=True, source="less_comments")
PS: Wrote the codes from memory, didn't test them. But should work.
You can write custom ListSerializer and put in CommentSerializer, then create custom field in ProductSerializer, wich source based on default related name:
class LimitedListSerializer(serializers.ListSerializer):
def to_representation(self, data):
data = data.all()[:100]
return super(FilteredListSerializer, self).to_representation(data)
class CommentSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
list_serializer_class = LimitedListSerializer
model = Comment
fields = [
'text',]
class Product(serializers.HyperlinkedModelSerializer):
related_comments = CommentSerializer(many=True, read_only=True, source='comment_set')
when you pass many=True, list serrializer will be called.
You'll want to work on the CommentSerializer's queryset to control which ones you keep.
You'll be able to do that by overriding get_queryset. For example, to filter them against the current user. Note that I took this example because it highlights how to use the request's context to filter against:
class CommentSerializer(serializers.HyperlinkedModelSerializer):
def get_queryset(self):
user = self.context['request'].user
queryset = Comment.objects.filter(user=user)
return queryset
class Meta:
model = Comment
fields = [
'text',
]