Control output of Django Rest Framework - python

My REST API is functioning correctly, but the output is all id numbers. How can I get 'role_type' to display the name instead of the ID number?
Output:
{"count": 2, "next": null, "previous": null, "results": [{"user": {"username": "smithb", "first_name": "Bob", "last_name": "Smith"}, "role_type": 2, "item": 1}, {"user": {"username": "jjones", "first_name": "Jane", "last_name": "Jones"}, "role_type": 2, "item": 1}]}
serializers.py
class RoleSerializer(serializers.ModelSerializer):
user = PersonShortSerializer(many=False, read_only=True)
class Meta:
model = Role
fields = 'user', 'role_type', 'item'
def get_role_type(self, obj):
return obj.name
models.py
class Role(models.Model):
role_type = models.ForeignKey('RoleType')
user = models.ForeignKey(Person)
item = models.ForeignKey('Assets.Item')
class RoleType(models.Model):
name = models.CharField(max_length=255)
permissions = models.ManyToManyField(RolePermission,
blank=True, null=True)
def __unicode__(self):
return self.name

Take a look at the different types of serializer relationship fields.
In particular RelatedField should do what you need as it'll represent the target of the relationship using its unicode value.
class RoleSerializer(serializers.ModelSerializer):
user = PersonShortSerializer(many=False, read_only=True)
role_type = serializers.RelatedField()
class Meta:
model = Role
fields = ('user', 'role_type', 'item')
Also note that RelatedField is a read only field, as there's no way to determine the appropriate model instance given the unicode representation. If you did need it to be writable you might want to look at implementing a custom relational field.

Related

Original exception text was: 'RelatedManager' object has no attribute 'type'. - Django Rest

models.py
class Sequence(models.Model):
category_id = models.ForeignKey(SequenceCategory, on_delete=models.CASCADE)
description = models.TextField()
code = models.CharField(max_length=50)
total_divisions = models.IntegerField()
status = models.BooleanField(default=True)
def __str__(self):
return self.code
class SequenceValue(models.Model):
TYPE_CHOICES =(
('N', 'Numeric'),
('A', 'Alphabet')
)
sequence_id = models.ForeignKey(Sequence, on_delete=models.CASCADE, blank=True, null=True, related_name='Sequence_details')
type = models.CharField(max_length=100, choices=TYPE_CHOICES)
value = models.CharField(max_length=50)
starting_value = models.CharField(max_length=50, null=True, blank=True)
increment_value = models.CharField(max_length=50, null=True, blank=True)
def __str__(self):
return self.value
serializers.py
class SequenceValueSerializer(serializers.ModelSerializer):
class Meta:
model = SequenceValue
# fields = '__all__'
exclude = ['sequence_id']
class SequenceSerializer(serializers.ModelSerializer):
Sequence_details = SequenceValueSerializer()
class Meta:
model = Sequence
fields = '__all__'
def create(self, validate_data):
Sequence_details_data = validate_data.pop('Sequence_details')
sequence_id = Sequence.objects.create(**validate_data)
SequenceValue.objects.create(sequence_id=sequence_id, **Sequence_details_data)
return sequence_id
views.py
class SequenceCreate(ListCreateAPIView):
queryset = Sequence.objects.all()
serializer_class = SequenceSerializer
Why do I get the error? When I refer to n number of articles I got a solution that put many=True something like this.
Sequence_details = SequenceValueSerializer(many=True)
But when I make changes on that then I can't get the Sequence details fields. How can I fix this issue? Can you give a solution, please?
Actual error-
Got AttributeError when attempting to get a value for field type on serializer SequenceValueSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the RelatedManager instance.
Original exception text was: 'RelatedManager' object has no attribute 'type'.
Data Passess
Data - {
"Sequence_details": [{
"type": "N",
"value": "123",
"starting_value": "1",
"increment_value": "1"
}],
"description": "asd",
"code": "qwe",
"total_divisions": 2,
"status": false,
"category_id": 3
}
Actual eroor screenshot
Adding many=True works because your request data passes Sequence_details as a list. In order to use it in your serializer, you can just iterate through the field and create your objects like this:
class SequenceSerializer(serializers.ModelSerializer):
Sequence_details = SequenceValueSerializer(many=True)
class Meta:
model = Sequence
fields = '__all__'
def create(self, validate_data):
Sequence_details_data = validate_data.pop('Sequence_details')
sequence_id = Sequence.objects.create(**validate_data)
for details in Sequence_details_data
SequenceValue.objects.create(sequence_id=sequence_id, **details)
return sequence_id
But if you don't want to do this, you can remove many=True and keep your serializer as is, but ensure that Sequence_details in your request data is not a list:
{
"Sequence_details": {
"type": "N",
"value": "123",
"starting_value": "1",
"increment_value": "1"
},
"description": "asd",
"code": "qwe",
"total_divisions": 2,
"status": false,
"category_id": 3
}
I can't comment so I will just mention it here :). type is actually a function in python which tells you about the class type of argument. I am not sure if this the issue but the can you try by renaming type to _type or something of your choice.

