How to Show Foreign Key data in Django rest framework? - django

Model:
class Demo(models.Model):
name = models.CharField(max_length=255)
desc = models.TextField()
user = models.ForeignKey('auth.User', on_delete=models.CASCADE)
Serializer:
class DemoSerializer(serializers.ModelSerializer):
class Meta:
model = Demo
fields = '__all__'
I have form in frontend side where I'm adding name, desc and assigning to User so here I'm getting on an issue.
I'm passing data to API {name: "demo", desc: "lorem ipsum", user: 1 }
It's working on save but after saving it's return same response but I want user first_name, last_name, and email in return response.
Because I have a table showing a list of demo table content. but always getting only User ID not a detail of user.
If I'm increasing depth of Serializer It's creating an issue in save time but on get records time I'm getting all details of User model. Like Password also in response so that is a security issue for me show all thing.

You can use depth = 1 to get all the data of foreign key object:
class DemoSerializer(serializers.ModelSerializer):
class Meta:
model = Demo
fields = '__all__'
depth = 1

You could separate the Create and Retrieve serializer. For example, the create serializer will be the one you are currently using:
class DemoCreateSerializer(serializers.ModelSerializer):
class Meta:
model = Demo
fields = '__all__'
On the other hand, Retrieve serializer will serialize the User with a different serializer using Nested Relationship.
class DemoRetrieveSerializer(serializers.ModelSerializer):
user = UserMinimalSerializer # or you could use your UserSerializer, see the last part of the answer
class Meta:
model = Demo
fields = ('id', 'name', 'desc', 'user')
read_only = ('id', 'name', 'desc', 'user',)
In your view, you will create the data with the first serializer and respond with the second. An example using APIView:
class DemoView(APIView):
def post(self, request, format=None):
create_serializer = DemoCreateSerializer(data=request.data)
if create_serializer.is_valid():
instance = create_serializer.save()
retrive_serializer = DemoRetrieveSerializer(instance)
return Response(retrive_serializer.data, status=status.HTTP_201_CREATED)
return Response(create_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
You might have to customize DRF provided views to achieve this, i.e. for Generic views.
Since you don't want to include all the fields of User model, you will have to write a minimal representation of User using another serializer.
class UserMinimalSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'first_name', 'last_name', 'email')
read_only = ('id', 'first_name', 'last_name', 'email',)
Hope it helps.

Related

DRF tries to create User object when used in nested serializer

DRF docs says that "By default nested serializers are read-only. If you want to support write-operations to a nested serializer field you'll need to create create() and/or update() methods in order to explicitly specify how the child relationships should be save."<<
In general this is true except for one case. When User class is a nested object in serializer.
Use case is to add existing user to newly created organization.
Consider my simple example where I noticed that issue:
views.py
class UserOrganizationViewSet(viewsets.ModelViewSet):
# authentication_classes = (JwtAuthentication, SessionAuthentication)
# permission_classes = (permissions.JWT_RESTRICTED_APPLICATION_OR_USER_ACCESS,)
serializer_class = UserOrganizationSerializer
queryset = UserOrganization.objects.all()
models.py:
class UserOrganization(models.Model):
name = models.CharField(max_length=256)
users = models.ManyToManyField(User, blank=True, related_name="user_organizations", through='OrganizationMember')
def __str__(self):
return self.name
class OrganizationMember(models.Model):
organization = models.ForeignKey(UserOrganization, on_delete=CASCADE)
user = models.ForeignKey(User, on_delete=CASCADE)
joined = AutoCreatedField(_('joined'))
serializers.py:
First version - User class has its own simple serializer and it is used as nested serializer:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ['username']
class UserOrganizationSerializer(serializers.ModelSerializer):
users = UserSerializer(many=True, required=False)
class Meta:
model = UserOrganization
fields = ['id', 'name', 'users']
read_only_fields = ['id']
Is this case I am able to fetch data via GET method (Organization created through admin panel):
But when I am trying to create organization I am getting error which says that user already exist:
So, I checked what will happen when I try to paste not existed username in JSON data send. According to expectation DRF says that I need to override create() method:
{
"name": "Test2",
"users": [
{
"username": "admins"
}
]
}
AssertionError: The `.create()` method does not support writable nested fields by default.
Write an explicit `.create()` method for serializer `organization.serializers.UserOrganizationSerializer`, or set `read_only=True` on nested serializer fields.
And there is a first weird behavior. DRF still tries to created User object:
My create method looks like this:
def create(self, validated_data):
print("validated_data", validated_data)
users = validated_data.pop('users', [])
user_organization = UserOrganization.objects.create(**validated_data)
print("users", users)
for user in users:
userSerializer = UserSerializer(user)
print("userSerializer.data", userSerializer.data)
username = userSerializer.data['username']
print("username", username)
user_organization.users.add(get_user_model().objects.get(username=username))
return user_organization
In this case my method isn't called at all.
In case when I passed user which does not exist, my create() method is called and exeption is thrown as it should be. (I know I am not handling errors, this is just sandbox):
{
"name": "Test2",
"users": [
{
"username": "admin1"
}
]
}
django.contrib.auth.models.User.DoesNotExist: User matching query does not exist.
New model class UserProfile and serializer for this class used as nested:
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = ['name']
class UserOrganizationSerializer(serializers.ModelSerializer):
profiles = UserProfileSerializer(many=True)
class Meta:
model = UserOrganization
fields = ['id', 'name', 'profiles']
read_only_fields = ['id']
def create(self, validated_data):
print("validated_data ", validated_data)
users = validated_data.pop('profiles', [])
user_organization = UserOrganization.objects.create(**validated_data)
print("create", users)
for user in users:
print("username = ", user['name'])
user_organization.profiles.add(UserProfile.objects.get(name=user['name']))
user_organization.save()
return user_organization
Small change was needed to change field users to profiles inside UserOrganization.
Creating organization with existed user profile (created by admin) via POST:
In this scenario there is no such problem as mentioned in first case.
Final solution using SlugReleatedField works as expected. No serializer used for User.
class UserOrganizationSerializer(serializers.ModelSerializer):
users = serializers.SlugRelatedField(many=True, queryset=get_user_model().objects.all(), slug_field='username')
class Meta:
model = UserOrganization
fields = ['id', 'name', 'users']
read_only_fields = ['id']
Above code does what I exactly need and is very simple. Here is small concern regarding performance when fetched all users from DB in this line:
queryset=get_user_model().objects.all()
Can someone explain why this happens what we could observe in scenario 1?
Sorry for this post being so long. I tried to shorten it as much I as can. I can put whole project in github if it will be needed for readability.

