Оглавление

Как создать и запустить простое Django-приложение?

Как создать модель?

Работа из консоли (terminal)

Как создать миграцию

Как создать view?

Как создать URL?

Как вернуть список?

Как создать URL с параметрами?

Как вернуть данные для карточки объекта?

Как вернуть ошибку 404?

Как принять query-параметры из URL?

Как создать и запустить простое Django-приложение?

Работа из PyCharm Создаем новый проект и виртуальное окружение. PyCharm Community Edition


                    
# Установить poetry (опционально)
pip install poetry
poetry init

# Установить пакет Django 
poetry add django  # через poetry
pip install django  # с помощью pip

# Создать Django-приложение
django-admin startproject hunting .


# Создать app — Python-пакет с обособленный куском функционала 
# например, функционал работы с вакансиями
./manage.py startapp vacancies


# Запустить сервер
./manage.py runserver
                

Как создать модель?

**Модель** — это класс, который описывает все поля (столбцы), которые будут храниться в таблице в базе данных. Для того чтобы создать модель, нужно объяснить класс-наследник models.Model. Например, вот так будет выглядеть модель вакансии с текстовым полем text:


                    # models.py

class Vacancy(models.Model):
	text = models.CharField(max_length=1000)
                

Работа из консоли (terminal)

Можно создать Django приложение заранее из консоли, а затем уже открыть в PyCharm.Note: Виртуальное окружение можно создать любым удобным способом - ниже в примере команда для создания виртуального окружения с помощью `[virtualenvwrapper](https://virtualenvwrapper.readthedocs.io/en/latest/install.html)` (его предварительно надо установить).


                    # Создать виртуальное окружение
mkvirtualenv hanting

# Создать папку для приложения
mkdir hanting
cd hanting

# Установить poetry (опционально)
pip install poetry
poetry init

# Установить пакет Django 
poetry add django  # через poetry
pip install django  # с помощью pip

# Создать Django-приложение
django-admin startproject hunting .


# Создать app — Python-пакет с обособленный куском функционала 
# например, функционал работы с вакансиями
./manage.py startapp vacancies


# Запустить сервер
./manage.py runserver
                

Как создать миграцию

После того, как мы создали модель, необходимо создать и накатить миграцию — скрипт, который применит наши изменения на базе данных:


                                             
./manage.py makemigrations
./manage.py migrate
                

Как создать view?

View — это функция, которая определяется в файле views.py вашего app. Эта функция обязательно должна принимать первым параметром request.


                                      
# views.py

def index(request):
    pass
                

Как создать URL?

Для того чтобы создать URL в нашем веб-приложении, необходимо добавить его в файл urls.py.


                                                    
# urls.py

from django.contrib import admin
from django.urls import path

from vacancy import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('vacancy/', views.index)
]
                

Как вернуть список?

Для этого нужно сначала вытащить его из базы данных через ORM:


                                                    
# views.py

vacancies = Vacancy.objects.all()
                

После этого перевести полученный список в JSON и вернуть его, завернув в JsonResponse.


                                                    
response = []
for vacancy in vacancies:
		 response.append({
			   "id": vacancy.id, 
				 "text": vacancy.text,
		 })
		
return JsonResponse(response, safe=False)
                

Пример полной вью, которая возвращает список всех вакансий:


                                                    
# views.py

def index(request):
		# Вытаскиваем все объекты
    vacancies = Vacancy.objects.all()

		# Переводим в JSON
		response = []
		for vacancy in vacancies:
		   response.append({
		        "id": vacancy.id, 
						"text": vacancy.text,
		   })
		
		# Возвращаем JsonResponse
		return JsonResponse(response, safe=False)
                

Как создать URL с параметрами?

Для этого нужно указать параметр в URL в формате <тип:имя>. Например:


                                                    
# urls.py

from django.contrib import admin
from django.urls import path

from blog import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('vacancy/', views.index),
    path('vacancy/<int:vacancy_id>', views.get),
]

Доступны следующие типы: 

- `str` — любая непустая строка.
- `int` — 0 или любое положительное число.
- `slug` — строка из ASCII букв или чисел, а также дефисы и подчеркивание. Например, building-your-1st-django_site
- `uuid` — универсальный уникальный идентификатор. Например, 075194d3-6885-417e-a8a8-6c931e272f00
- `path` — непустая строка, включая /.
                

Во вью эти параметры передаются в качестве аргументов функции:


                                                    
# views.py

def get(request, vacancy_id):
    pass
                

Как вернуть данные для карточки объекта?

Для этого нужно создать URL с параметром pk.


                                                    
# urls.py

from django.contrib import admin
from django.urls import path

from blog import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('vacancy/', views.index),
    path('vacancy/<int:vacancy_id>', views.get),
]
                

А во вью достать данные с помощью ORM метода get.


                                                    
vacancy = Vacancy.objects.get(pk=vacancy_id)
                

Пример готовой view для вакансии:


                                                    
# views.py

def get(request, vacancy_id):
    if request.method == "GET":
        vacancy = Vacancy.objects.get(pk=vacancy_id)

        return JsonResponse({
            "id": vacancy.id,
            "text": vacancy.text,
        })
                

Как вернуть ошибку 404?

Для того чтобы вернуть ошибку 404 — объект не найден, нужно обработать исключение DoesNotExists.


                                                    
# views.py

def get(request, vacancy_id):
    if request.method == "GET":
        try:
            vacancy = Vacancy.objects.get(pk=vacancy_id)
        except Vacancy.DoesNotExist:
            return JsonResponse({"error": "Not found"}, status=404)

        return JsonResponse({
            "id": vacancy.id,
            "text": vacancy.text,
        })
                

Как принять query-параметры из URL?

Query-параметры не указываются в самом URL, а обрабатываются во view.


                    # Создать # views.py

search_text = request.GET.get("text", None) окружение
                

Пример вью, которая вернет переданный ей параметр text в JSON:


                                                    
# views.py

def get(request, vacancy_id):
    if request.method == "GET":
       search_text = request.GET.get("text", None)

       return JsonResponse({"text": search_text})
                

Оглавление

Глоссарий

Как сделать сохранение модели?

Как написать собственные class-based view?

Наследуемся от view

Настраиваем URL

Наследуемся от generic view

