Django Tastypie - How to get related Resources with a single request - django

Suppose following Resources are given:
class RecipeResource(ModelResource):
ingredients = fields.ToManyField(IngredientResource, 'ingredients')
class Meta:
queryset = Recipe.objects.all()
resource_name = "recipe"
fields = ['id', 'title', 'description',]
class IngredientResource(ModelResource):
recipe = fields.ToOneField(RecipeResource, 'recipe')
class Meta:
queryset = Ingredient.objects.all()
resource_name = "ingredient"
fields = ['id', 'ingredient',]
A HTTP Request to myhost.com/api/v1/recipe/?format=json gives following response:
{
"meta":
{
...
},
"objects":
[
{
"description": "Some Description",
"id": "1",
"ingredients":
[
"/api/v1/ingredient/1/"
],
"resource_uri": "/api/v1/recipe/11/",
"title": "MyRecipe",
}
]
}
So far so good.
But now, I would like to exchange the ingredients resource_uri ("/api/v1/ingredient/1/") with something like that:
{
"id": "1",
"ingredient": "Garlic",
"recipe": "/api/v1/recipe/1/",
"resource_uri": "/api/v1/ingredient/1/",
}
To get following response:
{
"meta":
{
...
},
"objects":
[
{
"description": "Some Description",
"id": "1",
"ingredients":
[
{
"id": "1",
"ingredient": "Garlic",
"recipe": "/api/v1/recipe/1/",
"resource_uri": "/api/v1/ingredient/1/",
}
],
"resource_uri": "/api/v1/recipe/11/",
"title": "MyRecipe",
}
]
}

The answer is the attribute full=True:
ingredients = fields.ToManyField('mezzanine_recipes.api.IngredientResource', 'ingredients', full=True)

Related

How to change DRF API SlugRelatedField Response template

I have managed to create a working model with 2 different serializers, depending on what we are doing. Right now, ReadTitleSerializer returns the below JSON object:
[
{
"id": 1,
"category": {
"id": 1,
"name": "Movies",
"slug": "movie"
},
"genres": [
{
"id": 1,
"name": "Drama",
"slug": "drama"
}
],
"name": "Drama Llama",
"year": "1998-02-02",
"description": null,
"rating": null
}
]
And this is the response from WriteTitleSerializer:
{
"id": 1,
"category": "movie",
"genres": [
"drama"
],
"name": "Drama Llama",
"year": "1998-02-02",
"description": null,
"rating": null
}
How can I make WriteTitleSerializer respond similarly to ReadTitleSerializer? I am using SlugRelatedField in WriteTitleSerializer because the JSON input should be a list of slugs.
Input JSON
{
"name": "Drama Llama",
"year": "1998-02-02",
"category": "movie",
"genres": [
"drama"
]
}
serializers.py
class ReadTitleSerializer(serializers.ModelSerializer):
category = CategorySerializer()
genres = GenreSerializer(many=True)
class Meta:
model = Title
fields = '__all__'
read_only_fields = ('category', 'genres')
class WriteTitleSerializer(serializers.ModelSerializer):
category = SlugRelatedField(
slug_field='slug',
queryset=Category.objects.all(),
required=True
)
genres = SlugRelatedField(
slug_field='slug',
queryset=Genre.objects.all(),
many=True,
required=True
)
class Meta:
model = Title
fields = '__all__'

django-filters returns two the same models when an instance of a model has the same value

