Django Rest Framework Relationships Serialization - django-rest-framework

I am trying to get serialized data for the below model with foreign keys but in the output i am only getting the ids instead of the fields of the model associated with hyperlink.
I have tried getting all the fields of the Skills model using the commented lines in the profile serializer but no luck
models.py
class Profile(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
first_name = models.CharField(max_length=20, null=True)
last_name = models.CharField(max_length=20, null=True)
def __str__(self):
return 'Profile for user {}'.format(self.user.username)
class Skill(models.Model):
skill = models.CharField(max_length=20, null=True)
level = models.CharField(max_length=20, null=True)
user = models.ForeignKey(Profile, null=True,
on_delete=models.PROTECT, related_name='skills')
def __str__(self):
return '%s: %s: %s' % (self.user, self.skill, self.level)
serializer.py
class SkillSerializer(serializers.ModelSerializer):
class Meta:
model = Skill
fields = ('user', 'skill', 'level')
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
# skills = SkillSerializer(many=True, read_only=True) # serializes child model's data inside this model's data
# skills = serializers.HyperlinkedRelatedField(many=True, read_only=True, view_name='skill-detail')
# skills = serializers.StringRelatedField(many=True)
# skills = serializers.HyperlinkedIdentityField( view_name = "skill-list", lookup_field = "user")
url = HyperlinkedIdentityField( view_name="profile-detail", lookup_field = "id")
# user = serializers.ReadOnlyField(source='user.username')
# pdb.set_trace()
model = Profile
fields = ['id', 'user', 'url', 'skills']
views.py
class ProfileList(generics.ListCreateAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
class ProfileDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
class SkillList(generics.ListCreateAPIView):
queryset = Skill.objects.all()
serializer_class = SkillSerializer
class SkillDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Skill.objects.all()
serializer_class = SkillSerializer
urls.py
path('profiles/', views.ProfileList.as_view(), name='profile-list'),
path('profiles/<int:pk>/', views.ProfileDetail.as_view(), name='profile-detail'),
path('skills/', views.SkillList.as_view(), name='profile-list'),
path('skills/<int:pk>/', views.SkillDetail.as_view(), name='skill-list'),
Output: http://127.0.0.1:8000/authenticator/profiles/
[ "id": 6,
"user": 4,
"url": "http://127.0.0.1:8000/authenticator/profiles/6/",
"skills": [
57,
55
],
]
Expected output:
[ "id": 6,
"user": 4,
"url": "http://127.0.0.1:8000/authenticator/profiles/6/",
"skills": [
{
"user": 6,
"skill": "ABC",
"level": "Beginner"
},
{
"user": 6,
"skill": "DEF",
"level": "Intermediate"
},
]
]

This should normally work with the following in serializer.py:
class ProfileSerializer(serializers.ModelSerializer):
skills = SkillSerializer(many=True, read_only=True) # serializes child model's data inside this model's data
url = HyperlinkedIdentityField( view_name="profile-detail", lookup_field = "id")
class Meta:
model = Profile
fields = ['id', 'user', 'url', 'skills']
So basically uncomment the line in which the ProfileSerializer is told to serialize the skills with your SkillsSerializers, such that the entire skill objects are included in the result instead of its IDs.

You can use the concept of nested serializer for the solution.
Create two serializer ProfileReadSerializer and ProfileWriteSerializer
serializers.py
class SkillSerializer(serializers.ModelSerializer):
class Meta:
model = Skill
fields = ('user', 'skill', 'level')
class ProfileReadSerializer(serializers.ModelSerializer):
skills = SkillSerializer(many=True, read_only=True)
url = HyperlinkedIdentityField( view_name="profile-detail", lookup_field = "id")
class Meta:
model = Profile
fields = ('id', 'user', 'url', 'skills')
class ProfileWriteSerializer(serializers.ModelSerializer):
skills = SkillSerializer(many=True)
class Meta:
model = Profile
fields = ('id', 'user', 'url', 'skills')
In views you simply can use ModelViewSet to make things easy and use get_serializer_class
views.py
from rest_framework.viewsets import ModelViewSet
class ProfileVewSet(ModelViewSet):
queryset = Profile.objects.all()
def get_serializer_class(self):
if self.request.method == 'POST' or self.request.method == 'PUT' or self.request.method == 'PATCH':
return ProfileWriteSerializer
else:
return ProfileReadSerializer
And at last if you are using ModelViewSet you need to change the urls.py
urls.py
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register('profile', views.ProfileViewSet, base_name='profile')
urlpatterns = [
path('', include(router.urls))
]
I think this will solve your problem. Please have a look.

Related

DRF: use to_representation to hide fields

In my app, I want to hide absolute_url in the detail view and content in the list view. I always use different serializers and conditionals in view to achieve this goal. I wanted to know if it is a bad practice to use to_representation here.
serializers.py
class PostSerializer(serializers.ModelSerializer):
absolute_url = serializers.SerializerMethodField()
author = serializers.SlugRelatedField(slug_field="full_name", read_only=True)
category = serializers.SlugRelatedField(
queryset=Category.objects.all(),
slug_field="id",
)
def get_absolute_url(self, obj):
request = self.context.get("request")
return request.build_absolute_uri(obj.slug)
def to_representation(self, instance):
rep = super().to_representation(instance)
request = self.context.get("request")
if request.parser_context.get("kwargs").get("slug"):
rep.pop("absolute_url", None)
else:
rep.pop("content", None)
rep["category"] = CategorySerializer(instance.category).data["name"]
return rep
def create(self, validated_data):
validated_data["author"] = User.objects.get(
id=self.context.get("request").user.id
)
return super().create(validated_data)
class Meta:
model = Post
fields = [
"id",
"author",
"category",
"title",
"slug",
"content",
"status",
"image",
"absolute_url",
"created_at",
"updated_at",
"published_at",
]
read_only_fields = ["slug"]
views.py
class PostViewSet(ModelViewSet):
queryset = Post.objects.select_related("author", "category").all()
serializer_class = PostSerializer
lookup_field = "slug"

DRF nested serializer - object has no attribute

I am a newbie with django rest framework and python. and it is first time I asking a question here. thanks for helps from now..
--------------my models
class defined_views(models.Model):
name = models.CharField(max_length=150)
class defined_permissions(models.Model):
name = models.CharField(max_length=150)
class menuler(models.Model):
adi= models.CharField(max_length=150, null=True)
sira= models.PositiveIntegerField(null=True, blank=True)
parentId= models.ForeignKey('self', null=True, on_delete=models.CASCADE)
viewId= models.ForeignKey(defined_views, null=True, on_delete=models.CASCADE)
menu_type= models.CharField(max_length=150, null=True)
class user_menu_permission(models.Model):
userId = models.ForeignKey(User, null=False, on_delete=models.CASCADE)
menuID = models.ForeignKey(menuler, null=False, on_delete=models.CASCADE)
permissionID = models.ForeignKey(defined_permissions, null=True, on_delete=models.CASCADE)
-------------------serializer
class menulerSerializer(serializers.ModelSerializer):
class Meta:
model = menuler
fields = ['id', 'adi', 'sira', 'parentId', 'viewId', 'menu_type']
class user_menu_permissionSerializer(serializers.ModelSerializer):
menu = menulerSerializer()
class Meta:
model = user_menu_permission
fields = ['menuID', 'permissionID', 'menu']
class UserSerializer(serializers.ModelSerializer):
menuler = user_menu_permissionSerializer(
source='user_menu_permission_set', many=True)
class Meta:
model = User
fields = ['id', 'username', 'menuler']
------------------and view
class userView(APIView):
def get(self, request):
queryset = User.objects.all()
serializer = UserSerializer(queryset, many=True)
return Response(serializer.data)
I get this errror
"Got AttributeError when attempting to get a value for field menu on serializer user_menu_permissionSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the user_menu_permission instance.
Original exception text was: 'user_menu_permission' object has no attribute 'menu'."
if I change user_menu_permissionSerializer like that
class user_menu_permissionSerializer(serializers.ModelSerializer):
class Meta:
model = user_menu_permission
fields = ['menuID', 'permissionID']
I get result like that bu I want get menu as tree also.. but unfortunetly...
[
{
"id": 1,
"username": "admin",
"menuler": []
},
{
"id": 2,
"username": "taner.kader",
"menuler": [
{
"menuID": 2,
"permissionID": 1
},
{
"menuID": 3,
"permissionID": 1
},
{
"menuID": 5,
"permissionID": 1
}
]
}
]
I have solved like below
models
class defined_views(models.Model):
name = models.CharField(max_length=150)
class defined_permissions(models.Model):
name = models.CharField(max_length=150)
class menuler(models.Model):
adi= models.CharField(max_length=150, null=True)
sira= models.PositiveIntegerField(null=True, blank=True)
parentId= models.ForeignKey('self', null=True, on_delete=models.CASCADE)
viewId= models.ForeignKey(defined_views, null=True, on_delete=models.CASCADE)
menu_type= models.CharField(max_length=150, null=True)
permissionID = models.ManyToManyField(defined_permissions, through='user_menu_permission')
userId = models.ManyToManyField(User, through='user_menu_permission')
class user_menu_permission(models.Model):
# userId = models.ForeignKey(User, related_name='kullanici_menuler', null=False, on_delete=models.CASCADE)
userId = models.ForeignKey(User, null=False, on_delete=models.CASCADE)
menuID = models.ForeignKey(menuler,null=False, on_delete=models.CASCADE)
permissionID = models.ForeignKey(defined_permissions, null=True, on_delete=models.CASCADE)
serializers
class defined_permissionsSerializer(serializers.ModelSerializer):
class Meta:
model = defined_permissions
fields = ['id', 'name']
class defined_viewsSerializer(serializers.ModelSerializer):
class Meta:
model = defined_views
fields = ['id', 'name']
class menulerSerializer(serializers.ModelSerializer):
class Meta:
model = menuler
fields = ['id', 'adi', 'sira', 'parentId', 'viewId', 'menu_type']
# fields = ['adi']
class user_menu_permissionSerializer(serializers.ModelSerializer):
# menu = menulerSerializer(many=True)
class Meta:
model = user_menu_permission
fields = ['menuID', 'permissionID']
class UserSerializer(serializers.ModelSerializer):
# menuler = user_menu_permissionSerializer(
# source='user_menu_permission_set', many=True)
# kullanici_menuler = user_menu_permissionSerializer(many=True)
kullanici_menuler= menulerSerializer(source='menuler_set', many=True)
class Meta:
model = User
fields = ['id', 'username', 'kullanici_menuler']
and result
[
{
"id": 1,
"username": "admin",
"kullanici_menuler": []
},
{
"id": 2,
"username": "tayfun.uzun",
"kullanici_menuler": [
{
"id": 2,
"adi": "Yapılacaklar Listesi",
"sira": 1,
"parentId": 1,
"viewId": 1,
"menu_type": "view"
},
{
"id": 3,
"adi": "Raporlamalar",
"sira": 2,
"parentId": 1,
"viewId": 2,
"menu_type": "view"
},
{
"id": 5,
"adi": "Süreç İşleyişleri",
"sira": 1,
"parentId": 4,
"viewId": 3,
"menu_type": "view"
}
]
}
]
Try below something like this:
serializer.py :
class user_menu_permissionSerializer(serializers.ModelSerializer):
menu = menulerSerializer(many=True)
class Meta:
model = user_menu_permission
fields = ['menuID', 'permissionID', 'menu']
views.py :
class userView(APIView):
def get(self, request):
data = User.objects.all()
serializer = UserSerializer(data, many=True)
return Response(serializer.data)

How to get the nested objects in DRF

I have two models Role and User. User model has a foreign-key field associated with model Role.
Model Role
class Role(models.Model):
role_name = models.CharField(max_length=255, blank=False, unique=True)
def __str__(self):
return 'Role Object ({})'.format(self.id, self.role_name)
Model User
class User(AbstractBaseUser, PermissionsMixin):
first_name = models.CharField(max_length=255, blank=False)
last_name = models.CharField(max_length=255, blank=False)
email = models.EmailField(max_length=255, blank=False, unique= True)
phone_number = models.BigIntegerField(blank=False, unique= True)
password = models.TextField(blank=False)
company_name = models.CharField(max_length=255, null=True, unique=True)
is_active = models.BooleanField(default= False)
role = models.ForeignKey(Role, related_name='group_name', on_delete=models.CASCADE, blank=False)
business = models.ForeignKey(Businesses, on_delete=models.CASCADE, null=True)
objects = UserManager()
USERNAME_FIELD = 'email'
def __str__(self):
return 'User Object ({})'.format(self.id)
Using ModelSerializer and ListAPIView I want to get the list of all the users as shown below -
{
"first_name": "",
"last_name":"",
"email":"",
"phone_number":,
"is_active":"",
"role":{
"id":1,
"role_name": "Admin"
}
}
Also, I have created serializers -
class AdminSerializer(serializers.ModelSerializer):
role = RoleSerializer(read_only=True)
class Meta:
model = User
fields = ['id', 'first_name', 'last_name', 'email', 'phone_number', 'password', 'role', 'is_active' ]
read_only_field = ['role_name']
extra_kwargs = {
'password':{
'write_only':True
},
'is_active':{
'required':False
}
}
def create(self, validated_data):
return User.objects.create_user(**validated_data)
class RoleSerializer(serializers.ModelSerializer):
class Meta:
model = Role
field = ['id', 'role_name']
What exactly should I do to my serializer to achieve desire output. What exactly should I write in my ListAPIView() what a ListAPIVIew returns.
class UsersListAPIView(ListAPIView):
queryset = User.objects.all()
serializer_class = AdminSerializer
def list(self, request):
queryset = self.get_queryset().filter(role=request.query_params.get['id']).prefetch_related("group_name")
serializer = AdminSerializer(queryset, many=True)
return Response(serializer.data)
This returns an error -
TypeError: 'method' object is not subscriptable

Create model with foreign key and return serialized response

I'm trying to create a new note via a POST request that looks like this: {content: "hello world", user: 1} with a response that looks like this: { id: 1, content: "hello world", user: { id: 1, first_name: "Homer", last_name: "Simpson" }.
After reading at the other foreign key questions I've been able to create the note and get back something like: { id: 1, content: "hello world", user: 1 } but I really need the additional user information.
I attempted to modify the response using the to_representation like so:
class NotePOSTSerializer(serializers.ModelSerializer):
class Meta:
model = Note
fields = ('id', 'content', 'timestamp', 'user')
def to_representation(self, instance):
rep = super().to_representation(instance)
"""Convert `status` to human-readable."""
rep['user'] = UserSerializer(id=rep['user'])
return rep
but I got this error: TypeError: Object of type ModelState is not JSON serializable
This is what I've got so far. Thanks in advance!
models.py
class Article(models.Model):
id = models.PositiveSmallIntegerField('Article Id', null=True)
title = models.CharField('Title', max_length=100)
content = models.TextField('Description', blank=True, null=True)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
class Note(models.Model):
id = models.PositiveSmallIntegerField('Note Id', null=True)
content = models.TextField('Content', blank=True)
timestamp = models.DateTimeField('Timestamp', auto_now_add=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
views.py
class ArticleListView(viewsets.ModelViewSet):
serializer_class = ArticleSerializer
queryset = Article.objects.all()
class NoteListView(viewsets.ModelViewSet):
serializer_class =NoteSerializer
queryset = Note.objects.all()
def get_serializer_class(self):
assert self.serializer_class is not None, (
"'%s' should either include a `serializer_class` attribute, "
"or override the `get_serializer_class()` method."
% self.__class__.__name__
)
if self.request.method in ('POST', 'PUT'):
return NotePOSTSerializer
else:
return self.serializer_class
serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'first_name', 'last_name', 'email')
class NoteSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = Note
fields = ('id', 'content', 'timestamp', 'user')
class NotePOSTSerializer(serializers.ModelSerializer):
class Meta:
model = Note
fields = ('id', 'content', 'timestamp', 'user')
class ArticleSerializer(serializers.ModelSerializer):
notes = NoteSerializer(many=True, read_only=True)
class Meta:
model = Article
fields = ('id', 'title', 'content', 'created', 'updated', 'notes')
I'm getting completely different errors, so I will just provide working code
def to_representation(self, instance):
rep = super().to_representation(instance)
rep['user'] = UserSerializer(instance=User.objects.get(pk=instance.user_id)).data
# UserSerializer(instance=instance)
return rep

django-rest-framework CRUD many models in same view

I would like to be able to CRUD many models in the same API View.
In this case there are User and UserProfile Models in the same API View.
My Serializers
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = ('expected_calories',)
class UserSerializer(serializers.HyperlinkedModelSerializer):
expected_calories = serializers.
class Meta:
model = User
fields = ('url', 'username', 'email', 'first_name', 'last_name', 'expected_calories')
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
Then my views
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserProfileViewSet(viewsets.ModelViewSet):
queryset = UserProfile.objects.all()
serializer_class = UserProfileSerializer
GET Api Call to
/api/v1/user/2/
{
"url": "http://localhost:8000/api/v1/user/2/",
"username": "jsalchichon",
"email": "jsalchichon#example.com",
"first_name": "John",
"last_name": "Salchichon",
"expected_calories": 0
}
But when I try to update the expected_calories field to 2500, The field is not updated.
Is this the right way to do this? What else should I do to be able to update the expected_calories of the UserProfile Model?
Thanks to you in advance.
I just did this:
class UserSerializer(serializers.HyperlinkedModelSerializer):
expected_calories = serializers.Field(source='userprofile.expected_calories')
class Meta:
model = User
fields = ('url', 'username', 'email', 'first_name', 'last_name', 'expected_calories')
def validate_expected_calories(self, attrs, source):
expected_calories = self.data.get('expected_calories')
if not isinstance(expected_calories, int):
raise serializers.ValidationError("expected_calories must be an integer")
attrs['expected_calories'] = expected_calories or self.object.userprofile.expected_calories
return attrs
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
def post_save(self, obj, created=False):
super(UserViewSet, self).post_save(obj, created)
expected_calories = self.request.DATA.get('expected_calories')
if isinstance(expected_calories, int):
obj.userprofile.expected_calories = self.request.DATA.get('expected_calories',
obj.userprofile.expected_calories)
obj.userprofile.save()
Not sure if it's the right way to handle this but it works.

Resources