Django Rest Api

Playground API Backend using Django

How to Start Django Project

1. Install Django

  • create virtualenv

use pyenv

pyenv virtualenv <project-name>
pyenv activate <project-name>

or python venv on windows

python -m venv .venv
.venv\Scripts\activate
  • install django, start the project and upgrade lastest pip
pip install --upgrade pip
pip install django==4.2.16
pip freeze
django-admin startproject <project-name>

2. Prepare Starter

  • enter to project directory cd <project-name>
  • create starter files
touch .env .env.example .gitignore README.md requirements.txt
  • add package version to requirements.txt
Django==4.2.16

3. Init git repository and Install starter package

git init
pip install black django-environ psycopg2-binary
pip freeze

psycopg2-binary is Postgres lib, adjust this package if use other database engine

  • add package version to requirements.txt
black==24.10.0
django-environ==0.11.2
psycopg2-binary==2.9.10

4. Setup env variable and prepare database

  • create database on posgres
  • add env config starter .env and .env.example
DEBUG=True
SECRET_KEY=your-secret-key
ALLOWED_HOSTS=*
DATABASE_URL=psql://postgresuser:postgrespass@127.0.0.1:5432/postgresdb
# DATABASE_URL=sqlite://../db.sqlite3
STATIC_DIR=static
MEDIA_DIR=media

DJANGO_SUPERUSER_USERNAME=karamel
DJANGO_SUPERUSER_EMAIL=admin@karamel.id
DJANGO_SUPERUSER_PASSWORD=karameladmin12345
  • setting env variable on <project-name>/settings.py
import environ
import os


# Django-environ
env = environ.Env(DEBUG=(bool, False))

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# Take environment variables from .env file
environ.Env.read_env(os.path.join(BASE_DIR, ".env"))

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = env("SECRET_KEY")

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = env("DEBUG")

ALLOWED_HOSTS = env("ALLOWED_HOSTS").split(",")

# ...

# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases

DATABASES = {"default": env.db()}

# ...

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/

STATIC_URL = "/static/"
STATIC_ROOT = env("STATIC_DIR")

MEDIA_URL = "/media/"
MEDIA_ROOT = env("MEDIA_DIR")

# ...

5. Setup default User Model

  • create django apps member for manage user data
python manage.py startapp member
  • create model User on member/models.py
from django.db import models
from django.contrib.auth.models import AbstractUser


class User(AbstractUser):
    pass
  • setting variable on <project-name>/settings.py
INSTALLED_APPS = [
    # ...
    # apps
    "member.apps.MemberConfig",
]

# ...

AUTH_USER_MODEL = "member.User"
  • create generate superuser on command member/management/commands/generate_superuser.py
import os
from django.core.management.base import BaseCommand
from django.core.management import call_command
from member.models import User


class Command(BaseCommand):
    help = "Generate Superuser"

    def handle(self, *args, **options):
        username = os.environ.get("DJANGO_SUPERUSER_USERNAME", None)
        email = os.environ.get("DJANGO_SUPERUSER_EMAIL", None)
        is_admin_exists = User.objects.filter(
            username=username, email=email, is_superuser=True
        ).exists()
        if not is_admin_exists:
            call_command("createsuperuser", "--noinput")

6. Running

python manage.py makemigrations
python manage.py migrate
python manage.py generate_superuser
python manage.py runserver

Setup Django Package

Rest Framework, corsheader, swagger, Rest Auth, Allauth, Simple JWT

1. Install Package

pip install djangorestframework drf_yasg django-filter django-cors-headers dj-rest-auth 'django-allauth[socialaccount]' djangorestframework-simplejwt
pip freeze
  • add package version to requirements.txt
django-cors-headers==4.6.0
django-filter==24.3
djangorestframework==3.15.2
drf-yasg==1.21.8
dj-rest-auth==7.0.0
django-allauth[socialaccount]==65.2.0
djangorestframework-simplejwt==5.3.1

2. Setup settings.py

  • add configuration variable on <project-name>/settings.py
from datetime import timedelta

INSTALLED_APPS = [
    # ...
    # libs
    "corsheaders",
    "rest_framework",
    "rest_framework_simplejwt.token_blacklist",
    "drf_yasg",
    "dj_rest_auth",
    "allauth",
    "allauth.account",
    "allauth.socialaccount",
    "dj_rest_auth.registration",
]

MIDDLEWARE = [
    "corsheaders.middleware.CorsMiddleware",
    # ...
    "allauth.account.middleware.AccountMiddleware",
]

# ...

# corsheaders
CORS_ALLOW_ALL_ORIGINS = True

# DRF
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": (
        "rest_framework_simplejwt.authentication.JWTAuthentication",
    ),
    "DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend"],
    "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
    "PAGE_SIZE": 20,
}

# swagger
SWAGGER_SETTINGS = {
    "SECURITY_DEFINITIONS": {
        "Bearer": {
            "type": "apiKey",
            "name": "Authorization",
            "in": "header",
        },
    },
}