GraphQL Mutation in Graphene for Object with Foreign Key Relation

I'm building a simple CRUD interface with Python, GraphQL (graphene-django) and Django. The CREATE mutation for an Object (Ingredient) that includes Foreign Key relations to another Object (Category) won't work. I want to give GraphQL the id of the CategoryObject and not a whole category instance. Then in the backend it should draw the relation to the Category object.
In the Django model the Ingredient Object contains an instance of the Foreign key Category Object (see code below). Is the whole Category Object needed here to draw the relation and to use Ingredient.objects.select_related('category').all()?
The create mutation expects IngredientInput that includes all properties and an integer field for the foreign key relation. So the graphQL mutation itself currently works as I want it to.
My question is similar if not the same as this one but these answers don't help me.
models.py:
class Category(models.Model):
name = models.CharField(max_length=50, unique=True)
notes = models.TextField()
class Meta:
verbose_name = u"Category"
verbose_name_plural = u"Categories"
ordering = ("id",)
def __str__(self):
return self.name
class Ingredient(models.Model):
name = models.CharField(max_length=100)
notes = models.TextField()
category = models.ForeignKey(Category, on_delete=models.CASCADE)
class Meta:
verbose_name = u"Ingredient"
verbose_name_plural = u"Ingredients"
ordering = ("id",)
def __str__(self):
return self.name
schema.py:
class CategoryType(DjangoObjectType):
class Meta:
model = Category
class CategoryInput(graphene.InputObjectType):
name = graphene.String(required=True)
notes = graphene.String()
class IngredientType(DjangoObjectType):
class Meta:
model = Ingredient
class IngredientInput(graphene.InputObjectType):
name = graphene.String(required=True)
notes = graphene.String()
category = graphene.Int()
class CreateIngredient(graphene.Mutation):
class Arguments:
ingredientData = IngredientInput(required=True)
ingredient = graphene.Field(IngredientType)
#staticmethod
def mutate(root, info, ingredientData):
_ingredient = Ingredient.objects.create(**ingredientData)
return CreateIngredient(ingredient=_ingredient)
class Mutation(graphene.ObjectType):
create_category = CreateCategory.Field()
create_ingredient = CreateIngredient.Field()
graphql_query:
mutation createIngredient($ingredientData: IngredientInput!) {
createIngredient(ingredientData: $ingredientData) {
ingredient {
id
name
notes
category{name}
}
graphql-variables:
{
"ingredientData": {
"name": "milk",
"notes": "from cow",
"category": 8 # here I ant to insert the id of an existing category object
}
}
error-message after executoin the query:
{
"errors": [
{
"message": "Cannot assign \"8\": \"Ingredient.category\" must be a \"Category\" instance.",
"locations": [
{
"line": 38,
"column": 3
}
],
"path": [
"createIngredient"
]
}
],
"data": {
"createIngredient": null
}
}
I had this same problem today.
The Cannot assign \"8\": \"Ingredient.category\" must be a \"Category\" instance. error is a Django error that happens when you try to create an object using the foreign key integer directly instead of an object.
If you want to use the foreign key id directly you have to use the _id suffix.
For example, instead of using:
_ingredient = Ingredient.objects.create(name="milk", notes="from_cow", category=8)
You have to use either
category_obj = Category.objects.get(id=8)
_ingredient = Ingredient.objects.create(name="milk", notes="from_cow", category=category_obj)
or
_ingredient = Ingredient.objects.create(name="milk", notes="from_cow", category_id=8)
In the case of using GraphQL, you would have to set your InputObjectType field to <name>_id. In your case:
class IngredientInput(graphene.InputObjectType):
name = graphene.String(required=True)
notes = graphene.String()
category_id = graphene.Int()
This, however will make your field in the schema show up as categoryId. If you wish to keep the category name, you must change to:
category_id = graphene.Int(name="category")
Cheers!

Django Rest Framework- retrieving a related field on reverse foreign key efficiently

I have the following models that represent a working group of users. Each working group has a leader and members:
class WorkingGroup(models.Model):
group_name = models.CharField(max_length=255)
leader = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
class WorkingGroupMember(models.Model):
group = models.ForeignKey(WorkingGroup, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
In DRF, I want to efficiently retrieve all groups (there are several hundred) as an array of the following json objects:
{
'id': <the_group_id>
'group_name': <the_group_name>
'leader': <id_of_leader>
'members': [<id_of_member_1>, <id_of_member_2>, ...]
}
To do so, I have set up the following serializer:
class WorkingGroupSerializer(serializers.ModelSerializer):
members = serializers.SerializerMethodField()
class Meta:
model = WorkingGroup
fields = ('id', 'group_name', 'leader', 'members',)
def get_members(self, obj):
return obj.workinggroupmember_set.all().values_list('user_id', flat=True)
So that in my view, I can do something like:
groups = WorkingGroup.objects.all().prefetch_related('workinggroupmember_set')
group_serializer = WorkingGroupSerializer(groups, many=True)
This works, and gives the desired result, however I am finding it does not scale well at all, as the prefetching workinggroupmember_set does not seem to be used inside of the get_members method (Silky is showing a single query to grab all WorkingGroup objects, and then a query for each workinggroupmember_set call in the get_members method). Is there a way to set up the members field in the serializer to grab a flattened/single field version of workinggroupmember_set without using a SerializerMethodField? Or some other way of doing this that lets me properly use prefetch?
Problem here that you are doing values_list on top of all which nullifies your prefetch_related. There is currently no way to do prefetch with values_list see https://code.djangoproject.com/ticket/26565. What you can do is to transition this into python code instead of SQL
class WorkingGroupSerializer(serializers.ModelSerializer):
members = serializers.SerializerMethodField()
class Meta:
model = WorkingGroup
fields = ('id', 'group_name', 'leader', 'members',)
def get_members(self, obj):
return [wgm.user_id for wgm in obj.workinggroupmember_set.all()]
In a recent project with DRF v3.9.1 and django 2.1, I needed to recursively expose all the children of an object, by having only a direct connection to the parent, which could have had multiple children.
Before, if I was to request the "tree" of an object, I was getting:
{
"uuid": "b85385c0e0a84785b6ca87ce50132659",
"name": "a",
"parent": null
}
By applying the serialization shown below I get:
{
"uuid": "b85385c0e0a84785b6ca87ce50132659",
"name": "a",
"parent": null
"children": [
{
"uuid": "efd26a820b4e4f7c8e56c812a7791fcb",
"name": "aa",
"parent": "b85385c0e0a84785b6ca87ce50132659"
"children": [
{
"uuid": "ca2441fc7abf49b6aa1f3ebbc2dae251",
"name": "aaa",
"parent": "efd26a820b4e4f7c8e56c812a7791fcb"
"children": [],
}
],
},
{
"uuid": "40e09c85775d4f1a8578bba9c812df0e",
"name": "ab",
"parent": "b85385c0e0a84785b6ca87ce50132659"
"children": [],
}
],
}
Here is the models.py of the recursive object:
class CategoryDefinition(BaseModelClass):
name = models.CharField(max_length=100)
parent = models.ForeignKey('self', related_name='children',
on_delete=models.CASCADE,
null=True, blank=True)
To get all the reverse objects in the foreign key, apply a field to the serializer class:
class DeepCategorySerializer(serializers.ModelSerializer):
children = serializers.SerializerMethodField()
class Meta:
model = models.CategoryDefinition
fields = '__all__'
def get_children(self, obj):
return [DeepCategorySerializer().to_representation(cat) for cat in obj.children.all()]
Then apply this serializer to a DRF view function or generics class, such as:
re_path(r'categories/(?P<pk>[\w\d]{32})/',
generics.RetrieveUpdateDestroyAPIView.as_view(
queryset=models.CategoryDefinition.objects.all(),
serializer_class=serializers.DeepCategorySerializer),
name='category-update'),

Nested serializer "Through model" in Django Rest Framework

I am having difficulty serializing an intermediary "pivot" model and attach to each item in an Many-to-many relation in Django Rest Framework.
Example:
models.py:
class Member(models.Model):
name = models.CharField(max_length = 20)
groups = models.ManyToManyField('Group', through='Membership')
class Group(models.Model):
name = models.CharField(max_length = 20)
class Membership(models.Model):
member = models.ForeignKey('Member')
group = models.ForeignKey('Group')
join_date = models.DateTimeField()
serializers.py:
class MemberSerializer(ModelSerializer):
class Meta:
model = Member
class GroupSerializer(ModelSerializer):
class Meta:
model = Group
class MembershipSerializer(ModelSerializer):
class Meta:
model = Membership
I tried to follow the answers:
Include intermediary (through model) in responses in Django Rest Framework
But it's not exactly what I need
I need to generate the following output
{
"id": 1,
"name": "Paul McCartney",
"groups": [
{
"id": 3,
"name": "Beatles",
"membership": {
"id": 2,
"member_id": 1,
"group_id": 3,
"join_date": "2018-08-08T13:43:45-0300"
}
}
]
}
In this output I'm returning the related "Through Model" for each item in groups.
How can I generate serialize models in this way?
Based on the way you would like to show your output, I suggest you change your models to:
class Group(models.Model):
name = models.CharField(max_length=20)
members = models.ManyToManyField(
'Membership',
related_name='groups',
related_query_name='groups',
)
class Member(models.Model):
name = models.CharField(max_length=20)
class Membership(models.Model):
group = models.ForeignKey(
'Group',
related_name='membership',
related_query_name='memberships',
)
join_date = models.DateTimeField()
How Group model and Member model are ManytoMany, does not have a problem you let the relationship in Group model. It will be the easiest to output it in serialize. related_name and related_query_name are used to make the serialization and point the nested relation.
And finally, your serialize could be like this (I exemplified it with a create method):
class MembershipSerializer(ModelSerializer):
class Meta:
fields = ("id", "join_date",)
class GroupSerializer(ModelSerializer):
memberships = MembershipSerializer(many=True)
class Meta:
model = Group
fields = ("id", "name", "memberships",)
class MemberSerializer(ModelSerializer):
groups = GroupSerializer(many=True)
class Meta:
model = Member
fields = ("id", "name", "groups")
def create(self):
groups_data = validated_data.pop('groups')
member = Member.objects.create(**validated_data)
for group in groups_data:
memberships_data = group.pop('memberships')
Group.objects.create(member=member, **group)
for memberhip in memberships:
Membership.objects.create(group=group, **memberships)
The output will be:
{
"id": 1,
"name": "Paul McCartney",
"groups": [
{
"id": 3,
"name": "Beatles",
"memberships": [
{
"id": 2,
"join_date": "2018-08-08T13:43:45-0300"
}
]
}
]
}
In this output, I am not "nesting" the parent id but you can make it too, just declare into fields attributes.
By looking at your output it seems you want to show membership inside groups and groups inside member. I would recommend editing the serializer to something like this.
class MemberSerializer(ModelSerializer):
groups = GroupSerializer(many=True)
class Meta:
model = Member
fields = ("id","name","groups")
class GroupSerializer(ModelSerializer):
membership = MembershipSerializer()
class Meta:
model = Group
fields = ("id","name","membership")
class MembershipSerializer(ModelSerializer):
class Meta:
model = Membership
fields = "__all__"

Serializing ManyToMany relationship with intermediary model in Django Rest Framework

I'm having some trouble serializing many to many relationships with a through argument in DRF3
Very basically I have recipes and ingredients, combined through an intermediate model that specifies the amount and unit used of a particular ingredient.
These are my models:
from django.db import models
from dry_rest_permissions.generics import authenticated_users, allow_staff_or_superuser
from core.models import Tag, NutritionalValue
from usersettings.models import Profile
class IngredientTag(models.Model):
label = models.CharField(max_length=255)
def __str__(self):
return self.label
class Ingredient(models.Model):
recipe = models.ForeignKey('Recipe', on_delete=models.CASCADE)
ingredient_tag = models.ForeignKey(IngredientTag, on_delete=models.CASCADE)
amount = models.FloatField()
unit = models.CharField(max_length=255)
class RecipeNutrition(models.Model):
nutritional_value = models.ForeignKey(NutritionalValue, on_delete=models.CASCADE)
recipe = models.ForeignKey('Recipe', on_delete=models.CASCADE)
amount = models.FloatField()
class Recipe(models.Model):
name = models.CharField(max_length=255)
ingredients = models.ManyToManyField(IngredientTag, through=Ingredient)
tags = models.ManyToManyField(Tag, blank=True)
nutritions = models.ManyToManyField(NutritionalValue, through=RecipeNutrition)
owner = models.ForeignKey(Profile, on_delete=models.SET_NULL, blank=True, null=True)
def __str__(self):
return self.name
And these are currently my serializers:
from recipes.models import Recipe, IngredientTag, Ingredient
from rest_framework import serializers
class IngredientTagSerializer(serializers.ModelSerializer):
class Meta:
model = IngredientTag
fields = ('id', 'label')
class IngredientSerializer(serializers.ModelSerializer):
class Meta:
model = Ingredient
fields = ('amount', 'unit')
class RecipeSerializer(serializers.ModelSerializer):
class Meta:
model = Recipe
fields = ('id', 'url', 'name', 'ingredients', 'tags', 'nutritions', 'owner')
read_only_fields = ('owner',)
depth = 1
I've searched SO and the web quite a bit, but I can't figure it out. It would be great if someone could point me in the right direction.
I can get the list of ingredients to be returned like so:
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"url": "http://localhost:8000/recipes/1/",
"name": "Hallo recept",
"ingredients": [
{
"id": 1,
"label": "Koek"
}
],
"tags": [],
"nutritions": [],
"owner": null
}
]
}
But what I want is for the amount and unit to also be returned!
I got what I wanted in the following way:
from recipes.models import Recipe, IngredientTag, Ingredient
from rest_framework import serializers
class IngredientTagSerializer(serializers.ModelSerializer):
class Meta:
model = IngredientTag
fields = ('id', 'label')
class IngredientSerializer(serializers.ModelSerializer):
ingredient_tag = IngredientTagSerializer()
class Meta:
model = Ingredient
fields = ('amount', 'unit', 'ingredient_tag')
class RecipeSerializer(serializers.ModelSerializer):
ingredients = IngredientSerializer(source='ingredient_set', many=True)
class Meta:
model = Recipe
fields = ('url', 'name', 'ingredients', 'tags', 'nutritions', 'owner')
read_only_fields = ('owner',)
depth = 1
using the ingredient_tag's ingredient_set as a source for IngredientSerializer resulted in the response I required:
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"url": "http://localhost:8000/recipes/1/",
"name": "Hallo recept",
"ingredients": [
{
"amount": 200.0,
"unit": "g",
"ingredient_tag": {
"id": 1,
"label": "Koek"
}
},
{
"amount": 500.0,
"unit": "kg",
"ingredient_tag": {
"id": 3,
"label": "Sugar"
}
}
],
"tags": [],
"nutritions": [],
"owner": null
}
]
}
I don't know if this is the best way to go about it, so I'll wait til somebody who knows their DRF leaves a comment or perhaps someone posts something better before marking as answer.
While serializing the nested relations, you also have to serialize specifically those ManyToManyField.
Let me give you a small example:
class RecipeSerializer(serializers.ModelSerializer):
ingredients = serializers.SerializerMethodField()
def get_ingredients(self, obj):
serializer = IngredientSerializer(obj.ingredients)
return serializer.data
class Meta:
model = Recipe
fields = ('id', 'url', 'name', 'ingredients', 'tags', 'nutritions', 'owner')
read_only_fields = ('owner',)
depth = 1
Whatever your nested relation is (like ingredients, tags or nutritions), you can serialize them by creating a serializer method field. In that method, You can use your specific serializer so that it gives the json you want.
Be careful with the method name. If your ManyToManyField is "ingredients", your method name should be "ingredients" because DRF works with "get_".
For further information, check this:
Django Rest Framework - SerializerMethodField
Override the method to_representation of RecipeSerializer
and pass the instance of many to many filed to their serializer with many is True. or
tags = serializers.HyperlinkedRelatedField(
many=True,read_only=True,
)

Categories