Django REST framework: restricting user access for objects - django

I'm trying to build a REST API for books:
/api/book
/api/book/{book_id}
A user should have access to his books only. The way I'm doing this now is by filtering the result using username i.e Book.objects.all().filter(owner=request.user)
views.py
class Book_List(APIView):
permission_classes=(permissions.IsAuthenticated)
def get(self, request, format=None):
**books= Book.objects.all().filter(owner=request.user)**
serializer = BookSerializer(books, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = BookSerializer(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)
#/book/{pk}
class Book_Detail(APIView):
permission_classes = (permissions.IsAuthenticated)
def get_object(self, pk, request):
try:
return Book.objects.get(pk=pk, owner=request.user)
except Playlist.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
book = self.get_object(pk, request)
serializer = BookSerializer(playlist, context={'request': request})
return Response(serializer.data)
def put(self, request, pk, format=None):
book= self.get_object(pk)
serializer = BookSerializer(playlist, data=request.data, context={'request': request})
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
book= self.get_object(pk)
book.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
serializers.py
class BookSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Book
fields = ('url', 'owner','title', 'created_date', 'shared_with', 'tracks')
class UserSerializer(serializers.HyperlinkedModelSerializer):
books = serializers.HyperlinkedRelatedField(many=True,view_name='playlist-detail', read_only=True)
owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
model = User
fields = ('url', 'username', 'owner', 'books')
But is this the correct way?
Does Django Rest Framework provide any in-built solution for this?
Does the solution lie in permissions? If yes, then how do we set it for all objects created by a user (I understand the for getting a particular object we can put a permission check like obj.user==request.user). Am I right?

You could use the ModelViewset, which contains all the logic for the typical CRUD:
class BooksViewSet(ModelViewset):
serializer_class = BookSerializer
permission_classes=[permissions.IsAuthenticated, ]
def get_queryset(self):
return Books.objects.filter(owner=self.request.user)
def perform_create(self, serializer):
serializer.data.owner = self.request.user
super(BooksViewSet, self).perform_create(serializer)

Related

DRF: Creating mixin using different serializers & objects

I have two APIView classes with almost similar behaviour (and code). Different are only serializers and models object (Favorite & FavoriteSerializer for first class and PurchaseList and PurchaseListSerializer for second). As far as I understand it is a perfect usecase for mixin, according to DRF docs ([DRF: Creating custom mixins] 1https://www.django-rest-framework.org/api-guide/generic-views/).
Unfortunately, I'm struggling with how to define mixin without the definition of serializer & obj in it.
class FavoriteViewSet(APIView):
def get(self, request, recipe_id):
user = request.user.id
data = {"user": user, "recipe": recipe_id}
serializer = FavoriteSerializer(
data=data,
context={"request": request},
)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status.HTTP_201_CREATED)
def delete(self, request, recipe_id):
user = request.user
favorite_recipe = get_object_or_404(
Favorite, user=user, recipe__id=recipe_id
)
favorite_recipe.delete()
return Response(
"Рецепт удален из избранного", status.HTTP_204_NO_CONTENT
)
class PurchaseListView(APIView):
def get(self, request, recipe_id):
user = request.user.id
data = {"user": user, "recipe": recipe_id}
serializer = PurchaseListSerializer(
data=data, context={"request": request}
)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status.HTTP_201_CREATED)
def delete(self, request, recipe_id):
user = request.user
purchase_list_recipe = get_object_or_404(
PurchaseList, user=user, recipe__id=recipe_id
)
purchase_list_recipe.delete()
return Response(
"Рецепт удален из списка покупок", status.HTTP_204_NO_CONTENT
)
You can create your own mixin like this:
class CustomMixin:
serializer_class = None
model_class = None
def get(self, request, recipe_id):
user = request.user.id
data = {"user": user, "recipe": recipe_id}
serializer = self.serializer_class(
data=data,
context={"request": request},
)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status.HTTP_201_CREATED)
def delete(self, request, recipe_id):
user = request.user
obj = get_object_or_404(
self.model_class, user=user, recipe__id=recipe_id
)
obj.delete()
return Response(
"Рецепт удален из избранного", status.HTTP_204_NO_CONTENT
)
class FavoriteViewSet(CustomMixin, APIView):
serializer_class = FavoriteSerializer
model_class = Favorite
class PurchaseListView(CustomMixin, APIView):
serializer_class = PurchaseListSerializer
model_class = PurchaseList
Or use the GenericApiView with the bulti-in mixins
https://www.django-rest-framework.org/api-guide/generic-views/#genericapiview

