Skip to content

Latest commit

 

History

History
610 lines (444 loc) · 15.6 KB

04-drf_auth.md

File metadata and controls

610 lines (444 loc) · 15.6 KB

PRECLASS SETUP

# CREATING VIRTUAL ENVIRONMENT
# windows
py -m venv env
# windows other option
python -m venv env
# linux / Mac OS
vitualenv env

# ACTIVATING ENVIRONMENT
# windows
.\env\Scripts\activate
# linux / Mac OS
source env/bin/activate

# PACKAGE INSTALLATION
# if pip does not work try pip3 in linux/Mac OS
pip install django
# alternatively python -m pip install django
pip install python-decouple
pip freeze > requirements.txt
django-admin --version
django-admin startproject main .

gitignore

add a gitignore file at same level as env folder, and check that it includes .env and /env lines

Python Decouple

create a new file and name as .env at same level as env folder

copy your SECRET_KEY from settings.py into this .env file. Don't forget to remove quotation marks from SECRET_KEY

SECRET_KEY = django-insecure-)=b-%-w+0_^slb(exmy*mfiaj&wz6_fb4m&s=az-zs!#1^ui7j

go to settings.py, make amendments below

from decouple import config

SECRET_KEY = config('SECRET_KEY')

go to terminal

py manage.py migrate
py manage.py runserver

click the link with CTRL key pressed in the terminal and see django rocket.

INSTALLING DJANGO REST

go to terminal

py manage.py makemigrations
py manage.py migrate
pip install djangorestframework

go to settings.py and add 'rest_framework' app to installed apps

ADDING AN APP

go to terminal

py manage.py startapp student_api

go to settings.py and add 'student_api' app to installed apps

go to student_api.models.py

ADDING MODEL

from django.db import models

class Student(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)
    number = models.IntegerField(blank=True, null=True)

    def __str__(self):
        return f"{self.last_name} {self.first_name}"

go to student_api.admin.py

from django.contrib import admin
from .models import Student

# Register your models here.
admin.site.register(Student)

SERIALIZER FOR STUDENT MODEL

create serializers.py under student_api

from rest_framework import serializers
from .models import Student

class StudentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Student
        # fields = ["id", "first_name", "last_name", "number"]
        fields = '__all__'

SETING UP VIEWS

go to student_api.views.py

from django.shortcuts import HttpResponse
from .models import Student
from .serializers import StudentSerializer
from rest_framework import generics

# Create your views here.
def home(request):
    return HttpResponse('<h1>API Page</h1>')

class StudentList(generics.ListCreateAPIView):
    serializer_class = StudentSerializer
    queryset = Student.objects.all()

class StudentOperations(generics.RetrieveUpdateDestroyAPIView):
    serializer_class = StudentSerializer
    queryset = Student.objects.all()

SETING UP URLS

go to drf.urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('student_api.urls')),
]

go to student_api.urls.py

from django.urls import path
from .views import home, StudentList, StudentOpr

urlpatterns = [
    path('', home),
    path('student/', StudentList.as_view()),
    path('student/<int:pk>/', StudentOpr.as_view(), name="detail"),
]

RUNNING SERVER

py manage.py makemigrations
py manage.py migrate
py .\manage.py createsuperuser
pip freeze > requirements.txt
py .\manage.py runserver

CORS SETUP

pip install django-cors-headers

settings.py

ALLOWED_HOSTS = ['*']
INSTALLED_APPS = [
    # ...
    'corsheaders',
]
MIDDLEWARE = [
    # ...
    'django.contrib.sessions.middleware.SessionMiddleware',
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    # ...
]
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = True
CORS_EXPOSE_HEADERS = (
    'Access-Control-Allow-Origin: *',
)

HANDS-ON PART

Permissions determine whether a request should be granted or denied access.

Authorization checks who sends this request.

Permissions

Setting the permission policy

The default permission policy may be set globally, using the DEFAULT_PERMISSION_CLASSES setting. For example.

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ]
}

You can also set the authentication policy on a per-view, or per-viewset basis, using the APIView class-based views. Example:

from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView

class ExampleView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request, format=None):
        content = {
            'status': 'request was permitted'
        }
        return Response(content)

Available Permissions