display only some fields in get api response django serializer

I have an example model which has a fk relation with user model and Blog model. Now I have a get api which only requires certain fields of user to be displayed.
My model:
class Example(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
null=True,
related_name="user_examples",
)
blog = models.ForeignKey(
Blog,
on_delete=models.CASCADE,
null=True,
related_name="blog_examples",
)
/................./
Now my view:
class ExampleView(viewsets.ModelViewSet):
queryset = Example.objects.all()
serializer_class = ExampleSerializer
def list(self, request, *args, **kwargs):
id = self.kwargs.get('pk')
queryset = Example.objects.filter(blog=id)
serializer = self.serializer_class(queryset,many=True)
return Response(serializer.data,status=200)
My serializer:
class ExampleSerializer(serializers.ModelSerializer):
class Meta:
model = Example
fields = ['user','blog','status']
depth = 1
Now when I call with this get api, I get all example objects that is required but all the unnecessary fields of user like password, group etc . What I want is only user's email and full name. Same goes with blog, I only want certain fields not all of them. Now how to achieve this in a best way??
You will have to specify the required fields in nested serializers. e.g.
class BlogSerializer(serializers.ModelSerializer):
class Meta:
model = Blog
fields = ['title', 'author']
class ExampleSerializer(serializers.ModelSerializer):
blog = BlogSerializer()
class Meta:
model = Example
fields = ['user','blog','status']
are you setting depth in serializer's init method or anywhere else? beacause ideally it should only display id's and not anything else. if yes then set depth to zero and use serializer's method field to return data that you need on frontend. I can provide you with example code samples

Storing multiple values in django model though serializer