Django Rest FrameWork Add Model With User Foreign Key

I'm using Django version 3 and the Rest Framework, i have a model with a user foreignkey.
so every time a model is saved the user also need to be saved.
To be able to add or to edit a model via rest you need to be authenticated using token authentication.
I'm using ClassBasedViews, the problem is that i can't find a way to add a model because in my serializer the field user is excluded because i don't want it to be editable.
models.py:
class Chambre(models.Model):
local_id=models.PositiveIntegerField(unique=True)
nom=models.CharField(max_length=255)
user=models.ForeignKey(User,on_delete=models.CASCADE,blank='true')
class Meta:
unique_together = ('local_id', 'user',)
serializers.py:
class ChambreSerializer(serializers.ModelSerializer):
class Meta:
model = Chambre
exclude =['user',]
views.py:
class ChambreListApi(APIView):
"""
List all chambres, or create a new chambre.
"""
authentication_classes=(TokenAuthentication,)
permission_classes=(IsAuthenticated,)
def get(self, request, format=None):
chambres = Chambre.objects.filter(user=request.user)
serializer = ChambreSerializer(chambres, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = ChambreSerializer(data=request.data)
if serializer.is_valid():
serializer.save(commit=False)
serializer.user=request.user
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Try moving the logic that sets the user to the serializer. By default GenericAPIView will pass the request to the serializer via the context.
class ChambreSerializer(serializers.ModelSerializer):
class Meta:
model = Chambre
exclude =['user',]
def create(self, validated_data):
validated_data["user"] = self.context["request"].user
return super().create(validated_data)
When I followed the accepted solution posted by schillingt, I had to add context={'request': request} when I instantiated the serializer. The Django REST Framework docs mention adding context to a serializer here.
serializers.py
class NoteCreateSerializer(serializer.ModelSerializer):
class Meta:
model = Note
exclude = ['owner',]
def create(self, validated_data):
validated_data['owner'] = self.context['request'].user
return super().create(validated_data)
views.py
#api_view['POST']
def create_note(request):
note_serializer = NoteCreateSerializer(
data = request.data,
context = {'request': request} # this will populate 'self.context'
# in the serializer instance
)
# ...
Thanks so much for this solution, though! It ended an hours-long struggle.
thanks for the reply that was very useful, i've solved it by adding method to serializer.py like this :
class ChambreSerializer(serializers.ModelSerializer):
class Meta:
model = Chambre
exclude =['user',]
def set_the_user(self,request):
self.user=request.user
def create(self, validated_data):
validated_data["user"] = self.user
return super().create(validated_data)
and in the views.py :
def post(self, request, format=None):
serializer = ChambreSerializer(data=request.data)
serializer.set_the_user(request)
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)

How to Specify the structure of the payload and Specify headers in every request Django URLS