AllowAny

The AllowAny permission class will allow unrestricted access, regardless of if the request was authenticated or unauthenticated.

IsAuthenticated

The IsAuthenticated permission class will deny permission to any unauthenticated user, and allow permission otherwise. This permission is suitable if you want your API to only be accessible to registered users.

IsAdminUser

The IsAdminUser permission class will deny permission to any user, unless user.is_staff is True in which case permission will be allowed. This permission is suitable if you want your API to only be accessible to a subset of trusted administrators.

IsAuthenticatedOrReadOnly

The IsAuthenticatedOrReadOnly will allow authenticated users to perform any request. Requests for unauthorised users will only be permitted if the request method is one of the "safe" methods; GET, HEAD or OPTIONS.

DjangoModelPermissions

This permission class ties into Django's standard django.contrib.auth model permissions. This permission must only be applied to views that have a .queryset property or get_queryset() method.

DjangoModelPermissionsOrAnonReadOnly

Similar to DjangoModelPermissions, but also allows unauthenticated users to have read-only access to the API.

DjangoObjectPermissions

This permission class ties into Django's standard object permissions framework that allows per-object permissions on models.

Authentication

Setting the authentication scheme

The default authentication schemes may be set globally, using the DEFAULT_AUTHENTICATION_CLASSES setting. For example.

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.TokenAuthentication',
    ]
}

You can also set the authentication scheme on a per-view or per-viewset basis, using the APIView class-based views. Example

from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView

class ExampleView(APIView):
    authentication_classes = [SessionAuthentication, BasicAuthentication]

TokenAuthentication

This authentication scheme uses a simple token-based HTTP Authentication scheme. Token authentication is appropriate for client-server setups, such as native desktop and mobile clients.

In Basic Authentication you need to send always send username and password, in every request in headers. But this is not safe. To handle this issue token authentication was introduced. When a user logged in an authentication is sent as a response. After that in every request this token will be sent in heades instead of password and username.

These tokens should be stored in backend database. We have options to handle the this sequence. Most used case is to create a token when a user logged in and save it in db and then delete this token whenever user logged out.

To use the TokenAuthentication scheme you'll need to configure the authentication classes to include TokenAuthentication, and additionally include rest_framework.authtoken in your INSTALLED_APPS setting:

go to settings.py and add 'rest_framework.authtoken' into installed apps

INSTALLED_APPS = [
    ...
    'rest_framework.authtoken'
]

go to terminal

py manage.py migrate

navigate to admin dashboard and check Token table

add users, and token to these users

go to postman send Token

Adding an App for Authentication

go to terminal

py manage.py startapp user_api

go to settings.py and add 'user_api' into installed apps

go to models.py

from django.db import models

# Create your models here.
from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
from rest_framework.authtoken.models import Token


@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_auth_token(sender, instance=None, created=False, **kwargs):
    if created:
        Token.objects.create(user=instance)

go to serializers.py

from django.contrib.auth.models import User
from rest_framework import serializers


class RegistrationSerializer(serializers.ModelSerializer):

    class Meta:
        model = User
        fields = ['username', 'email', 'password']

go to main urls.py, and add following line into urlpatterns

urlpatterns = [
    ...
    path('user/', include('user_api.urls')),
]

go to user_api urls.py

from rest_framework.authtoken.views import obtain_auth_token
from django.urls import path
from .views import registration_view, logout_view

urlpatterns = [
    path('login/', obtain_auth_token, name='login'),
    path('register/', registration_view, name='register'),
    path('logout/', logout_view, name='logout'),
]

go to views.py

from rest_framework.decorators import api_view
from rest_framework.response import Response
from .serializers import RegistrationSerializer
from rest_framework.authtoken.models import Token

# Create your views here.

@api_view(['POST'])
def registration_view(request):
    if request.method == 'POST':
        serializer = RegistrationSerializer(data=request.data)
        data = {}
        if serializer.is_valid():
            password = serializer.validated_data.get('password')
            serializer.validated_data['password'] = make_password(password)
            user = serializer.save()
            token, _ = Token.objects.get_or_create(user=user)
            data = serializer.data
            data['token'] = token.key
        else:
            data = serializer.errors
        return Response(data)