Какие есть поля в модели?

Как вывести модель в админку?

Глоссарий

Generic views — встроенные в Django классы, реализующие в себе базовые методы и шаблоны, упрощающие написание собственных views.

Как сделать сохранение модели?

## csrf_exempt В Django из коробки встроена защита от CSRF-атак. Это здорово, но мешает нам обращаться к нашему API со сторонних сервисов (например, тестировать через Postman). Эту проверку можно отключить с помощью декоратора `@csrf_exempt` вот так:


                                                            
# views.py
from django.views.decorators.csrf import csrf_exempt


@csrf_exempt
def index(request):
    if request.method == "GET":
        vacancies = Vacancy.objects.all()

        search_text = request.GET.get("text", None)
        if search_text:
            vacancies = vacancies.filter(text=search_text)

        response = []
        for vacancy in vacancies:
            response.append({
                "id": vacancy.id,
                "text": vacancy.text,
            })

        return JsonResponse(response, safe=False)
    elif request.method == "POST":
        vacancy_data = json.loads(request.body)

        vacancy = Vacancy()
        vacancy.text = vacancy_data["text"]

        vacancy.save()
        
        return JsonResponse({
            "id": vacancy.id,
            "text": vacancy.text,
        })
                        

Как написать собственные class-based view?

Для того чтобы написать собственные class-based view, нам нужно проделать действия, схожие с теми, что мы делали для function-based view: - написать класс, отнаследованный от view; - настроить URL для нашей view.

Наследуемся от view

Чтобы создать view, нужно: 1. Создать класс-наследник от view. 2. Переопределить нужные нам методы (get/post). 3. Добавить декоратор `@method_decorator(csrf_exempt, name='dispatch')`при необходимости.


                                                            
# views.py

import json

from django.http import JsonResponse
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt

from blog.models import Vacancy


@method_decorator(csrf_exempt, name='dispatch')
class VacancyView(View):
    def get(self, request):
        vacancies = Vacancy.objects.all()

        search_text = request.GET.get("text", None)
        if search_text:
            vacancies = vacancies.filter(text=search_text)

        response = []
        for vacancy in vacancies:
            response.append({
                "id": vacancy.id,
                "text": vacancy.text,
            })

        return JsonResponse(response, safe=False)

    def post(self, request):
        vacancy_data = json.loads(request.body)

        vacancy = Vacancy.objects.create(
            text=vacancy_data["text"],
        )

        return JsonResponse({
            "id": vacancy.id,
            "text": vacancy.text,
        })
                        

Настраиваем URL

Основное отличие настройки URL для class-based view: нам нужно вызвать у нашего класса метод as_view.


                                                            
# urls.py

urlpatterns = [
    path('admin/', admin.site.urls),
    path('vacancy/', VacancyView.as_view()),
    path('vacancy/<int:pk>/', VacancyDetailView.as_view()),
]
                        

Наследуемся от generic view

Работа с generic view сильно похожа на то, что мы делаем с view, но заранее готового в generic view больше, а значит, нашего кода меньше. Порядок действий: 1. Наследуемся от нужной view. 2. Определяем атрибут model. 3. Переопределяем необходимый метод. Также помним, что для доступа к объекту модели (для DetailView) доступен вызов `self.get_object()`, а для списка записей (для ListView) — `self.object_list`.


                                                            
# views.py

from django.http import JsonResponse
from django.views.generic import DetailView

from blog.models import Vacancy


class VacancyDetailView(DetailView):
    model = Vacancy

    def get(self, request, *args, **kwargs):
        try:
            vacancy = self.get_object()
        except Vacancy.DoesNotExist:
            return JsonResponse({"error": "Not found"}, status=404)

        return JsonResponse({
            "id": vacancy.id,
            "text": vacancy.text,
        })
                        

Какие есть поля в модели?

В Django предусмотрено множество видов полей, практически на все случаи жизни. Для удобства мы приготовили схему всех базовых полей, доступных «из коробки».

Как вывести модель в админку?

Для того чтобы наша модель появилась в панели администратора Django (еще одна прекрасная встроенная возможность)


                                                            
# admin.py 

admin.site.register(Vacancy)
                        

Оглавление

Глоссарий

PostgreSQL

Настройка Django-приложения

Связи между моделями

Many2Many

Как отобразить M2M в JSON вручную

Meta

Еще немного о GenericView

UpdateView

DeleteView

Обработка DoesNotExists

Глоссарий

PostgreSQL — реляционная база данных с открытым исходным кодом.

PostgreSQL

Есть два варианта установки PostgreSQL: 1. Установить к себе на компьютер по инструкции из [официальной документации](https://www.postgresql.org/download/). 2. Установить docker-контейнер с уже готовой и настроенной СУБД.


                                                        
docker run --name skypro-postgres -e POSTGRES_PASSWORD=postgres -d postgres
                    

Настройка Django-приложения

После того как мы установили PostgreSQL, необходимо также сказать нашему приложению способы обращения к базе. Делается это в файле settings.py.


                                                        
# settings.py

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',  
        'NAME': 'postgres',
        'USER': 'postgres',
        'PASSWORD': 'postgres',
        'HOST': 'localhost',
        'PORT': '5432',
    }
}

Описание полей:

- ENGINE — оставляем без изменений.
- NAME — название нашей базы (если ничего не меняли, оставляем как в примере).
- USER — имя пользователя (если ничего не меняли, оставляем как в примере).
- PASSWORD — пароль от нашего пользователя (если ничего не меняли, оставляем как в примере).
- HOST — URL, на котором развернута база (в нашем случае localhost).
- PORT — порт, на котором поднята СУБД (если ничего не меняли, оставляем как в примере).
                    

После всех манипуляций выше нам еще понадобится поставить отдельный пакет для работы с нашей СУБД.


                                                        
poetry add psycopg2
                    

Связи между моделями

Связь One2Many в Django реализована с помощью поля `ForeignKey`. При использовании этого поля необходимо указать два обязательных параметра: - модель, на которую будет ссылаться это поле (позиционный аргумент); - `on_delete` — действия при удалении записи, на которую мы ссылаемся. Например, вот так можно создать модель Parent, ссылающуюся на модель User:


                                                        
# models.py
from django.db import models


