I created a simple model API with Django Rest Framework.. For the most part i followed quick start tutorial on DRF site.
I am accessing it from angular app using Authorization token. Everything works fine except when i wish to make a new post...
This is the error I get null value in column "owner_id" violates not-null constraint
This is my code:
models.py
class Post(models.Model)
title = models.CharField(max_length=100, blank=False)
point = models.PointField(max_length=255, null=True)
owner = models.ForeignKey('auth.User', related_name='posts')
active = models.NullBooleanField()
created = models.DateTimeField(auto_now_add=True)
expires = models.DateTimeField(blank=True, null=True)
serializers.py
class PostSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
model = Post
fields = ( 'owner', 'id', 'title', 'point', 'active', 'created', 'expires')
views.py
class PostList(APIView):
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def post(self, request, format=None):
serializer = PostSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
Thanks.
OK, lets take this one step at a time. First, perform_create() method is defined in the create mixin that is used by ModelViewSet. Overriding it in the APIView derived view class doesn't do anything. So your Post creation is all handled by the post() method. The easiest way to fix the error is to add owner=self.request.user to your serializer.save() call in post().
That said, you might want to consider redefining the whole thing as a ModelViewSet, since you will likely need a full set of CRUD APIs anyway.
class PostViewSet(viewsets.ModelViewSet):
class Meta:
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
Related
Using class Based (APIView) in Django rest framework for Getting and Patch (Updating) UserInfo data.
views.py
class getUserInfo(APIView):
permission_classes = [permissions.IsAuthenticated]
def get(self, request, format=None):
user = request.user
userinfos = user.userinfo_set.all()
serializer = UserInfoSerializers(userinfos, many=True)
return Response(serializer.data)
def patch(self, request, pk, format=None):
user = UserInfo.objects.get(id=pk)
serializer = UserInfoSerializers(instance=user, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
serializers.py
from django.contrib.auth.models import User
from .models import UserInfo
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'first_name', 'username')
class UserInfoSerializers(serializers.ModelSerializer):
user = UserSerializer(many=False, required=True)
class Meta:
model = UserInfo
fields = ('id', 'picture', 'profession', 'user')
Everything is working so far so good. Able to GET and PATCH (Update) logged-in user data.
While Testing the API in Postman, I found out that if User1 is logged in he can change the data of User2 by only using the pk of User2.
urls.py
urlpatterns = [
path('userinfo/', views.getUserInfo.as_view(), name="UserInfo"),
path('userinfo/<str:pk>/', views.getUserInfo.as_view()),
path('api/token/', views.MyTokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('register/', views.RegisterView.as_view(), name='auth_register'),
]
Using rest_framework_simplejwt for Auth
models.py
from django.contrib.auth.models import User
class UserInfo(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
picture = models.ImageField(upload_to="profile_pics", null=True)
profession = models.CharField(max_length=200, null=True)
def __str__(self):
return "%s's Profile Picture" % self.user
Any help would be appreciated
Don't use the primary key to get the user.You are using user = request.user to get the user on get method, use the same mechanism also on update. Then the login user can only update his/her info not others info or another way you can check the user = UserInfo.objects.get(id=pk) is same as the current user request.user . If not you can show an exception.
For Retrieving and Updating an object, you can use RetrieveUpdateAPIView
class GetUserInfo(generics.RetrieveUpdateAPIView):
permission_classes = [IsAuthenticated]
queryset = UserInfo.objects.all()
serializer_class = UserInfoSerializers
def get_object(self):
return self.request.user
Here we are getting an object, it will be called from get_object method. Instead of getting user using PK, we get the current user.
You can use same url for getting and updating the user, just change the method in postman while you hit the api. GET for retrieving and PATCH for partial update.
path('userinfo/', views.GetUserInfo.as_view(), name="UserInfo"),
Hello everyone reading this post. I got such issue.
So, first of all I have such models layout
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
description = models.TextField(max_length=4000, null=True)
liked_profiles = models.ManyToManyField('self', related_name='likes')
disliked_profiles = models.ManyToManyField('self', related_name='dislikes')
class Image(models.Model):
profile = models.ForeignKey(Profile, on_delete=models.CASCADE, related_name='images', max_length=6)
path = models.ImageField(upload_to='profile_images')
So, I want to create a drf endpoint that will receive some kind of image, create it, and link to the profile model. But I really not sure how to implement this(I want to make it with django viewsets).
The main goal is not to provide another viewset class (like ImageViewSet), but to make it part of ProfileViewSet. So now I have such viewset (contains a method to update the description)
class ProfileViewSet(viewsets.ModelViewSet):
queryset = Profile.objects.all()
permission_classes = (IsAuthenticated, )
#action(detail=True, methods=['PUT'])
def set_description(self, request, pk=None):
profile = self.get_object()
serializer = DescriptionSerializer(data=request.data)
if serializer.is_valid():
profile.description = request.data['description']
profile.save()
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
I want to add something like "add_image" method to it.
How can I implement it and is it actually possible(otherwise you can help me to implement it with another viewset)?
I will be extremely grateful for your help
You can do sth similar to your set_description:
#action(
detail=True,
methods=["post"],
serializer_class=ImageSerializer, # write your custom serializer, override save() method and save images from self.context["request"].FILES.items()
)
def create_image(self, request, pk=None):
instance = self.get_object()
serializer = self.get_serializer(instance, data=self.request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
I am trying to create a booking api for a website.
For this, I used perform_create function in ListCreateApiView. But, it was someone else who helped me and told me to use perform_create function.
But, I was thinking, it should be possible using create function and is a right approach rather than perform_create function.
Also, I don't really know the difference between these two functions and don't really know when to use which
Here is my code:
class BookingCreateAPIView(ListCreateAPIView):
permission_classes= [IsAuthenticated]
queryset = Booking.objects.all()
serializer_class = BookingSerializer
def perform_create(self, serializer):
# user = self.request.user
package = get_object_or_404(Package, pk= self.kwargs['pk'])
serializer.save(user=self.request.user,package=package)
Here is my serializer:
class BookingSerializer(serializers.ModelSerializer):
# blog = serializers.StringRelatedField()
class Meta:
model = Booking
fields = ['name', 'email', 'phone', 'bookedfor']
Here is my model:
class Booking(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
package = models.ForeignKey(Package, on_delete=models.CASCADE, related_name='package')
name = models.CharField(max_length=255)
email = models.EmailField()
phone = models.CharField(max_length=255)
bookedfor = models.DateField()
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('created_at',)
if you looking in source code:
ListCreateApiView call
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
then inside create call
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
That mean perform_create called after serializer validated.
And for what you want, you can override different method.
In my opinion:
If you want custom Response return from api, you override create
If you want doing anything special after serializer validated, but before object created in database, you override perform_create. Like your example, it want checking Package exists and save request.user in field user
I have the following :
I am working with DRF, based JWT token.
I want to associate an experiment with a USER, i.e when a post request is arriving I want to be able to save that post request with the Foreginkey it needed for the author by the user whom sent the request.
The POST request is always authenticated and never anonymous, i.e request.user is always exist ( I can see it when debugging)
I tried to add the following
def create(self, request, **kwargs):
request.data["author"] = request.user
serializer = ExperimentsSerializers(data=request.data)
if serializer.is_valid():
serializer.save()
return....
But is_valid return always False ( the only time ts was true, was when I took out the author from the ExperimentsSerializers fields....
will be happy for any leads....
my code attached below
Model.py:
class User(AbstractUser):
pass
def __str__(self):
return self.username
class Experiments(models.Model):
name = models.CharField(max_length=40)
time = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
View.py:
filter_backends = [DjangoFilterBackend, filters.OrderingFilter]
serializer_class = ExperimentsSerializers
queryset = Experiments.objects.all()
filterset_fields = '__all__'
permission_classes = (permissions.IsAuthenticated,)
serializers.py
class ExperimentsSerializers(serializers.ModelSerializer):
class Meta:
model = models.Experiments
fields = '__all__'
You can just pass additional data with save arguments:
def create(self, request, **kwargs):
serializer = ExperimentsSerializers(data=request.data)
if serializer.is_valid():
serializer.save(author=request.user)
Note that you may need to specify author field as read_only so it would not be required in request body:
class ExperimentsSerializers(serializers.ModelSerializer):
class Meta:
model = models.Experiments
fields = '__all__'
read_only_fields = ['author']
One more approach can be to use
HiddenField with default value set to CurrentUserDefault
This way that field will not be exposed at the same time current user will be accessible and other operations can be done on that user context.
author = serializers.HiddenField(default=serializers.CurrentUserDefault())
Something like this:
class ExperimentsSerializers(serializers.ModelSerializer):
author = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = models.Experiments
fields = '__all__'
Reference :
HiddenField - https://www.django-rest-framework.org/api-guide/fields/#hiddenfield
CurrentUserDefault - https://www.django-rest-framework.org/api-guide/validators/#currentuserdefault
I've tried something like this, it does not work.
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
def save(self):
user = self.context['request.user']
title = self.validated_data['title']
article = self.validated_data['article']
I need a way of being able to access request.user from my Serializer class.
You cannot access the request.user directly. You need to access the request object, and then fetch the user attribute.
Like this:
user = self.context['request'].user
Or to be more safe,
user = None
request = self.context.get("request")
if request and hasattr(request, "user"):
user = request.user
More on extra context can be read here
Actually, you don't have to bother with context. There is a much better way to do it:
from rest_framework.fields import CurrentUserDefault
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
def save(self):
user = CurrentUserDefault() # <= magic!
title = self.validated_data['title']
article = self.validated_data['article']
As Igor mentioned in other answer, you can use CurrentUserDefault. If you do not want to override save method just for this, then use doc:
from rest_framework import serializers
class PostSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault())
class Meta:
model = Post
CurrentUserDefault
A default class that can be used to represent the current user. In order to use this, the 'request' must have been provided as part of the context dictionary when instantiating the serializer.
in views.py
serializer = UploadFilesSerializer(data=request.data, context={'request': request})
This is example to pass request
in serializers.py
owner = serializers.HiddenField(
default=serializers.CurrentUserDefault()
)
Source From Rest Framework
Use this code in view:
serializer = UploadFilesSerializer(data=request.data, context={'request': request})
then access it with this in serializer:
user = self.context.get("request").user
For those who used Django's ORM and added the user as a foreign key, they will need to include the user's entire object, and I was only able to do this in the create method and removing the mandatory field:
class PostSerializer(serializers.ModelSerializer):
def create(self, validated_data):
request = self.context.get("request")
post = Post()
post.title = validated_data['title']
post.article = validated_data['article']
post.user = request.user
post.save()
return post
class Meta:
model = Post
fields = '__all__'
extra_kwargs = {'user': {'required': False}}
You can pass request.user when calling .save(...) inside a view:
class EventSerializer(serializers.ModelSerializer):
class Meta:
model = models.Event
exclude = ['user']
class EventView(APIView):
def post(self, request):
es = EventSerializer(data=request.data)
if es.is_valid():
es.save(user=self.request.user)
return Response(status=status.HTTP_201_CREATED)
return Response(data=es.errors, status=status.HTTP_400_BAD_REQUEST)
This is the model:
class Event(models.Model):
user = models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
date = models.DateTimeField(default=timezone.now)
place = models.CharField(max_length=255)
You can not access self.context.user directly. First you have to pass the context inside you serializer. For this follow steps bellow:
Some where inside your api view:
class ApiView(views.APIView):
def get(self, request):
items = Item.object.all()
return Response(
ItemSerializer(
items,
many=True,
context=request # <- this line (pass the request as context)
).data
)
Then inside your serializer:
class ItemSerializer(serializers.ModelSerializer):
current_user = serializers.SerializerMethodField('get_user')
class Meta:
model = Item
fields = (
'id',
'name',
'current_user',
)
def get_user(self, obj):
request = self.context
return request.user # <- here is current your user
In GET method:
Add context={'user': request.user} in the View class:
class ContentView(generics.ListAPIView):
def get(self, request, format=None):
content_list = <Respective-Model>.objects.all()
serializer = ContentSerializer(content_list, many=True,
context={'user': request.user})
Get it in the Serializer class method:
class ContentSerializer(serializers.ModelSerializer):
rate = serializers.SerializerMethodField()
def get_rate(self, instance):
user = self.context.get("user")
...
...
In POST method:
Follow other answers (e.g. Max's answer).
You need a small edit in your serializer:
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
def save(self):
user = self.context['request'].user
title = self.validated_data['title']
article = self.validated_data['article']
Here is an example, using Model mixing viewsets. In create method you can find the proper way of calling the serializer. get_serializer method fills the context dictionary properly. If you need to use a different serializer then defined on the viewset, see the update method on how to initiate the serializer with context dictionary, which also passes the request object to serializer.
class SignupViewSet(mixins.UpdateModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet):
http_method_names = ["put", "post"]
serializer_class = PostSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
kwargs['context'] = self.get_serializer_context()
serializer = PostSerializer(instance, data=request.data, partial=partial, **kwargs)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data)
The solution can be simple for this however I tried accessing using self.contenxt['request'].user but not working in the serializer.
If you're using DRF obviously login via token is the only source or maybe others that's debatable.
Moving toward a solution.
Pass the request.user instance while creating serializer.create
views.py
if serializer.is_valid():
watch = serializer.create(serializer.data, request.user)
serializer.py
def create(self, validated_data, usr):
return Watch.objects.create(user=usr, movie=movie_obj, action=validated_data['action'])
If you are using generic views and you want to inject current user at the point of saving the instance then you can override perform_create or perform_update:
def perform_create(self, serializer):
serializer.save(user=self.request.user)
user will be added as an attribute to kwargs and you can access it through validated_data in serializer
user = validated_data['user']
drf srz page
in my project it worked my user field was read only so i needed to get
user id in the create method
class CommentSerializer(serializers.ModelSerializer):
comment_replis = RecursiveField(many=True, read_only=True)
user = UserSerializer(read_only=True)
class Meta:
model = PostComment
fields = ('_all_')
def create(self, validated_data):
post = PostComment.objects.create(**validated_data)
print(self._dict_['_kwargs']['data']["user"]) # geting #request.data["user"] # <- mian code
post.user=User.objects.get(id=self._dict_['_kwargs']['data']["user"])
return post
in my project i tried this way and it work
The best way to get current user inside serializer is like this.
AnySerializer(data={
'example_id': id
}, context={'request': request})
This has to be written in views.py
And now in Serializer.py part
user = serializers.CharField(default=serializers.CurrentUserDefault())
This "user" must be your field in Model as any relation like foreign key