# dj-rest-auth
REST_USE_JWT = True
REST_AUTH = {
    "USE_JWT": True,
    "TOKEN_MODEL": None,
    "JWT_AUTH_HTTPONLY": False,
    "JWT_AUTH_RETURN_EXPIRATION": True,
    "USER_DETAILS_SERIALIZER": "api.auth.serializers.UserDetailsSerializer",
}

# allauth
ACCOUNT_AUTHENTICATION_METHOD = "username_email"
ACCOUNT_EMAIL_VERIFICATION = "none"
AUTHENTICATION_BACKENDS = (
    "django.contrib.auth.backends.ModelBackend",
    "allauth.account.auth_backends.AuthenticationBackend",
)

# simplejwt
SIMPLE_JWT = {
    "ACCESS_TOKEN_LIFETIME": timedelta(minutes=5),
    "REFRESH_TOKEN_LIFETIME": timedelta(days=1),
    "AUTH_HEADER_TYPES": ("Bearer",),
    "ROTATE_REFRESH_TOKENS": True,
    "BLACKLIST_AFTER_ROTATION": True,
}

3. Setup Serializers

  • create api/auth/serializers.py
from rest_framework import serializers
from member.models import User


class UserDetailsSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = (
            "username",
            "email",
            "first_name",
            "last_name",
        )

4. Setup Views

  • create api.auth.views.py
from django.utils.translation import gettext_lazy as _
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from dj_rest_auth.views import LogoutView
from rest_framework.response import Response
from rest_framework import status


class CustomLogoutView(LogoutView):
    """
    Calls Django logout method and delete the Token object
    assigned to the current User object.

    Accepts/Returns nothing.
    """

    @swagger_auto_schema(
        request_body=openapi.Schema(
            type=openapi.TYPE_OBJECT,
            properties={
                "refresh": openapi.Schema(
                    type=openapi.TYPE_STRING,
                    title="Refresh",
                    description="Logout with blacklist Refresh token",
                    min_length=1,
                )
            },
            required=["refresh"],
        ),
    )
    def post(self, request, *args, **kwargs):
        if request.data.get("refresh", None):
            return super().post(request, *args, **kwargs)

        return Response(
            {"detail": _("Refresh token was not included in request data.")},
            status=status.HTTP_400_BAD_REQUEST,
        )

5. Setup Urls

  • add urlpatterns on <project-name>/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("api/", include("api.urls")),
]
  • create file api/urls.py
from django.urls import path, re_path, include
from django.conf import settings
from rest_framework import permissions
from drf_yasg.views import get_schema_view
from drf_yasg import openapi
from drf_yasg.generators import OpenAPISchemaGenerator

from api.auth.views import CustomLogoutView


class BothHttpAndHttpsSchemaGenerator(OpenAPISchemaGenerator):
    def get_schema(self, request=None, public=False):
        schema = super(BothHttpAndHttpsSchemaGenerator, self).get_schema()
        schema.schemes = ["http", "https"]
        return schema


schema_view = get_schema_view(
    openapi.Info(
        title="PLAYGROUND API",
        default_version="v1",
        description="PLAYGROUND Swagger docs",
        license=openapi.License(name="BSD License"),
    ),
    public=True,
    permission_classes=[permissions.AllowAny],
    generator_class=BothHttpAndHttpsSchemaGenerator,
)

urlpatterns = [
    path("auth/logout/", CustomLogoutView.as_view(), name="rest_logout"),
    path("auth/", include("dj_rest_auth.urls")),
    path("auth/registration/", include("dj_rest_auth.registration.urls")),
]

if settings.DEBUG:
    urlpatterns += [
        # Documentation Route
        re_path(
            r"^swagger(?P<format>\.json|\.yaml)$",
            schema_view.without_ui(cache_timeout=0),
            name="schema-json",
        ),
        re_path(
            r"^docs/$",
            schema_view.with_ui("swagger", cache_timeout=0),
            name="schema-swagger-ui",
        ),
        re_path(
            r"^redoc/$",
            schema_view.with_ui("redoc", cache_timeout=0),
            name="schema-redoc",
        ),
    ]

Let's go to Project Structure

Example goods apps with model Category, Product and ProductVarian

1. Create app and model

python manage.py startapp goods
  • setting variable on <project-name>/settings.py
INSTALLED_APPS = [
    # ...
    "goods.apps.GoodsConfig",
]
  • create model Category, Product and ProductVarian on goods/models.py example
  • create admin.register on goods/admin.py example
python manage.py makemigrations
python manage.py migrate

2. Create simple endpoint with urls, views, and serializers

example

  • create dir api/category, api/product, api/variant
  • create file urls.py, views.py and serializers.py on each dir
  • add urlpatterns on api/urls.py

How to Development

  • clone project. git clone <url-repository>
  • create virtualenv
  • copy .env.example and rename to .env file
  • install requirements.txt
pip install -r requirements.txt