Django: How to use model properties within custom methods in a serializer? - django

from rest_framework import serializers
class UserProfileSerializer(serializers.ModelSerializer):
"""Serializes data for User."""
url = serializers.URLField(source='profile_url')
def profile_url(self):
"""Return user profile endpoint."""
return reverse('user:profile_view', kwargs=self.context.get('id', None))
class Meta:
model = User
fields = ('id', 'url')
How can I access User.id within profile_url? There doesn't appear to be any id parameter within the context or at least I don't think I'm accessing properly.

Actually, you may want to change the field on your serializer to be:
url = serializers.SerializerMethodField('profile_url')
This will give the method profile_url an extra parameter, the object that is being serialized. So in your case, this becomes:
def profile_url(self, obj):
"""Return user profile endpoint."""
return reverse('user:profile_view', args=[obj.id,])

Related

How to set read_only dynamically in django rest framework?

I am trying to check if the user id not equal to 1 then he should not be able to update few fields. I tried something similar to the following code but it did not work because of the following issues
self.user.id don't actually return the user I need to get the authenticated user in different why?
the def function maybe should have a different name like update?
also the general way maybe wrong?
class ForAdmins(serializers.ModelSerializer)):
class Meta:
model = User
fields = '__all__'
class ForUsers(serializers.ModelSerializer)):
class Meta:
read_only_fields = ['email','is_role_veryfied','is_email_veryfied']
model = User
fields = '__all__'
class UsersSerializer(QueryFieldsMixin, serializers.ModelSerializer):
def customize_read_only(self, instance, validated_data):
if (self.user.id==1):
return ForAdmins
else:
return ForUsers
class Meta:
# read_only_fields = ['username']
model = User
fields = '__all__'
You can make the decision which serializer you want to pass from your views
or
you can do it inside modelSerializer update method.
for getting user from Serializer class Try:
request = self.context.get('request', None)
if request:
user = request.user
for getting user from View class Try:
user = self.request.user

Why my get_queryset doesn't work when used on Model with OneToOneField

Hello Django Programmers,
I have an issue which I don't understand.
Here is my model class:
class Profile(models.Model):
name = models.CharField(max_length=50)
employeeView = models.BooleanField(default=True)
owner = models.OneToOneField(User, related_name="profile", on_delete=models.CASCADE, null=True)
This class extends my User Model. Please notice that I'm using here OneToOneField in relation to User Model.
This model is serialized with that Serializer:
class ProfileSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Profile
fields = (
'url',
'pk',
'name',
'employeeView')
And finally I have view which I use to process GET (list) and POST requests to the database:
below view works fine (for GET request), but it gives me all Profiles related with all users
class ProfileList(generics.ListCreateAPIView):
permission_classes = [
permissions.IsAuthenticated
]
serializer_class = ProfileSerializer
name = 'profile-list'
queryset = Profile.objects.all()
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
Because I would like to create view which could give me only profile for My user, I modifiet above view like so:
class ProfileList(generics.ListCreateAPIView):
permission_classes = [
permissions.IsAuthenticated
]
serializer_class = ProfileSerializer
name = 'profile-list'
##queryset = Profile.objects.all()
def get_queryset(self):
return self.request.user.profile.all()
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
But when I send the GET request I get this error:
{code}
'Profile' object has no attribute 'all'
{code}
So my question here is more general. Above view will work when I will use in my Profile model ForeignKey field instead of OneToOneField. In such case when in my view I will request for all objects which belongs to my user like so:
def get_queryset(self):
return self.request.user.profile.all()
I will get necessary data. But how to ask for objects when we have OneToOneField? It seems that the procedure is different, but I can not find it anywhere.
all() implies that there are many related objects, so this is applicable for reverse one-to-many (ForeignKey) and many-to-many (ManyToManyField) calls.
In case of one-to-one (OneToOneField), as the name suggests, one object can be related to only one of the object of the related model, hence there's no point of all-like methods here.
Now, the get_queryset method is supposed to return a queryset of objects, not a single one. So you can not use self.request.user.profile as that refers to only one instance.
You need to return a queryset with the only instance of Profile that is related to the requesting User:
def get_queryset(self):
return Profile.objects.filter(owner=self.request.user)
The documentation https://docs.djangoproject.com/en/2.2/topics/db/examples/one_to_one/ says that you would access the user profile by using user.profile. This makes sense, since it’s a OneToOneField and so we shouldn’t expect a QuerySet, just a single object.

