I am new in Django and I have managed to build a small API using DRF. I have my angular.js client end posting user auth details and DRF returns a token which looks like this:
{ 'token' : '9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b' }
Based on the tutorial, I am supposed to retrieve the details from request.user
But I don't know where to do this. I find it confusing since it doesn't give a good example. Anyone with an idea on how go around it? Your input is highly appreciated.
Below is the code of my view and serializer.
from serializers import ExampleSerializer
from models import Example
from rest_framework import viewsets
class ExampleViewSet(viewsets.ModelViewSet):
"""
Example api description
"""
queryset = Example.objects.all()
serializer_class = ExampleSerializer
Serializer
from models import Example
from rest_framework import serializers
class ExampleSerializer(serializers.ModelSerializer):
class Meta:
model = Example
fields = ('id', 'field_one', 'field_two', 'created_at', 'updated_at')
depth = 1
Keeping in mind that I am also new to Angular and DRF...
If you are already receiving the token, then on the angularjs side, you need to be including the token in the headers of your subsequent requests. Perhaps like this abbreviated code from the authentication request:
$http({auth request code here}).then(function(response){
var token = response.headers().token
$http.defaults.headers.common['Authorization'] = 'Token ' + token;
});
In your ViewSet you would likely want
authentication_classes = (TokenAuthentication,)
along with whatever permission_classes are relevant.
If you are including the Token in the Angular http request, then I believe you can reference the user with request.user, like perhaps
def list(self, request):
queryset = SomeObject.objects.filter(owner=request.user)
Or, here is another use (User model is django.contrib.auth.models.User):
class UserView(RetrieveAPIView):
model = User
serializer_class = UserSerializer
def retrieve(self, request, pk=None):
"""
If provided 'pk' is "me" then return the current user.
"""
if request.user and pk == 'me':
return Response(UserSerializer(request.user).data)
return super(UserView, self).retrieve(request, pk)
In my case, I am trying to test my API with an API REST Client. When I put the Header in the configuration, it works.
Authorization: Token <<token>>
Related
I am using react components and to test my code I wrote a quick custom authentication class and defined in the settings for the rest_framework as follow:
DEFAULT_AUTHENTICATION_CLASSES += ['restaurant.rest_api.dev.DevAuthentication']
This works great and uses the user I tell it to
class DevAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
user = User.objects.get(id=6)
return (user,None)
However, I am now trying to use ListAPIView from generics and the user that prints out is an anonymous user which means that it is not using my authentication class. I found that classes can be explicitly defined inside the function and so I did
class SearchUsersAPIView(generics.ListAPIView):
search_fields = ['email', 'first_name', 'last_name', 'phone_number']
filter_backends = (filters.SearchFilter,)
queryset = CustomUser.objects.all()
authentication_classes = [restaurant.rest_api.dev.DevAuthentication]
serializer_class = userSerializer
#permission_classes([IsAuthenticated])
def dispatch(self,request, *args, **kwargs):
self.request = request
print(self.request.user)
if request.user.is_anonymous or request.user.is_user_superuser()==False:
response = Response({"message":"Action Denied"}, status = status.HTTP_400_BAD_REQUEST)
response.accepted_renderer = JSONRenderer()
response.accepted_media_type = "application/json"
response.renderer_context = {}
return response
return super().dispatch(request,*args, **kwargs)
I must add that if I write a simple function view with the api_view decorator the user authenticated is the one that I have specified. I wonder why this is not working only for this class and uses its defaults which are basic and session authentication. Is there a way to achieve what I want?
I added a view right above and it seems to work fine:
#api_view(['GET'])
def search_users_view(request):
print(request.user)
return Response({"message":"Action Denied"}, status = status.HTTP_200_OK)
system shows the anonymous user when it fails to check the authentication of users per view, so you can try adding
permission_classes=[IsAuthenticated]
I don't know how to return token if user is present in my database.
I have User model with login and password fields and I have created some users from dajngo admin site. In Urls i have registered slug:
path('api-token/', AuthToken, name = 'api-token')
Auth token class looks like that ( this is exapmple from rest-framework documentation).
class AuthToken(ObtainAuthToken):
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data,
context={'request': request})
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
return Response({
'token': token.key,
})
I want to check if user exist in mu sql-lite database and if so, return the token. Unfortunattly I don't understand this code. Can somebody explain me what is it doing and how can I change it to meet my requirements.
Another issue is that I have User view which returns users from my db
class UserView(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
It is reqistered this way
router = routers.DefaultRouter()
router.register('users', views.UserView)
urlpatterns = [
path('',include(router.urls)),
]
Is this going to work? I'm not sure because there is no checking if somebody pass a token and if token is correct
If you don't use custom token authentication, then you should use build in auth.
https://www.django-rest-framework.org/api-guide/authentication/#by-exposing-an-api-endpoint
Just use the view provided by DRF
from rest_framework.authtoken import views
urlpatterns += [
url(r'^api-token-auth/', views.obtain_auth_token)
]
Regarding authorization on your UserView, see: https://www.django-rest-framework.org/api-guide/authentication/#setting-the-authentication-scheme
you can just add
permission_classes = [IsAuthenticated]
It allows only authenticated users to use that endpoint
I have two simple models
class User(AbstractUser):
pass
class Vacation(Model):
id = models.AutoField(primary_key=True)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
I am not really sure what is the scalable way of doing user permissions for Django Rest Framework. In particular:
Users should only be able to see their own vacations
On the /vacation endpoint, user would see a filtered list
On the /vacation/$id endpoint, user would get a 403 if not owner
Users should only be able to Create/Update vacations as long as they are the owners of that object (through Foreign Key)
What is the best way to achieve this in a future-proof fashion. Say if further down the line:
I add a different user type, which can view all vacations, but can only create/update/delete their own
I add another model, where users can read, but cannot write
Thank you!
From the docs:
Permissions in REST framework are always defined as a list of permission classes. Before running the main body of the view each permission in the list is checked. If any permission check fails an exceptions.PermissionDenied or exceptions.NotAuthenticated exception will be raised, and the main body of the view will not run.
REST framework permissions also support object-level permissioning. Object level permissions are used to determine if a user should be allowed to act on a particular object, which will typically be a model instance.
For your current need you can define your own Permission class:
class IsVacationOwner(permissions.BasePermission):
# for view permission
def has_permission(self, request, view):
return request.user and request.user.is_authenticated
# for object level permissions
def has_object_permission(self, request, view, vacation_obj):
return vacation_obj.owner.id == request.user.id
And add this permission to your view. For example on a viewset:
class VacationViewSet(viewsets.ModelViewSet):
permission_classes = (IsVacationOwner,)
One thing is important to notice here, since you will respond with a filtered list for '/vacations', make sure you filter them using the request.user. Because object level permission will not be applicable for lists.
For performance reasons the generic views will not automatically apply object level permissions to each instance in a queryset when returning a list of objects.
For your future requirement, you can always set the permissions conditionally with the help of get_permissions method.
class VacationViewSet(viewsets.ModelViewSet):
def get_permissions(self):
if self.action == 'list':
# vacations can be seen by anyone
# remember to remove the filter for list though
permission_classes = [IsAuthenticated]
# or maybe that special type of user you mentioned
# write a `IsSpecialUser` permission class first btw
permission_classes = [IsSpecialUser]
else:
permission_classes = [IsVacationOwner]
return [permission() for permission in permission_classes]
DRF has great documentation. I hope this helps you to get started and helps you to approach different use cases according to your future needs.
I would suggest you to use drf-viewsets link. We are going to use vacation viewset to do this work.
our urls.py
from your_app.views import VacationViewSet
router.register('api/vacations/', VacationViewSet)
our serializers.py
from rest_framework import serializers
from your_app.models import Vacation
class VacationSerializer(serializers.ModelSerializer):
class Meta:
model = Vacation
fields = ('id', 'owner',)
read_only_fields = ('id',)
our views.py
Here we are going to overwrite viewset's retrive and list method. There are other possible way to do that but i like this most as i can able to see what is happening in code. Django model viewset inherited link of drf-mixins retrive and list method.
from rest_framework import viewsets, permissions, exceptions, status
from your_app.models import Vacation, User
from your_app.serializers import VacationSerializer
class VacationViewSet(viewsets.ModelViewSet):
queryset = Vacation.objects.all()
permission_classes = [IsAuthenticated]
serializer = VacationSerializer
# we are going to overwrite list and retrive
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
# now we are going to filter on user
queryset = queryset.filter(owner=self.request.user)
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
# not permitted check
if instance.owner is not self.request.user:
raise exceptions.PermissionDenied()
serializer = self.get_serializer(instance)
return Response(serializer.data)
Django rest framework provides in-build settings for this
Just import the required permission and add it to you class variable permission_classes
in my_name.api.views
from rest_framework.permissions import ( AllowAny, IsAuthenticated, IsAdminUser, IsAuthenticatedOrReadOnly,)
class Vacation(ListAPIView):
serializer_class = VacationListSerializer
permission_classes = [IsAuthenticated]
You can add multiple permission classes as a list
Furthur, in case this is not helpful, you can always filter the model objects as
Mymodel.objects.filter(owner = self.request.user)
I'm new to DRF, and I'm trying to build a webhook that gives out lists of model objects and also allows these objects to be updated. I followed this tutorial http://www.django-rest-framework.org/tutorial/quickstart/, and have the following serializer and view:
class Task(serializers.ModelSerializer):
class Meta:
model = Task
fields = ('user', 'task', 'unixTime')
View:
class RequestViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows reqests to be viewed or edited.
"""
queryset = Task.objects.filter(done = False).order_by('-unixTime')
serializer_class = Task
paginate_by = None
def list(self, request, *args, **kwargs):
self.object_list = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(self.object_list, many=True)
return Response({'results': serializer.data})
I'm pretty sure I have to include a def update under def list, but the online resources I found were a bit unclear on how to implement them and what they do. Any help is appreciated.
#hackerman, Hmm..., if you followed the next step,
http://www.django-rest-framework.org/tutorial/quickstart/#urls
You will get an api address, it may looks like http://localhost:8000/task/1/, assume here is a task obj (id=1) in your db. Please open it in your browser and check that api works or not.
And then, you need a http client (requests is a good choice) to create a PUT request with json string data.
Hope those can help.
May be you just need to rename the serializer.
class TaskSerializer(serializers.ModelSerializer):
And don't forget replace in the viewset
serializer_class = TaskSerializer
After it you can remove your list method, because it is standard.
Suppose I have a serializeDeviceGroup and a APIView class for posting devices into the group.
The serializer for DeviceGroup is
class DeviceGroupSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(source='token', format='hex', read_only=True)
class Meta:
model = DeviceGroup
fields = ['id','name']
class DevicesGroupsAPIView(APIView):
permission_classes = (permissions.IsAuthenticated,)
def post(self, request, token=None, format=None):
print('reqquest', request.data)
print('token', token)
device_group_instance = DeviceGroup.objects.get(token=token)
for device_token in request.data['devices']:
device = Device.objects.get(token=device_token, owner=request.user)
device.group = device_group_instance
device.save()
In above post function, is it compulsory to create a instance of serializer and check if serializer is valid then return the response.
The relation between Device and DeviceGroup is a device can be on only one group and a group can have multiple devices(list of device ids)
How should the post function be if i need to use DeviceGroupSerializer to post the list of devices? I did not understand this serializer and view part clearly.
Django REST framework is loosely coupled so you can bypass serializers.
However, depending on what you are doing, this may requires some work. Note that for POST you may perform some checks by yourself instead.
Tom Christies post on Django REST framework performances optimization illustrate how you can remove parts of the framework.