Ось готовий урок, створений у стилі CS50, спеціально для теми Django REST Framework: ViewSets та Routers.
🎓 CS50: ViewSets та Routers — Магія автоматизації
Привіт, друзі! 👋 Це CS50, і сьогодні ми перестанемо писати код. Ну, майже.
Ми поговоримо про те, як розробники втомлюються робити одне й те саме, і як вони вигадують інструменти, щоб працювати менше, а робити більше. Сьогоднішня тема — ViewSets та Routers. Це той момент у навчанні Django REST Framework (DRF), коли ви відчуєте себе трохи чарівниками.
1. 🔥 Вступ: Проблема та мотивація
Уявіть, що ви — архітектор великого житлового комплексу. До вас приходить замовник і каже: "Мені потрібен будинок". Ви креслите план, будуєте стіни, проводите світло, воду, газ. Потім приходить інший: "Мені теж потрібен будинок, такий самий, тільки адреса інша". Ви знову креслите, знову будуєте. Третій, четвертий, п'ятий...
У світі API ми робили це постійно. Пам'ятаєте APIView або GenericViews?
Для кожної сутності (наприклад, Книги) ми писали:
1. Клас для GET (список).
2. Клас для POST (створити).
3. Клас для GET (один об'єкт).
4. Клас для PUT (оновити).
5. Клас для DELETE (видалити).
А потім ми йшли у urls.py і вручну прописували кожен шлях: /books/, /books/<id>/.
Риторичне запитання: Чи не здається вам, що це порушує головний принцип програмування — DRY (Don't Repeat Yourself)? Якщо всі ці дії стандартні (CRUD), чому ми пишемо їх щоразу заново?
Сьогодні ми візьмемо цей "будівельний майданчик" і замінимо його на 3D-принтер, який друкує будинки автоматично.
2. 🧠 Теоретична база (без нудьги)
Давайте розберемо два поняття, які працюють у тандемі, як Бетмен і Робін.
🔹 1. ViewSet (Набір представлень)
Раніше ви думали методами HTTP: get, post, delete.
ViewSet пропонує думати діями:
* Замість "GET всіх записів" — ми кажемо list.
* Замість "POST нового запису" — ми кажемо create.
* Замість "GET одного запису" — ми кажемо retrieve.
* Замість "DELETE" — ми кажемо destroy.
Як це працює під капотом? ViewSet — це просто клас, який об'єднує логіку для всіх цих дій в одному місці. Це як швейцарський ніж: замість п'яти окремих викруток у вас є один інструмент з п'ятьма насадками.
🔹 2. Router (Маршрутизатор)
Якщо ViewSet — це сам будинок, то Router — це GPS-навігатор.
Раніше ви вручну писали в urls.py: "Якщо користувач йде на /books/, відправ його сюди".
Router робить це за вас. Ви просто кажете йому: "У мене є ViewSet для книг, зроби для нього URL-адреси". І він автоматично створює всі необхідні шляхи:
* GET /books/
* POST /books/
* GET /books/{id}/
* PUT /books/{id}/
* ...і так далі.
❗️ Запам'ятайте головне: * ViewSet описує ЩО ми робимо з даними (логіка). * Router описує ДЕ це знаходиться (URL-адреси).
3. 🧪 Приклади (Show me the code!)
Давайте подивимось на еволюцію коду. Уявімо, що ми робимо API для бібліотеки.
Крок 0: Як було раніше (Біль) 😫
(Не пишіть це, просто подивіться)
# views.py
class BookList(generics.ListCreateAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
class BookDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
# urls.py
urlpatterns = [
path('books/', BookList.as_view()),
path('books/<int:pk>/', BookDetail.as_view()),
]
Це коротко, але якщо у нас 10 моделей? Це 20 класів і 20 URL-адрес.
Крок 1: ModelViewSet (Магія) ✨
Що ви очікуєте побачити? Ймовірно, один клас замість двох.
# views.py
from rest_framework import viewsets
from .models import Book
from .serializers import BookSerializer
class BookViewSet(viewsets.ModelViewSet):
"""
Цей клас автоматично надає `list`, `create`, `retrieve`,
`update` та `destroy` дії.
"""
queryset = Book.objects.all()
serializer_class = BookSerializer
Стоп. Це все?
Так. ModelViewSet успадковує всю стандартну поведінку. Ми просто вказали звідки брати дані і як їх перетворювати в JSON.
Крок 2: Підключаємо Router 🗺️
Тепер urls.py. Замість ручного прописування шляхів:
# urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import BookViewSet
# Створюємо роутер
router = DefaultRouter()
# Реєструємо наш ViewSet (вказуємо префікс 'books')
router.register(r'books', BookViewSet)
# Включаємо згенеровані URL у список
urlpatterns = [
path('', include(router.urls)),
]
Що ми отримали? Якщо ви зараз запустите сервер, Router згенерував для вас не тільки шляхи, а й API Root — красиву сторінку, де ви бачите список усіх доступних ресурсів.
Крок 3: Трохи складніший приклад (Кастомна дія)
А що, якщо нам треба не просто стандартний CRUD, а, наприклад, "Позначити книгу як прочитану"? Стандартного методу немає.
Ми використовуємо декоратор @action.
from rest_framework.decorators import action
from rest_framework.response import Response
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
# detail=True означає, що дія стосується однієї конкретної книги (по ID)
@action(detail=True, methods=['post'])
def mark_as_read(self, request, pk=None):
book = self.get_object()
book.is_read = True
book.save()
return Response({'status': 'book marked as read'})
Тепер Router автоматично створить URL: POST /books/{id}/mark_as_read/.
Геніально, правда?
4. 🛠 Практична частина
Час забруднити руки кодом! Відкривайте IDE.
Завдання 1: База
Створіть просту модель Movie (title, year, director). Напишіть для неї MovieViewSet і підключіть через DefaultRouter. Перевірте в браузері, чи працюють усі операції (створення, видалення тощо).
Завдання 2: Тільки для читання
Змініть ModelViewSet на ReadOnlyModelViewSet.
Запитання: Що станеться, коли ви спробуєте відправити POST-запит? Чому це корисно для, наприклад, списку категорій товарів?
Завдання 3: Кастомна логіка
Додайте до MovieViewSet дію @action(detail=False), яка повертає тільки фільми, що вийшли після 2000 року. Назвіть метод modern_movies.
Підказка: URL має виглядати як /movies/modern_movies/.
Завдання 4: Міні-кейс
У вас є модель User. Використовуючи ViewSet, забороніть видалення користувачів (метод destroy), але залиште всі інші функції.
Підказка: Можна успадковуватися не від ModelViewSet, а від конкретних міксинів, або просто перевизначити метод destroy і кинути помилку.
Питання "А що, якщо..."
А що, якщо у нас два ViewSet-и (Книги і Автори) в одному файлі urls.py? Як це зробити з одним роутером? (Спробуйте зареєструвати обидва в одному об'єкті router).
5. 💡 Мислення як у розробника
Як досвідчений інженер дивиться на ViewSets?
-
Не використовуйте гармату для стрільби по горобцях. Якщо вам потрібен тільки метод GET для однієї конкретної сторінки (наприклад, "Про нас"), не треба робити ViewSet. Використовуйте простий
APIView. ViewSets ідеальні для ресурсів (бази даних), де потрібен стандартний набір операцій. -
Explicit is better than implicit (Іноді). Новачки часто губляться в "магії" ViewSet. "Звідки взявся цей метод?". Якщо логіка стає дуже складною і нестандартною, краще розписати методи вручну, щоб інший розробник (або ви через місяць) зрозуміли, що там відбувається.
-
DefaultRouter — ваш друг для налагодження. Він створює веб-інтерфейс (Browsable API). Це супер зручно для тестування без Postman на ранніх етапах.
6. 🧩 Підсумок
Сьогодні ми з вами:
* ✅ Зрозуміли, що писати одні й ті самі методи вручну — це нудно і неефективно.
* ✅ Дізналися, що ViewSet об'єднує всю логіку (CRUD) в один клас.
* ✅ Побачили, що Router автоматично будує карту URL-адрес.
* ✅ Навчилися додавати свої кнопки на цей пульт керування через @action.
Тепер ви вмієте: створювати повноцінний CRUD API за 5 хвилин, використовуючи менше 10 рядків коду.
Що далі? Зараз наш API відкритий для всіх. Будь-хто може видалити ваші книги чи фільми! Наступного разу ми поговоримо про Permissions (Права доступу) та Authentication. Ми поставимо охоронця на вході в наш будинок.
А поки що — це був CS50. Щасти вам у коді!