class Parent(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
                    

Many2Many

Связь Many2Many в Django реализована с помощью поля `ManyToManyField`. В отличие от `ForeignKey` у этого поля только один обязательный аргумент — модель, на которую мы будем ссылаться. Например, вот так мы в уроке создавали Many2Many связь между вакансией и скилами:


                                                        
# models.py
from django.db import models


class Vacancy(models.Model):
    skills = models.ManyToManyField(Skill)
                    

Как отобразить M2M в JSON вручную

Поскольку M2M-связь — это целый запрос в другую таблицу, то вывести содержимое поля, как мы это делали раньше, не получится. Для того чтобы при отображении JSON нашей модели сделать M2M-поле «читаемым», можно воспользоваться строчкой ниже.


                                                        
your_field = list(self.object.skills.all().values_list("name", flat=True))
                    

Meta

Помимо полей, в модели Django есть еще вложенный класс — `Meta`. Он используется для определения служебной информации о модели: - название таблицы в БД, - сортировка по умолчанию, - пары (или тройки, четверки и т. д.) уникальных полей - и т. д. Самое частое использование класса Meta — это определение русскоязычного названия нашей модели в админ-панели.


                                                        
# models.py
from django.db import models


class Skill(models.Model):
    name = models.CharField(max_length=20)
    
    class Meta:
        verbose_name = "Навык"
        verbose_name_plural = "Навыки"
                    

Еще немного о GenericView

В прошлом уроке мы начали изучать GenericView. Ниже — примеры того, как написать CreateView, UpdateView и DeleteView.


                                                        
СreateView

# models.py
from django.db import models


class Skill(models.Model):
    name = models.CharField(max_length=20)
    
    class Meta:
        verbose_name = "Навык"
        verbose_name_plural = "Навыки"
                    

UpdateView


                        #                                 
# views.py

@method_decorator(csrf_exempt, name='dispatch')
class VacancyUpdateView(UpdateView):
    model = Vacancy
    fields = ["slug", "text", "status", "skills"]

    def post(self, request, *args, **kwargs):
        super().post(request, *args, **kwargs)

        vacancy_data = json.loads(request.body)
        self.object.slug = vacancy_data["slug"]
        self.object.text = vacancy_data["text"]
        self.object.status = vacancy_data["status"]

        try:
            self.object.full_clean()
        except ValidationError as e:
            return JsonResponse(e.message_dict, status=422)

        self.object.save()
        return JsonResponse({
            "id": self.object.id,
            "user_id": self.object.user_id,
            "slug": self.object.slug,
            "text": self.object.text,
            "status": self.object.status,
            "skills": self.object.skills,
            "created": self.object.created,
        }) виртуальное окружение
                    

DeleteView


                                                        
# views.py 

@method_decorator(csrf_exempt, name='dispatch')
class VacancyDeleteView(DeleteView):
    model = Vacancy
    success_url = "/"

    def delete(self, request, *args, **kwargs):
        super().delete(request, *args, **kwargs)

        return JsonResponse({"status": "ok"}, status=200)
                    

Обработка DoesNotExists

DoesNotExists — это ошибка, которая возникает в Django, когда искомая нами запись не существует. Обработка этой ошибки — достаточно типичная и рутинная работа в веб-разработке. Как и для всего рутинного, в Django есть готовые решения: - get_or_create — этот метод возвращает нам запись либо же создает ее, если возникла ошибка. - update_or_create — этот метод обновляет запись в БД или же создает новую, если по указанным параметрам ничего не было найдено. - get_object_or_404 — этот метод возвращает запись или «кидает» ошибку 404.


                                                        
Например, вот так мы с вами переписали в уроке работу со skill:

for skill in vacancy_data["skills"]:
    skill_obj, _ = Skill.objects.get_or_create(name=skill)
    self.object.skills.add(skill_obj)
                    

Оглавление

Сортировка

Limit и offset

Пагинация

Группировка

Aggregate

Join

Сортировка

Для сортировки в Django ORM используется метод order_by. Он принимает параметром названия столбцов через запятую. Если необходимо сортировать по убыванию, просто поставь перед название столбца “-”.

### По умолчанию Так же в Django есть возможность настроить сортировку по умолчанию: эта сортировка будет применяться ко всем запросам написанным для данной модели. Настраивается такая сортировка через параметр `ordering` класса `Meta`


                        # views.py

self.object_list = self.object_list.order_by('name')
                    

                        # models.py 


class Vacancy(models.Model):
    # YOUR CODE HERE

    class Meta:
        ordering = ['name']
                    

Limit и offset

SQL выражения limit и offset в Django ORM реализованы с помощью slice:


                        # views.py

# вот так можно вытащить первые 10 записей
self.object_list = Vacancy.objects.all()[:10]

# а вот так 5 записей начиная со 2
self.object_list = Vacancy.objects.all()[2:5]
                    

Пагинация

Настроить пагинацию самостоятельно можно в 2 простых шага: 1. Добавляем в настройки проекта константу с количеством записей на странице

Добавляем в отображение списка обработку страниц


                        # settings.py 

TOTAL_ON_PAGE = 10
                    

                        # views.py 

class VacancyListView(ListView):
    model = Vacancy

    def get(self, request, *args, **kwargs):
        # YOUR CODE HERE

				# считаем сколько всего записей в таблице
        total_vacancies = self.object_list.count()

				# сохраняем номер страницы, который от нас требуется
        page = int(request.GET.get("page", 0))
				# высчитываем с какого по какой объект нужно взять на страницу
        offset = page * settings.TOTAL_ON_PAGE
        if offset > total_vacancies:
            self.object_list = []
        elif offset:
            self.object_list = self.object_list[offset:settings.TOTAL_ON_PAGE]
        else:
            self.object_list = self.object_list[:settings.TOTAL_ON_PAGE]

        vacancies = []
        for vacancy in self.object_list:
            vacancies.append({
                "id": vacancy.id,
                "name": vacancy.name,
                "text": vacancy.text,
            })

				# добавляем в ответ параметры в общим числом записей и 
				# количеством записей на странице
        response = {
            "items": vacancies,
            "total": total_vacancies,
            "per_page": settings.TOTAL_ON_PAGE,
        }
        return JsonResponse(response, safe=False)
                    

Группировка

Для группировки в Django есть два метода: `annotate` и `aggregate` ### Annotate Это метод добавляет к итоговой таблице колонку, где для каждой строки будет посчитан результат. Например вот так можно посчитать количество вакансий у каждого юзера:


                        # views.py 

# здесь будет храниться QS, в котором все поля модели User + поле vacancies
users_qs = User.objects.annotate(vacancies=Count('vacancy'))
                    

Aggregate

А этот метод посчитает общее значение по всей таблице и выдаст одну строку. Например вот так можно посчитать максимальную цену книги:


                        # views.py

# здесь будет храниться словарь вида {'price__max': 81}
max_price = Book.objects.all().aggregate(Max('price'))
                    

Join

По умолчанию Django не делает join, поэтому если вы знаете, что будете обращаться к колонкам связанной модели, необходимо самостоятельно вызвать метод `select_related`, в который передать список моделей. Так же, Django умеет заранее запрашивать данные из моделей со связью m2m, для этого необходимо вызвать метод `prefetch_related` Например вот так мы с вами в уроке подтягивали для вакансии данные об авторе и скилах:


                        # views.py

self.object_list = self.object_list.select_related('user').prefetch_related('skills')
                    

Оглавление

Глоссарий

Как установить DRF

Зарегистрировать приложение в settings.py

Как выбрать Generic View

Как написать ListView

Как вернуть в ListView пагинацию

Как написать RetrieveView

Как написать RetrieveView по slug

Как написать UpdateView

Виды Serializer-классов

ModelSerializer (для всех полей)

ModelSerializer (для некоторых полей)

ModelSerializer (с исключением полей)

Serializer для связанной модели skills (по id)

Serializer для связанной модели skills (по name)

Serializer для связанной модели skills (через связанную модель)

Глоссарий

**DRF** — фреймворк поверх Django, с помощью которого можно очень удобно писать REST API на Python. **GenericView** — готовые и простые в использовании классы из DRF. Мы применяем их, чтобы написать view для какого-то конкретного URL. **Mixin** — класс-примесь, который реализует один нужный нам метод. **Serializer** — класс, который превращает JSON в Python-объект и, наоборот, Python-объект в JSON.

Как установить DRF


                                                      
poetry add djangorestframework
                    

Зарегистрировать приложение в settings.py


                                                                         
# settings.py

INSTALLED_APPS = [
		# тут стандартные apps из джанго 
    'rest_framework',
		# тут ваши apps
]
                    

Как выбрать Generic View


                                                                         
CreateAPIView => Cоздать запись
ListAPIView => Вытащить все записи
RetrieveAPIView => Вытащить одну запись
DestroyAPIView => Удалить одну запись
UpdateAPIView => Изменить одну запись
ListCreateAPIView => Вытащить все записи и создать
RetrieveUpdateAPIView => Вытащить одну запись и обновить
RetrieveDestroyAPIView => Вытащить одну запись и удалить
RetrieveUpdateDestroyAPIView => Вытащить одну запись, обновить и удалить
                    

Как написать ListView


                                                                         
# views.py

from rest_framework.generics import ListAPIView

class VacancyListView(ListAPIView):
    queryset = Vacancy.objects.all()
    serializer_class = VacancySerializer
                    

Как вернуть в ListView пагинацию


                                                                         
# settings.py

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 10
}
                    

