what is the control flow of django rest framework - django

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 :)

Related

React/Django App: Need to update a field in a put route, with a custom function in the backend, before saving with a serializer

I'm trying to change the content of a field with my own function. I'm using a simplified function that adds commas between each word. I want to be able to send my comma-fied sentence to the frontend but I don't know how to do that with the serializer that was given in Django documentation. I can't find any examples online of someone trying to do this online. I also need to do this in the backend because some of my other custom functions need access to a specific python library.
Here is my api > views.py
#api_view(['PUT'])
def accentLine(request, pk):
data = request.data
line = Lines.objects.get(id=pk)
if data['accent'] == 'shatner':
shatnerLine = line.content.replace(' ', ', ')
line.translation = shatnerLine
line.save()
serializer = LineSerializer(instance=line, data=data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
Here is my api > models.py
class Lines(models.Model):
# userId = models.ForeignKey(Users, on_delete=models.CASCADE)
script = models.ForeignKey(Scripts, null=True, on_delete=models.SET_NULL)
title = models.CharField(max_length=50, null=True)
content = models.TextField(max_length=5000, null=True)
accent = models.CharField(max_length=50, default='english')
translation = models.TextField(max_length=5000, null=True)
updatedAt = models.DateField(auto_now=True)
Here is my api > serializers.py
from rest_framework.serializers import ModelSerializer
from .models import Lines
class LineSerializer(ModelSerializer):
class Meta:
model = Lines
fields = '__all__'
First and foremost, you are calling save on the model instance twice by calling it directly on the instance, and then again on the serializer. The serializer save method will perform save on the model instance itself. Docs on saving instances with serializers: https://www.django-rest-framework.org/api-guide/serializers/#saving-instances
To achieve what you want to do you should create a custom serializer field probably called TranslationField and either override the to_internal_value method to perform your string mutations before the data is persisted to the database, or override to_representation which will perform the string mutations before the data is returned from the serializer. It depends on if you wish to persist the field... with commas or add the commas when getting the data via serializer. Docs on custom fields here: https://www.django-rest-framework.org/api-guide/fields/#custom-fields
Once you set up your custom field you will not perform any mutations in your accentLine view and instead simply pass the request data to the serializer like so
#api_view(['PUT'])
def accentLine(request, pk):
data = request.data
line = Lines.objects.get(id=pk)
serializer = LineSerializer(instance=line, data=data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)

django REST framework api without using models

I'm new to django rest framework. In my project i need to send api on a request, here i'm not using model data instead i want to send a dictionary as api response which is read from external database like mongodb. how to do this?
viewset code
class LivedataViewSet(viewsets.ModelViewSet):
queryset = LiveData.objects.all()
serializer_class = LiveDataSerializer
def get_queryset(self):
qs = super().get_queryset()
user_id = str(self.request.query_params.get('user'))
if user_id:
queryset = qs.filter(user=user_id)
return queryset
else:
return qs
and serializer code is
class LiveDataSerializer(serializers.ModelSerializer):
class Meta:
model = LiveData
fields = ('id', 'user', 'status')
this code works but it uses model here i need same function without model.
You need APIView instead of ModelViewSet, with that you can define your own endpoints, without tying it to a model.
Docs: https://www.django-rest-framework.org/api-guide/views/

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.

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

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,])

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