Django Rest: Serializers Based on Multiple Querysets - django-rest-framework

I'm working with serializers as described by the link:Serializer relations (section PrimaryKeyRelatedField)
I have a slight different need, I'm sure it's really easy.
class Album(models.Model):
album_name = models.CharField(max_length=100)
artist = models.CharField(max_length=100)
class Track(models.Model):
album = models.ForeignKey(Album, related_name='tracks')
order = models.IntegerField()
title = models.CharField(max_length=100)
duration = models.IntegerField()
class SomeWidget(models.Model):
album = models.ForeignKey(Album)
track = models.ForeignKey(Track)
name = models.CharField(max_length=100)
description = models.CharField(max_length=100)
My need, I need to return the following:
{
'album_name': 'Things We Lost In The Fire',
'artist': 'Low',
'tracks': [
'1: Sunflower',
'2: Whitetail',
'3: Dinosaur Act',
...
],
'widget': [
{
'id': '1234',
'name': 'my widget',
'description': 'my description'
}
]
}
I am trying:
class WidgetField(serializers.RelatedField):
def to_representation(self, value):
return {
'id': '1234'
....
}
class TrackListingField(serializers.RelatedField):
def to_representation(self, value):
...
class AlbumSerializer(serializers.ModelSerializer):
tracks = TrackListingField(many=True)
widget = WidgetField()
class Meta:
model = Album
fields = ('album_name', 'artist', 'tracks')
I keep getting the error:
AssertionError: Relational field must provide a `queryset` argument, override `get_queryset`, or set read_only=`True`.
Thanks

Ok, got it. It took a little bit of Googling / trial and error. Apparently you can override the function that returns data for the field.
For example:
class AlbumSerializer(serializers.ModelSerializer):
widget = serializers.SerializerMethodField()
def get_widget(self, data):
return {
'id': data.id
}
OR, you can do the following:
class WidgetSerializer(serializers.ModelSerializer):
class Meta:
model = Widget
fields = ('id', 'name', 'description',)
.... and in the AlbumSerializer.get_widget function:
def get_widget(self, data):
widget = Widget.objects.get(album=data.album, track=data.track)
return WidgetSerializer(widget, many=False, context=self.context).data
Finally, you don't have to use the function name "get_widget". You can name it whatever you want. Example:
class AlbumSerializer(serializers.ModelSerializer):
widget = serializers.SerializerMethodField("fn_override")
def fn_override(self, data):
....
You can follow the pattern in this SO question: Django REST Framework: adding additional field to ModelSerializer

Related

TypeError: Field 'id' expected a number but got <class 'rest_framework.fields.CurrentUserDefault'>