Как написать RetrieveView


                                                                         
# views.py

from django.views.generic import RetrieveAPIView

class VacancyDetailView(RetrieveAPIView):
    queryset = Vacancy.objects.all()
    serializer_class = VacancySerializer
                    

Как написать RetrieveView по slug


                                                                         
# views.py

from django.views.generic import RetrieveAPIView

class VacancyDetailView(RetrieveAPIView):
    queryset = Vacancy.objects.all()
    serializer_class = VacancySerializer
		lookup_field = 'slug_field'
                    

Как написать UpdateView


                                                                         
# views.py

from django.views.generic import UpdateAPIView

class VacancyUpdateView(UpdateAPIView):
    queryset = Vacancy.objects.all()
    serializer_class = VacancySerializer
                    

Виды Serializer-классов

### Serializer для простого Python-объекта Используем, когда данные не привязаны к модели.


                                                                         
# serializers.py

class VacancySerializer(serializers.Serializer):
    id = serializers.IntegerField()
    name = serializers.CharField(max_length=50)
    text = serializers.CharField(max_length=1000)
    username = serializers.CharField()
                    

ModelSerializer (для всех полей)

Используем, когда нужны все поля модели.


                                                                         
# serializers.py

class VacancySerializer(serializers.ModelSerializer):

    class Meta:
        model = Vacancy
        fields = '__all__'
                    

ModelSerializer (для некоторых полей)

Используем, когда нужно только несколько определенных полей в модели.


                                                                         
# serializers.py

class VacancySerializer(serializers.ModelSerializer):

    class Meta:
        model = Vacancy
        fields = ['id', 'name', 'text', 'username']
                    

ModelSerializer (с исключением полей)

Используем, когда нужны все поля модели, кроме некоторых.


                                                                         
# serializers.py

class VacancyCreateSerializer(serializers.ModelSerializer):
    class Meta:
        model = Vacancy
        exclude = ["id", "skills"]
                    

Serializer для связанной модели skills (по id)


                                                                         
# serializers.py

class VacancySerializer(serializers.ModelSerializer):
    class Meta:
        model = Vacancy
        fields =['id','name','text','username', 'skills']
                    

Serializer для связанной модели skills (по name)


                                                                         
# serializers.py

class VacancySerializer(serializers.ModelSerializer):
    skills = serializers.SlugRelatedField(many=True,
        read_only=True,
        slug_field='name'
    )

    class Meta:
        model = Vacancy
        fields = ['id', 'name', 'text', 'username']
                    

Serializer для связанной модели skills (через связанную модель)


                                                                         
# serializers.py

class SkillsSerializer(serializers.ModelSerializer):
    class Meta:
        model = Skill
        fields = '__all__'


class VacancySerializer(serializers.ModelSerializer):
    skills = SkillsSerializer(many=True)

    class Meta:
        model = Vacancy
        fields = ['id', 'name', 'text', 'username', 'skills']
                    

Оглавление

Глоссарий

ViewSets

Разновидности ViewSets в DRF

