Group base permission rest api in Django - python

i am using Django rest framework for my login api. Now i want to login only certain group user through api. I have created group and assigned user to the group.I have created a permission class and used to APIView but it's show ""Authentication credentials were not provided."
Here is my permissions.py class
from rest_framework.permissions import BasePermission
class FanOnlyPermission(BasePermission):
def has_permission(self, request, view):
if request.user and request.user.groups.filter(name='fan'):
return True
return False
Here is my view.py
from rest_framework import views, permissions, status, generics
class UserLoginApiView(generics.GenericAPIView):
"""
User Login Api View
"""
permission_classes = (FanOnlyPermission, )
def post(self, request):
"""
Handle POST request
:param request:
:return:
"""
serializer = UserLoginSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I have tried through postman. when i print request.user from permission.py it shoed "Anonymous user".

You need to implement custom authentication backend,
See this answer: how to create login authentication based on some condition in django restframework
And then config the AUTHENTICATION_BACKENDS in the settings.py:
AUTHENTICATION_BACKENDS = ['accounts.backends.AuthBackend']

Related

Creating custom Django Rest Framework permision

I have created a django rest custom permision and for testing purposes I made sure has_object_permission and has_permission retrun False.
from rest_framework import permissions
class IsWorker(permissions.BasePermission):
message = "Login with Supplier Account"
def has_object_permission(self, request, view, obj):
return False
def has_permission(self, request, view):
return False
How comes this view doesn't work as expected?
from packages.models import Package
from .serializers import WorkerPackageSerializer
from rest_framework.permissions import IsAuthenticated
from api.permissions import IsWorker
class WorkerPackageDetailViewSet(generics.RetrieveUpdateDestroyAPIView):
serializer_class = WorkerPackageSerializer
permission_classes = (IsAuthenticated, IsWorker)
lookup_field = "id"
def get_queryset(self):
return Package.objects.all()
Expectations:
I thought it would return a forbidden error with the message Login with Supplier Account but if the user is not authenticated it returns a not authenticated error and if the user is authenticated it returns the data without any error.

Token auth - django rest framework

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

Creat REST Api Using Django Rest Framework

