Учебный проект на Django REST Framework, демонстрирующий кастомную реализацию авторизации и ролевую модель доступа.
В рамках учебного задания реализовано:
- ✅ Кастомная аутентификация (не встроенная DRF)
- ✅ JWT токены с использованием библиотеки python-jose
- ✅ Ролевая модель (admin, manager, basic, guest)
- ✅ Декораторы для разграничения доступа
- ✅ Тесты на pytest
- ✅ Для настроек swagger указаны SWAGGER_SETTINGS в auth_project/settings.py
- Python
- Django REST Framework
- PostgreSQL (или в тестовом режиме sqlite3)
- JWT (кастомная реализация)
- pytest (тестирование)
- UV (управление зависимостями)
- Python 3.10+
- PostgreSQL
- UV (менеджер пакетов; необязательно)
# Setup by UV
uv python install 3.13
uv python pin 3.13
uv sync # one command reate .venv and install dependencies
source .venv/bin/activate
cp .env.example .env # correct variables for postgres
make db
# Run the app
make run # or: uv run python main.py- check docs: http://127.0.0.1:8000/swagger/
По заданию реализация авторизации не должна быть основана на встроенной возможности фреймворка, а организована своя. Для аутентификации использую выдачу JWT-токенов.
- admin - полный доступ ко всем ресурсам
- manager - расширенные права на управление контентом
- basic - базовый доступ для авторизованных пользователей
- guest - не требует авторизации, минимальные права
- API имеют ограничение доступа.
Реализация доступа с помощью декоратора над каждым из методов.
пример
@require_auth_role(UserRole.BASIC)
Либо у UserRole можно указать не BASIC, а любой из остальных. У Админа - права на всё.
- Есть url регистрации по уникальному юзернейму и паролю
- Авторизация через юзернейм и пароль - равна запросу на выдачу jwt токена, который действует ограниченное время, и по которому есть доступ к другим url.
- Каждый пользователь может "удалить" свой профиль - запись остается в базе, но -> is_active = False. После этого доступ к url по токену прекращается, и новый "логин" не осуществляется.
- Изменения как обновление информации о любом пользователе или его роли может выполнять только админ, изменять информацию "о себе" кроме роли может каждый зарегистрированный (кто не гость).
- После изменения роли нужно перелогиниться (обновить токен).
- После удаления (деактивации) пользователя его токен становится недействительным.
- В базе минимальное количество таблиц (MVP): user, role, category, recipe. Рецепты и их категории - условные эндпоинты для сайта. Таблицы связаны: в каждой категории может быть много рецептов. Только список рецептов доступен гостю.
- Для запроса зарегистрированного пользователя нужно передать -H 'Authorization:bearer xxx' в хедере (или можно через соответствующую функцию в сваггере).
- Logout анулирует токен - нужно логиниться заново.
uv venv
source .venv/bin/activate
VIRTUAL_ENV=.venv/
uv pip install ruff
uv pip list
uv add django djangorestframework
django-admin startproject myproject
python manage.py startapp api
Не называть app именем app! # for myself
делать runserver из внутренней папки
INSTALLED_APPS = [
# ... default django apps ...
'rest_framework', # Add Django Rest Framework
'api', # Add your app
]
python manage.py makemigrations
python manage.py migrate
python manage.py migrate api zero # reverse
Get username and password from query parameters:
username = request.query_params.get('username')
password = request.query_params.get('password')
curl -X 'GET' \
'http://0.0.0.0:8000/api/books-author/' \
-H 'accept: application/json' \
-d '' -H 'Authorization:bearer xxx'
curl -X 'POST' \
'http://0.0.0.0:8000/api/books-author/' \
-H 'accept: application/json' \
-d '' -H 'Authorization:bearer xxx' \
-d '
{
"author": {
"name": "string"
},
"title": "string",
"publish_date": "2026-01-30"
}'
curl -X 'POST' \
'http://127.0.0.1:8000/api/token/gen' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-H 'X-CSRFTOKEN: 86ChJcJ8ywmz6fKsWNhUFQNssTok7P5Y' \
-d '{
"username": "joe",
"password": "123"
}'
curl -X 'POST' 'http://0.0.0.0:8000/v1/?username=joe&password=123'
get username and password from body in view
username = request.data.get("username")
password = request.data.get("password")
curl -X 'POST' \
'http://127.0.0.1:8000/api/register' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-H 'X-CSRFTOKEN: 86ChJcJ8ywmz6fKsWNhUFQNssTok7P5Y' \
-H 'X-Client-Secret:xxx' \
-d '{
"username": "joe",
"password": "123"
}'
curl -X 'POST' \
'http://127.0.0.1:8000/api/edit-role' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '' -H 'Authorization:bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImpvZSIsImV4cGlyZSI6IjIwMjYtMDEtMzBUMTM6NDI6NDcuNDA5Njk4KzAwOjAwIiwicm9sZSI6ImFkbWluIn0.XWL-iV5jUq5Or7_jy2kI2NrN7oLv5G33ay7UvheBcDU' \
-d '{
"role_id": 5,
"id": 1
}'
curl -X 'POST' \
'http://127.0.0.1:8000/api/edit-role' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '' -H 'Authorization:bearer xxx' \
-d '{
"role_id": 5,
"id": 1
}'
curl -X 'DELETE' \
'http://127.0.0.1:8000/api/profile/1/' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '' -H 'Authorization:bearer xxx' \
-d '{
"role_id": 5,
"id": 1
}'