Как написать ViewSet

Как написать GenericViewSet

Как написать ModelViewset

Стандартные методы в ViewSet

Как настроить URL для ViewSet

Поиск с помощью ORM

Lookup для связей

Q-запросы

F-запросы

Цепочки запросов

Глоссарий

**ViewSet** — класс, который содержит в себе все базовые API-методы для одной модели. **Router** — класс, который помогает превратить ViewSet в набор URL. **Lookup fields** — ключевые слова в Django ORM для формирования WHERE-блока SQL-запроса. **Q-запросы** — запросы с использованием класса Q(), пример для использования ИЛИ в WHERE-блоке SQL-запроса. **F-запросы** — запросы с использованием класса F() для обращения к значению текущего столбца.

ViewSets

Как это подключить, чтобы заработало?


                                                                         
# views.py

# from rest_framework.viewsets import <нужный вам viewset>
from rest_framework.viewsets import ModelViewSet
                    

Разновидности ViewSets в DRF


                                                                         
Класс, в котором нет готовых action-методов (самый базовый) => ViewSet
Класс, в котором есть несколько полезных готовых методов, но нет action => GenericViewSet
Штука, которая содержит в себе базовые методы работы с модельками => ModelsViewSet
Класс, который годится только для чтения данных => GenericViewSet ReadOnlyViewSet
                    

Как написать ViewSet


                                                                         
# views.py

from rest_framework import viewsets


class UserViewSet(viewsets.ViewSet):
    def list(self, request):
				queryset = User.objects.all()
        serializer = UserSerializer(queryset, many=True)
        return Response(serializer.data)

    def create(self, request):
        serializer = UserSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        serializer.save(serializer)

        return Response(serializer.data, status=status.HTTP_201_CREATED)


    def retrieve(self, request, pk=None):
				queryset = User.objects.all()
        user = get_object_or_404(queryset, pk=pk)
        serializer = UserSerializer(user)
        return Response(serializer.data)

    def update(self, request, pk=None):
				queryset = User.objects.all()
        user = get_object_or_404(queryset, pk=pk)

        serializer = UserSerializer(user, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)
        serializer.save(serializer)

        return Response(serializer.data)

    def partial_update(self, request, pk=None):
        kwargs['partial'] = True
        return self.update(request, *args, **kwargs)

    def destroy(self, request, pk=None):
        queryset = User.objects.all()
        user = get_object_or_404(queryset, pk=pk)
        user.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)
                    

Как написать GenericViewSet


                        # views.py

                                                     
from rest_framework import viewsets


class ItemViewSet(viewsets.GenericViewSet):
    serializer_class = ItemSerializer
    queryset = Item.objects.all()

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        serializer.save(serializer)

        return Response(serializer.data, status=status.HTTP_201_CREATED)

    def list(self, request):
        serializer = self.get_serializer(self.get_queryset(), many=True)
        return self.get_paginated_response(self.paginate_queryset(serializer.data))

    def retrieve(self, request, pk):
        item = self.get_object()
        serializer = self.get_serializer(item)
        return Response(serializer.data)

    def destroy(self, request):
        item = self.get_object()
        item.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)
                    

Как написать ModelViewset


                                            
# views.py

class SkillViewSet(ModelViewSet):
    queryset = Skill.objects.all()
    serializer_class = SkillSerializer
                    

Стандартные методы в ViewSet


                        
.list() => Выводит список всех записей из модели
.retrieve() => Выводит одну запись из модели по ключу
.create() => Создает запись 
.update() => Обновляет все поля записи 
.partial_update() => Обновляет только переданные поля записи
.destroy() => Удаляет запись по ключу
                    

Как настроить URL для ViewSet


                                                        
# urls.py

from rest_framework import routers


router = routers.SimpleRouter()
router.register('skill', SkillViewSet)

urlpatterns = [
    # тут все остальные маршруты
]

urlpatterns += router.urls
                    

Поиск с помощью ORM

Lookup для связей


                                                        
# views.py

from django.db.models import Q

class VacancyListView(ListAPIView):
    queryset = Vacancy.objects.all()
    serializer_class = VacancySerializer

    def get(self, request, *args, **kwargs):
				skill_name = request.GET.getlist("skill", None)
        
				self.queryset = self.queryset.filter(
            skills__name__contains=skill_name
        )

				....
                    

Q-запросы


                                                        
# views.py

from django.db.models import Q

class VacancyListView(ListAPIView):
    queryset = Vacancy.objects.all()
    serializer_class = VacancySerializer

    def get(self, request, *args, **kwargs):
				skills = request.GET.getlist("skill", None)
        skills_q = None
        for skill_name in skills:
            if not skills_q:
                skills_q = Q(skills__name__contains=skill_name)
            else:
                skills_q |= Q(skills__name__contains=skill_name)
        if skills_q:
            self.queryset = self.queryset.filter(skills_q)

				...
                    

F-запросы


                                                        
# views.py

from django.db.models import F


class ReporterUpdateView(UpdateAPIView):
    queryset = Reporter.objects.all()
    serializer_class = ReporterSerializer

    def patch(self, request, *args, **kwargs):
				reporter = Reporters.objects.get(name='Tintin')
				reporter.stories_filed = F('stories_filed') + 1
				reporter.save()
				
				...
                    

Цепочки запросов


                                                        
# views.py

class VacancyLikeView(ListModelMixin, UpdateAPIView):
    queryset = Vacancy.objects.all()
    serializer_class = VacancySerializer

    def put(self, request, *args, **kwargs):
        Vacancy.objects.filter(pk__in=request.data).update(likes=F('likes') + 1)

				...
                    

Оглавление

Как создать своего пользователя

Как сделать регистрацию

Авторизация по токену в DRF

Реализация «выхода»

Авторизация по JWT

# Как отправить запрос с токеном ## AuthToken

JWT

Как создать своего пользователя

**Шаг 0 (если уже были применены миграции)** Отменяем миграции для auth.


                                                        
./manage.py migrate auth zero
                    

Наследуемся от AbstactUser (не забудьте переписать save!).


                        # models.py 

from django.db import models
from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    MALE = "m"
    FEMALE = "f"
    SEX = [(MALE, "Male"), (FEMALE, "Female")]

    sex = models.CharField(max_length=1, choices=SEX)
                    