Here is my code;
models.py
class Home(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return str(self.user)
class GeneralHomeFeatures(models.Model):
home = models.ForeignKey(
Home, on_delete=models.CASCADE, related_name="general_home_features"
)
home_feature = models.CharField(max_length=100, null=True, blank=True)
def __str__(self):
return str(self.home)
serializer.py
class GeneralHomeFeaturesSerializer(serializers.ModelSerializer):
class Meta:
model = GeneralHomeFeatures
exclude = ["home"]
filterset.py
class HomeFilter(filters.FilterSet):
home_feature = filters.CharFilter(field_name="general_home_features__home_feature", lookup_expr="icontains")
class Meta:
model = Home
fields = [
"home_feature",
]
once I give GeneralHomeFeatures class the same value twice, once filtered, it returns the same instance twice. Example; I make a request to this url - http://localhost:8000/api/homes/?home_feature=Pool and it returns this;
[
{
"id": 1,
"user": "cliffaust",
"general_home_features": [
{
"id": 1,
"home_feature": "Pool"
},
{
"id": 2,
"home_feature": "Children Play Ground"
},
{
"id": 7,
"home_feature": "Pool"
}
],
},
{
{
"id": 1,
"user": "cliffaust",
"general_home_features": [
{
"id": 1,
"home_feature": "Pool"
},
{
"id": 2,
"home_feature": "Children Play Ground"
},
{
"id": 7,
"home_feature": "Pool"
}
],
},
{
"id": 3,
"user": "cliffaust",
"general_home_features": [
{
"id": 4,
"home_feature": "Pool"
},
{
"id": 6,
"home_feature": "Children Play Ground"
}
],
}
]
because home_feature of GeneralHomeFeatures has two the same value(pool), it seems like django-filter is returning the same instance twice(based on the serializer id).
I don't know if this is a fault in my code, or its just how it works. Also, is they a better way of doing something like this?

Serialize ManyToManyField with foreign key of nested

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__"]

Django-Tastypie self children

Trying to make api for multiple subtasks.
I have the task model, that can have another task as a parent:
class Task(models.Model):
parent_task = models.ForeignKey("Task", null=True, blank=True)
name = models.CharField(max_length=64)
def __unicode__ (self):
return self.name
Now I'm trying to make tastypie resource:
class TaskResource(ModelResource):
parent_task = fields.ForeignKey(TaskResource, 'parent_task', full=False) <-- ERROR HERE
class Meta:
queryset = Task.objects.all()
resource_name = 'task'
list_allowed_methods = ['get', 'put', 'post', 'delete']
include_resource_uri = False
def dehydrate(self, bundle, for_list=False):
bundle.data["subtasks"] = "how?" <-- HOW??
return bundle
Thanks for your time.
P.S. I need something like this:
[
{
"id": 1,
"name": "Task 1",
"subtasks": [
{
"id": 1,
"name": "Task 1",
"subtasks": [...]
}
]
},
{
"id": 2,
"name": "Task 2",
"subtasks": "how?"
}
]
Almost a copy of Including child resources in a Django Tastypie API but not exactly.
So your first problem is that you specify relation to self wrong. It should be just self:
parent_task = fields.ForeignKey('self', 'parent_task', null=True, full=False)
Secondly, notice null=True - parent could be null.
Lastly, you just need to add another relation field and ask for the full details
subtasks = fields.ToManyField('self', 'task_set', full=True)
task_set is a related_name for the Task.parent_task field.
The resulting code is:
class TaskResource(ModelResource):
parent_task = fields.ForeignKey('self', 'parent_task', null=True, full=False)
subtasks = fields.ToManyField('self', 'subtasks', full=True)
class Meta:
queryset = Task.objects.all()
resource_name = 'task'
list_allowed_methods = ['get', 'put', 'post', 'delete']
include_resource_uri = False
And the result:
{
"meta": {
"previous": null,
"total_count": 3,
"offset": 0,
"limit": 20,
"next": null
},
"objects": [
{
"parent_task": null,
"subtasks": [
{
"parent_task": "/api/v1/task/1/",
"subtasks": [],
"id": 2,
"name": "Root's Child 1"
},
{
"parent_task": "/api/v1/task/1/",
"subtasks": [],
"id": 3,
"name": "Root's Child 2"
}
],
"id": 1,
"name": "Root Task"
},
{
"parent_task": "/api/v1/task/1/",
"subtasks": [],
"id": 2,
"name": "Root's Child 1"
},
{
"parent_task": "/api/v1/task/1/",
"subtasks": [],
"id": 3,
"name": "Root's Child 2"
}
]
}

Django REST Meta data for M2M missing

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"}
]
}