17. Django: основы

Django vs FastAPI
Заголовок раздела «Django vs FastAPI»| Django | FastAPI | |
|---|---|---|
| Подход | ”Batteries included” | Минималистичный |
| ORM | Встроенный | SQLAlchemy/другие |
| Admin | Из коробки | Нет |
| Шаблоны | Встроенные | Нет (только API) |
| Async | Частичный | Нативный |
| Лучше для | Полный сайт + API | Только API |
| Популярность | Instagram, Pinterest, Disqus | Растёт быстро |
Выбирай Django когда: нужна admin-панель, полный сайт с шаблонами, встроенная аутентификация. Выбирай FastAPI когда: только API, нужна максимальная скорость, нативный async.
Установка и первый проект
Заголовок раздела «Установка и первый проект»pip install django djangorestframework
# Создать проектdjango-admin startproject mysite .
# Создать приложениеpython manage.py startapp blog
# Запустить миграцииpython manage.py migrate
# Создать суперпользователя (для admin)python manage.py createsuperuser
# Запустить серверpython manage.py runserverСтруктура проекта
Заголовок раздела «Структура проекта»mysite/├── manage.py # управляющий скрипт├── mysite/ # настройки проекта│ ├── settings.py # конфигурация│ ├── urls.py # главный роутер│ └── wsgi.py # WSGI/ASGI сервер└── blog/ # приложение ├── migrations/ # миграции БД ├── admin.py # регистрация в admin ├── apps.py # конфигурация приложения ├── models.py # модели (таблицы БД) ├── serializers.py # DRF сериализаторы ├── urls.py # маршруты приложения └── views.py # обработчики запросовsettings.py
Заголовок раздела «settings.py»import osfrom pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = os.environ.get("SECRET_KEY", "dev-secret-key")
DEBUG = os.environ.get("DEBUG", "True") == "True"
ALLOWED_HOSTS = ["localhost", "127.0.0.1"]
INSTALLED_APPS = [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", # Сторонние "rest_framework", # Наши приложения "blog",]
DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": BASE_DIR / "db.sqlite3", # Для PostgreSQL: # "ENGINE": "django.db.backends.postgresql", # "NAME": os.environ.get("DB_NAME"), # "USER": os.environ.get("DB_USER"), # "PASSWORD": os.environ.get("DB_PASSWORD"), # "HOST": os.environ.get("DB_HOST", "localhost"), # "PORT": os.environ.get("DB_PORT", "5432"), }}
# Django REST FrameworkREST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": [ "rest_framework.authentication.SessionAuthentication", "rest_framework_simplejwt.authentication.JWTAuthentication", ], "DEFAULT_PERMISSION_CLASSES": [ "rest_framework.permissions.IsAuthenticatedOrReadOnly", ], "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", "PAGE_SIZE": 10,}from django.db import modelsfrom django.contrib.auth.models import User
class Category(models.Model): name = models.CharField(max_length=100, unique=True) slug = models.SlugField(max_length=100, unique=True) description = models.TextField(blank=True)
class Meta: verbose_name = "Категория" verbose_name_plural = "Категории" ordering = ["name"]
def __str__(self): return self.name
class Post(models.Model): STATUS_DRAFT = "draft" STATUS_PUBLISHED = "published" STATUS_CHOICES = [ (STATUS_DRAFT, "Черновик"), (STATUS_PUBLISHED, "Опубликован"), ]
title = models.CharField("Заголовок", max_length=200) slug = models.SlugField(max_length=200, unique=True) body = models.TextField("Содержание") status = models.CharField(max_length=10, choices=STATUS_CHOICES, default=STATUS_DRAFT)
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="posts") category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, related_name="posts") tags = models.ManyToManyField("Tag", blank=True, related_name="posts")
created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) published_at = models.DateTimeField(null=True, blank=True)
class Meta: ordering = ["-created_at"]
def __str__(self): return self.title
@property def is_published(self): return self.status == self.STATUS_PUBLISHED
class Tag(models.Model): name = models.CharField(max_length=50, unique=True)
def __str__(self): return self.name
# Создаём миграцию и применяем:# python manage.py makemigrations# python manage.py migrateAdmin Panel
Заголовок раздела «Admin Panel»from django.contrib import adminfrom .models import Post, Category, Tag
@admin.register(Post)class PostAdmin(admin.ModelAdmin): list_display = ["title", "author", "status", "created_at"] list_filter = ["status", "category", "author"] search_fields = ["title", "body"] prepopulated_fields = {"slug": ("title",)} # автозаполнение slug date_hierarchy = "created_at" raw_id_fields = ["author"]
# Кастомные действия actions = ["make_published"]
def make_published(self, request, queryset): count = queryset.update(status="published") self.message_user(request, f"Опубликовано {count} постов") make_published.short_description = "Опубликовать выбранные"
admin.site.register(Category)admin.site.register(Tag)
# Открой: http://localhost:8000/admin# Там уже всё работает — CRUD для всех моделей!Views с Django REST Framework
Заголовок раздела «Views с Django REST Framework»from rest_framework import serializersfrom .models import Post, Category, Tag
class CategorySerializer(serializers.ModelSerializer): class Meta: model = Category fields = ["id", "name", "slug", "description"]
class PostListSerializer(serializers.ModelSerializer): author_name = serializers.CharField(source="author.get_full_name") category = CategorySerializer()
class Meta: model = Post fields = ["id", "title", "slug", "status", "author_name", "category", "created_at"]
class PostDetailSerializer(serializers.ModelSerializer): author_name = serializers.CharField(source="author.username", read_only=True)
class Meta: model = Post fields = "__all__" read_only_fields = ["author", "created_at", "updated_at"]
# blog/views.pyfrom rest_framework import viewsets, permissions, filtersfrom rest_framework.decorators import actionfrom rest_framework.response import Responsefrom django_filters.rest_framework import DjangoFilterBackendfrom .models import Post, Categoryfrom .serializers import PostListSerializer, PostDetailSerializer, CategorySerializer
class PostViewSet(viewsets.ModelViewSet): queryset = Post.objects.select_related("author", "category").prefetch_related("tags") filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] filterset_fields = ["status", "category", "author"] search_fields = ["title", "body"] ordering_fields = ["created_at", "title"]
def get_serializer_class(self): if self.action == "list": return PostListSerializer return PostDetailSerializer
def get_permissions(self): if self.action in ["list", "retrieve"]: return [permissions.AllowAny()] return [permissions.IsAuthenticated()]
def perform_create(self, serializer): serializer.save(author=self.request.user)
@action(detail=True, methods=["post"]) def publish(self, request, pk=None): post = self.get_object() post.status = "published" post.save() return Response({"status": "published"})
# blog/urls.pyfrom rest_framework.routers import DefaultRouterfrom .views import PostViewSet
router = DefaultRouter()router.register("posts", PostViewSet)
urlpatterns = router.urls# GET /posts/ → list# POST /posts/ → create# GET /posts/{id}/ → retrieve# PUT /posts/{id}/ → update# PATCH /posts/{id}/ → partial_update# DELETE /posts/{id}/ → destroy# POST /posts/{id}/publish/ → publish (custom action)from django.contrib import adminfrom django.urls import path, include
urlpatterns = [ path("admin/", admin.site.urls), path("api/v1/", include("blog.urls")), path("api/v1/auth/", include("rest_framework.urls")),]Задание
Заголовок раздела «Задание»# Создай Django приложение "tasks" — менеджер задач:## Модели:# - Project: name, description, owner, members (M2M), created_at# - Task: title, description, project, assignee, priority (1-5),# status (todo/in_progress/done), due_date, created_at# - Comment: task, author, text, created_at## API (DRF ViewSets):# - /projects/ — CRUD (создатель или участник)# - /projects/{id}/tasks/ — вложенные задачи# - /tasks/{id}/comments/ — комментарии к задаче## Admin:# - Красивые list_display для всех моделей# - Фильтры и поиск# - Кастомное действие "Назначить на меня"В следующем уроке — Django ORM и модели!