@api_view(['POST'])
def logout_view(request):
    if request.method == 'POST':
        request.user.auth_token.delete()
        data = {
            'message': 'logout'
        }
        return Response(data)

Custom Permission

To implement a custom permission, override BasePermission and implement either, or both, of the following methods: .has_permission(self, request, view) .has_object_permission(self, request, view, obj) The methods should return True if the request should be granted access, and False otherwise.

under student_api create permissions.py

from rest_framework import permissions

class IsAdminOrReadOnly(permissions.IsAdminUser):

    def has_permission(self, request, view):
        if request.method in permissions.SAFE_METHODS:
            return True
        else:
            return bool(request.user and request.user.is_staff)

class IsAddedByUserOrReadOnly(permissions.BasePermission):

    def has_object_permission(self, request, view, obj):
        if request.method in permissions.SAFE_METHODS:
            return True
        else:
            return obj.user == request.user or request.user.is_staff

go to student_api models.py and make following amendments

from django.db import models
from django.contrib.auth.models import User

class Student(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)
    number = models.IntegerField(blank=True, null=True)
    user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True)

    def __str__(self):
        return f"{self.last_name} {self.first_name}"

Here we added a new field as user which has one to many relation with User table. When we do migrations it will ask what do with current students in our db as they don't have any user currently. When asked you can type 1 and then press enter.

go to terminal

py manage.py makemigrations
py manage.py migrate

go to student_api views.py

from django.shortcuts import HttpResponse
from .models import Student
from .serializers import StudentSerializer
from rest_framework import generics

from rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnly
# from rest_framework.authentication import SessionAuthentication, BasicAuthentication, TokenAuthentication
from .permissions import IsAdminOrReadOnly, IsAddedByUserOrReadOnly
# from rest_framework.response import Response
# from rest_framework import status
# Create your views here.
def home(request):
    return HttpResponse('<h1>API Page</h1>')

class StudentList(generics.ListCreateAPIView):
    serializer_class = StudentSerializer
    queryset = Student.objects.all()
    permission_classes = [IsAdminOrReadOnly]
    # authentication_classes = [BasicAuthentication]
    def perform_create(self, serializer):
        serializer.save(user=self.request.user)

    # def create(self, request, *args, **kwargs):
    #     serializer = self.get_serializer(data=request.data)
    #     if not serializer.is_valid(raise_exception=False):
    #         return Response({"message": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
    #     serializer.validated_data['user'] = request.user
    #     self.perform_create(serializer)
    #     headers = self.get_success_headers(serializer.data)
    #     return Response({"message": "created successfully"}, status=status.HTTP_201_CREATED, headers=headers)

class StudentOperations(generics.RetrieveUpdateDestroyAPIView):
    serializer_class = StudentSerializer
    queryset = Student.objects.all()
    # permission_classes = [IsAuthenticatedOrReadOnly]
    # permission_classes = [IsAuthenticated]
    permission_classes = [IsAddedByUserOrReadOnly]

BONUS PART

go to terminal

pip install dj-rest-auth

Add dj_rest_auth app to INSTALLED_APPS in your django settings.py:

INSTALLED_APPS = (
    ...,
    'rest_framework',
    'rest_framework.authtoken',
    ...,
    'dj_rest_auth'
)

Add dj_rest_auth main urls:

urlpatterns = [
    ...,
    path('auth/', include('dj_rest_auth.urls'))
]

Migrate your database

python manage.py migrate

Registration (optional)

If you want to enable standard registration process you will need to install django-allauth by using pip install 'dj-rest-auth[with_social]'.

Add django.contrib.sites, allauth, allauth.account, allauth.socialaccount and dj_rest_auth.registration apps to INSTALLED_APPS in your django settings.py:

Add SITE_ID = 1 to your django settings.py

INSTALLED_APPS = (
    ...,
    'django.contrib.sites',
    'allauth',
    'allauth.account',
    'allauth.socialaccount',
    'dj_rest_auth.registration',
)

SITE_ID = 1

Add dj_rest_auth.registration urls:

urlpatterns = [
    ...,
    path('auth/registration/', include('dj_rest_auth.registration.urls'))
]

Migrate your database

python manage.py migrate