I have a model like this:
class Professional(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
dummy_text = models.CharField(max_length=300)
a serializer like this:
class ProfessionalSerializer(serializers.ModelSerializer):
class Meta:
model = Professional
fields = '__all__'
and a view like this one:
class CreateProfessional(generics.CreateAPIView):
serializer_class = ProfessionalSerializer
The thing is, I need to pass the current authenticated user for a given request as the user for my serializer, I'm getting an error because obviously the user field is required as stated in my model, but I can't find an elegant way to do so, how could I go about it?
Set the user as a read_only_fields in the serializer meta. This will prevent accepting the user data from the payload.
class ProfessionalSerializer(serializers.ModelSerializer):
class Meta:
model = Professional
fields = '__all__'
read_only_fields = ["user"]
Then, override the perform_create(...) method of the view class
class CreateProfessional(generics.CreateAPIView):
serializer_class = ProfessionalSerializer
def perform_create(self, serializer):
serializer.save(user=self.request.user)
You can make validate method with like validate_user and do following code inside.
self.context['view'].request.user()
class CreateProfessional(generics.CreateAPIView):
serializer_class = ProfessionalSerializer
def create(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data, context={'request': request})
serializer.is_valid(raise_exception=True)
serializer.save()
class ProfessionalSerializer(serializers.ModelSerializer):
class Meta:
model = Professional
fields = '__all__'
def create(self, validated_data):
user = self.context['request'].user
like this you can get your current user
I found a very nice way to do it, I dont wan't to return custom responses, I will let that to the framework, what I did was the following in my view:
def get_serializer(self, *args, **kwargs):
# redefine method to parameterize the serializer
# while leaving the response handling to the
# framework
serializer_class = self.get_serializer_class()
kwargs['context'] = self.get_serializer_context()
kwargs['context']['user'] = self.request.user
return serializer_class(*args, **kwargs)
and in my serializer:
class EntitySerializer(serializers.ModelSerializer):
class Meta:
model = Entity
fields = '__all__'
extra_kwargs = {'user': {'required': False}}
def create(self, validated_data):
user = self.context['request'].user
entity = Entity.objects.create(user=user, **validated_data)
return entity
You can use ModelViewSet instead of single API Views. ModelViewSet handles all the crud operations automatically.
Let's say you have the model
class Professional(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
dummy_text = models.CharField(max_length=300)
and the serializer
class ProfessionalSerializer(serializers.ModelSerializer):
class Meta:
model = Professional
fields = '__all__'
So you can add a ModelViewSet
class ProfessionalViewSet(ModelViewSet):
queryset = Professional.objects.all()
permission_classes = [IsAuthenticated]
serializer_class = ProfessionalSerializer
this will handle all the crud operations for your model. Following example class will show you the methods that a viewset has.
class UserViewSet(viewsets.ViewSet):
"""
Example empty viewset demonstrating the standard
actions that will be handled by a router class.
If you're using format suffixes, make sure to also include
the `format=None` keyword argument for each action.
"""
def list(self, request):
pass
def create(self, request):
pass
def retrieve(self, request, pk=None):
pass
def update(self, request, pk=None):
pass
def partial_update(self, request, pk=None):
pass
def destroy(self, request, pk=None):
pass
these will handle all the actions. checkout the documentation for more
you'll also have to add the router to handle all the urls automatically which is also covered in the documentation (above link). Following is an example(urls.py).
from myapp.views import UserViewSet
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'users', UserViewSet, basename='user')
urlpatterns = router.urls
Related
Perhaps the question is wrongly worded. I created user profile using Django through the following blocks of code:
models.py
class = Profile (models.Models):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
first name = models.CharField(max_length=50)
•••
serializer.py
class profile_serializer(serializers.ModerlSerializer)
class Meta:
model = Profile
fields = '__all__'
views.py
class profile_view(generics.ListCreateAPIView)
queryset = Profile.objects.all().filter(user=instance)
urls.py
urlspatterns = [path('profile', profile_view.as_view(), name='user_profile)
I definitely do not know how to implement the filter method to ensure that only the logged in user is retrieved. Or is there a better approach to obtain a specific user? If I use Project.objects.all() without the filter I get all the registered user as expected. But I don't know how to retrieve a particular user.
Hmm, I would do something like this:
from rest_framework.response import Response
from rest_framework import status, generics, permissions
class UserView(generics.GenericAPIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = profile_serializer
def get(self, request):
user = request.user
return Response(profile_serializer(user).data,status=status.HTTP_200_OK)
Basically when a user is authenticated, their user is present in the request.
Here's the UserSerializer, comments was hard to format. (Ironically for a tech forum?)
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
def get(self, instance):
return instance
def patch(self, instance, validated_data):
instance.model_method()
return super().update(instance, validated_data)
def create(self, validated_data):
user = self.context['request'].user
return super().update(user, validated_data)
And here's the profile_serializer:
class profile_serializer(serializers.ModerlSerializer):
user = UserSerializer(read_only=True)
class Meta:
model = Profile
fields = ('user', 'first_name', )
I have my Comment model:
class Comment(models.Model):
"""A comment is a content shared by a user in some post."""
user = models.ForeignKey('users.User', on_delete=models.CASCADE, null=False)
post = models.ForeignKey('posts.Post', on_delete=models.CASCADE, null=False)
content = models.TextField(max_length=1000, null=False, blank=False)
def __str__(self):
"""Return the comment str."""
return "'{}'".format(self.content)
Its serializer:
class CommentSerializer(serializers.ModelSerializer):
"""Comment model serializer."""
user = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Comment
fields = '__all__'
def create(self, validated_data):
"""Create a new comment in some post, by request.user."""
validated_data['user'] = self.context['request'].user
return super().create(validated_data)
def list(self, request):
"""List all the comments from some post."""
if 'post' not in request.query_params:
raise ValidationError('Post id must be provided.')
q = self.queryset.filter(post=request.query_params['post'])
serializer = CommentSerializer(q, many=True)
return Response(serializer.data)
The viewset:
class CommentViewSet(viewsets.ModelViewSet):
serializer_class = CommentSerializer
queryset = Comment.objects.all()
def get_permissions(self):
permissions = []
if self.action == 'create' or self.action == 'destroy':
permissions.append(IsAuthenticated)
return [p() for p in permissions]
def get_object(self):
"""Return comment by primary key."""
return get_object_or_404(Comment, id=self.kwargs['pk']) # this is the drf's get_object_or_404 function
def destroy(self, request, *args, **kwargs):
"""Delete a comment created by request.user from a post."""
pdb.set_trace()
instance = self.get_object()
if instance.user != request.user:
raise ValidationError('Comment does not belong to the authenticated user.')
self.perform_destroy(instance)
def retrieve(self, request, pk=None):
pass
def update(self, request, pk=None):
pass
def partial_update(self, request, pk=None):
pass
So far so good when it comes to list, create and retrieve. But when it comes to delete/destroy (Idk the difference) I don't know how to get the URL for the DELETE request.
I'm using Postman to do so.
Urls.py:
router = routers.SimpleRouter()
router.register(r'comments', CommentViewSet, basename='comments')
pdb.set_trace() # Put a pdb here to see what does router.urls have.
urlpatterns = [
# Non api/simple django views
path('create_comment/', create_comment, name='create_comment'),
path('delete_comment/', delete_comment, name='delete_comment'),
# rest api views
path('rest/', include(router.urls))
]
When I debug the router.urls, the terminal shows this:
[<URLPattern '^comments/$' [name='comments-list']>, <URLPattern '^comments/(?P<pk>[^/.]+)/$' [name='comments-detail']>]
Why there is no url for create and destroy?
I could create some Comments from the api, and I didn't even know how did I get the POST request url for the create function, I just guessed lol, but that's not what you want when you're programming right?
I have checked the Routers documentation but I don't get it. Please give some help! Many thanks.
The answer is simple, Django uses the comments-list list for create and list operation and comments-detail for update and delete operations.
That is, there are only two URL end-points, but it supports several actions, which can be performed by changing the HTTP methods
You can use HTTP GET foo-bar/comments/ to retrieve all comments where as HTTP POST foo-bar/comments/ can be used to create a new comment.
I'm not sure what I'm doing wrong, but the authenticated user is not registered in the serialiser.
Models.py
class Post(models.Model):
posted_by = models.ForeignKey('auth.User', on_delete=models.CASCADE)
def save(self, *args, **kwargs):
super(Post, self).save(*args, **kwargs)
Serializers.py
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = '__all__'
def create(self, validated_data):
post = Post.objects.create(**validated_data)
# extra code to add images
views.py
class PostViewSet(viewsets.ModelViewSet):
serializer_class = PostSerializer
permission_classes = (
permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly, )
def perform_create(self, serializer):
serializer.save(posted_by=self.request.user)
I don't understand why serializer.save(posted_by=self.request.user) doesn't work as intended. It should pass the required information about the field.
When I do a POST request, I get the error that
{
"posted_by": [
"This field is required."
]
}
I think it's something to do with the create method in the serialiser. For some reason, posted_by is not present in validated_data (or something similar). I would like to know what exactly is happening behind the scenes.
just specify posted_by as read only field.
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = '__all__'
read_only_fields = ('posted_by', )
I am trying to implement authentication by combining Django Rest Framework and Angular, but I am suffering from user information update.
Angular sends it to Django with the PUT method, Django accepts the request with View "AuthInfoUpdateView".
class AuthInfoUpdateView(generics.GenericAPIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = AccountSerializer
lookup_field = 'email'
queryset = Account.objects.all()
def put(self, request, *args, **kwargs):
serializer = AccountSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
At this time, Django accepts the request as below.
request.data = {'email': 'test3#example.com', 'username': 'test3', 'profile': 'i am test3'}
request.user = test3#example.com
And the serializer is implementing as below.
from django.contrib.auth import update_session_auth_hash
from rest_framework import serializers
from .models import Account, AccountManager
class AccountSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=False)
class Meta:
model = Account
fields = ('id', 'username', 'email', 'profile', 'password')
def create(self, validated_data):
return Account.objects.create_user(request_data=validated_data)
def update(self, instance, validated_data):
insntance.username = validated_data.get('username', instance.username)
insntance.email = validated_data.get('email', instance.email)
insntance.profile = validated_data.get('profile', instance.profile)
instance = super().update(instance, validated_data)
return instance
I tried to update the user from Angular in such an implementation, and the following response is returned.
"{"username":["account with this username already exists."],"email":["account with this email address already exists."]}"
It is thought that it is because you did not specify the record to update, but is there a way to solve it smartly without changing the current configuration so much?
I need your help.
use
class AuthInfoUpdateView(generics.UpdateAPIView):
use http method patch can partial_update your instance.
method PATCH -> partial update instance
method PUT -> update instance
I am trying to implement a base serializer and I am following the "http://www.django-rest-framework.org/api-guide/serializers/#baseserializer".
My urls.py:
url(r'^auth/myuser/(?P<pk>[0-9]+)/profile/$', UserProfileViewSet.as_view({'patch':'update'}), name='user-profile'),
Views.py:
class UserProfileViewSet(viewsets.ModelViewSet):
queryset = CustomUser.objects.all()
serializer_class = UserProfileSerializer
def get(self,request,pk,*args,**kwargs):
user_instance = CustomUser.objects.get(pk=pk)
dashboard_data = UserProfileSerializer(user_instance)
content = {'result': dashboard_data}
return Response(content)
Serializers.py:
class UserProfileSerializer(serializers.BaseSerializer):
def to_representation(self,obj):
return{
'email':obj.email,
'first_name':obj.first_name,
'last_name':obj.last_name,
'date_of_birth':obj.date_of_birth,
'gender':obj.get_gender_display(),
'location':obj.location,
'calling_code':obj.callingcode,
'phone_primary':obj.phone_primary,
'phone_secondary':obj.phone_secondary,
'website':obj.website
}
But I am getting the error "User object is not JSON serializable", and I don't find any attributes of the User obj that are not serializable.
I already found some answers on SO, but I don't find any similar steps in the django rest framework api guide. So looking for a solution that is in sync with the api-guide.
Serializer:
It seems what you need is a ModelSerializer
The ModelSerializer class provides a shortcut that lets you automatically create a Serializer class with fields that correspond to the Model fields.
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = CustomerUser
fields = '__all__'
I have set fields attribute to __all__ to indicate that all the fields in the model are serialized. But, you can also specify which fields to include exaclty:
fields = ('email', 'first_name', 'last_name',) # etc.
ViewSet:
The second point is about the ModelViewSet where you don't really need to implement the get method because it is already implemented for you. You just need to declare the queryset and the serializer_class just like you have already done:
class UserProfileViewSet(viewsets.ModelViewSet):
queryset = CustomUser.objects.all()
serializer_class = UserProfileSerializer
I guess you'll have to render the response in JSON format before returning it.
class JSONResponse(HttpResponse):
"""
An HttpResponse that renders its content into JSON.
"""
def __init__(self, data, **kwargs):
content = JSONRenderer().render(data)
kwargs['content_type'] = 'application/json'
super(JSONResponse, self).__init__(content, **kwargs)
class UserProfileViewSet(viewsets.ModelViewSet):
queryset = CustomUser.objects.all()
serializer_class = UserProfileSerializer
def get(self,request,pk,*args,**kwargs):
user_instance = CustomUser.objects.get(pk=pk)
dashboard_data = UserProfileSerializer(user_instance)
content = {'result': dashboard_data}
return JSONResponse(content, status=200)
You'll need following imports for this,
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
If this doesn't work just try passing dashboard_data to the JSONResponse function