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)
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"),
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'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
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)
So i have been trying to upload a file to a method using DRF with no luck so far.
I was able to upload to a ModelViewSet using (FormParser, MultiPartParser,) with no problems, but i really need to use it in something like this http://localhost:8000/api/v1/women/{pk}/upload_avatar/ where i want to first filter the woman by id and upload to her avatar (which is a foreign key to a multimedia model). I tried using a nested resource library with no luck.
So far i have in my modelviewset:
class WomenNativePassportViewSet(viewsets.ModelViewSet):
queryset = Women.objects.all()
serializer_class = WomenNativePassportSerializer
authentication_classes = (NoAuthentication,)
permission_classes = (AllowAny,)
parser_classes = (FormParser, MultiPartParser,)
#detail_route(
methods=['post', 'put', 'patch', 'get'], permission_classes=[AllowAny],
authentication_classes=[NoAuthentication], serializer_class=MultimediaSerializer,
parser_classes=(FormParser, MultiPartParser,)
)
def upload_avatar(self, request, pk=None, *args, **kwargs):
if 'POST' in request._method or 'PATCH' in request._method:
# Write code to save the file??
else:
multimedia = Multimedia.objects.filter(user_profiles_avatares__pk=pk)
page = self.paginate_queryset(multimedia)
serializer = self.get_pagination_serializer(page)
return Response(serializer.data)
My models:
class Women(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL)
avatar = models.ForeignKey(
'core.Multimedia', blank=True, null=True,
related_name='user_profiles_avatares'
)
class Multimedia(models.Model):
file = models.FileField(upload_to=upload_to, null=True, blank=True)
thumbnail = models.FileField(upload_to=upload_to, null=True, blank=True)
Basically i want to know if this is the right path i am taking, and if yes how can i properly save the uploaded file in the model??
Here is some code of what i did to overcome this problem. Although Kevin Brown answer probably works, i find my code a little "easier" approach:
#detail_route(
methods=['post', 'put', 'patch', 'get'], permission_classes=[AllowAny],
authentication_classes=[NoAuthentication], serializer_class=MultimediaSerializer,
parser_classes=(FormParser, MultiPartParser,)
)
def upload_avatar(self, request, pk=None):
# Because we are using nested resources this was the only way i found to
# upload a file. Maybe there is a better way
if request.method in ['PATCH', 'POST']:
avatar = request.FILES.get('avatar')
if not avatar:
return Response(status=404)
try:
woman = WomenNativePassport.objects.get(pk=pk)
except WomenNativePassport.DoesNotExist:
return Response(status=404)
else:
request.FILES['thumbnail'] = request.FILES['avatar']
serializer = AvatarSerializer(
data=request.DATA, files=request.FILES
)
if serializer.is_valid():
woman.avatar.thumbnail.save(str(avatar), File(avatar))
return Response(status=204)
else:
return Response(status=404)
else:
multimedia = Multimedia.objects.filter(user_profiles_avatares__pk=pk)
page = self.paginate_queryset(multimedia)
serializer = self.get_pagination_serializer(page)
return Response(serializer.data)
# serializer
class AvatarSerializer(serializers.Serializer):
thumbnail = serializers.ImageField()
Any uploaded files should be available in request.FILES, a dictionary keyed by the field that they was used when uploading. Once you have the file, it's a matter of handling it similar to any other uploaded file in Django.
If you can, I would use a second serializer that wraps the Multimedia model so the image validation and saving can be done automatically through Django REST Framework. There is an ImageField that will automatically validate the image by Pillow which you can use on the serializer.