NOTE: не забудьте, что при создании такого пользователя, необходимо будет вызвать метод set_password. Например, в сериализаторе:


                                                        
# serializer.py

class UserCreateSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = '__all__'

    def create(self, validated_data):
        user = User.objects.create(**validated_data)
        
        user.set_password(validated_data["password"])
        user.save()

        return user
                    

Переопределяем модель user для всего приложения.


                        # settings.py

AUTH_USER_MODEL = 'authentification.User'
                    

Применяем миграции.


                        ./manage.py makemigrations
./manage.py migrate
                    

Как сделать регистрацию

Регистрация — это обычное сохранение модели пользователя.


                        # serializers.py

from rest_framework import serializers

from authentication.models import User


class UserCreateSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = '__all__'
                    

                        # urls.py

from django.urls import path

from authentication.views import UserCreateView

urlpatterns = [
    path('create/', UserCreateView.as_view()),
]
                    

                        # views.py 

from rest_framework.generics import CreateAPIView

from authentification.models import User
from authentification.serializers import UserCreateSerializer


class UserCreateView(CreateAPIView):
    model = User
    serializer_class = UserCreateSerializer
                    

Авторизация по токену в DRF

Добавляем приложение AuthToken.


                        # settings.py 


INSTALLED_APPS = [
                  
    'rest_framework',
    'rest_framework.authtoken',

]
                    

Далее накатываем миграции. Настраиваем приложение на проверку токена.


                        # settings.py 

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
    ]
}
                    

Добавляем ручки для авторизации.


                        # urls.py
from rest_framework.authtoken import views

urlpatterns = [
		...,
    path('login/', views.obtain_auth_token)
]
                    

Реализация «выхода»


                        # views.py

class Logout(APIView):
    def get(self, request, format=None):
        request.user.auth_token.delete()
        return Response(status=status.HTTP_200_OK)
                    

Авторизация по JWT

Устанавливаем пакет djangorestframework-simplejwt.


                        poetry add  djangorestframework-simplejwt
                    

Добавляем библиотеку в приложение и настраиваем его на проверку JWT.


                        # settings.py 

INSTALLED_APPS = [

    'rest_framework_simplejwt',

]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ]
}
                    

Добавляем ручки.


                        # urls.py

urlpatterns = [
		...,
    path('token/', TokenObtainPairView.as_view()),
    path('token/refresh/', TokenRefreshView.as_view()),
]
                    

# Как отправить запрос с токеном ## AuthToken

В заголовок запроса добавить header.


                        Authorization: Token <your_token>
                    

JWT

В заголовок запроса добавить header.


                        Authorization: Bearer <your_token>
                    

Оглавление

Глоссарий

Как защитить view авторизацией

Как добавить пользователю роль

Как написать свой Permission

Глоссарий

Permission — класс, который отвечает за проверку доступа пользователя к какой-то функциональности, например, чтению сообщений или редактированию пользователей.

Как защитить view авторизацией

Заполнить во view атрибут permission_classes.


                        # views.py 

from rest_framework.permissions import IsAuthenticated

class VacancyDetailView(RetrieveAPIView):
    queryset = Vacancy.objects.all()
    serializer_class = VacancySerializer
    permission_classes = [IsAuthenticated]
                    

Как добавить пользователю роль

В Django есть своя ролевая модель, реализованная через Groups, но ею редко пользуются, предпочитая писать свои роли.


                        # models.py

class User(AbstractUser):

    UNKNOWN = "unknown"
    EMPLOYEE = "employee"
    HR = "hr"
    ROLE = [(UNKNOWN, "unknown"), (EMPLOYEE, "employee"), (HR, "hr")]

    role = models.CharField(max_length=8, choices=ROLE, default=UNKNOWN)
                    

Как написать свой Permission

- Отнаследоваться от `permissions.BasePermission`. - Переопределить `has_permission` и вернуть значение boolean.


                        # permissions.py 
from rest_framework import permissions

from authentification.models import User


class VacancyCreatePermission(permissions.BasePermission):
    message = 'Adding vacancies for non hr user not allowed.'

    def has_permission(self, request, view):
        if request.user.role != User.HR:
            return False
        return True
                    

Оглавление

Валидация модели

Доступные атрибуты

Дополнительные классы

Кастомная

Валидация в сериализаторах

Кастомная

Проверка через функцию

Проверка через класс

Валидация модели

В Django встроено два варианта валидации: с помощью атрибутов и с помощью дополнительных классов. Ниже мы рассмотрим пример каждой из них.

Доступные атрибуты

Мы подготовили для вас таблицу-шпаргалку всех доступных атрибутов полей моделей.

Вы уже умеете применять этот тип валидаторов. Например, мы часто использовали его, когда ограничивали количество символов в текстовом поле модели.


                            # models.py 
from django.db import models


class Post(models.Model):
   title = models.CharField(max_length=100)
   slug = models.SlugField(max_length=20)
   text = models.CharField(max_length=1000)
                        

Дополнительные классы

