Awesome
django-rest-api-cheatsheet
A simple cheatsheet for a Django REST API backend.
Development Environment (Mac OSX)
- Install Python 3
$ brew install python3
- Install pipenv
$ pip3 install pipenv
Initial Project Setup
$ mkdir projdir && cd projdir
$ pipenv install django==2.1
NOTE: pip 18.1 is buggy! Current fix (immediately after the previous step) is this:
$ pip3 install pipenv
$ pipenv run pip install pip==18.0
$ pipenv install
Initial Project Setup (Continued)
$ pipenv install pylint
$ pipenv shell
(projdir) $ django-admin startproject my_project .
(projdir) $ python manage.py migrate
(projdir) $ python manage.py createsuperuser
(projdir) $ python manage.py runserver
Suggestion: Change the URL of the admin site in my_project/urls.py
.
Adding a Django app to the project
(projdir) $ python manage.py startapp products
- Add the new app to
INSTALLED_APPS
list inmy_project/settings.py
.
# my_project/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# Local
'products.apps.ProductsConfig',
]
Creating a Model for the project
- Go to
products/models.py
and make model:
# products/models.py
from django.db import models
from django.contrib.auth.models import User # if you need user here...
class Product(models.Model):
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=250)
description = model.TextField()
image = models.ImagesField(uploaf_to='dir/of/image/')
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
(See https://docs.djangoproject.com/en/2.1/ref/models/fields/ for model fields.]
- Create migration file (make sure you add the app name at the end).
(projdir) $ python manage.py makemigrations products
(projdir) $ python manage.py migrate
- Register the model in
products/admin.py
.
# products/admin.py
from django.contrib import admin
from .models import Product # Added in this step
admin.site.register(Product) # Added in this step
Setting up Django REST Framework
(projdir) $ pipenv install djangorestframework==3.8.2
- Add to
INSTALLED_APPS
and addREST_FRAMEWORK
object inmy_project/settings.py
:
# my_project/settings.py
...
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 3rd-party
'rest_framework', # Added in this step
# Local
'products',
]
REST_FRAMEWORK = { # Added in this step
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny', # Just for now...
]
}
...
Setting up APIs for an App
- Create serializers for the models in the app.
# products/serializers.py (a new file)
from rest_framework import serializers
from . models import Product
class ProductSerializer(serializers.ModelSerializer):
class Meta:
fields = {'id', 'created_by', 'name', 'description', 'image', 'created_at', }
model = Product
- Set up URLs for the app.
First, create paths for app (create products/urls.py
):
# products/urls.py -- new file added
from . views import ProductList, ProductDetail
urlpatterns = [
path('', ProductList.as_view()),
path('<int:pk>/' ProductDetail.as_view()),
]
Then, create path in project-level URLS file:
# my_project/urls.py
from django.contrib.import admin
from django.urls import path, include # 'include' added in this step
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/products/', include('products.urls')) # Added in this step
]
- Set up the views for the app:
# products/views.py
from rest_framework import generics # Added
from . models import Product # Added
from . serializers import ProductSerializer # Added
class ProductList(generics.ListCreateAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
class ProductDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
Allow Cross-Origin Resource Sharing (CORS)
- Install package
(projdir) $ pipenv install django-cors-headers==2.4.0
- Update maindir/settings.py
2a. Add
corsheaders
inINSTALLED_APPS
:
INSTALLED_APPS = [
...
# 3rd party
'rest_framework',
'corsheaders',
...
]
2b. Add middlewares to the front of the MIDDLEWARE
list:
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
...
]
2c. Add CORS_ORIGIN_WHITELIST
:
CORS_ORIGIN_WHITELIST = (
'mydomain.com' # Whatever domain
)
Setting Permissions
View-Level Permissions
- In the views.py file, import
permissions
from therest_framework
package.
from rest_framework import generics, permissions
- Add
permissions_classes
member variable to a view class.
class ProductList(generics.ListCreateAPIView):
permissions_classes = (permissions.IsAuthenticated,)
Project-Level Permissions
- Add permissions settings in setting.py, under the
REST_FRAMEWORK
object and in the'DEFAULT_PERMISSION_CLASSES'
list.
# project_dir/settings.py
...
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
]
}
Built-in project-level permissions settings include:
- AllowAny
- IsAuthenticated
- IsAdminUser
- IsAuthenticatedOrReadOnly
Custom permissions
- In file where custom permissions class is defined (Suggestion: make a permissions.py file), import
permissions
package:
from rest_framework import permissions
- Declare a class that extends
permissions.BasePermission
. Example:
class IsOwnerOrReadOnly(permissions.BasePermission):
- Override boolean methods
has_permission()
orhas_object_permission()
def has_object_permission(self, request, view, obj):
# Read-only permissions allowed for SAFE_METHODS (GET, OPTIONS, HEAD)
if request.method in permissions.SAFE_METHODS:
return True
# Write permissions for owner
return obj.owner == request.user
User Authentication
Basic Authentication
-
Client makes HTTP request
-
Server responds with 401 status (
Unauthorized
) andwww-Authenticate
HTTP header -
Client sends credentials back with Authorization HTTP header
-
Server checks credentials; responds with 200 OK or 403 Forbidden code.
+
Simple-
Must send credentials for every request-
Insecure
Session Authentication
-
User enters credentials (logs in).
-
Server verifies credentials.
-
Server creates session object; stores in database.
-
Server sends client session ID; client stores as cookie.
-
When user logs out, session ID destroyed by client and server.
+
Credentials sent only once.+
More efficient lookup for session ID.-
Session ID only valid in browser where logged in.-
Cookie sent out for every request, even no authorization required ones.
Token Authentication
-
User sends credentials to server.
-
Unique token generated and stored by client as cookie or local storage.
-
Token passed in header of each HTTP request.
-
Server verifies token validity to see if user is authenticated.
+
Tokens stored only in client.+
Token can be shared by multiple front-ends.-
Tokens can become large.-
Token usually contains all user info.
Setting Up Token Authentication
- In settings.py, add
'DEFAULT_AUTHENTICATION_CLASSES'
list to theREST_FRAMEWORK
object:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
]
}
- NOTE:
SessionAuthentication
needed for using Browsable API.
- Add
'rest_framework.authtoken'
toINSTALLED_APPS
in settings.py:
INSTALLED_APPS = [
...
# 3rd party
'rest_framework',
'rest_framework.authtoken',
...
]
- Sync the database with
migrate
command.
(projdir) $ python manage.py migrate
(projdir) $ python manage.py runserver
Setting Up Authentication APIs (using django-rest-auth)
- Install packages via command line
(projdir) $ pipenv install django-rest-auth==0.9.3
- Add to
INSTALLED_APPS
in settings.py
INSTALLED_APPS = [
...
# 3rd party
'rest_framework',
'rest_framework.authtoken',
'rest_auth',
...
]
- Include
'rest_auth.urls'
to project's urls.py.
urlpatterns = [
...
path('api/v1/rest-auth/', include('rest_auth.urls')),
]
Setting Up User Registration APIs (using the django-allauth package)
- Command line install:
(projdir) $ pipenv install django-allauth==0.37.1
- Add multiple configs to the
INSTALLED_APPS
list in settings.py
INSTALLED_APPS = [
...
'django.contrib.sites',
# 3rd party
'rest_framework',
'rest_framework.authtoken',
'allauth', ##
'allauth.account', ##
'allauth.socialaccount', ##
'rest_auth',
'rest_auth.registration', ##
...
]
...
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
SITE_ID = 1
- Add url route in
projdir/urls.py
.
urlpatterns = [
...
path('api/v1/rest-auth/registration/', include('rest_auth.registration.urls')),
]
Implementing Viewsets (e.g., improving on views)
- In an app's views.py, import
viewsets
from therest_framework
package.
from rest_framework import viewsets
(We are additionally no longer requiring the importing of generics
or permissions
.)
- Create view class that extends
viewset.ModelViewSet
.
class ProductViewSet(viewset.ModelViewSet):
permission_classes = (IsAuthorOrReadOnly,)
queryset = Product.objects.all()
serializer_class = ProductSerializer
Implementing Routers (e.g., improving of url patterns)
- Import
SimpleRouter
from therest_framework.routers
package.
from rest_framework.routers import SimpleRouter
- Import the Viewset classes (we also no longer import the old View classes).
from .views import ProductViewSet
- Create an instance of
SimpleRouter
, register routes for theViewSet
s, and then set theurlpatterns
variable to the router's urls.
router = SimpleRouter()
router.register('products', ProductViewSet, base_name='products')
urlpatterns = router.urls
Generating API Schema
- Install Core API package via command line.
(projdir) $ pipenv install coreapi==2.3.3
- Set up
get_schema_view
from therest_framework.schemas
package in projdir urls.py.
from rest_framework.schemas import get_schema_view
schema_view = get_schema_view(title='My APIs')
- Set up route for the schema.
urlpatterns = [
...
path('schema/', schema_view),
]
Implementing API Documentation
Option 1: Using Django REST Framework's Built-In Documentation
- In projdir urls.py, import
include_docs_urls
fromrest_framework
.
from rest_framework.documentation import include_docs_urls
- Add the route for the documentation.
urlpatterns = [
...
path('docs/', include_docs_urls(title='My APIs')),
path('schema/', schema_view),
]
Option 2: Using Django REST Swagger
- Install
django-rest-swagger
package.
(projdir) $ pipenv install django-rest-swagger==2.2.0
- Add app to the
INSTALLED_APPS
list in settings.py.
INSTALLED_APPS = [
...
# 3rd party
...
'rest_framework.authtoken',
'rest_framework_swagger',
'allauth',
...
]
- In projdir urls.py, import
get_swagger_view
, and use it when creating the'swagger-docs'
route.
from rest_framework_swagger.views import get_swagger_view
...
API_TITLE = 'My APIs'
schema_view = get_swagger_view(title=API_TITLE)
...
urlpatterns = [
...
path('swagger-docs/', schema_view),
]
- To tie the login/logout buttons in the swagger view to the APIs for login/logout in settings.py.
SWAGGER_SETTINGS = {
'LOGIN_URL': 'rest_framework:login',
'LOGOUT_URL': 'rest_framework:logout',
}
Adding Pagination
- In settings.py, add two properties:
DEFAULT_PAGINATION_CLASS
andPAGE_SIZE
REST_FRAMEWORK = {
...
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10
}