Перейти к содержанию

Создание проекта

Установить необходимо огромное количество библиотек. Создадим файл requirements.txt со следующим содержимым:

requirements.txt
Django==4.2.7
django-cors-headers==4.3.0
djangorestframework==3.14.0
djangorestframework-simplejwt==5.3.0
django-allauth==0.58.1
dj-rest-auth==5.0.1
dj_database_url==2.1.0
drf-spectacular==0.26.5
gunicorn==21.2.0
psycopg[c]==3.1.12
python-dotenv==1.0.0
starlette==0.31.1

Чуть подробнее о каждой библиотеке:

  • Django - сам веб-фреймворк, который мы будем использовать;
  • django-cors-headers - настройка CORS;
  • djangorestframework - библиотека для REST API с использованием Django;
  • djangorestframework-simplejwt - управление JWT токенами с помощью DRF;
  • django-allauth + dj-rest-auth - удобная авторизация с помощью API, в том числе с помощью различных сторонних провайдеров (включая OIDC, который мы и будем использовать);
  • dj_database_url - парсер БД-URL в настройки Django;
  • drf-spectacular - Swagger для DRF;
  • gunicorn - популярный веб-сервер, с помощью которого и будем запускать наш бэкенд;
  • psycopg[c] - адаптер для PostgreSQL;
  • python-dotenv + starlette - удобное управление переменными окружения;

Теперь установим все библиотеки следующей командой:

pip install -r requirements.txt

Создадим проект laboratory_work_3 и сразу несколько приложений:

django-admin startproject laboratory_work_3
cd laboratory_work_3
python manage.py startapp adapter
python manage.py startapp oidc
python manage.py startapp survey

Приложение adapter будет представлять минимально необходимое взаимодействие с адаптерами.
Приложение oidc будет отвечать за авторизацию с помощью OIDC ITMO.ID.
В приложении survey будет всё взаимодействие с опросом.

Немного изменим структуру проекта и добавим некоторые файлы для удобного взаимодействия и дальнейшего деплоя:

.
├─ src/
  ├─ apps/
    ├─ adapter/
    ├─ oidc/
    ├─ survey/
    └─ __init__.py
  ├─ delivery/
    ├─ __init__.py
    ├─ asgi.py
    ├─ gunicorn.conf.py
    ├─ urls.py
    └─ wsgi.py
  ├─ manage.py
  └─ settings.py
├─ .env
├─ docker-compose.yml
├─ Dockerfile
└─ requirements.txt

Все файлы основные файлы проекта мы перенесли в директорию src, это будет отправная точка для создания docker образа.

Файлы проекта, отвечающие за поднятие сервера перенесли в отдельную директорию delivery и добавили туда файл настроек для gunicorn.

Приложения перенесли в отдельную директорию apps.