Если среди атрибутов не нашлось ничего, что подходило бы под задачи, можно посмотреть классы валидаторов вот здесь: [https://docs.djangoproject.com/en/4.0/ref/validators/#built-in-validators](https://docs.djangoproject.com/en/4.0/ref/validators/#built-in-validators). Используются они следующим образом:


                            #models.py 

from django.core.validators import MinValueValidator

class Vacancy(models.Model):
    
    
    min_experience = models.IntegerField(null=True, validators=[MinValueValidator(0)])
                        

Кастомная

Если совсем ничего не подошло, можно написать собственный валидатор.


                            Валидатор – это метод, принимающий value — значение поля, которое мы хотим сохранить, и возвращающий ValidationError в случае ошибки. 
                        

Подключается валидатор так же, как и классы валидаторов выше: через атрибут validators.


                            # models.py 
from datetime import date

def check_date_not_past(value: date):
    if value < date.today():
        raise ValidationError(
            '%(value)s is in the past',
            params={'value': value},
        )


class Vacancy(models.Model):
    
    created = models.DateField(auto_now_add=True, validators=[check_date_not_past])
                        

Валидация в сериализаторах

## Встроенная Поля валидаторов, как и поля моделей, имеют собственные атрибуты для проверки данных.


                            # serializers.py

class VacancySerializer(serializers.Serializer):
    id = serializers.IntegerField()
    name = serializers.CharField(max_length=50)
    text = serializers.CharField(max_length=1000)
    username = serializers.CharField()
                        

Полный список атрибутов можно посмотреть здесь: [https://www.django-rest-framework.org/api-guide/fields/#core-arguments](https://www.django-rest-framework.org/api-guide/fields/#core-arguments). Однако в случае с валидаторами моделей все атрибуты наследуются из модели, поэтому переписывать их не имеет смысла. Как и у моделей, у валидаторов есть дополнительные классы для проверки данных.


                            # serializers.py 

class VacancyCreateSerializer(serializers.ModelSerializer):
    slug = serializers.SlugField(
        validators=[UniqueValidator(queryset=Vacancy.objects.all())]
    )

    class Meta:
        model = Vacancy
        exclude = ["id"]
                        

Кастомная

Если встроенных возможностей не хватило, можно написать свою проверку.

Проверка через функцию

Валидаторы поддерживают методы проверок, аналогичные тем, что мы писали в моделях.


                            # serializers.py

def even_number(value):
    if value % 2 != 0:
        raise serializers.ValidationError('This field must be an even number.')

class VacancySerializer(serializers.Serializer):
    id = serializers.IntegerField(validators=[even_number])
    name = serializers.CharField(max_length=50)
    text = serializers.CharField(max_length=1000)
    username = serializers.CharField()
                        

Проверка через класс

Также в валидаторах появляется возможность писать проверки классами: для этого нужно определить метод __call__. Он должен принимать параметр value и возвращать serializers.ValidationError в случае ошибки (как и в методе-валидаторе). Помимо метода __call__, можно еще определить __init__, что позволяет нам передавать в валидатор дополнительные параметры.


                            # serializers.py 

class NotInStatusValidator:
    def __init__(self, statuses):
        if not isinstance(statuses, list):
            statuses = [statuses]

        self.statuses = statuses

    def __call__(self, value):
        if value in self.statuses:
            raise serializers.ValidationError("Incorrect status")


class VacancyCreateSerializer(serializers.ModelSerializer):
    slug = serializers.SlugField(
        validators=[UniqueValidator(queryset=Vacancy.objects.all(), lookup='contains')]
    )
    status = serializers.CharField(validators=[NotInStatusValidator(["open", "closed"])])

    class Meta:
        model = Vacancy
        exclude = ["id"]
                        

Оглавление

Глоссарий

Настройка окружения

Как отправить запрос на ручку?

Как написать свою фикстуру?

Как работать с базой данных?

Фабрики

Как создать фабрику?

Как пользоваться фабрикой?

Как создавать пользователей в тестах?

@pytest.mark.parametrize

Структура проекта с тестами

Глоссарий

Фабрика — класс, на основе которого будут генерироваться и предзаполняться данными модели в тестах. Фикстуры — блок кода, обернутый в декоратор `@pytest.fixture()`, который затем можно использовать как аргумент для функции-теста. Фабрики также могут использоваться в качестве фикстур. pytest-django — python-пакет для работы с pytest в Django. pytest-factoryboy — python-пакет для создания фабрик в Django. Параметризация — использование переменных в коде тестов вместо конкретных значений.

Настройка окружения

Прежде чем начать тестировать Django-приложение, необходимо поставить пакет для тестирования.


                            poetry add pytest-django
                        

Затем нужно настроить pytest, добавив файл pytest.ini с содержимым, описанным ниже, в корень проекта.


                            # pytests.ini 

[pytest]
DJANGO_SETTINGS_MODULE = hanting.settings
                        

Как отправить запрос на ручку?

Для того чтобы имитировать запросы пользователей в pytest-django, есть специальная фикстура client, которая умеет делать запросы.


                            # simple_test.py 

def test_root_not_found(client):
    response = client.get("/")
    assert response.status_code == 404
                        

Как написать свою фикстуру?

Чтобы написать свою фикстуру, нужно создать функцию в файле fixtures.py, добавить ей декоратор @pytest.fixture() и подключить ее в conftest.py, как показано ниже.


                            # fixtures.py

import pytest


@pytest.fixture()
def my_fixture():
    return 1
                        

                            # conftest.py 

pytest_plugins = "tests.fixtures"
                        

                            # test.py

def test_fixture(my_fixture):
		assert my_fixture == 1
                        

А еще можно писать фикстуры сразу в conftest.py.


                            # conftest.py 

import pytest


@pytest.fixture()
def my_fixture():
    return 1
                        

                            # test.py

def test_fixture(my_fixture):
		assert my_fixture == 1
                        

Как работать с базой данных?

Для того чтобы в тесте сохранять данные в БД и проверять, сохранились они или нет, необходимо добавить декоратор @pytest.mark.django_db. Ниже — пример теста на сохранение данных в БД.


                            # vacancy_create_test.py 
from datetime import datetime

import pytest

from vacancies.models import Vacancy

@pytest.mark.django_db
def test_vacancy_detail(client):
    vacancy = Vacancy.objects.create(
        slug="123",
        name="123"
    )

    expected_response = {
        "id": vacancy.pk,
        "slug": "123",
        "name": "123"
    }

    response = client.get(f"/vacancy/{vacancy.pk}/")

    assert response.status_code == 200
    assert response.data == expected_response
                        

Фабрики

Для того чтобы не писать сохранение моделей каждый раз, используются фабрики моделей. Для их созданий потребуется дополнительный пакет.


                            poetry add pytest-factoryboy
                        

Как создать фабрику?

Чтобы создать фабрику, нужно унаследовать класс factory.django.DjangoModelFactory, прописать, для какой модели эта фабрика и какие поля мы хотим определить по умолчанию.


                            # factories.py

import factory

from vacancies.models import Vacancy


class VacancyFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Vacancy

    slug = "test"
    name = "test"
    text = "test text"
                        

Как пользоваться фабрикой?

Можно использовать фабрику как фикстуру. Для этого необходимо подключить ее в conftest, как показано ниже.


                            # conftest.py

from pytest_factoryboy import register

from tests.factories import SkillFactory


# Factories
register(VacancyFactory)
                        

Использование фикстурой представлено ниже.


                            from datetime import datetime

import pytest


@pytest.mark.django_db
def test_vacancy_detail(client, vacancy):
    expected_response = {
        "id": vacancy.pk,
        "created": datetime.now().strftime("%Y-%m-%d"),
        "skills": [],
        "slug": "test",
        "name": "test",
        "text": "test text",
        "status": "draft",
        "is_archived": False,
        "min_experience": None,
        "likes": 0,
        "user": None
    }

    response = client.get(f"/vacancy/{vacancy.pk}/")

    assert response.status_code == 200
    assert response.data == expected_response
                        

Еще можно использовать фабрику напрямую, как мы это делали в тесте на список.


                            # vacancy_list_test.py

import pytest

from tests.factories import VacancyFactory
from vacancies.serializers import VacancySerializer


@pytest.mark.django_db
def test_list_view(client):
    vacancies = VacancyFactory.create_batch(10)

    response = client.get("/vacancy/")

    assert response.status_code == 200
    assert response.data == VacancySerializer(vacancies, many=True).data
                        

Как создавать пользователей в тестах?

Чтобы создать пользователя в тестах, нам понадобится фикстура django_user_model. Ниже увидим пример из урока, где мы создавали пользователя с ролью «hr» и получали его токен.


                            @pytest.fixture()
@pytest.mark.django_db
def hr_token(client, django_user_model):
    username = "hr"
    password = "123qwe"

    django_user_model.objects.create_user(
				username=username, password=password, role="hr")
    
		response = client.post(
				"/auth/login/", 
				{"username": username, "password": password}, 
				format='json'
		)

    return response.data["token"]
                        

                            # vacancy_create_test.py


@pytest.mark.django_db
def test_create_vacancy(client, hr_token):
    expected_response = {
        "slug": "123",
        "name": "123"
    }

    Skill.objects.create(name="test")
    response = client.post("/vacancy/create/", data={
        "slug": "123",
        "name": "123"
    }, HTTP_AUTHORIZATION="Token " + hr_token)

    assert response.status_code == 201
    assert response.data == expected_response
                        

@pytest.mark.parametrize

@pytest.mark.parametrize — декоратор, который помогает сократить количество кода в тестах. Он используется тогда, когда у нас есть несколько тестов с одинаковым кодом, который отличается только значением нескольких параметров. Представленные ниже куски кода идентичны и показывают, как работает этот декоратор.


                            @pytest.mark.parametrize("test_input,expected", [("1+1", 2), ("1+2", 3)])
def test_eval(test_input, expected):
    assert eval(test_input) == expected
                        

                            def test_eval_1():
    assert eval(“1+1”) == 2

def test_eval_2():
    assert eval(“1+2”) == 3
                        

Структура проекта с тестами

Для более легкого понимания также прикладываем пример полной структуры дерева проекта с тестами.

Оглавление

## Базовая настройка документации ### Установка пакетов

Настройка URL

Кастомизация

Кастомизация ViewSet

## Базовая настройка документации ### Установка пакетов

Есть два самых популярных приложения для оформления документации: drf-yasg и drf-spectacular. Оба поддерживают Swagger и ReDoc, однако drf-spectacular более новый и поддерживает формат OpenAPI 3.0, поэтому в нашем курсе рассматриваем его. Устанавливается он как обычный python-пакет.


                            poetry add drf-spectacular
                        

После установки нужно добавить приложение в INSTALLED_APPS в файл settings.py и настроить константу DEFAULT_SCHEMA_CLASS, как показано ниже:


                            # settings.py 

INSTALLED_APPS = [
    # YOUR APPS
    'drf_spectacular',
]


# YOUR SETTINGS



REST_FRAMEWORK = {
    # OTHER REST FRAMEWORK SETTINGS 
    'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}
                        

Больше ничего делать не Нужно. Но, если хочется, можно еще переопределить базовые заголовки нашей документации.


                            # settings.py

SPECTACULAR_SETTINGS = {
    'TITLE': 'Hunting API',
    'DESCRIPTION': 'Awesome hunting project',
    'VERSION': '1.0.0',
}
                        

Настройка URL

Для того чтобы настроить URL документации, нужно прописать не один, а два URL: - Первый (в нашем примере  ’api/schema/’) — URL, по которому будет отдаваться схема OpenAPI. - Второй (в нашем примере ‘api/schema/swagger-ui/’) — URL, по которому будет отдаваться сама документация. Обратите также внимание на параметр `url_name='schema'` в вызове `SpectacularSwaggerView.as_view`. Он должен совпадать с именем URL схемы OpenAPI.


                            # urls.py

from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView


urlpatterns = [
		# YOUR ROUTES HERE

    path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
    path('api/schema/swagger-ui/', 
					SpectacularSwaggerView.as_view(url_name='schema'), 
					name='swagger-ui'),
]
                        

Кастомизация

### Кастомизация View Для переопределения чего-то в сгенерированной по методу View документации используется декоратор `@extend_schema`. Он умеет переопределять практически всё. Из самого важного: - request определяет Serializer для входящих данных (если автоматически определился не тот, что нужен). - responses определяет список возможных Serializer для ответа. - description определяет описание метода в документации. - summary определяет короткое описание метода. - deprecated отмечает, что метод устарел и будет удалён в ближайшее время. Пример использования:


                            # views.py 

class VacancyListView(ListAPIView):
    queryset = Vacancy.objects.all()
    serializer_class = VacancySerializer

    @extend_schema(
        description="Retrieve vacancy list",
        summary="Vacancy list"
    )
    def get(self, request, *args, **kwargs):
        # Method code here
                        

Кастомизация ViewSet

Для ViewSet используется отдельный декоратор `@extend_schema_view`, т. к. обычно мы обычно не пишем методы во ViewSet. В этом декораторе объявлены атрибуты по названиям методов, которые есть у ViewSet. Для переопределения документации в атрибут с соответствующим названием метода необходимо передать результат вызова `@extend_schema`. Например, ниже мы переопределяем документацию для метода `list`:


                            # views.py

@extend_schema_view(
    list=extend_schema(description="Retrieve skills", summary="Skills list"),
)
class SkillViewSet(ModelViewSet):
    queryset = Skill.objects.all()
    serializer_class = SkillSerializer
                        
© 2023 Все права защищены