December 23, 2023

Практические советы для начинающих django-разработчиков

Около месяца назад стартовали новый проект на Django и так вышло, что на нём работают Junior+ и Intern в паре. Причём, все последние проекты Junior+ разработчика были на FastAPI, из-за чего Django он подзабыл. По мере разработки я собирал в заметках все советы, которые давал разработчикам на проекте.

Для вашего удобства, я разбил советы по смысловым группам.

Общие советы

  • старайтесь держать нейминг приложений согласованным, например, если вы назвали какое-то приложение в множественном числе, в то время, как все остальные названы в единственном числе, — это будет выглядеть неаккуратно;
  • для чувствительных данных, вроде SECRET_KEY следует использовать переменные среды;
  • если вам необходимо быстро стартануть проект, вы можете использовать наш шаблон для django, всё настроено под работу через docker.

Разработка моделей

  • когда вы делаете поле ForeignKey, не называйте его model_name_id, лучше просто model_name, поскольку model_name_id в случае FK резервируется ORM под возможность получать именно id, а не инстанс модели целиком, что позволяет избежать лишних запросов к БД;
  • если вы напишите человекочитаемый verbose_name у всех полей для каждой модели - это сильно облегчит пользование админкой Django;
  • если вы используете choices, вам следует завести их ключи свойствами класса модели, либо вынести их в отдельный класс, чтобы обращаться к константе, а не указывать конкретное значение, например:
# models.py

from django.db import models

class Student(models.Model):
    FRESHMAN = "FR"
    SOPHOMORE = "SO"
    JUNIOR = "JR"
    SENIOR = "SR"
    GRADUATE = "GR"
    
    YEAR_IN_SCHOOL_CHOICES = {
        FRESHMAN: "Freshman",
        SOPHOMORE: "Sophomore",
        JUNIOR: "Junior",
        SENIOR: "Senior",
        GRADUATE: "Graduate",
    }
    
    year_in_school = models.CharField(
        max_length=2,
        choices=YEAR_IN_SCHOOL_CHOICES,
        default=FRESHMAN,
    )
    
    def is_upperclass(self):
        return self.year_in_school in {self.JUNIOR, self.SENIOR}
  • зачастую вместо сигналов вам следует использовать хуки жизненного цикла (это стороннее решение), поскольку сигналы усложняют дебаг, особенно, когда их много, в то время как хуки жизненного цикла указываются в классе модели, что позволяет чётко понимать, на какое действие они ориентированы;
  • в том же случае, если вы решили всё-таки использовать сигнал и он по какой-то причине не срабатывает, убедитесь, что вы его действительно подключили, мы обычно делаем это через функцию ready у класса конфигурации приложения, например:
# apps.py

from django.apps import AppConfig

class ExampleAppConfig(AppConfig):
    default_auto_field = "django.db.models.BigAutoField"
    name = "example_app"
    verbose_name = "Пример"
    
    def ready(self):
        # Implicitly connect signal handlers decorated with @receiver.
        from . import signals

Разработка API

  • при написании представлений в Django старайтесь в названии представления отражать то, к чему оно действительно относится, также используйте нормальную форму, к примеру не DetailedModelName, а ModelNameDetailView;
  • при использовании DRF следует обратить внимание на такие библиотеки, как:
    • drf-yasg — автодокументирование через swagger;
    • djoser — библиотека, которая из коробки предоставляет вам готовое приложение для авторизации и регистрации различными способами;
    • django-cors-headers — библиотека для настройки правил CORS внутри django-приложения;
    • django-filter — удобная библиотека для написания фильтров.

Django settings

  • приложения следует разделить на смысловые группы, я предлагаю такие группы:
    • DJANGO_APPS — служебные приложения django;
    • THIRD_PARTY_APPS — сторонние приложения;
    • INTERNAL_APPS — наши встроенные приложения.

      Например:
# Django-specific apps
DJANGO_APPS = [
    "jazzmin",
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
]

# External apps
THIRD_PARTY_APPS = [
    "corsheaders",
    "rest_framework",
    "drf_spectacular",
    "djoser",
    "django_filters",
    "tinymce",
    "constance",
]

# Internal apps
INTERNAL_APPS = [
    "users",
]

INSTALLED_APPS = [*DJANGO_APPS, *THIRD_PARTY_APPS, *INTERNAL_APPS]
  • не забудьте указать параметр CSRF_TRUSTED_ORIGINS (в противном случае, у вас может не работать админ-панель), референс.

Docker, docker-compose, gunicorn

  • не забывайте про файл .dockerignore, это может значительно ускорить процесс сборки;
  • если при работе с docker вам не хватает hot reload (автоматическое применение изменений), вам необходимо сделать всего 2 вещи:
    • указать ключ --reload у команды gunicorn;
    • смонтировать volume с кодом бэкенда в контейнер.
    • пример реализации:
backend:
	image: template_django_core
	container_name: template_django_core
	build: ./django_core
	env_file:
		- ./django_core/.env

	command: gunicorn django_core.wsgi:application --bind 0.0.0.0:8000 --reload
	
	expose:
		- 8000
	volumes:
		- ./django_core:/var/www/apps/django_core
  • отличие директивы expose от ports заключается в том, что директива ports экспортирует порт наружу из контейнера, а expose делает порт доступным внутри сети докера.

Админ-панель

  • следует уделить отдельное внимание тому, как работает админ-панель и формы редактирования данных в ней, я писал об этом ранее, но приведу ссылку на stackoverflow;
  • если вы хотите реализовать feature toggle, справочники или просто хранить любую редактируемую конфигурацию в админке, чтобы иметь возможность управлять ей без перезапуска приложения, вам стоит присмотреться к библиотеке django-constance;
  • при настройке ModelAdmin, уделите отдельное внимание возможности fieldsets, поскольку так выводимая информация будет более структурированный.

Заключение

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