Below are my urls.py I need to Specify the structure of the payload and Specify headers in every request
from django.conf.urls import url, include
from rest_framework.routers import DefaultRouter
from . import views
router = DefaultRouter()
router.register("patient", views.PatientsApiView)
urlpatterns = [
url(r'', include(router.urls))
]
Below are my views.py
I have made 1 view with model viewset and the other with the generic API view
class PatientsApiView(viewsets.ModelViewSet):
model = Patient
fields = ("id", "first_name", "last_name", "phone", "email", "created_at")
"""
Create a serializer in serializer.py
class EmbryoSerializer(serializers.ModelSerializer):
class Meta:
model = Embryo
fields = ("id", "name", "analysis_result", "created_at", "patient")
"""
class EmbryoApiView(APIView):
def get(self, request, format=None):
embryo = Embryo.objects.all()
serializer = EmbryoSerializer(embryo, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = EmbryoSerializer(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 put(self, request, pk, format=None):
embryo = self.get_object(pk)
serializer = EmbryoSerializer(embryo, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def patch(self, request, user_id):
user = User.objects.get(id=user_id)
serializer = EmbryoSerializer(embryo, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
router.register("embryo", views.EmbroApiView)
How do you think I can achieve this in my views.py
Specifying the structure of the payload and headers for every request is best done in views.py and serializers.py
Payload will be validated in serializers . Headers you can simply pass to Response().
For instance
return Response( … , headers={'My-Header': 'hello'}) will add the header.
example:
class EmbryoApiView(APIView):
def get(self, request, format=None):
embryo = Embryo.objects.all()
serializer = EmbryoSerializer(embryo, many=True)
return Response(serializer.data, headers={'My-Header': 'Below are all the embroys for this patient'})

DjangoRestFramework - How to pass a serializer form to the frontend?

I'm using the DjangoRestFramework. I have a UserSerialzer in my serializers.py file:
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'password', 'email', )
This is my urls.py file:
urlpatterns = [
url(r'^$', views.HomePageView.as_view()),
url(r'^users$', views.user_list.as_view()),
url(r'^users/(?P<pk>[0-9]+)$', views.user_detail.as_view()),
]
and this is my views.py file:
class HomePageView(TemplateView):
template_name = "home.html"
def get_context_data(self, **kwargs):
context = super(HomePageView, self).get_context_data(**kwargs)
# context['users'] = User.objects.all()
return context
class user_list(APIView):
"""
List all users, or create a new user.
"""
serializer_class = UserSerializer
def get(self, request):
users = User.objects.all()
serializer = UserSerializer(users, many=True)
return Response(serializer.data)
def post(self, request):
serializer = UserSerializer(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)
class user_detail(APIView):
"""
Get, update or delete a specific user.
"""
serializer_class = UserSerializer
def get_object(self, pk):
try:
return User.objects.get(pk=pk)
except User.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
def get(self, request, pk):
user = self.get_object(pk)
serializer = UserSerializer(user)
return Response(serializer.data)
def put(self, request, pk):
user = self.get_object(pk)
serializer = UserSerializer(user, data=request.DATA)
if serialzier.is_valid():
serializier.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
user = self.get_object(pk)
user.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
When I go to 127.0.0.1:8000/users, DjangoRestFramework has the API page which shows a list of users (JSON objects) and it also has a form which has "username", "password" and "email". This form seems to be validating correctly (checks if the email is a real email, and checks if username is unique and less than 30 characters). Is there a way for me to pass this form to the frontend when a user goes to 127.0.0.1:8000 (calling HomePageView)?
I'm in the process of using AngularJS on the frontend (not sure if this information helps or not).
Well here are a few things I think we might need to point out. Django forms are normally what you would use to create a new user, with a post request just like you are trying to do through DRF. Now you can do this through DRF, but thats not really what it is for, django forms would be more appropriate for what you are doing. Unless of course you are building an API that you want API users to be able to use to create new users on your platform, in which case continue onward. I am linking a tutorial I used when I first started using DRF with Angular, maybe you will find it helpful.
http://blog.kevinastone.com/getting-started-with-django-rest-framework-and-angularjs.html

django-rest-framework return created object but with fewer fields

When creating and object in POST method how do you return only a few fields of created object? This is taken from the documentation:
def post(self, request, format=None):
serializer = SnippetSerializer(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)
Here serializer.data includes all of the fields defined in serializer but I only want to return just a few of them.
Well I see multiple possibilites here:
First (my favorite), is send request object to serializer, then serializer will dynamically select your wanted fields:
def post(self, request, format=None):
serializer = SnippetSerializer(data=request.data,context={'request': request})
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)
Then inside serializer:
class SnippetSerializer(serializers.IDontKnow):
class Meta:
model = YourModel
fields = ('url', 'field1','field2','field3')
write_only_fields = ()
def __init__(self, *args, **kwargs):
super(SnippetSerializer, self).__init__(*args, **kwargs)
if self.context != {}:
request = self.context['request']
if request.method == 'POST':
self.write_only_fields = {'field1':self.fields['field1'], 'field3':self.fields['field3']}
This should make field1 and field3 only writeable, so they won't be returned.
Second, maybe easier solution is to define other serializer, unique for the post method:
def post(self, request, format=None):
serializer = PostSnippetSerializer(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)
you can specify which field you dont wont to be retuned:
class PostSnippetSerializer(serializers.IDontKnow):
class Meta:
model = YourModel
fields = ('url', 'field1','field2','field3')
write_only_fields = ('field1','field3')
field1 and field 3 wont be returned in response,
Third way, is to directly create your response in your view:
def post(self, request, format=None):
serializer = SnippetSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
results = {'url': serializer.data['url'],'field2':serializer.data['field2']}
return Response(results, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
This will return only url and field2, so field1 and field3 will be saved but not returned.
For anyone else having the same question, this is what Tom Christie has said:
Either:
Consider using write_only=True on the fields you don't want as output.
Use a different serializer for returning the response to the one you use for validation.
Just return the response data directly, without using a serializer.