Add another argument to route for DRF

There are set of API endpoints generated by default by Django Rest Framework. Example this one :
^api/ ^ ^provinces/(?P<pk>[^/.]+)/$ [name='province-detail']
produces http://127.0.0.1:8000/api/provinces/02/ which is fine.
It uses the actual code bellow:
class ProvinceSerializer(serializers.ModelSerializer):
""" Serializer to represent the Province model """
class Meta:
model = Province
fields = ("name", "code")
I want to add another route, so that I can have another endpoint for example:
^api/ ^ ^provinces/(?P<pk>[^/.]+)/(?P<product>[^/.]+)/$ [name='province-product-detail']
So that I can do like this http://127.0.0.1:8000/api/provinces/02/apple/ and access the second argument in a method of the serializer. I'm trying to do like this :
class ProvinceSerializer(serializers.ModelSerializer):
""" Serializer to represent the Province model """
class Meta:
model = Province
fields = ("name", "code")
#detail_route(methods=['post'])
def set_product(self, request, product=None):
return product
I've find out I was using the #detail_route in the wrong place. We just have to do this:
class ProvinceDistrictViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to view or edit province.
"""
queryset = Province.objects.all()
serializer_class = ProvinceDistrictsSerializer
# For get provinces
#detail_route(methods=['get'], url_path='(?P<product>\d+)')
def update_product(self, request, pk, product=None):
""" Updates the object identified by the pk and add the product """
queryset = Province.objects.filter(pk=pk)
serializer = ProvinceDistrictsSerializer(queryset, many=True, context={'product': product})
return Response(serializer.data, status=status.HTTP_200_OK)

what is the control flow of django rest framework

I am developing an api for a webapp. I was initially using tastypie and switched to django-rest-framework (drf). Drf seems very easy to me. What I intend to do is to create nested user profile object. My models are as below
from django.db import models
from django.contrib.auth.models import User
class nestedmodel(models.Model):
info = models.CharField(null=True, blank=True, max_length=100)
class UserProfile(models.Model):
add_info = models.CharField(null=True, blank=True, max_length=100)
user = models.OneToOneField(User)
nst = models.ForeignKey(nestedmodel)
I have other models that have Foreignkey Relation. My Serializers are as below
from django.contrib.auth.models import User, Group
from rest_framework import serializers
from quickstart.models import UserProfile, nestedmodel
class NestedSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = nestedmodel
fields = ('info', )
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'email', 'groups')
class GroupSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Group
fields = ('url', 'name')
class UserProfileSerializer(serializers.HyperlinkedModelSerializer):
user = UserSerializer()
nst = NestedSerializer()
class Meta:
model = UserProfile
user = UserSerializer(many=True)
nested = NestedSerializer(many=True)
fields = ('nst', 'user')
I can override methods like create(self, validated_data): without any issues. But What I want to know is to which method should the response returned by create() goes, or in other words Which method calls create(). In tastypie Resources.py is the file to override to implement custom methods. And Resources.py contains the order in which methods are being called. Which is the file in drf that serves the same purpose and illustrates the control flow like Resources.py in tastypie?.
So the flow goes something like:
Viewset's create() method which is implemented in CreateModelMixin
That creates serializer and validates it. Once valid, it uses viewset's perform_create()
That calls serializer's save() method
That then in turn calls serializer's either create() or update() depending if instance was passed to serializer (which it was not in step 1)
create() or update() then create/update instance which is then saved on serializer.instance
Viewset then returns response with data coming from serializer.data
serializer.data is actually a property on serializer which is responsible for serializing the instance to a dict
To serialize data, to_representation() is used.
Then response data (Python dict) is rendered to a output format via renderers which could be json, xml, etc
And Resources.py contains the order in which methods are being called. Which is the file in drf that serves the same purpose and illustrates the control flow like Resources.py in tastypie?.
Guess that would be a combination of files. Its probably better to think in terms of classes/concepts you are touching since in DRF you can inherit from multiple things for create your classes. So the thing which glues everything together are viewsets. Then there are various viewset mixins which actually glue the viewset to the serializer and different CRUD operations.
I figured out second part of question myself. get/create object can be done by using custom code in overriden def create(self, request, *args, **kwargs): in views.py. Code is as pasted below. Once Again, for clarity this is views.py not serializers.py. Also json with posted values can be accessed from request.DATA
class NestedViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows Nested objects to be viewed or edited.
"""
queryset = nestedmodel.objects.all()
serializer_class = NestedSerializer
def create(self, request, *args, **kwargs):
info = request.DATA['info']
user = User.objects.get(username=request.DATA['user']['username'])
profile = UserProfile.objects.get(user=user)
nst = nestedmodel.objects.create(info=info, user=user, profile=profile)
serialized_obj = serializers.serialize('json', [ nst, ])
json_serialized = json.loads(serialized_obj)
data = json.dumps(json_serialized[0])
return Response(data)
Thanks for the help #miki275 :)

