I have a django orm queryset like this.
[{"id": 1,"type": "en", "name": "aaa"},
{"id": 3,"type": "en", "name": "ccc"},
{"id": 2, "type": "it", "name": "bbb"}]
I would like to change like this, grouping a list of dictionaries by type using DRF serializer, I tried many possible solutions but could not get the answer.
[{"type": "en", "data": {"id": 1, "name": "aaa"},
{"type": "it", "data": {"id": 2, "name": "bbb"},
{"type": "it", "data": {"id": 2, "name": "ccc"}]
In[1]: from rest_framework import serializers
In[2]: raw_data = [{"id": 1,"type": "en", "name": "aaa"},
...: {"id": 3,"type": "en", "name": "ccc"},
...: {"id": 2, "type": "it", "name": "bbb"}]
In[3]: class DataSerializer(serializers.Serializer):
...: id = serializers.IntegerField()
...: name = serializers.CharField()
...:
In[4]: class MainSerializer(serializers.Serializer):
...: type = serializers.CharField()
...: data = serializers.SerializerMethodField()
...:
...: def get_data(self, obj):
...: return DataSerializer(obj).data
...:
In[5]: MainSerializer(raw_data, many=True).data
Out[5]: [OrderedDict([('type', 'en'), ('data', {'id': 1, 'name': 'aaa'})]), OrderedDict([('type', 'en'), ('data', {'id': 3, 'name': 'ccc'})]), OrderedDict([('type', 'it'), ('data', {'id': 2, 'name': 'bbb'})])]
or easier:
class MainSerializer(serializers.Serializer):
type = serializers.CharField()
data = DataSerializer(source='*')
Related
Currently, I am working on a DFR project where can successfully get all data but i need some modify with the json data.
Here Is the code
class PermissionSerializers(serializers.ModelSerializer):
class Meta:
model = Permission
fields = ['id', 'name', 'codename']
def to_representation(self, instance):
return {
'model': instance.content_type.name,
'data' :{
'id': instance.id,
'name': instance.name,
'codename': instance.codename,
}
}
And i get this JSON format,
{
"next": "http://127.0.0.1:8000/en/ga/api-version/common/admin/permissions/?page=4",
"previous": "http://127.0.0.1:8000/en/ga/api-version/common/admin/permissions/?page=2",
"total": 33,
"page": 3,
"page_size": 10,
"results": [
{
"model": "user",
"data": {
"id": 25,
"name": "Can add user",
"codename": "add_user"
}
},
{
"model": "user",
"data": {
"id": 29,
"name": "Admistrative Access",
"codename": "admin_access"
}
},
But I want to modify with something like this which has model name on top and then all available data inside a dictionary:
{
"model": "user",
"data": {
"id": 26,
"name": "Can change user",
"codename": "change_user"
},
{
"id": 25,
"name": "Can add user",
"codename": "add_user"
},
},
You get something like this because you have pagination in your API, if you don't want it just disable pagination.
I came up with this solution:
def list(self, request):
_models_list = [
'organization','user','group', 'logentry', 'organizationtype',
'keyword', 'productsupport','feedbacksupport','twittercredential']
models = ContentType.objects.filter(model__in = _models_list)
model_dict = {
'model': '',
'data':''
}
results = []
for model in models:
_permissions = []
access = ' Access'
if model.model == 'group':
model_dict['model'] = 'Role'+ access
elif model.model == 'organizationtype':
model_dict['model'] = 'Organization Type'+ access
elif model.model == 'productsupport':
model_dict['model'] = 'Product'+ access
elif model.model == 'feedbacksupport':
model_dict['model'] = 'Feedback'+ access
else:
model_dict['model'] = model.model.capitalize()+ access
permissions = Permission.objects.filter(content_type = model)
for premission in permissions:
_permissions.append(premission)
serializer = PermissionSerializers(_permissions, many=True)
data = serializer.data
model_dict['data'] = data
results.append(model_dict.copy())
return Response(results)
I need to serialize attributes of an item related from many to many, but grouped by each type of attribute.
Models
from django.db import models
class AtributoTipo(models.Model):
nombre = models.CharField(max_length=40)
slug = models.SlugField(max_length=40, unique=True)
class Atributo(models.Model):
tipo = models.ForeignKey(AtributoTipo, on_delete=models.CASCADE)
nombre = models.CharField(max_length=40)
slug = models.SlugField(max_length=40)
class Articulo(models.Model):
codigo = models.CharField(max_length=18, unique=True, db_index=True)
nombre = models.CharField(max_length=255)
atributos = models.ManyToManyField(Atributo, related_name="articulos")
Normal DRF serializers:
from rest_framework import serializers
class AtributoTipoSerializer(serializers.ModelSerializer):
class Meta:
model = AtributoTipo
fields = ["__all__"]
class AtributoSerializer(serializers.ModelSerializer):
tipo_slug = serializers.ReadOnlyField(source="tipo.slug")
tipo_nombre = serializers.ReadOnlyField(source="tipo.nombre")
class Meta:
model = Atributo
fields = ["__all__"]
class ArticuloSerializer(serializers.ModelSerializer):
atributos = AtributoSerializer(many=True, read_only=True) # the dude
class Meta:
model = Articulo
fields = ["__all__"]
This is the result with serializers without any weird method:
{
"id": 44906,
"codigo": "DE0058751",
"atributos": [
{
"id": 15107,
"tipo": 76,
"tipo_slug": "talla",
"tipo_nombre": "Talla",
"nombre": "39",
"slug": "39"
},
{
"id": 43454,
"tipo": 76,
"tipo_slug": "talla",
"tipo_nombre": "Talla",
"nombre": "40",
"slug": "40"
},
{
"id": 23234,
"tipo": 15,
"tipo_slug": "color",
"tipo_nombre": "Color",
"nombre": "Rojo",
"slug": "rojo"
},
{
"id": 12408,
"tipo": 15,
"tipo_slug": "color",
"tipo_nombre": "Color",
"nombre": "Verde",
"slug": "verde"
}
]
}
Desired result:
{
"id": 44906,
"codigo": "DE0058751",
"atributos": [
{
"id": 76,
"slug": "talla",
"nombre": "Talla",
"atributos": [
{
"id": 15107,
"tipo": 76,
"nombre": "39",
"slug": "39"
},
{
"id": 12408,
"tipo": 76,
"nombre": "40",
"slug": "40"
}
]
},
{
"id": 15,
"slug": "color",
"nombre": "Color",
"atributos": [
{
"id": 34234,
"tipo": 15,
"nombre": "Rojo",
"slug": "rojo"
},
{
"id": 2323,
"tipo": 15,
"nombre": "Verde",
"slug": "verde"
}
]
}
]
}
The problem is that you lose the context of the article.
I tried itertools groupby, but I think there must be another logic with DRF.
You can redefine to_representation of ListSerializer and use ListSerializer as a field of your ArticuloSerializer:
class GroupedAttributosSerializer(serializers.ListSerializer):
def to_representation(self, data):
data = super().to_representation(data)
tipo_slug_to_objects = defaultdict(list)
for d in data:
tipo_slug_to_objects[d["tipo_slug"]].append(d)
result = []
for tipo_slug, objects in tipo_slug_to_objects.items():
result.append(
{
"id": objects[0]["tipo"],
"slug": tipo_slug,
"nombre": objects[0]["tipo_nombre"],
"attributos": [
{"id": attribute["id"],
"tipo": attribute["tipo"],
"nombre": attribute["nombre"],
"slug": attribute["slug"]}
for attribute in objects
]
}
)
return result
class ArticuloSerializer(serializers.ModelSerializer):
atributos = GroupedAttributosSerializer(child=AtributoSerializer(), read_only=True, default=[])
class Meta:
model = Articulo
fields = ["__all__"]
I use userProfile model the following code. It has "OneToOneField" for djoser auth_user. I want gel all data for users.
from django.db import models
from django.contrib.auth.models import User
class userProfile(models.Model):
user=models.OneToOneField(User,on_delete=models.CASCADE,related_name="profile")
date_joined=models.DateTimeField(auto_now_add=True)
updated_on=models.DateTimeField(auto_now=True)
def __str__(self):
return self.user.username
and seralizers class show in bellow;
from rest_framework import serializers
from .models import userProfile
class userProfileSerializer(serializers.ModelSerializer):
user=serializers.StringRelatedField(read_only=True)
class Meta:
model=userProfile
fields='__all__'
I want to get all users using ;
class userAccountsListView(ListAPIView):
queryset=userProfile.objects.all()
serializer_class=userProfileSerializer
It return data like;
[
{
"id": 1,
"date_joined": "2020-04-16T16:50:38.218964+03:00",
"updated_on": "2020-04-16T16:50:38.218996+03:00",
"user": 5
},
{
"id": 2,
"date_joined": "2020-04-30T13:53:48.859116+03:00",
"updated_on": "2020-04-30T13:53:48.859149+03:00",
"user": 6
}
]
I want to get all users info;
[
{
"id": 1,
"date_joined": "2020-04-16T16:50:38.218964+03:00",
"updated_on": "2020-04-16T16:50:38.218996+03:00",
"user": 5,
"first_name": "xxxxx",
"last_name": "xxxxx",
"email":"xxxx",
},
{
"id": 2,
"date_joined": "2020-04-30T13:53:48.859116+03:00",
"updated_on": "2020-04-30T13:53:48.859149+03:00",
"user": 6,
"first_name": "xxxxx",
"last_name": "xxxxx",
"email":"xxxx",
}
]
I added the own CurrentUserSerializer.
class CurrentUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'email', 'id','first_name','last_name')
class userProfileSerializer(serializers.ModelSerializer):
user=CurrentUserSerializer(read_only=True)
class Meta:
model=userProfile
fields='__all__
I get json output;
[
{
"id": 1,
"user": {
"username": "x",
"email": "x",
"id": 5,
"first_name": "x",
"last_name": "x"
},
"date_joined": "2020-04-16T16:50:38.218964+03:00",
"updated_on": "2020-04-16T16:50:38.218996+03:00"
},
{
"id": 2,
"user": {
"username": "x",
"email": "x",
"id": 6,
"first_name": "x",
"last_name": "x"
},
"date_joined": "2020-04-30T13:53:48.859116+03:00",
"updated_on": "2020-04-30T13:53:48.859149+03:00"
}
]
On my json output I don't seem to get key value pairs on my m2m field attribute_answers. see the code below. How to I add in the attribute_answers fields?
json
{
"id": 20,
"name": "Jake",
"active": true,
"type": {
"id": 1,
"name": "Human",
"active": true,
"created": "2013-02-12T13:31:06Z",
"modified": null
},
"user": "jason",
"attribute_answers": [
1,
2
]
}
Serializer
class ProfileSerializer(serializers.ModelSerializer):
user = serializers.SlugRelatedField(slug_field='username')
attribute_answers = serializers.PrimaryKeyRelatedField(many=True)
class Meta:
model = Profile
depth = 2
fields = ('id', 'name', 'active', 'type', 'user', 'attribute_answers')
def restore_object(self, attrs, instance=None):
"""
Create or update a new snippet instance.
"""
if instance:
# Update existing instance
instance.name = attrs.get('name', instance.name)
instance.active = attrs.get('active', instance.active)
instance.type = attrs.get('type', instance.type)
instance.attribute_answers = attrs.get('attribute_answers', instance.attribute_answers)
return instance
# Create new instance
return Profile(**attrs)
If I understand your question correctly, you want a Nested Relationship. In your ProfileSerializer, you'll want:
attribute_answers = AttributeAnswerSerializer(many=True)
This will display all the attributes of each associated attribute_answer model and give you something like:
{
"id": 20,
"name": "Jake",
"active": true,
"type": {
"id": 1,
"name": "Human",
"active": true,
"created": "2013-02-12T13:31:06Z",
"modified": null
},
"user": "jason",
"attribute_answers": [
{"id": 1, "foo": "bar"},
{"id": 2, "foo": "bar2"}
]
}
this is my model
class Nisit(models.Model):
and this
class Page(models.Model):
followingNisit = models.ManyToManyField(Nisit,blank=True)
this is my resource
class NisitResource(ModelResource):
page = fields.ToManyField('chula.api.PageResource','page_set',null=True)
class Meta:
queryset = Nisit.objects.all()
resource_name = 'nisit'
filtering = {
'page' : ALL_WITH_RELATIONS,
'id' : ALL,
}
class PageResource(ModelResource):
followingNisit = fields.ToManyField(NisitResource, 'followingNisit',null=True)
reporter = fields.ManyToManyField(ReporterResource,'reporter')
followers_count = fields.CharField(attribute='followers_count')
class Meta:
queryset = Page.objects.all()
resource_name = 'page'
authorization= Authorization()
filtering = {
'id':ALL,
'followingNisit': ALL_WITH_RELATIONS,
}
It's ok when I request -------127.0.0.1:8000/api/v2/page/?format=json&followingNisit__id=1
But In the opposite way ,when i request ---------127.0.0.1:8000/api/v2/nisit/?format=json&page__id=1, I will get this error
{"error_message": "Cannot resolve keyword 'page_set' into field. Choices are: displayName, facebook, faculty, friend, id, major, n_id, name, page, password, picture, reporter, year_in_school", "traceback": "Traceback (most recent call last):\n\n File \"D:\\Study\\SeniorProject\\Code\\mysite\\tastypie\\resources.py\", line 202, in wrapper\n response = callback(request, *args, **kwargs)\n\n File \"D:\\Study\\SeniorProject\\Code\\mysite\\tastypie\\resources.py\", line 441, in dispatch_list\n return self.dispatch('list', request, **kwargs)\n\n File \"D:\\Study\\SeniorProject\\Code\\mysite\\tastypie\\resources.py\", line 474, in dispatch\n response = method(request, **kwargs)\n\n File \"D:\\Study\\SeniorProject\\Code\\mysite\\tastypie\\resources.py\", line 1127, in get_list\n objects = self.obj_get_list(request=request, **self.remove_api_resource_names(kwargs))\n\n File \"D:\\Study\\SeniorProject\\Code\\mysite\\tastypie\\resources.py\", line 1890, in obj_get_list\n base_object_list = self.apply_filters(request, applicable_filters)\n\n File \"D:\\Study\\SeniorProject\\Code\\mysite\\tastypie\\resources.py\", line 1862, in apply_filters\n return self.get_object_list(request).filter(**applicable_filters)\n\n File \"C:\\Python27\\lib\\site-packages\\django\\db\\models\\query.py\", line 624, in filter\n return self._filter_or_exclude(False, *args, **kwargs)\n\n File \"C:\\Python27\\lib\\site-packages\\django\\db\\models\\query.py\", line 642, in _filter_or_exclude\n clone.query.add_q(Q(*args, **kwargs))\n\n File \"C:\\Python27\\lib\\site-packages\\django\\db\\models\\sql\\query.py\", line 1250, in add_q\n can_reuse=used_aliases, force_having=force_having)\n\n File \"C:\\Python27\\lib\\site-packages\\django\\db\\models\\sql\\query.py\", line 1122, in add_filter\n process_extras=process_extras)\n\n File \"C:\\Python27\\lib\\site-packages\\django\\db\\models\\sql\\query.py\", line 1316, in setup_joins\n \"Choices are: %s\" % (name, \", \".join(names)))\n\nFieldError: Cannot resolve keyword 'page_set' into field. Choices are: displayName, facebook, faculty, friend, id, major, n_id, name, page, password, picture, reporter, year_in_school\n"}
I have also been struggling with the same FieldError thrown from setup_joins, and I think I just solved my issue. I was trying to filter through a Many-to-Many relationship, and could never get the "_set" in fields.ToManyField() to work. After debugging the TastyPie code in the error case and in a working simple filter, I realized it may be possible to bypass the need for the intermediary resource altogether.
Here is what worked for me, I hope it helps in your situation. Since I don't know your model setup, I'll make up a similar example.
First, the models:
### models.py ###
from django.db import models
class Ingredient(models.Model):
name = models.CharField(max_length=100)
description = models.TextField()
def __unicode__(self):
return '%s' % (self.name)
class RecipeIngredient(models.Model):
recipe = models.ForeignKey('Recipe')
ingredient = models.ForeignKey('Ingredient')
weight = models.IntegerField(null = True, blank = True)
def __unicode__(self):
return '%s: %s' % (self.recipe, self.ingredient)
class Recipe(models.Model):
title = models.CharField(max_length=100)
ingredients = models.ManyToManyField(Ingredient, through='RecipeIngredient')
def __unicode__(self):
return '%s' % (self.title)
And here are the ModelResources:
### api.py ###
from tastypie.resources import ModelResource, ALL, ALL_WITH_RELATIONS
from tastypie import fields
from some_app.models import Ingredient, Recipe
class IngredientResource(ModelResource):
class Meta:
queryset = Ingredient.objects.all()
resource_name = 'ingredient'
filtering = {
'name': ALL,
}
class RecipeResource(ModelResource):
ingredients = fields.ToManyField(
'some_app.api.IngredientResource',
'ingredients',
full=True)
class Meta:
queryset = Recipe.objects.all()
resource_name = 'recipe'
filtering = {
'title': ALL,
'ingredients': ALL_WITH_RELATIONS,
}
Notice I do not have a RecipeIngredientResource, I hook directly to the IngredientResource, which works because the Recipe model includes the ManyToManyField ingredients with the option through='RecipeIngredient'
An example URL to filter all recipes for a particular ingredient then looks like:
http://localhost:8000/api/recipes/recipe/?ingredients__name=blueberry
And, for completeness, here's a fixture for a Django app named 'some_app' to save time entering data for anyone wanting to implement this example:
[
{
"pk": 1,
"model": "some_app.ingredient",
"fields": {
"name": "apple",
"description": "a tempting fruit"
}
},
{
"pk": 2,
"model": "some_app.ingredient",
"fields": {
"name": "cherry",
"description": "a red fruit"
}
},
{
"pk": 3,
"model": "some_app.ingredient",
"fields": {
"name": "blueberry",
"description": "a blue fruit"
}
},
{
"pk": 4,
"model": "some_app.ingredient",
"fields": {
"name": "flour",
"description": "used for baking and stuff"
}
},
{
"pk": 5,
"model": "some_app.ingredient",
"fields": {
"name": "sugar",
"description": "makes stuff sweet"
}
},
{
"pk": 1,
"model": "some_app.recipeingredient",
"fields": {
"recipe": 1,
"weight": 3,
"ingredient": 1
}
},
{
"pk": 2,
"model": "some_app.recipeingredient",
"fields": {
"recipe": 1,
"weight": 2,
"ingredient": 4
}
},
{
"pk": 3,
"model": "some_app.recipeingredient",
"fields": {
"recipe": 1,
"weight": 4,
"ingredient": 5
}
},
{
"pk": 4,
"model": "some_app.recipeingredient",
"fields": {
"recipe": 2,
"weight": 8,
"ingredient": 2
}
},
{
"pk": 5,
"model": "some_app.recipeingredient",
"fields": {
"recipe": 2,
"weight": 4,
"ingredient": 4
}
},
{
"pk": 6,
"model": "some_app.recipeingredient",
"fields": {
"recipe": 2,
"weight": 6,
"ingredient": 5
}
},
{
"pk": 7,
"model": "some_app.recipeingredient",
"fields": {
"recipe": 3,
"weight": 15,
"ingredient": 3
}
},
{
"pk": 8,
"model": "some_app.recipeingredient",
"fields": {
"recipe": 3,
"weight": 5,
"ingredient": 4
}
},
{
"pk": 9,
"model": "some_app.recipeingredient",
"fields": {
"recipe": 3,
"weight": 6,
"ingredient": 5
}
},
{
"pk": 1,
"model": "some_app.recipe",
"fields": {
"title": "Apple Pie"
}
},
{
"pk": 2,
"model": "some_app.recipe",
"fields": {
"title": "Cherry Pie"
}
},
{
"pk": 3,
"model": "some_app.recipe",
"fields": {
"title": "Blueberry Pie"
}
}
]
This is the thing, if django tastypie works very similar to django (as anyone could expect) you are using a bad keyword, in you case, It wouldn't be page_set, use just page instead and it will work.
I recommend to you using a plural in the field name
pages = fields.ToManyField('chula.api.PageResource','page_set',null=True)
so the forward relation is pages and the backward relation is page_set I cant remember which now. But anyway it will look nicer.