Теперь перейдём в файл settings.py и настроим все наши приложения и библиотеки:

  • Загрузим переменные окружения и будем использовать их значения для важных настроек (важно не забыть указать их в .env файле):

    load_dotenv()
    
    env = Config(".env")
    
    # Build paths inside the project like this: BASE_DIR / 'subdir'.
    BASE_DIR = Path(__file__).resolve().parent.parent
    
    
    # Quick-start development settings - unsuitable for production
    # See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
    
    # SECURITY WARNING: keep the secret key used in production secret!
    SECRET_KEY = env("SECRET_KEY", cast=str)
    
    # SECURITY WARNING: don't run with debug turned on in production!
    DEBUG = env("DEBUG", cast=bool, default=False)
    
    ALLOWED_HOSTS = list(env("ALLOWED_HOSTS", cast=CommaSeparatedStrings, default=[]))
    
    CSRF_TRUSTED_ORIGINS = list(env("CSRF_TRUSTED_ORIGINS", cast=CommaSeparatedStrings, default=[]))
    

  • Добавим все библиотеки и приложения в INSTALLED_APPS:

    INSTALLED_APPS = [
        "django.contrib.admin",
        "django.contrib.auth",
        "django.contrib.contenttypes",
        "django.contrib.sessions",
        "django.contrib.messages",
        "django.contrib.staticfiles",
        "rest_framework",
        "rest_framework.authtoken",
        "drf_spectacular",
        "dj_rest_auth",
        "django.contrib.sites",
        "allauth",
        "allauth.account",
        "dj_rest_auth.registration",
        "allauth.socialaccount",
        "allauth.socialaccount.providers.openid_connect",
        "apps.oidc",
        "apps.adapter",
        "apps.survey",
    ]
    

  • Добавим ID сайта (который далее создадим в админ-панели):

    SITE_ID = 4
    

  • Добавим мидлварь для CORS первым элементом (важно) и мидлварь для авторизации:

    MIDDLEWARE = [
        "corsheaders.middleware.CorsMiddleware",
        "django.middleware.security.SecurityMiddleware",
        "django.contrib.sessions.middleware.SessionMiddleware",
        "django.middleware.common.CommonMiddleware",
        "django.middleware.csrf.CsrfViewMiddleware",
        "django.contrib.auth.middleware.AuthenticationMiddleware",
        "django.contrib.messages.middleware.MessageMiddleware",
        "django.middleware.clickjacking.XFrameOptionsMiddleware",
        "allauth.account.middleware.AccountMiddleware",
    ]
    

  • Настройки для CORS будем так же брать из переменных окружения:

    CORS_ALLOWED_ORIGINS = list(env("CORS_ALLOWED_ORIGINS", cast=CommaSeparatedStrings, default=[]))
    CORS_ALLOW_CREDENTIALS = True
    

  • Поменяем путь до файла url.py, потому что мы изменили структуру проекта:

    ROOT_URLCONF = "delivery.urls"
    

  • Также изменим путь до wsgi.py:

    WSGI_APPLICATION = "delivery.wsgi.application"
    

  • Получим ссылку на БД из переменных окружения (рекомендуется использовать PostgreSQL) и будем использовать SQLite, если в .env ничего не указано.
    Распарсим эту ссылку и используем в настройках БД:

    DATABASE_URL = env("DATABASE_URL", cast=str, default="sqlite:///db.sqlite3")
    
    DATABASES = {
        "default": dj_database_url.parse(DATABASE_URL),
    }
    

  • Поменяем язык на русский:

    LANGUAGE_CODE = "ru-RU"
    

  • Помянем часовой пояс на GMT+3:

    TIME_ZONE = "Europe/Moscow"
    

  • Поменяем хранилище для статических файлов:

    STORAGES = {
        "default": {
            "BACKEND": "django.core.files.storage.FileSystemStorage",
        },
        "staticfiles": {
            "BACKEND": "django.contrib.staticfiles.storage.ManifestStaticFilesStorage",
        },
    }
    

  • Укажем пути хранения статических файлов:

    STATICFILES_DIRS = [BASE_DIR / "static"]
    STATIC_URL = "/static/"
    STATIC_ROOT = "/var/www/html/static/"
    

  • Настроим логгер:

    LOGGING = {
        "version": 1,
        "disable_existing_loggers": False,
        "handlers": {
            "console": {"level": "INFO" if not DEBUG else "DEBUG", "class": "logging.StreamHandler"},
        },
        "loggers": {
            "root": {
                "handlers": ["console"],
                "level": "INFO" if not DEBUG else "DEBUG",
            }
        },
    }
    

  • Настроим DRF:
    В качестве класса для авторизации укажем JWTCookieAuthentication из библиотеки dj_rest_auth.
    Все ответы API будем выводить в виде JSON, используя класс по умолчанию JSONRenderer.
    Схемой для документации укажем drf_spectacular.
    Настроим тротлинг до 20 запросов в минуту.

    REST_FRAMEWORK = {
        "DEFAULT_AUTHENTICATION_CLASSES": ("dj_rest_auth.jwt_auth.JWTCookieAuthentication",),
        "DEFAULT_RENDERER_CLASSES": ("rest_framework.renderers.JSONRenderer",),
        "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
        "DEFAULT_THROTTLE_CLASSES": [
            "rest_framework.throttling.AnonRateThrottle",
            "rest_framework.throttling.UserRateThrottle",
        ],
        "DEFAULT_THROTTLE_RATES": {"anon": "20/minute", "user": "20/minute"},
    }
    

  • Настроим кэш, который необходим для работы тротлинга:

    CACHES = {
        "default": {
            "BACKEND": "django.core.cache.backends.db.DatabaseCache",
            "LOCATION": "survey_cache_table",
        }
    }
    

  • Укажем время жизни JWT токенов:

    SIMPLE_JWT = {
        "ACCESS_TOKEN_LIFETIME": timedelta(minutes=30),
        "REFRESH_TOKEN_LIFETIME": timedelta(days=1),
    }
    

  • Настроим dj_rest_auth:

    REST_AUTH = {
        "USE_JWT": True,
        "JWT_AUTH_COOKIE": "survey-auth",
        "JWT_AUTH_RETURN_EXPIRATION": True,
        "JWT_AUTH_HTTPONLY": False,
        "JWT_SERIALIZER_WITH_EXPIRATION": "apps.oidc.serializers.CustomJWTSerializerWithExpiration",
    }
    

  • Получим Client ID для клиента ITMO.ID из переменной окружения:

    ITMO_ID_CLIENT_ID = env("ITMO_ID_CLIENT_ID", cast=str)
    

  • Настроим django-allauth:

    ACCOUNT_EMAIL_VERIFICATION = "none"
    ACCOUNT_USERNAME_REQUIRED = False
    SOCIALACCOUNT_STORE_TOKENS = True
    SESSION_LOGIN = False
    

  • Настроим Swagger:

    SPECTACULAR_SETTINGS = {
        "TITLE": "Adapter API",
        "VERSION": "0.0.1",
        "SERVE_INCLUDE_SCHEMA": False,
        "SWAGGER_UI_SETTINGS": {
            "filter": True,
        },
        "COMPONENT_SPLIT_REQUEST": True,
    }
    

  • Получим ссылки на бэкенд и фронтенд из переменных окружения:

    BE_BASE_URL = env("BE_BASE_URL", cast=str)
    FE_BASE_URL = env("FE_BASE_URL", cast=str)