Seems I need to apply a dot notation to CurrentUserDefault() object, tried .id but failed
class DotPrivateSerializer(serializers.ModelSerializer):
tag = serializers.SerializerMethodField()
def get_tag(self,obj):
queryset=TagPrivate.objects.filter(user=serializers.CurrentUserDefault) # <--TypeError
return TagPrivateSerializer(queryset).data
models.py
class DotPrivate(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
name = models.CharField(max_length=255)
description = models.TextField(max_length=350, blank=True)
lon = models.CharField(max_length=20)
lat = models.CharField(max_length=20)
rating = models.FloatField(validators=[MinValueValidator(0.0), MaxValueValidator(5.0)])
tag = models.ForeignKey('TagPrivate', on_delete=models.PROTECT)
in the following link in the first answer I found some solution but I do not completly understand it:
'CurrentUserDefault' object has no attribute 'user'
class TagPrivateSerializer(serializers.ModelSerializer):
class Meta:
model = TagPrivate
fields = ('id', 'name')
read_only_fields = ('id',)
You can not use CurrentUserDefault, this is just a value that the Django serializer will use for a default=… parameter, and then later swap for the request user.
You can fetch this from the request in the context, so:
class DotPrivateSerializer(serializers.ModelSerializer):
tag = serializers.SerializerMethodField()
def get_tag(self, obj):
queryset = TagPrivate.objects.filter(user=self.context['request'].user)
return TagPrivateSerializer(queryset).data
In the ModelViewSet, you will need to pass the user, so:
class DotPrivateViewSet(ModelViewSet):
queryset = # …
permission_classes = # …
serializer_class = DotPrivateSerializer
def get_serializer_context(self):
context = super().get_serializer_context()
context.update(request=self.request)
return context

wagtail get_api_representation() not called

I am using Streamfields in Wagtail and got the following problem:
I would like to print out the image path instead of the id and followed the instructions here:
Custom representation of Streamfield in rest API
But it seems that get_api_representation() is never called in my case.
urls.py
router.register(r'page', PageView, basename='page')
views.py
class PageView(viewsets.mixins.RetrieveModelMixin, GenericViewSet):
queryset = Page
serializer_class = PageSerializer
serializers.py
class PageSerializer(serializers.ModelSerializer):
class Meta:
model = Page
fields = ('title', 'body')
models.py
class APIImageChooserBlock(ImageChooserBlock):
def get_api_representation(self, value, context=None):
if value:
return {
'id': value.id,
'title': value.title,
'image': value.get_rendition('width-1000').attrs_dict
}
class ImageTextBlock(blocks.StructBlock):
image = APIImageChooserBlock()
text = blocks.CharBlock()
class Page(wagtail_models.Page):
body = StreamField(
[
('MyBlock', ImageTextBlock()),
('Newsletter', blocks.StructBlock([
('text', blocks.RichTextBlock()),
])),
]
)
content_panels = wagtail_models.Page.content_panels + [
StreamFieldPanel('body', classname="full", heading=_('Content'))
]
Can somebody identify the problem here?

DRF How to parse text field and serialize result to set of models

I have next models in models.py
class Rule(models.Model):
name = models.CharField(max_length=30)
description = models.TextField()
class Question(models.Model):
question = models.TextField(default='')
answer = models.TextField(default='')
rules = models.TextField(default='') # here I decided to store rules names only, separated by coma
I have next serializers in serializers.py
class RuleSerializer(serializers.ModelSerializer):
class Meta:
model = Rule
fields = ['name', 'description']
class QuestionSerializer(serializers.ModelSerializer):
class Meta:
model = Question
fields = ['question', 'answer', 'rules']
as result output I have next JSON
[
{
"question": "How are you?",
"answer": "fine",
"rules": "rule 1, rule 2, rule 3"
}
]
but how can I convert rules string to objects? I found one solution, but it is means I should convert/serialize Rule manually
class RuleSerializer(serializers.ModelSerializer):
class Meta:
model = Rule
fields = ['name', 'description']
class QuestionSerializer(serializers.ModelSerializer):
rules = serializers.SerializerMethodField()
class Meta:
model = Question
fields = ['question', 'answer', 'rules']
def get_rules(self, obj):
names = [name.strip() for name in obj.rules.split(",")]
rules = Rule.objects.all().filter(name__in=names)
return [{
"name": rule.name,
"description": rule.description,
} for rule in rules]
And of course I can do it manually, but how to do it with already existing serializer?
def get_rules(self, obj):
names = [name.strip() for name in obj.rules.split(",")]
rules = Rule.objects.all().filter(name__in=names)
serializer = RuleSerializer(rules)
serializer.is_valid() # Mandatory
return serializer.data

Custome the display's field in django REST

I use Django REST and I would know if it is possible to customise the display of attributes in the json response.
Exemple :
class MyModel(models.Model):
name = models.CharField(max_length=300)
and my serializer :
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ['name']
But instead to see {'name' : 'its value'}, I would see {'My customed model name' : 'its value'}.
Do you think that it's possible?
Thank you very much.
You can override the to_representation method of the serializer to change the name of the field:
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ['name']
def to_representation(self, instance):
ret = super().to_representation(instance)
# ret is an OrderedDict, so this will change the order of the result.
ret['custom_name'] = ret.pop('name')
return ret
def to_internal_value(self, data):
# if you want to write to the serializer using your custom name.
data['name'] = data.pop('custom_name')
return super().to_internal_value(data)
One way you could do it is using a SerializerMethodField (https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield)
class MyModelSerializer(serializers.ModelSerializer):
my_customed_model_name = serializers.SerializerMethodField()
def get_my_customed_model_name(self, obj):
return obj.name
class Meta:
model = MyModel
Although if you want the field name to have spaces in it, this solution won't work for you.
You can do this in this way
class MyModelSerializer(serializers.ModelSerializer):
other_name = serializers.CharField(source='name')
class Meta:
model = MyModel
fields = ['other_name']

Custom Nested serializer and lookups

I have a Category model and a Menu Model and I want to display the JSON data as follows:
{
"category":{
"category": "Something",
"menu": [
{
"id": 1,
"dish": "Sample Dish",
"price": 150,
"restaurant": 1
},
{
"id": 1,
"dish": "Sample Dish",
"price": 150,
"restaurant": 1
},
}
}
Here are the models:
class Menu(models.Model):
dish = models.CharField(max_length=250)
category = models.ForeignKey('Menu_Category',on_delete=models.CASCADE,related_name='menu')
price = models.IntegerField()
restaurant = models.ForeignKey('Restaurant',on_delete=models.CASCADE,related_name='menu')
def __str__(self):
return self.dish
class Menu_Category(models.Model):
category = models.CharField(max_length=255,default='')
def __str__(self):
return self.category
class Meta:
verbose_name = 'Menu Category'
verbose_name_plural = 'Menu Categories'
and here is the seriailizer:
class MenuSerializer(serializers.Serializer):
class Meta:
model = Menu
fields =['dish','price','restaurant']
class MenuCategorySerializer(serializers.Serializer):
menu = MenuSerializer(read_only=True,many=True)
class Meta:
model = Menu_Category
fields = ['category','menu']
I have tried building up some custom nested serializers as well and cant seem to get the JSON data right.
You should be able to do this by creating the property on the category model that returns it's related menus. Just FYI(You can also use self.menu_set.all()). See below for the model change
class Menu_Category(models.Model):
category = models.CharField(max_length=255,default='')
def __str__(self):
return self.category
class Meta:
verbose_name = 'Menu Category'
verbose_name_plural = 'Menu Categories'
#property
def menu(self):
return Menu.objects.filter(category=self)
In the serializer, change the serializer inheritence to ModelSerializer. You shouldn't need to change anything else unless your property name in models.py is different to the serializer fieldname
Sources in Rest framework serializers
class MenuCategorySerializer(serializers.ModelSerializer):
menu = MenuSerializer(read_only=True,many=True, source='menu') # -- Source must be the same as declared in models.py property
class Meta:
model = Menu_Category
fields = ['category','menu']
You have to add id to MenuSerializer fields and you can use reverse relation menu_set with source parameter in serializer like below
class MenuSerializer(serializers.Serializer):
class Meta:
model = Menu
fields =['id', 'dish','price','restaurant']
class MenuCategorySerializer(serializers.Serializer):
menu = MenuSerializer(read_only=True,many=True, source='menu_set')
class Meta:
model = Menu_Category
fields = ['category','menu']

Resources