I am trying to create a Restful Api for following methods to run jenkins jobs to run on saucelabs. I wanna queue jobs using restful API. I am using Django Restful Framework.
CreateMethod :
Accepts two fileds: ProjectName and URL
and returns a Token ID.
VerifyStatus:
Accepts Token ID and returns three fields. TokenID, running:True/False and
no_of_jobs: integervalue (0 if Not specified)
relseaseMethod:
Accepts release token call and returns true if success.
I am new to Restful API with, I am trying to run Jenkins job on sauce lab and queue them using a restful api on python Djano restframework. Guide me through.
Views.py
class RegisterTestMethodView(APIView):
authentication_classes = [SessionAuthentication, TokenAuthentication, BasicAuthentication]
permission_classes = [IsAuthenticated] #No access (not even read if not authorized)
def post(self, request, format=None):
serializer = RegisterTestMethodSerializers(data=request.data)
if serializer.is_valid():
serializer.save()
return Response({'tokenid':serializer.data['id']}, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class CheckStatusView(APIView):
def get_object(self, pk):
try:
return Jobs.objects.get(pk=pk)
except Jobs.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
snippet = self.get_object(pk)
serializer = RegisterTestMethodSerializers(snippet)
return Response({"tokenid":serializer.data["id"], "Runtestnow" : False, "VMcount" : 0 })
class ReleaseTokenView(APIView):
def get_object(self, pk):
try:
return Jobs.objects.get(pk=pk)
except Jobs.DoesNotExist:
raise Http404
def delete(self, request, pk, format=None):
snippet = self.get_object(pk)
snippet.delete()
return Response(data={'Deleted':True}, status=status.HTTP_204_NO_CONTENT)
Serailizers.py
rom rest_framework import serializers
from .models import Jobs
from random import random
RegisterTestMethodSerializers(serializers.HyperlinkedModelSerializer):
class Meta:
model = Jobs
fields = ('id','name','url')
Models.py
from django.db import models
# Create your models here.
class Jobs(models.Model):
name = models.CharField(max_length=100)
url = models.URLField()
def __str__(self):
return self.name
Urls.py
from django.urls import path, include
from . import views
from .views import (RegisterTestMethodView,
RegisterTestMethodViewDetail,
CheckStatusView,
ReleaseTokenView
)
from rest_framework import routers
from rest_framework.authtoken.views import obtain_auth_token
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
urlpatterns = [
path('', include(router.urls)),
path('registertestmethod/',RegisterTestMethodView.as_view()),
path('registertestmethod/<int:pk>/',
RegisterTestMethodViewDetail.as_view()),
path('checkstatus/<int:pk>', CheckStatusView.as_view()),
path('releasetoken/<int:pk>', ReleaseTokenView.as_view()),
]
I have addded my Code here. I have other classes and function in my project as well.I tried to delete all of those. You might see extra import as a result of that. My code does following things.
POST --> http://localhost:8000/registertestmethod/
{
"name": "Name",
"url": "https://www.google.com"
}
returns
{
"tokenid": 32 #unique token ID return
}
This tokenid should be long I am using id as of now.
GET --> http://localhost:8000/checkstatus/32
is returning
{
"tokenid": 32, #unique tokenid refering to register info
"Runtestnow": false, #if job is running
"VMcount": 0 # number of VM used in sauce lab by the Jobs
}
DELETE --> http://localhost:8000/releasetoken/32 #should delete the jobs after done.
is deleting and returning
{
"Deleted": true
}
I want it to be dynamic and store info in database. Token should return everything on checkstatus.
You can use the ModelViewSet approach for this, this here is a very simple example for an API endpoint.
views.py
from rest_framework.viewsets import ModelViewSet
from api.serializers import SaucelabsSerializer
from rest_framework.response import Response
class SaucelabModelViewSet(ModelViewSet):
serializer_class = SaucelabSerializer
queryset = Sauselab.objects.all()
http_method_names = ['get', 'head', 'options', 'post']
def create(self, request):
pnam = request.data.get('project_name', None)
url = request.data.get('url', None)
if pnam and url:
# do something here
return Response({'success': 'Your success message'}, status=status.HTTP_200_OK)
else:
return Response({"error": "Your error message"}, status=status.HTTP_400_BAD_REQUEST)
serializers.py
from rest_framework.serializer import ModelSerializer
from appname.models import Saucelab
class SaucelabSerializer(ModelSerializer):
class Meta:
model = Saucelab
fields = '__all__'
appname/models.py
from django.db import models
class Saucelab(models.Model)
project_name = models.CharField(max_length=255)
url = models.URLField()
urls.py
from rest_framework.routers import DefaultRouter
from api import views
router = DefaultRouter()
router.register('your-endpoint-name', views.SaucelabModelViewSet, basename='your-url-name')
urlpatterns = []
urlpatterns += router.urls
This is a very basic example, where you create a model called saucelab which has the two fields you require, ie.project_name and url.
We create a app called api with two files inside it that aren't auto generated, serializers.py and urls.py. We create the most basic model serializer and ask it to serialize all fields of model Saucelab. Then we create a simple modelviewset which out of the box gives you a CRUD functionality. You can override the create method if you need to run some specific conditions otherwise dont override any methods and just make request to the endpoint with appropriate HTTP methods.
Here are a few links you can read
https://www.django-rest-framework.org/api-guide/viewsets/#modelviewset
https://www.django-rest-framework.org/api-guide/serializers/#modelserializer
To generate a random token
from django.utils.crypto import get_random_string
print(get_random_string(length=25))
output
u'rRXVe68NO7m3mHoBS488KdHaqQPD6Ofv'

Can we use serializer_class attribute with APIView(django rest framework)?

As per the DRF documentation, the serializer_class attribute should be set when using GenericAPIView. But why does the serializer_class attribute even works with APIView?
Here is my API code:
class UserView(APIView):
serializer_class = SignupSerializer
#transaction.atomic
def post(self, request):
email = get_data_param(request, 'email', None)
password = get_data_param(request, 'password', None)
params = request.POST.copy()
params["username"] = email
serializer = UserSerializer(data=params)
if serializer.is_valid(raise_exception=True):
user = serializer.save()
user.set_password(password)
user.save()
params["user"] = user.id
serializer = CustomProfileSerializer(data=params)
if serializer.is_valid(raise_exception=True):
profile = serializer.save()
return Response(response_json(True, profile.to_dict(), None))
class SignupSerializer(serializers.Serializer):
email = serializers.EmailField(max_length=100)
password = serializers.CharField(max_length=50)
When I browse this API in the browser it does show the email and password fields as input but if I don't set this serializer_class attribute, no input fields are shown.
Ideally, this serializer_class attribute should not work with APIView. I have searched a lot but there is nothing available related to this.
Can anyone please provide an explanation for this behavior? Thanks.
I think this can help you.
create serializer.py and write:
from rest_framework import serializers
from .models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('email', 'password')
and views.py:
from rest_framework.response import Response
from rest_framework.views import APIView
from .serializers import UserSerializer
from .models import User
class AddUserAPIView(APIView):
def post(self, request):
user_serializer = UserSerializer(data=request.data)
if user_serializer.is_valid():
user = user_serializer.save()
user.set_password(user_serializer.data["password"])
return Response({'message': 'user added successfully!'})
return Response({'message': user_serializer.errors})
You are absolutely right!!:
APIView doesn't utilize a serializer_class (by default) because it is not meant to handle any request processing logic!
What happens though is that the BrowsableAPIRenderer that is used to render the API in the browser checks for a serializer_class attribute and set's it as the View serializer if it exists. We can see this in the BrowsableAPIRenderer code:
The _get_serializer class of the renderer:
def _get_serializer(self, serializer_class, view_instance, request, *args, **kwargs):
kwargs['context'] = {
'request': request,
'format': self.format,
'view': view_instance
}
return serializer_class(*args, **kwargs)
And the way it is used to set the renderer serializer if it exists, inside the get_rendered_html_form:
Line 483: has_serializer_class = getattr(view, 'serializer_class', None)
Lines 497 to 509:
if has_serializer:
if method in ('PUT', 'PATCH'):
serializer = view.get_serializer(instance=instance, **kwargs)
else:
serializer = view.get_serializer(**kwargs)
else:
# at this point we must have a serializer_class
if method in ('PUT', 'PATCH'):
serializer = self._get_serializer(view.serializer_class, view,
request, instance=instance, **kwargs)
else:
serializer = self._get_serializer(view.serializer_class, view,
request, **kwargs)
In essence, you accidentally override the BrowserAPIRenderer's default behavior regarding the APIView by providing the serializer_class attribute. For what is worth, my opinion on the matter is that this should not be possible!
I use the django rest framework default get_schema_view() to provide auto-generated openapi schema from which I auto generate a javascript client for.
This works for ViewSets, but the payload wasn't being provided for views defined by APIView.
Where I have defined serializers, I found that adding get_serializer() method to my APIView classes allowed the schema to be generated with the serializer defined payload.
from rest_framework.response import Response
from rest_framework.views import APIView
from .serializers import UserSerializer
from .models import User
class AddUserAPIView(APIView):
def get_serializer(self, *args, **kwargs):
return UserSerializer(*args, **kwargs)
def post(self, request):
user_serializer = UserSerializer(data=request.data)
if user_serializer.is_valid():
user = user_serializer.save()
user.set_password(user_serializer.data["password"])
return Response({'message': 'user added successfully!'})
return Response({'message': user_serializer.errors})

django rest framework error Cannot apply DjangoModelPermissions on a view that does not set `.queryset` or have a `.get_queryset()` method

i am using django 1.9.5 and rest framework 3.x(DRF).I have just following the tutorial from official django rest framework,you can say its getting start wiht DRF,i have write following views , urls to see how api works using DRF,
views
class DepartMentList(APIView):
"""
List of all departments or create a department
"""
def get(self, request, format=None):
departments = Department.objects.all()
serializer = DepartmentSerializer(departments)
return Response(serializer.data)
def post(self, request, format=None):
serializer = DepartmentSerializer(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)
urls
from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from organizations import views
urlpatterns = [
url(r'^departments/$', views.DepartMentList.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)
and this is the setting.py where i have added the following rest framework dict for DEFAULT_PERMISSION_CLASSES
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
]
}
now when i run the endpoint for department to see the list of departments,then i am getting this following error,
'Cannot apply DjangoModelPermissions on a view that '
AssertionError: Cannot apply DjangoModelPermissions on a view that does not set `.queryset` or have a `.get_queryset()` method.
what does actually causes the error? i have investigated,but can't figure it out.
UPDATE
class DepartMentDetail(APIView):
"""
Retrieve, update or delete a department instance.
"""
def get_object(self, pk):
try:
return Department.objects.get(pk=pk)
except Department.DoesNotExist:
raise Http404
def get(self,request,pk,format=None):
department = self.get_object(pk)
serializer = DepartmentSerializer(department)
return Response(serializer.data)
def put(self,request,pk,format=None):
department = self.get_object(pk)
serializer = DepartmentSerializer(department,data=request.data)
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):
department = self.get_object(pk)
department.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
DjangoRestFramework requiries you to set queryset class argument or implement get_queryset method on your view. It checks it when applying permission class. Because DjangoModelPermissionsOrAnonReadOnly has has_permission method as shown below, and this method check if your view has queryset variable or get_queryset method.
def has_permission(self, request, view):
# Workaround to ensure DjangoModelPermissions are not applied
# to the root view when using DefaultRouter.
if getattr(view, '_ignore_model_permissions', False):
return True
if hasattr(view, 'get_queryset'):
queryset = view.get_queryset()
else:
queryset = getattr(view, 'queryset', None)
assert queryset is not None, (
'Cannot apply DjangoModelPermissions on a view that '
'does not set `.queryset` or have a `.get_queryset()` method.'
)
perms = self.get_required_permissions(request.method, queryset.model)
return (
request.user and
(request.user.is_authenticated() or not self.authenticated_users_only) and
request.user.has_perms(perms)
)
as you see has_permission method makes assert for queryset variable
Your view should look like this
class DepartMentList(APIView):
"""
List of all departments or create a department
"""
queryset = Department.objects.all()
def get(self, request, format=None):
serializer = DepartmentSerializer(self.queryset)
return Response(serializer.data)
def post(self, request, format=None):
serializer = DepartmentSerializer(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)
P.S use http://www.django-rest-framework.org/api-guide/generic-views/ it is much cleaner))

Categories