I have model Product:
class Product(TranslatableModel):
name = models.CharField(max_length=255, unique=True)
translations = TranslatedFields(
description=models.TextField(),
)
and in administration on product detail I have tabs with languages. For example tabs EN, CZ, each includes disctiption. So PUT request looks like:
{
'product': '1',
'id': 1,
'name': 'Name',
'translations': {
'cz': {'desctiption': 'Description cz'},
'en': {'desctiption': 'Description en'}
}
}
I founded in django-hvad TranslationsMixin that allows me to do that request.
in serializers I have:
class ProductTranslationSerializer(serializers.ModelSerializer):
class Meta:
exclude = ['description']
class ProductSerializer(TranslationsMixin, serializers.ModelSerializer):
class Meta:
model = Product
translations_serializer = ProductTranslationSerializer
fields = (
'name',
'description',
)
Question is how will looks ModelViewSet for this request? Can I choose language like 'language_code=en', filter that query and get something like:
[
{
id: 1
name: "name"
descritpion: "descritpion"
},
....
]
Thank you!
the most likely way i achieve is:
models.py
class Product(TranslatableModel):
category = models.ForeignKey('product.ProductCategory',
related_name='product_category',
null=True,
on_delete=models.SET_NULL,
verbose_name=u'category')
cover = models.ImageField(upload_to=product_cover,
null=True,
verbose_name=u'cover')
translations = TranslatedFields(
title=models.CharField(max_length=100,
null=True,
verbose_name=u'title'),
summary=models.TextField(null=True,
verbose_name=u'summary'),
model=models.CharField(max_length=255,
null=True,
blank=True,
verbose_name=u'model'),
price=models.DecimalField(default=0.00,
max_digits=10,
decimal_places=2,
blank=True,
validators=[MinValueValidator(0)],
verbose_name=u'price'),
content=models.TextField(verbose_name=u'content'),
publish_time=models.DateTimeField(default=timezone.now,
verbose_name=u'发布publish_time')
)
view_times = models.IntegerField(default=0,
verbose_name=u'view_times ')
views.py
class ProductViewSet(ModelViewSet):
serializer_class = ProductListSerializer
def get_queryset(self):
if 'language_code' in self.request.GET:
language_code = self.request.GET.get('language_code')
queryset = Product.objects.language(language_code).order_by('-id')
else:
queryset = Product.objects.language().order_by('-id')
return queryset
serializers.py
class ProductCategorySerializer(TranslatableModelSerializer):
class Meta:
model = ProductCategory
fields = '__all__'
class ProductListSerializer(TranslatableModelSerializer):
category = ProductCategorySerializer(read_only=True)
class Meta:
model = Product
exclude = ['is_abandon', 'content', ]
urls.py
from rest_framework import routers
router = routers.DefaultRouter()
router.register(r'product', ProductViewSet, base_name='api-product')
...
result:
http://192.168.1.108/api/product/?language_code=zh-hans you get:
{
"count": 1,
"page_num": 1,
"page_no": 1,
"next": "",
"previous": "",
"results": [
{
"id": 2,
"category": {
"id": 2,
"create_time": "2017-08-10 16:49:41",
"update_time": "2017-08-18 08:56:02",
"name": "测试",
"language_code": "zh-hans"
},
"create_time": "2017-08-18 08:53:46",
"update_time": "2017-08-18 08:56:28",
"cover": "http://192.168.1.108/media/product/20170818-085346-518_59.jpg",
"view_times": 0,
"title": "标题",
"summary": "简介",
"model": null,
"price": "90.00",
"publish_time": "2017-08-18 08:53:00",
"language_code": "zh-hans"
}
]
}
http://192.168.1.108/api/product/?language_code=en you get:
{
"count": 1,
"page_num": 1,
"page_no": 1,
"next": "",
"previous": "",
"results": [
{
"id": 2,
"category": {
"id": 2,
"create_time": "2017-08-10 16:49:41",
"update_time": "2017-08-18 08:56:02",
"name": "测试",
"language_code": "zh-hans"
},
"create_time": "2017-08-18 08:53:46",
"update_time": "2017-08-18 09:00:23",
"cover": "http://192.168.1.108/media/product/20170818-085346-518_59.jpg",
"view_times": 0,
"title": "title",
"summary": "summary",
"model": "model",
"price": "91.00",
"publish_time": "2017-08-18 08:56:04",
"language_code": "en"
}
]
}
this way fk will not change language,if you want fk change language too,use:
urls.py
urlpatterns += i18n_patterns(
'''
url(r'^api/', include(router.urls)),
prefix_default_language=False,
)
change language by
http://192.168.1.108/zh-hans/api/product/
to
http://192.168.1.108/en/api/product/
your will get:
{
"count": 1,
"page_num": 1,
"page_no": 1,
"next": "",
"previous": "",
"results": [
{
"id": 2,
"category": {
"id": 2,
"create_time": "2017-08-10 16:49:41",
"update_time": "2017-08-18 08:56:02",
"name": "test",
"language_code": "en"
},
"create_time": "2017-08-18 08:53:46",
"update_time": "2017-08-18 09:00:23",
"cover": "http://192.168.1.108/media/product/20170818-085346-518_59.jpg",
"view_times": 0,
"title": "title",
"summary": "summary",
"model": "model",
"price": "91.00",
"publish_time": "2017-08-18 08:56:04",
"language_code": "en"
}
]
}
Answer from owner of django-hvad github repo #spectras:
If I understand well, you want to use a different serialized form for the collection endpoint and the item endpoint. That is, GET-1, PUT, POST would use the translations mixin, while GET-many would not.
You need two have two different serializers for this:
The one you created
another one that would be, for instance,
class ProductItemSerializer(TranslatableModelSerializer): ...
another one that would be, for instance,
class ProductItemSerializer(TranslatableModelSerializer): ...
Given that, you can add to your ModelViewSet a method that dynamically chooses the serializer class depending on request type:
class SomeModelViewSet(ModelViewSet):
# serializer_class = not needed, we use the method instead
def get_serializer_class(self):
if self.action == 'list':
return ProductItemSerializer
return ProductSerializer
An alternate method, perhaps easier to use on the Javascript side, would be to make 2 viewsets for the Product model, a read-write one being translation aware (using ProductSerializer) and a read-only one not translation aware, using (ProductItemSerializer).
In the same vein, I usually only have translation-unaware serializers and add a /api/product/details/ endpoint that is translation-aware. This way, I only have to handle the complexity of a dict object when going into edit/detailed mode on the client side.
Related
the problem is this, i've created a snippet and i want to expose it but i don't know how to serialize nested data. here my code:
models.py
#register_snippet
class TeamMember(ClusterableModel):
name = models.CharField(max_length=80)
description = models.CharField(max_length=80)
panels = [
FieldPanel('name'),
InlinePanel('tasks', label=('Tasks')),
]
class Task(Orderable):
team_member = ParentalKey('adm.TeamMember', related_name="tasks")
task_name = models.CharField(max_length=80)
endpoints.py
class TeamMemberAPIEndpoint(BaseAPIViewSet):
model = TeamMember
body_fields = BaseAPIViewSet.body_fields + [
'name',
'description',
'tasks',
]
listing_default_fields = BaseAPIViewSet.listing_default_fields = [
'name',
'description',
'tasks',
]
the result is:
"items": [
{
"name": "python team",
"description": "",
"tasks": [
{
"id": 1,
"meta": {
"type": "adm.Task"
}
},
{
"id": 2,
"meta": {
"type": "adm.Task"
}
}
]
}
how can i resolve this problem?
You have several ways to get the nested data.
provide a dedicated serializer for the nested items within the API Endpoint.
the wagtail way. Define additional api properties.
from rest_framework import serializers
from wagtail.api import APIField
class Task(Orderable):
team_member = ParentalKey('adm.TeamMember', related_name="tasks")
task_name = models.CharField(max_length=80)
class TaskSerializer(serializers.ModelSerializer):
class Meta:
model = Task
fields = (
"id",
"task_name",
)
#register_snippet
class TeamMember(ClusterableModel):
name = models.CharField(max_length=80)
description = models.CharField(max_length=80)
panels = [
FieldPanel('name'),
InlinePanel('tasks', label=('Tasks')),
]
api_fields = [
APIField("tasks", serializer=TaskSerializer(many=True)),
]
the response looks like this:
{
"meta": {
"total_count": 1
},
"items": [
{
"name": "Member 1",
"tasks": [
{
"id": 1,
"task_name": "Do this"
},
{
"id": 2,
"task_name": "Do that"
}
]
}
]
}
Iam using Django with Restframework.
Iam trying to get a json output from serializers in a nested manner with key as one of the fields from model. I acheived the nested JSON but the json key for them is troubling me.
Here is my code and expected results out of it:
Models.py
class Tags(models.Model):
tagId = models.CharField(primary_key=True, max_length=100, default=1)
section = models.CharField(max_length=100,default=1)
def __str__(self):
return self.section
class TagItem(models.Model):
section= models.ForeignKey(Tags, on_delete=models.CASCADE,default=1,related_name="items")
select = models.BooleanField(default=False)
name = models.CharField(max_length=50)
def __str__(self):
return self.name
serializers.py
class TagItemModelSerializer(serializers.ModelSerializer):
class Meta:
model = TagItem
fields = '__all__'
class TagModelSerializer(serializers.ModelSerializer):
items = TagItemModelSerializer(many=True)
class Meta:
model = Tags
fields = ['pk','section', 'items']
Expected output:
{
"crafts" : { //craft is comming from Tag model's "section"
"id": 1,
"section": "crafts",
"items": [
{
"id": "10",
"select": false,
"name": "Wood",
"category": "crafts"
},
]
},
"states" : {
"id": 2,
"section": "states",
"items": [
{
"id": "20",
"select": false,
"name": "Andhra Pradesh",
"category": "states"
}
]
},
"others" : {
"id": 3,
"section": "others",
"items": [
{
"id": "30",
"select": false,
"name": "Volunteer",
"category": "others"
}
]
}
}
Current output:
[ //cant get the key of Tag model's "section"
{
"pk": "1",
"section": "states",
"items": [
{
"id": 6,
"select": false,
"name": "Assam",
"section": "1"
}
]
},
{
"pk": "2",
"section": "others",
"items": [
{
"id": 12,
"select": false,
"name": "Artisan",
"section": "2"
}
]
},
{
"pk": "3",
"section": "crafts",
"items": [
{
"id": 9,
"select": false,
"name": "Metal",
"section": "3"
}
]
}
]
At your case, you would like to have a custom representation I think?
You can adjust it with overriding to_representation()
I provide two approaches:
serializer -> the better way but not quite the output you prefer
view
class TagModelView(views.APIView):
def get(self, request):
qs = Tags.objects.all()
serializer = TagModelSerializer(qs, many=True)
return Response(data=serializer.data)
serializer
class TagItemModelSerializer(serializers.ModelSerializer):
category = serializers.SerializerMethodField()
class Meta:
model = TagItem
fields = ['id', 'select', 'name', 'category']
def get_category(self, instance):
return instance.section.section
class TagModelSerializer(serializers.ModelSerializer):
items = TagItemModelSerializer(many=True)
class Meta:
model = Tags
fields = ['pk', 'section', 'items']
def to_representation(self, instance):
container = super().to_representation(instance)
return {container.get('section'): container}
Making representation in your view -> output you would like to get
view
def get(self, request):
qs = Tags.objects.all()
serializer = TagModelSerializer(qs, many=True)
container = dict()
for element in serializer.data:
container.update(**element)
return Response(data=container)
serializer
No change to my proposal obove.
update
I saw that your primary key of Tags is tagId and you are using fields = ['pk', 'section', 'items'] but you would like to have id as a key-name.
Relabeling can help you.
class TagModelSerializer(serializers.ModelSerializer):
items = TagItemModelSerializer(many=True)
id = serializers.CharField(source='tagId')
class Meta:
model = Tags
fields = ['id', 'section', 'items']
I'm using Django Rest Framework for API and I wrote the model Category in which I have parent and I'm using it like this: parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True)
In API it looks like this:
{
"id": 7,
"name": "softwares",
"img": "",
"parent": 1
},
{
"id": 8,
"name": "databases",
"img": "",
"parent": 1
},
{
"id": 9,
"name": "appearance",
"img": "",
"parent": 2
},
{
"id": 10,
"name": "media",
"img": "",
"parent": 2
},
{
"id": 11,
"name": "system",
"img": "",
"parent": 2
},
Here is Category Serializer:
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = '__all__'
And Category View Set:
class CategoryViewSet(viewsets.ModelViewSet):
queryset = Category.objects.all()
serializer_class = CategorySerializer
action_to_serializer = {
"retrieve": CategoryDetailSerializer,
}
def get_serializer_class(self):
return self.action_to_serializer.get(
self.action,
self.serializer_class
)
How can I make the category itself and not just the id?
Assuming you won't be handling with an infinite sequence of relations and are handling this with just one level of parenting, you can achieve that by using nested serializers, an example on your scenario would be something like this:
class NestedCategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ('id', 'name', 'img')
class CategorySerializer(serializers.ModelSerializer):
parent = NestedCategorySerializer()
class Meta:
model = Category
fields = '__all__'
If your relationship needs more depth levels, you can improve this example to include always the serialized parent - be careful with cycles.
I am new to Django rest framework.i am trying to get child model records to the parent model as a field so that all the RefreshmentImage models records are available in games_sports list.i have posted sample code.
model.py
class Refreshment(models.Model):
title = models.CharField(max_length=200, unique=True)
type = models.CharField(max_length=200)
charges = models.DecimalField(max_digits=12, decimal_places=2, help_text="Charges per hour")
class RefreshmentImage(models.Model):
refreshment = models.ForeignKey(Refreshment, on_delete=models.CASCADE)
image = models.FileField(upload_to="refreshment_image/", null=True, blank=True)
serializers.py
class EntertainmentSerializer(serializers.ModelSerializer):
class Meta:
model = Refreshment
fields = '__all__'
class RefreshmentImageSerializer(serializers.ModelSerializer):
refreshment = EntertainmentSerializer(read_only=True, many=True)
class Meta:
model = RefreshmentImage
fields = '__all__'
views.py
def all_games_sports(request):
entertainment = Refreshment.objects.all()
serialize = EntertainmentSerializer(instance=entertainment,many=True)
serial = RefreshmentImageSerializer(instance=entertainment,many=True)
main = {'status': True, 'code': CODE_SUCCESSFUL, 'msg': SUCCESS, 'games_sports': serialize.data,'image':serial.data}
return HttpResponse(json.dumps(main), content_type='application/json')
what i got is like:
games_sports": [
{
"id": 1,
"title": "yyy",
"type": 1,
"charges": "500.00",
},
{
"id": 2,
"title": "xxxxx",
"type": "something",
"charges": "501.00",
}
*******
],
"image": [
{
"id": 1,
"image": null,
"date_created": "2019-03-03T08:16:15.538024+05:30"
},
**********
]
i want it to be:
games_sports": [
{
"id": 1,
"title": "yyy",
"type": 1,
"charges": "500.00",
"image": [
{
"id": 1,
"image": image_path,
"date_created": "2019-03-03T08:16:15.538024+05:30"
},
}
***********
],
Try this snippet
#serializers.py
"""I've re-arranged the order of 'RefreshmentImageSerializer' serializer and 'EntertainmentSerializer' serializer"""
class RefreshmentImageSerializer(serializers.ModelSerializer):
class Meta:
model = RefreshmentImage
fields = '__all__'
class EntertainmentSerializer(serializers.ModelSerializer):
image = RefreshmentImageSerializer(many=True, source='refreshmentimage_set')
class Meta:
model = Refreshment
fields = '__all__'
# views.py
"""Added DRF stuffs such as 'api_view' and 'Response'"""
from rest_framework.decorators import api_view
#api_view()
def all_games_sports(request):
entertainment = Refreshment.objects.all()
serialize = EntertainmentSerializer(instance=entertainment, many=True)
main = {'status': True, 'code': "CODE_SUCCESSFUL", 'msg': "SUCCESS", 'games_sports': serialize.data}
return Response(main)
{
"status": true,
"code": "CODE_SUCCESSFUL",
"msg": "SUCCESS",
"games_sports": [
{
"id": 1,
"image": [
{
"id": 1,
"image": null,
"refreshment": 1
},
{
"id": 3,
"image": "refreshment_image/jpg-icon.png",
"refreshment": 1
}
],
"title": "t1",
"type": "tt1",
"charges": "123.00"
},
{
"id": 2,
"image": [
{
"id": 2,
"image": "refreshment_image/asd.jpg",
"refreshment": 2
}
],
"title": "t2",
"type": "tt2",
"charges": "321.00"
},
{
"id": 3,
"image": [
{
"id": 4,
"image": "refreshment_image/Screenshot_from_2018-10-26_16-32-41.png",
"refreshment": 3
},
{
"id": 5,
"image": "refreshment_image/twitter.png",
"refreshment": 3
}
],
"title": "t3",
"type": "tt3",
"charges": "754.00"
}
]
}
What I've done here?
re-arranged the order of serializer to avoid not defined errors
added a new field in EntertainmentSerializer class to show the images associated with the Refreshment object
In views.py I've added DRF pieces of stuff, which is more suitable
References
#api_view() decorator
DRF's Response() class
DRF Nested Serializers
The source keyword argument
Hope this helps!!
I need to add additional fields that are not part of my model to the json response. Is this possible? The models are connected using multiple tables, but do not have a direct connection.
models.py
class Message(models.Model):
body = models.TextField(max_length=500, blank=False)
sender = models.ForeignKey(User)
timestamp = models.DateTimeField(auto_now_add=True)
conversation = models.ForeignKey(Conversation)
serializers.py
class MessageSerializer(serializers.ModelSerializer):
conversation = serializers.ReadOnlyField(source='conversation.id')
sender = serializers.ReadOnlyField(source='sender.id')
# task = TaskSerializer(source='conversation__task_worker__task.id')
# This commented code above is not working, but this is what I need.
class Meta:
model = Message
fields = '__all__'
So what I need, is to pass the value of task, which can be found by going back a few tables. E.g. if the current model is Message, I need to:
Message -> Conversation -> Task_worker -> Task (Get this)
My current json response is:
{
"count": 2,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"conversation": 1,
"sender": 2,
"body": "Hello There",
"timestamp": "2018-01-31T17:48:19.680113Z"
},
{
"id": 3,
"conversation": 1,
"sender": 2,
"body": "Can you do the task",
"timestamp": "2018-02-01T09:39:24.797218Z"
}
]
}
Ideally, I would like the following response:
{
"count": 2,
"next": null,
"previous": null,
"conversation": 1,
"task": 5,
"any_extra_fields_key": 'value',
"results": [
{
"id": 1,
"sender": 2,
"body": "Hello There",
"timestamp": "2018-01-31T17:48:19.680113Z"
},
]
}
The response is after (list view): GET /api/conversations/<conversation-id>/messages/
EDIT: added views.py
class MessageList(generics.ListCreateAPIView):
serializer_class = MessageSerializer
def get_queryset(self):
conversation = Conversation.objects.get(pk=self.kwargs['conv_id'])
queryset = Message.objects.filter(conversation=conversation)
return queryset
def perform_create(self, serializer):
conversation = Conversation.objects.get(pk=self.kwargs['conv_id'])
serializer.save(sender=self.request.user, conversation=conversation)
EDIT 2:
class MessageSerializer(serializers.ModelSerializer):
sender = serializers.ReadOnlyField(source='sender.id')
children_list = serializers.SerializerMethodField('get_children')
def get_children(self, obj):
serializer = TaskSerializer(source='conversation__task_worker__task', many=True)
return serializer.data
class Meta:
model = Message
fields = ['children_list','sender','conversation','timestamp']
outputs:
{
"count": 2,
"next": null,
"previous": null,
"results": [
{
"children_list": [],
"sender": 2,
"conversation": 1,
"timestamp": "2018-01-31T17:48:19.680113Z"
},
{
"children_list": [],
"sender": 2,
"conversation": 1,
"timestamp": "2018-02-01T09:39:24.797218Z"
}
]
}
First of all, children list is empty, but it should be the task, and also it's still inside the 'results' key, creating a lot of duplication, since the children_list is the same task.
SerializerMethodField is what I needed to get me the data that I want. However, the structure is still a problem
class MessageSerializer(serializers.ModelSerializer):
sender = serializers.ReadOnlyField(source='sender.id')
task_id = serializers.SerializerMethodField()
def get_task_id(self, obj):
task = Task.objects.filter(task_worker__conversation__message=obj)[0]
return task.id
class Meta:
model = Message
fields = ['task_id','sender','conversation','timestamp']