Changing serializer fields on the fly

For example I have the following serializer:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
'userid',
'password'
)
But I don't want to output password on GET (there are other fields in my real example of course). How can I do that without writing other serializer? Change the field list on the fly. Is there any way to do that?
You appear to be looking for a write-only field. So the field will be required on creation, but it won't be displayed back to the user at all (the opposite of a read-only field). Luckily, Django REST Framework now supports write-only fields with the write_only attribute.
In Django REST Framework 3.0, you just need to add the extra argument to the extra_kwargs meta option.
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
'userid',
'password'
)
extra_kwargs = {
'password': {
'write_only': True,
},
}
Because the password should be hashed (you are using Django's user, right?), you are going to need to also hash the password as it is coming in. This should be done on your view, most likely by overriding the perform_create and perform_update methods.
from django.contrib.auth.hashers import make_password
class UserViewSet(viewsets.ViewSet):
def perform_create(self, serializer):
password = make_password(self.request.data['password'])
serializer.save(password=password)
def perform_update(self, serializer):
password = make_password(self.request.data['password'])
serializer.save(password=password)
In Django REST Framework 2.x, you need to completely redefine the password field on the serializer.
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
class Meta:
model = User
fields = (
'userid',
'password'
)
In order to hash the password ahead of time in Django REST Framework 2.x, you need to override pre_save.
from django.contrib.auth.hashers import make_password
class UserViewSet(viewsets.ViewSet):
def pre_save(self, obj, created=False):
obj.password = make_password(obj.password)
super(UserViewSet, self).pre_save(obj, created=created)
This will solve the common issue with the other answers, which is that the same serializer that is used for creating/updating the user will also be used to return the updated user object as the response. This means that the password will still be returned in the response, even though you only wanted it to be write-only. The additional problem with this is that the password may or may not be hashed in the response, which is something you really don't want to do.
this should be what you need. I used a function view but you can use class View or ViewSet (override get_serializer_class) if you prefer.
Note that serializer_factory also accept exclude= but, for security reason, I prefer use fields=
serializer_factory create a Serializer class on the fly using an existing Serializer as base (same as django modelform_factory)
==============
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
'userid',
'password'
)
#api_view(['GET', 'POST'])
def user_list(request):
User = get_user_model()
if request.method == 'GET':
fields=['userid']
elif request.method == 'POST':
fields = None
serializer = serializer_factory(User, UserSerializer, fields=fields)
return Response(serializer.data)
def serializer_factory(model, base=HyperlinkedModelSerializer,
fields=None, exclude=None):
attrs = {'model': model}
if fields is not None:
attrs['fields'] = fields
if exclude is not None:
attrs['exclude'] = exclude
parent = (object,)
if hasattr(base, 'Meta'):
parent = (base.Meta, object)
Meta = type(str('Meta'), parent, attrs)
if model:
class_name = model.__name__ + 'Serializer'
else:
class_name = 'Serializer'
return type(base)(class_name, (base,), {'Meta': Meta, })
Just one more thing to #Kevin Brown's solution.
Since partial update will also execute perform_update, it would be better to add extra code as following.
def perform_update(self, serializer):
if 'password' in self.request.data:
password = make_password(self.request.data['password'])
serializer.save(password=password)
else:
serializer.save()
As far as i can tell from the docs, the fastest way would be to simply have 2 serializers becalled conditionally from your view.
Also, the docs show this other alternative, but it's a little too meta:
http://www.django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields
it involves creating smart initializer methods, gives an example. i'd just use 2 serializers, if i'd know those changes are the only ones i'll make. otherwise, check the example