I am fairly new to django rest framework. I have these tables in my database:
1) MainCategroies - which stores a list of all education fields.
2) College - which stores list of all college of my state.
3) CollegeCategoryLink - which stores the link between colleges and the categories to which they belong( here same college can fall under multiple categories). created a model with two foreign-key column
4) Users - the users of my app.
5) UserCategoryLink - link between the users and their selected categories. created a model with two foreign-key column
6) UserCollegeLink - link between the users and their selected colleges. created a model with two foreign-key column
Now the users will select their preferable categories from the list
and that will be stored in my database and then i will return the
related colleges back. All the data will come in json format from my
ionic app.
i have written serializers for each model and created viewsets for CRUD operations. Now i am confused, how to store the data through viewset methods? I am currently doing this:
class UserCategoryLinkViewset(viewsets.ViewSet):
serializer_class = UserCategoryLinkSerializer
def create(self, request):
selectedCats = []
collegeList = []
data = JSONParser().parse(request)
for field in data:
selectedCats.append(field['cat'])
ucl = UserCategoryLink()
ucl.user = collegeAppUser.objects.get(id=field['user'])
ucl.cat = MainCategories.objects.get(id=field['cat'])
if not UserCategoryLink.objects.filter(user=field['user'], cat=field['cat']).exists():
ucl.save()
for cats in selectedCats:
queryset = CollegeCategoryLink.objects.filter(category_id=cats)
serializer = CollegeCategoryLinkSerializer(queryset, many=True)
for clg in serializer.data:
queryset_college = College.objects.filter(id=clg['college_id'])
serializer_college = CollegeSerializer(queryset_college, many=True)
collegeList.append(serializer_college.data)
return JSONResponse(collegeList)
And here are my serializers:
from rest_framework import serializers
from manageApp.models import collegeAppUser,MainCategories,UserCategoryLink, CollegeCategoryLink, College, UserCollegeLink
class collegeAppUserSerializer(serializers.ModelSerializer):
class Meta:
model = collegeAppUser
fields = ('id', 'username', 'password')
class MainCategorySerializer(serializers.ModelSerializer):
class Meta:
model = MainCategories
fields = ('id', 'category_name')
class UserCategoryLinkSerializer(serializers.ModelSerializer):
class Meta:
model = UserCategoryLink
fields = ('id', 'user', 'cat')
class CollegeCategoryLinkSerializer(serializers.ModelSerializer):
class Meta:
model = CollegeCategoryLink
fields = ('id', 'college_id', 'category_id')
class CollegeSerializer(serializers.ModelSerializer):
class Meta:
model = College
fields = ('id', 'college_name', 'college_url')
class UserCollegeLinkSerializer(serializers.ModelSerializer):
class Meta:
model = UserCollegeLink
fields = ('id', 'user', 'college')
But this is not the correct way to accomplish what i need to do as i am directly setting the data in my model and saving it, no use of serializer in here. I want to store the data through serializer rather then directly using my model.
Try to relate models.
You can see the rest framework documentation: Relations!
For your case, you would use Nested relationships! =]

How to use serializer to create a new object with a foreignkey

suppose this model:
class Tweek(models.Model):
content = models.CharField(max_length=140)
date = models.DateTimeField(auto_now_add=True)
author = models.ForeignKey(User, related_name='author')
class Meta:
ordering = ['-date']
def __unicode__(self):
return self.content
Everthing works fine, now i try to bind a rest api uppon. I've installed the django rest framework and I can retrieve tweeks but I cannot create new ones.
I have this serializer for the Tweek model:
class TweekSerializer(serializers.ModelSerializer):
author = UserSerializer()
class Meta:
model = Tweek
fields = ('content', 'date', 'author')
def create(self, validated_data):
author_data = validated_data.pop('author')
author = User.objects.get(username=author_data)
return Tweek.objects.create(author=author, **validated_data)
and the user serializer somply looks like:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'first_name', 'last_name')
but the post returns
{
"author": {
"username": [
"This field must be unique."
]
}
}
I've followed the doc, but i'm lost with this case :(
Any idea ?
The issue here is that any field with unique=True set, like the username field on the Django User model, will automatically have the UniqueValidator added to the serializer field. This validator is what is triggering the message you are seeing, and you can remove the validation by setting validators to [] (an empty list) when initializing the field.
The other issue that you are going to run into is that you are trying to create an object with a foreign key, but you are returning the full serialized object in your response. This issue is easier fixed by using a second field for setting the id, that is write-only, and using the original serializer field for the nested representation and making that read-only.
You can find more information in the following Stack Overflow question: DRF: Simple foreign key assignment with nested serializers?

Django Rest Framework - Serialize multiple models

I have two models, the default User model and a UserProfile model that extends the user model:
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name='UserProfile')
universidad = models.ForeignKey(Universidad, related_name ='universidad')
group_admin = models.ManyToManyField(Group, related_name = 'group_admin')
I'm trying to obtain a user field that allows to GET, PUT and POST data with this format:
'username' = 'foo'
'password' = 'password'
'email' = 'a#b.com'
'universidad' = 'Harvard'
'group_admin' = [1] #list of groups id
I have used nested serializers but it's read only.
I also proved this solution, but i obtain a KeyError: 'universidad'.
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'password', 'email','universidad', 'group_admin')
write_only_fields = ('password',)
Any help would be appreciated.
The KeyError occurs because you are trying to use the reverse relationship of UserProfile in the serializer. Reverse relationships aren't automatically included in HyperLinkedModelSerializers. See the docs for more information and try:
fields = ('url', 'username', 'password', 'email','UserProfile__universidad', 'group_admin')