Populate Related Field Based on Query Parameter - django

I'm trying to implement a way to show full details of a related field, rather than just the ID or a specific field, when it is specified in a query parameter, e.g http://api_url/courses?populate="author"
Currently, the ID of the author field is shown like so, with this URL - http://api_url/courses
"data": [
{
"author": "e1d5b311-f6b5-4909-8caf-da6ff025a4fc",
....
}
]
I need it to be able to be displayed in full when a ?populate="author" parameter is added to the URL - http://api_url/courses?populate="author" should show this:
"data": [
{
"author": {
"id" : "e1d5b311-f6b5-4909-8caf-da6ff025a4fc",
"first_name" : "string",
"last_name" : "string",
},
....
}
]
I can currently show full details of the field by using a nested serializer, like so, or show just the UUID (or any other field) using a SlugRelatedField.
class CourseSerializer(serializers.ModelSerializer):
author = UserSerializer()
class Meta:
model = Course
fields = "__all__"
Any help is much appreciated.

Related

Django can i only pass "id" in POST request, despite displaying nested fields?

in my post requests to OrderProduct model, i want to only have to pass order.id and product.id and it works... untill i add a serializer to retrieve product.name. It might be because i didnt understand documentation about nested requests, but im unable to advance further into my project :(
[
{
"id": 2,
"order": 1,
"product": 1,
}
]
^ here's how it looks without nested serializer, and thats the data that i wanna have to input
[
{
"id": 2,
"order": 1,
"product": {
"id": 1,
"name": "gloomhaven",
},
},
^ here's how it looks after i add an additional serializer. I pretty much want these nested fields to be read only, with me still being able to send simple post requests
here are my serializers
class OrderProductSerializer(serializers.ModelSerializer):
product = Product()
class Meta:
model = OrderProduct
fields = [
"id",
"order",
"product"]
class Product(serializers.ModelSerializer):
class Meta:
model = Product
fields = (
"id",
"name")
Is there any way for me to accomplish this? Thank you for trying to help!
Just overwrite to_representation method of the serializer
def to_representation(self, instance):
response = super().to_representation(instance)
response['other_field'] = instance.id# also response['other_field'] = otherSerializer(instance.model)
return response
This can solve your problem
I think you are missing many=True
class OrderProductSerializer(serializers.ModelSerializer):
product = Product(many=True)
class Meta:
model = OrderProduct
fields = [
"id",
"order",
"product"]

Fetch and return category and all related objects

I have a Bookmark and a BookmarkCategory object. I'd like to be able to fetch JSON that looks like this:
GET -> localhost:8000/api/bookmarks
[
"python": {
"title": "Python",
"bookmarks": [
{
"title": "Python Documentation",
"url": "https://docs.python.org"
}
]
},
"javascript": {
"title": "Javascript",
"bookmarks": [
{
"title": "Python Documentation",
"url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript"
}
]
}
]
Here's my models:
class BookmarkCategory(models.Model):
title = models.CharField(max_length=255)
class Bookmark(models.Model):
title = models.CharField(max_length=255)
url = models.CharField(max_length=255)
category = models.ManyToManyField(BookmarkCategory)
Here's how I would query all the BookmarkCategory objects:
from .models import BookmarkCategory
bookmarks = BookmarkCategory.objects.all()
The JSON doesn't have to look exactly like this. I just need to get all my BookmarkCategory objects along with all the related Bookmark objects so I can iterate over them after I make a GET request to fetch them.
You'll have to use select_related on "bookmark_set", to fetch them along. If you are using DRF, you need to create a nested serializer configuration for bookmarks. Here is an example without DRF:
categories = BookmarkCategory.objects.select_related("bookmark_set")
items = []
for category in categories:
items.append(
{
"title": category.title,
"bookmarks": category.bookmark_set.values_list("title", "url"),
}
)

Django Rest Framework JSON API - Update M2M relationship not persisting

I'm trying to update a resource in a PATCH.
The resource is updated fine, but the resources in the M2M table don't change.
Models
class StatementOfAdvice(Model):
id = HashidUnsignedAutoField(primary_key=True, salt="StatementOfAdvice", min_length=15)
loan_purposes = ManyToManyField(
to=StaticLoanPurpose, through=StatementOfAdviceLoanPurpose, related_name="loan_purposes"
)
class StaticLoanPurpose(Model):
id = CharField(db_column="loan_purpose_id", primary_key=True, max_length=150)
value = CharField(max_length=150, unique=True)
class StatementOfAdviceLoanPurpose(Model):
id = HashidUnsignedAutoField(primary_key=True, salt="StatementOfAdviceLoanPurpose", min_length=15)
statement_of_advice = ForeignKey(to="StatementOfAdvice", on_delete=DO_NOTHING)
loan_purpose = ForeignKey(to="StaticLoanPurpose", on_delete=DO_NOTHING)
Serializers
class StatementOfAdviceSerializer(Serializer):
included_serializers = {"client_account": ClientAccountSerializer, "loan_purpose": StaticLoanPurposeSerializer}
loan_purposes = ResourceRelatedField(many=True, read_only=False, queryset=StaticLoanPurpose)
class Meta:
model = StatementOfAdvice
fields = "_all_"
class StaticLoanPurposeSerializer(Serializer):
class Meta:
model = StaticLoanPurpose
fields = "_all_"
My PATCH request: http://localhost:8000/statements_of_advice/zELX1KdyZjgQGkp/
Payload:
{
"data": {
"type": "StatementOfAdvice",
"attributes": {},
"relationships": {
"loan_purposes": {
"data": [
{
"type": "StaticLoanPurpose",
"id": "construct_io"
},
{
"type": "StaticLoanPurpose",
"id": "other_purpose"
},
{
"type": "StaticLoanPurpose",
"id": "purchase_io"
}
]
}
},
"id": "zELX1KdyZjgQGkp"
}
}
The result I expect from this PATCH request is 3 records in the linking table StatementOfAdviceLoanPurpose. But I get none.
If anyone could help me here I would greatly appreciate it.
Ok, Then. The solution I found was actually using rest_framework_json_api.views.AutoPrefetchMixin in my StatementOfAdviceViewSet.
class StatementOfAdviceViewSet(BaseViewSet, AutoPrefetchMixin):
queryset = StatementOfAdvice.objects.all()
serializer_class = StatementOfAdviceSerializer
filterset_class = StatementOfAdviceFilterSet
Reference link:
https://django-rest-framework-json-api.readthedocs.io/en/stable/apidoc/rest_framework_json_api.views.html?highlight=manytomany#rest_framework_json_api.views.AutoPrefetchMixin.get_queryset
But this only fixes part of the problem. Now I can insert records in the linking table using the same payload as in the question.
But I cannot remove them. I tried changing the request method to PUT assuming that since PUT is supposed to replace all values of the resource the framework would handle it to me. But it doesn't.
So besides adding the AutoPrefetchMixin in the viewset. I also had override the update() function in the StatementOfAdviceSerializer to achieve the desired behaviour.
The advantage of using AutoPrefetchMixin is that you don't have to override the create() method.

Specifying field names in serializer class acting as another serializer class's field

Suppose for below ModelSerializer class
class UserSongSerializer(serializers.ModelSerializer):
user = serializers.SerializerMethodField()
song_likes = serializers.ReadOnlyField() # This is model's property field
song_shares = serializers.ReadOnlyField()
song_plays = serializers.ReadOnlyField()
song_price = serializers.ReadOnlyField()
genre = GenreSerializer(many=True,required=False,context={'key':5})
language = LanguageSerializer(many=True, required=False)
Passing specific context kwarg like below
genre = GenreSerializer(many=True,required=False,context={'fields':['name']})
Since I want to retrieve only name field in Genre model class in some specific cases, I overrided GenreSerializer class's get_fields_name method so that I can mention specific fields only when required via context
class GenreSerializer(serializers.ModelSerializer):
def get_field_names(self, *args, **kwargs):
"""
Overriding ModelSerializer get_field_names method for getting only specific fields in serializer when mentioned in SerializerClass arguments
"""
field_names = self.context.get('fields', None)
if field_names:
return field_names
return super(GenreSerializer, self).get_field_names(*args, **kwargs)
class Meta:
model = Genre
fields = '__all__'
However, I am unable to get any 'fields' (getting None) key inside overrided get_fields_name method. I know of other ways as well like using StringRelatedField but that would change the output representation to
"genre":[
"Pop",
"Rock"
]
Whereas, I want to stick to my original representation
"genre": [
{
"id": 3,
"name": "Pop",
"created_date": "2018-09-05T17:05:59.705422+05:30",
"updated_date": "2018-09-20T14:43:02.062107+05:30",
"status": false
},
{
"id": 4,
"name": "Rock",
"created_date": "2018-09-05T17:06:06.889047+05:30",
"updated_date": "2018-09-17T16:45:22.684044+05:30",
"status": true
},
{
"id": 5,
"name": "Classical",
"created_date": "2018-09-05T17:06:14.216260+05:30",
"updated_date": "2018-09-05T17:06:14.275082+05:30",
"status": true
}
]
UPDATE - What I want is like this
"genre": [
{
"name": "Pop"
},
{
"name": "Rock"
},
{
"name": "Classical"
}
]
Contexts are meant to be set to the root serializer only.
Whenever UserSongSerializer will be instantiated it'll override the nested genre context.
If you are using generic views, you'll want to override the view's get_serializer_context and add your own context there. It's documented at the bottom of the methods section
PS: context are "shared" to serializers, fields, validators.
PPS: Don't alter context after it's been set you it's going to be sort of undefined behavior.

Django-REST-Framework "GroupBy" ModelSerializer

I have the following situation
class MyModel(models.Model):
key = models.CharField(max_length=255)
value = models.TextField(max_length=255)
category = models.CharField(max_length=4)
mode = models.CharField(max_length=4)
the fields key, category and mode are unique together. I have the following objects:
m1 = MyModel(key='MODEL_KEY', value='1', category='CAT_1' mode='MODE_1')
m2 = MyModel(key='MODEL_KEY', value='2', category='CAT_1' mode='MODE_2')
m3 = MyModel(key='MODEL_KEY', value='1', category='CAT_2' mode='MODE_1')
m4 = MyModel(key='MODEL_KEY', value='2', category='CAT_2' mode='MODE_2')
I want to expose an API that will group by key and category so the serialized data will look something like this:
{
"key": "MODEL_KEY",
"category": "CAT_1"
"MODE_1": { "id": 1, "value": "1" }
"MODE_2": { "id": 2, "value": "2" }
},
{
"key": "MODEL_KEY",
"category": "CAT_2"
"MODE_1": { "id": 3, "value": "1" }
"MODE_2": { "id": 4, "value": "2" }
}
Is there any way of doing this in django rest framework with ModelSerializer.
There is module that allows you to group Django models and still work with a QuerySet in the result: https://github.com/kako-nawao/django-group-by
Using the above to form your queryset:
# Postgres specific!
from django.contrib.postgres.aggregates.general import ArrayAgg
qs = MyModel.objects.group_by('key', 'category').annotate(
mode_list=ArrayAgg('mode')).order_by(
'key', 'category').distinct()
You can then access the properties key, category and mode_list on the resulting QuerySet items as attributes like qs[0].mode_list. Therefore, in your serializer you can simply name them as fields.
The model_list field might require a SerializerMethodField with some custom code to transform the list.
Note that you need an aggregation if you don't want to group by mode, as well.