18
loading...
This website collects cookies to deliver better user experience
$ django-admin startproject project_name
Using virtualenv:
$ cd project_name
$ virtualenv ENV_NAME
$ source ENV_NAME/bin/activate
Using Python venv:
$ cd project_name
$ python3 -m venv
$ source ENV_NAME/bin/activate
Using pipenv:
$ pipenv install -r requirements.txt # (Python 2)
$ pipenv3 install -r requirements.txt # (Python 3)
Using venv:
$ pip install -r requirements.txt # (Python 2)
$ pip3 install -r requirements.txt # (Python 3)
$ python manage.py startapp app_name
$ python manage.py migrate
$ python manage.py runserver
project_name/settings.py
, add the following line of code under INSTALLED_APPS
:INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app_name.apps.App_name',
]
TEMPLATES
(project_name/settings.py
)TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': ['templates/'], # HERE
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
project_name/settings.py
)STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
project_name/urls.py
)from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('INSERT_URL', include('APP_NAME.urls')),
]
urls.py
for the app (app_name/urls.py
).$ pip install djangorestframework
INSTALLED_APPS
as follows. Then, remigrate using the command python manage.py migrate
.INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
# To include support for DEFAULT_AUTHENTICATION_CLASSES, must include this and migrate!
'rest_framework.authtoken',
'app_name.apps.AppName',
]
REST_FRAMEWORK
settings as follows. Descriptions of the settings as included for your quick reference.# ...
# Disable the Browsable HTML API
DEFAULT_RENDERER_CLASSES = (
'rest_framework.renderers.JSONRenderer',
)
# Only enable the browseable HTML API in dev (DEBUG=True)
if DEBUG:
DEFAULT_RENDERER_CLASSES = DEFAULT_RENDERER_CLASSES + (
'rest_framework.renderers.BrowsableAPIRenderer',
)
REST_FRAMEWORK = {
# Disable the Browsable HTML API UI when in production (DEBUG=False)
'DEFAULT_RENDERER_CLASSES': DEFAULT_RENDERER_CLASSES,
# Pagination allows you to control how many objects per page are returned
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 5,
# The default permission policy may be set globally, using the DEFAULT_PERMISSION_CLASSES setting
# To be explained in detail later in the guide
# 'DEFAULT_PERMISSION_CLASSES': [
# 'rest_framework.permissions.AllowAny',
# 'rest_framework.permissions.IsAuthenticated',
# 'rest_framework.permissions.IsAuthenticatedOrReadOnly',
# 'rest_framework.permissions.DjangoModelPermissions',
# 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly',
# 'rest_framework.permissions.DjangoObjectPermissions',
# 'rest_framework.permissions.TokenHasReadWriteScope',
# ],
# A list or tuple of authentication classes, that determines the default set of authenticators used when accessing the request.user or request.auth properties.
# The default authentication schemes may be set globally, using the DEFAULT_AUTHENTICATION_CLASSES setting
# To be explained in detail later in the guide
'DEFAULT_AUTHENTICATION_CLASSES': (
# 'rest_framework.authentication.BasicAuthentication',
# 'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
),
# ...
DEFAULT_RENDERER_CLASSES
value from 'rest_framework.renderers.BrowsableAPIRenderer'
to 'rest_framework.renderers.JSONRenderer'
depending on which environment the web app is running at.app_name/models.py
and create your model similar to the following:from django.db import models
class Todo(models.Model):
title = models.CharField(max_length=120)
description = models.TextField()
completed = models.BooleanField(default=False)
def __str__(self):
return self.title + " (" + str(self.id) + ")"
serializers.py
inside app_name
:$ touch serializers.py
from rest_framework import serializers
from django.contrib.auth.models import User, Group
from .models import *
class TodoSerializer(serializers.ModelSerializer):
class Meta:
model = Todo
# fields = ('id', 'title', 'description', 'completed')
# Shortcut for getting all fields
fields = '__all__'
APIView
ModelViewSet
Generics
@api_view
decorator)@api_view
decorator for working with function based views. The wrapper providse a few bits of functionality such as making sure you receive Request instances in your view, and adding context to Response objects so that content negotiation can be performed.from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .models import *
from .serializers import *
# FBV for list of all Todo objects
@api_view(['GET', 'POST'])
def todo_list(request):
"""
List all code todos, or create a new todo.
"""
if request.method == 'GET':
todos = Todo.objects.all()
serializer = TodoSerializer(todos, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = TodoSerializer(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)
@api_view(['GET', 'PUT', 'DELETE'])
def todo_detail(request, pk):
"""
Retrieve, update or delete a code todo.
"""
try:
todo = Todo.objects.get(pk=pk)
except todo.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
if request.method == 'GET':
serializer = TodoSerializer(todo)
return Response(serializer.data)
elif request.method == 'PUT':
serializer = TodoSerializer(todo, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == 'DELETE':
todo.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
APIView
)APIView
class is the wrapper used for working with class-based views.from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .models import *
from .serializers import *
class TodoList(APIView):
"""
List all todos, or create a new todo.
"""
def get(self, request, format=None):
todos = Todo.objects.all()
serializer = TodoSerializer(todos, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = TodoSerializer(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 TodoDetail(APIView):
"""
Retrieve, update or delete a todo instance.
"""
def get_object(self, pk):
try:
return Todo.objects.get(pk=pk)
except Todo.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
todo = self.get_object(pk)
serializer = TodoSerializer(todo)
return Response(serializer.data)
def put(self, request, pk, format=None):
todo = self.get_object(pk)
serializer = TodoSerializer(todo, 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):
todo = self.get_object(pk)
todo.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
ModelViewSets
)ModelViewSet
inherits from GenericAPIView
and includes implementations for various actions. In other words, you don't need implement basic actions as list, retrieve, create, update or destroy.APIView
, APIView
allow us to define functions that match standard HTTP methods like GET, POST, PUT, PATCH, etc. Meanwhile, because CRUD is so common, DRF provides ModelViewSet
which allow us to define functions that match to common API object actions like LIST, CREATE, RETRIEVE, UPDATE, etc.GenericViewSet
(the 4th type), to create CRUD, GenericViewSet
needs two classes(ListCreateAPIView
and RetrieveUpdateDestroyAPIView
). ModelViewSet
needs only one class(ModelViewSet
)ModelViewSet
and other types of views is that ModelViewSet
support creating url pattern automatically with DRF router while others require you to configure the API endpoint manually yourself in urls.py
.ModelViewSet
views look like:from .models import *
from .serializers import *
from django.shortcuts import render
from django.contrib.auth.models import User, Group
from rest_framework import permissions, viewsets
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = User.objects.all().order_by('-date_joined')
serializer_class = UserSerializer
class GroupViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
queryset = Group.objects.all()
serializer_class = GroupSerializer
GenericViewSet
)views.py
module even more.GenericViewGenericsSet
inherits from GenericAPIView
but does not provide any implementations of basic actions. Just only get_object
, get_queryset
.Generics
needs two classes(ListCreateAPIView
and RetrieveUpdateDestroyAPIView
).GenericViewSet
for our Todo app API:from .models import *
from .serializers import *
from django.shortcuts import render
from rest_framework.response import Response
from rest_framework import generics, permissions, status
class TodoList(generics.ListCreateAPIView):
queryset = Todo.objects.all()
serializer_class = TodoSerializer
class TodoDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Todo.objects.all()
serializer_class = TodoSerializer
urls.py
inside our app. All we have to do next is to link the views we configured just now inside the file.# todo/urls.py
from . import views
from rest_framework import routers
from django.urls import path, include
# Rename views to avoid conflict with app views
from rest_framework.authtoken import views as rest_views
"""
When using viewsets instead of views, we can automatically generate the URL conf for our API, by simply registering the viewsets with a router class.
If we need more control over the API URLs we can simply drop down to using regular class-based views (APIViews), and writing the URL conf explicitly.
"""
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)
# Rename views to avoid conflict with app views
from rest_framework.authtoken import views as rest_views
urlpatterns = [
# URLs for class-based views (Generics, APIViews)
# http://localhost:8000/todo/
# http://localhost:8000/todo/<int:pk>
path('todo/', views.TodoList.as_view(), name='todo_list'),
path('todo/<int:pk>', views.TodoDetail.as_view(), name='todo_detail'),
# URLs for class-based views (ModelViewSets)
# http://localhost:8000/general/users/
# http://localhost:8000/general/groups/
path('general/', include(router.urls)),
# Include default login and logout views for use with the browsable API.
# Optional, but useful if your API requires authentication and you want to use the browsable API.
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
# API to generate auth token from user. Note that the URL part of the pattern can be whatever you want to use.
path('api-token-auth/', rest_views.obtain_auth_token, name='api-token-auth'),
# URLs for function-based views
# http://localhost:8000/todo/
# http://localhost:8000/todo/<int:pk>
# path('todo/', views.todo_list),
# path('todo/<int:pk>', views.todo_detail),
]
python manage.py runserver
command then navigate to the API endpoint based on the URL pattern that you configured. For this project, it is http://localhost:8000/todo/ or http://127.0.0.1:8000/todo/Using HTTPie (recommended):
$ http GET localhost:8000/todo/
Using curl:
$ curl -X GET localhost:8000/todo/
Using HTTPie (recommended):
HTTP/1.1 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 153
Content-Type: application/json
Date: Sat, 10 Jul 2021 03:56:59 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.9.5
Vary: Accept, Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"completed": false,
"description": "FYP2 task for Aug",
"id": 7,
"title": "Research on Django-React stack"
}
]
}
Using curl:
{"count":1,"next":null,"previous":null,"results":[{"id":7,"title":"Research on Django-React stack","description":"FYP2 task for Aug","completed":false}]}%
Using HTTPie (recommended):
http POST localhost:8000/todo/ title="Research on RF for EDM" description="FYP2 task for Aug"
HTTP/1.1 201 Created
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 94
Content-Type: application/json
Date: Sat, 10 Jul 2021 04:01:48 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.9.5
Vary: Accept, Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
{
"completed": false,
"description": "FYP2 task for Aug",
"id": 10,
"title": "Research on RF for EDM"
}
Using curl:
curl -X POST -d 'title=Research on SVM for EDM&description=FYP2 task for Sept' localhost:8000/todo/
{"id":11,"title":"Research on SVM for EDM","description":"FYP2 task for Sept","completed":false}%
AllowAny
permission class which will allow unrestricted access, regardless of if the request was authenticated or unauthenticated. This is certainly dangerous and not ideal for our web app especially during production. Hence, we need to configure the permissions for restricted access on our REST APIs.AllowAny
AllowAny
permission class will allow unrestricted access, regardless of if the request was authenticated or unauthenticated.IsAuthenticated
IsAuthenticated
permission class will deny permission to any unauthenticated user, and allow permission otherwise.IsAdminUser
IsAuthenticatedOrReadOnly
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
.queryset
property set. DjangoModelPermissionsOrAnonReadOnly
DjangoModelPermissions
, but also allows unauthenticated users to have read-only access to the API.DjangoObjectPermissions
django-guardian
.TokenHasReadWriteScope
OAuthAuthentication
and OAuth2Authentication
classes, and ties into the scoping that their backends provide. More info about this here.settings.py
)
REST_FRAMEWORK = {
#...
# The default permission policy may be set globally, using the DEFAULT_PERMISSION_CLASSES setting:
# 'DEFAULT_PERMISSION_CLASSES': [
# 'rest_framework.permissions.AllowAny',
# 'rest_framework.permissions.IsAuthenticated',
# 'rest_framework.permissions.IsAuthenticatedOrReadOnly',
# 'rest_framework.permissions.DjangoModelPermissions',
# 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly',
# 'rest_framework.permissions.DjangoObjectPermissions',
# 'rest_framework.permissions.TokenHasReadWriteScope',
# ],
views.py
)
class TodoList(generics.ListCreateAPIView):
queryset = Todo.objects.all()
# View-level permissions
# IsAuthenticatedOrReadOnly, which will ensure that authenticated requests get read-write access,
# and unauthenticated requests get read-only access.
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
serializer_class = TodoSerializer
class TodoDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Todo.objects.all()
# View-level permissions
# IsAuthenticatedOrReadOnly, which will ensure that authenticated requests get read-write access,
# and unauthenticated requests get read-only access.
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
serializer_class = TodoSerializer
http POST localhost:8000/todo/ title="Research on Django-React stack" description="FYP2 task for Aug"
HTTP/1.1 403 Forbidden
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 58
Content-Type: application/json
Date: Sat, 10 Jul 2021 04:01:28 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.9.5
Vary: Accept, Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
{
"detail": "Authentication credentials were not provided."
}
BasicAuthentication
SessionAuthentication
TokenAuthentication
TokenAuthentication
scheme you'll need to configure the authentication classes to include TokenAuthentication
, and additionally include rest_framework.authtoken
in your INSTALLED_APPS
setting inside settings.py
. More on this later in the guide.settings.py
:REST_FRAMEWORK = {
#...
# The default permission policy may be set globally, using the DEFAULT_AUTHENTICATION_CLASSES setting:
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
),
}
'DEFAULT_AUTHENTICATION_CLASSES'
to Token Authentication
REST_FRAMEWORK = {
#...
# The default permission policy may be set globally, using the DEFAULT_AUTHENTICATION_CLASSES setting:
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
}
app_name/urls.py
from . import views
from rest_framework import routers
from django.urls import path, include
# Rename views to avoid conflict with app views
from rest_framework.authtoken import views as rest_views
urlpatterns = [
#...
# Include default login and logout views for use with the browsable API.
# Optional, but useful if your API requires authentication and you want to use the browsable API.
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
# API to generate auth token from user. Note that the URL part of the pattern can be whatever you want to use.
path('api-token-auth/', rest_views.obtain_auth_token, name='api-token-auth'),
]
- Using Django manage.py command:
```shell
$ python manage.py drf_create_token USERNAME
```
- Using CLI
```shell
$ http POST API_ENDPOINT username=USERNAME password=PASSWORD
```
- Using `views.py`
```python
from rest_framework.authtoken.models import Token
token = Token.objects.create(user=...)
print(token.key)
```
$ http POST API_ENDPOINT model_fields 'Authorization: Token TOKEN'
API_ENDPOINT
refers to the url pattern of the API e.g. localhost:8000/todo/ drf_spectacular
allows you to:pip
$ pip install drf-spectacular
drf-spectacular
to INSTALLED_APPS
in settings.py
INSTALLED_APPS = [
# ...
'drf_spectacular',
]
AutoSchema
with DRF
REST_FRAMEWORK = {
# YOUR SETTINGS
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}
project_name/urls.py
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
urlpatterns = [
# YOUR PATTERNS
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
# Optional UI:
path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
]
18