#30. Models

Models (Модели)

enter image description here

План

I. Модель

  1. Модель и ORM
  2. Установка и настройка PostgreSQL
  3. Миграции
  4. Команда migrate
  5. Создание моделей
  6. Команда makemigrations
  7. Команда showmigrations
  8. Основные типы полей
  9. Опции полей
  10. Опции Meta
  11. Связи
  12. Интерфейс администратора

II. Практика / Домашнее задание
III. Литература (что почитать)

Модель и ORM

Веб-приложения Django получают доступ и управляют данными через объекты Python, называемые моделями.
Модель в Django — это Python объект, который определяет структуру хранения данных в базе данных, включая типы полей и их максимальный размер, значения по умолчанию, параметры списка выбора, текст справки для документации, текст меток для форм и т. д. Обычно одна модель представляет одну таблицу в базе данных.
Определение модели не зависит от конкретной базы данных. После того, как вы выбрали какую базу данных хотите использовать, вам не нужно напрямую работать с ней - вы просто пишете свою структуру модели и код, а Django делает всю работу, связанную с базой данных за вас.

Особенности:

  • Каждая модель - это класс, унаследованный от django.db.models.Model
  • Атрибут модели представляет поле в базе данных.
  • Django предоставляет автоматически созданное API для доступа к данным;

ORM (англ. Object-Relational Mapping, рус. объектно-реляционное отображение, или преобразование) — технология программирования, которая связывает базы данных с концепциями объектно-ориентированных языков программирования, создавая «виртуальную объектную базу данных».

enter image description here

Установка и настройка PostgreSQL

Прежде, чем начать

По умолчанию в Django используется SQLite в качестве базы данных. И в целом, для начала ее вполне хватает. Главная ее особенность - все хранится в одном файле и ничего больше не надо устанавливать.
Но мы же не ищем легких путей.
Традиционно в связке с Django используют PostgreSQL, которую надо установить.

Скачать и установить можно тут.

Cоздание базы и пользователя в PostgreSQL

Прекрасная статья по этому поводу под Linux

Предположим, что база у вас установлена, и пароль для пользователя postgres создан.

Заходим в консоль базы данных

Под Windows:

psql -U postgres

Под Linux:

sudo -u postgres psql

Под Windows вы должны увидеть нечто похожее:


Создаём базу с кодировкой UTF8, чтобы избежать проблем с русским и другими языками в базе.

create database mydb with encoding 'UTF8';

Создаём пользователя для пользования этой базой.

create user myuser with password 'mypass';

Даём новому пользователю права для использования новой базой.

grant all on database mydb to myuser;

Консоль в конце должна выглядеть так:


В результате этих манипуляций мы создали пользователя myuser с паролем mypass и бузу данных mydb, после чего предоставили пользователю все права на взаимодейсвие с этой базой.

Дополнительные команды:
\l список таблиц
\du список пользовательей

Для выхода из консоли наберите \q и нажмите Enter.

Конфигурация Django

Открываем проект и далее файл settings.py

И находим переменную DATABASES

Если ничего не меняли, то выглядеть должна так:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

Заменяем на

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'mydb',
        'USER': 'myuser',
        'PASSWORD': 'mypass',
        'HOST': 'localhost',
        'PORT': '5432',
    }
}

Где:
ENGINE - (движок) модуль, отвечающий за работу базы данных, для кажой базы данных он свой.

NAME - имя базы,

USER - имя пользователя,

PASSWORD - пароль пользователя,

HOST - хост (урл), расположение базы,

PORT - порт (5432 стандартный порт для postgres, если вы его изменили при установке, укажите свой).

Для того, чтобы это заработало, нужно установить тот самый “движок”.

pip install psycopg2  # windows
pip install psycopg2-binary  # *nix

в зависимости от вашей операционной системы.
Запустите серевер и проверте работоспособность.
python manage.py runserver

Миграции

Миграции - это способ Django распространять изменения, которые вы вносите в свои модели (добавление поля, удаление модели и т.д.), в схему вашей базы данных. Они разработаны, чтобы быть в основном автоматическими, но вам нужно знать, когда выполнять миграции, когда их запускать и с какими общими проблемами вы можете столкнуться.

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


Потом появляется новая версия приложения, в которой какая-то табличка удаляется, с остальными ничего не происходит. Что это такое? Можно сказать, что это и есть состояние, которое можно описать миграцией.

Что же такое миграции?

С одной стороны, это код, который меняет состояние базы данных. С другой, это процесс, который мы запускаем.

Какими свойствами должны обладать миграции? Важно, чтобы состояния, между которыми мы переключались в версиях приложения, были атомарными. Если, например, мы хотим, чтобы у нас появилось две таблицы, а появится только одна, это может привести к не очень хорошим последствиям на продакшене.

Важно, чтобы мы могли откатить наши изменения, потому что если вы выкатываете новую версию, она не взлетает и вы не можете откатиться, все обычно заканчивается плохо.

Также важно, чтобы версии были упорядочены, чтобы вы могли построить цепочку по тому, как они накатывались.

Команда migrate

Обратите внимание на вот эту надпись:
enter image description here

Когда мы создаём Django проект, мы создаём приложения для своих нужд, но на самом деле, внутри уже есть несколько приложений для общих нужд, admin, auth, contenttype, session.
Все их мы разберем немного позже, в данный момент критичным является то, что в каждом из этих приложений находится информация о том что должно храниться в базе, а наша, только что созданная база, не имеет нужных таблиц, в соответствии с моделями описанными в этих приложениях.
Файлы миграций уже есть, но сам процесс не был запущен.

Для применения нужно выполнить команду

python manage.py migrate

Если всё ок, то результат выполнения должен выглядеть примерно так:
enter image description here

Для того чтобы Django увидела какие-либо изменения нужно добавлять каждое своё приложение в settings.py.

Находим в этом файле переменную INSTALLED_APPS
и дописываем наше приложение, чтобы получилось:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myapp',
]

Теперь всё готово, для того чтобы начинать разработку собственных моделей!

Создание моделей

В файле myapp/models.py

Напишем вот это:

from django.db import models


class Article(models.Model):
    name = models.CharField(max_length=100)
    text = models.TextField(null=True, blank=True)

Мы создали нашу первую модель, состоящую из 3 полей, поля name, text, id, при чём id создался автоматически без нашего участия, и автоматически полуил аттрибут primary key. Поле name не может содержать более 100 символов. Поле text может быть “пустым” или отсутствовать полностью.

Для того, чтобы наши изменения попали в базу, нужно создать, а затем применить миграцию.

Команда makemigrations

Теперь мы применим команду

python manage.py makemigrations

И увидим нечто похожее на:

enter image description here

Django сообщает, что миграция была создана, давайте проверим, откроем папку myapp/migrations и увидим там новый файл 0001_initial.py

Выглядеть он будет вот так:
enter image description here

Где мы можем убедиться, что Django действительно создала за нас поле id

Команда showmigrations

Чтобы убедиться, что миграция применена или нет, используется команда

python manage.py showmigrations

Результат:
enter image description here

Как мы можем видеть, наша миграция существует, но не применена, давайте применим её, при помощи
python manage.py migrate
enter image description here

И сравним showmigrations теперь:
enter image description here

Основные типы полей (детальнее)

AutoField

Автоинкрементное поле IntegerField. Используется для хранения ID. Скорее всего вам не придется использовать это поле, первичный ключ будет автоматически добавлен к модели.

BooleanField

Хранит True или False

my_flag = models.BooleanField()

Charfield

Строковое поле, для строк малого и большого размера, принимает обязательный аргумент max_length - максимальное кол-во символов.
Часто используемые флаги null и blank, null=True, означает, что поле может быть None, blank=True, означает, что поле может быть пустой строкой ‘’

my_char = models.CharField(max_length=255, null=True, blank=True)

TextField

Для больших объемов текста используйте TextField

Date и DateTime field

Поля для хранения даты, и даты и времени

Принимают аргументы auto_now и auto_now_add.
auto_now_add обозначает, что при создании объекта это поле будет автоматически заполненное, текущей датой и временем.
auto_now будет обновляться каждый раз, когда объект сохраняется (часто используют для сохранения даты создания и даты обновления объекта).

created_at = models.DateField(auto_now=True)
updated_at = models.DateTimeField(auto_now_add=True)

IntegerField

Хранение целых чисел от -2147483648 до 2147483647

int_number = models.IntegerField()

Decimal Field

Хранение float чисел

Обязательніе параметры max_digits, decimal_places. Первое максимальное кол-во символов, второе кол-во знаков после запятой.

float_number = models.DecimalField(decimal_places=2, max_digits=12)

EmailField

Такой же текстовый как и CharField с проверкой на валидность имейла

FileField

Для хранения файлов, можно указать upload_to - место для хранения файлов, если не указано, будет использованно, то, что в settings.py

my_file = models.FileField()

Image Field

Тоже что и FileField, с валидацией для картинок

URL Field

Текстовый тип, для хранения урлов.

my_url = models.URLField()

Опции полей (детальнее)

Следующие аргументы доступны для всех типов полей. Все необязательные.

null

Если True, Django будет хранить пустые значения как NULL в базе данных. По умолчанию установлено значение False.

blank

Если True, поле может быть пустым. По умолчанию установлено значение False.

choices

Последовательность (например, список или кортеж) двухэлементных кортежей (например, [(A, B), (A, B) ...]), которая будет использоваться как варианты значений для поля. Если этот параметр указан, в форме будет использоваться select для этого поля.

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

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)

default

Значение по умолчанию для поля. Это может быть значение или вызываемый объект. Если он вызывается, он будет вызываться каждый раз, когда создается новый объект.

unique

Если True, это поле должно быть уникальным во всей таблице.

verbose_name

Удобочитаемое имя для поля. Если подробное имя не указано, Django автоматически создаст его, используя имя атрибута поля, преобразовав подчеркивание в пробелы.

Опции Meta (детальнее)

Модель можно дополнительно настраивать, используя внутренний class Meta, например:

from django.db import models

class Student(models.Model):
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)

    class Meta:
        ordering = ['last_name']
        verbose_name = 'student'
        verbose_name_plural = 'students'

Метаданные модели - это «все, что не является полем», например параметры сортировки (ordering), имя таблицы базы данных (db_table) или удобочитаемые имена в единственном и множественном числе (verbose_name и verbose_name_plural). Это все не обязательные параметры, и добавление class Meta к модели совершенно необязательно.

db_table

Имя таблицы базы данных, используемой для модели:

db_table = 'music_album'

ordering

Порядок сортировки по умолчанию для объекта, для использования при получении списков объектов:

ordering = ['-order_date']

Это кортеж или список строк и/или выражений запроса. Каждая строка представляет собой имя поля с необязательным префиксом «-», который указывает на порядок сортировки по убыванию. Поля без начального «-» будут упорядочены по возрастанию.

unique_together

Принимает коллекцию коллекций, например список списков, каждый список, должен содержать набор строк, с именами полей. При указании этого набора, данные поля будут совместно уникальны (Если совместно уникальны имя и фамилия, то может быть сколько угодно объектов с именем Мария, и сколько угодно объектов с фамилией Петрова, но только один объект с такой комбинацией.)

unique_together = [['driver', 'restaurant'], ['driver', 'phone_number']]

Если есть только одно нужное значение может быть одним списком.

unique_together = ['driver', 'restaurant']

verbose_name

Удобочитаемое имя для объекта, единственное число:

verbose_name = "pizza"

Если оно не задано, Django будет использовать поддельную версию имени класса: CamelCase становится camel case.

verbose_name_plural

Имя во множественном числе для объекта:

verbose_name_plural = "stories"

Если не задано, Django будет использовать verbose_name + "s".

Связи

Модели могут быть связанны между собой, для этого существует 3 типа связей

  • OneToOne
  • ForeignKey
  • ManyToMany

One to one

Связь один к одному, чаще всего используется для какого-либо однозначного определения разных моделей. Допустим, пользователя, и модели где хранятся его настройки.

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

from django.db import models

class Customer(models.Model):
    name = models.CharField(max_length=120),
    age = models.IntegerField()

class CustomerSettings(models.Model):  
    preferred_color = models.CharField()  
    customer = models.OneToOneField(  
        Customer,  
        on_delete=models.CASCADE,  
        related_name='customer',  
    )

Foreign Key

Самая распространенная связь. Один ко многим. Допустим у нас есть книга, её написал конкретный автор, но это не значит, что этот автор написал только эту книгу.

class Author(models.Model):
    name = models.CharField(max_length=120)

class Book(models.Model):
    name = models.CharField(max_length=120)
    year_of_public = models.DateField()
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')

Many to Many

Многие ко многим, допустим, вы описываете базу для кинопоиска, один фильм может быть снят несколькими режиссерами, но при этом каждый из режиссеров может снять больше одного фильма, такая связь называется ManyToMany Для построения таких связей мы используем связь ManyToMany:

from django.db import models

class Publication(models.Model):
    title = models.CharField(max_length=30)

    class Meta:
        ordering = ['title']

    def __str__(self):
        return self.title

class Article(models.Model):
    headline = models.CharField(max_length=100)
    publications = models.ManyToManyField(Publication)

    class Meta:
        ordering = ['headline']

    def __str__(self):
        return self.headline

На самом деле “под капотом” создаётся дополнительная таблица, которая хранит информацию о связи между двумя видами моделей.

Если нам нужно контролировать эту таблицу, мы можем делать это при помощи специального слова through

from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=128)

    def __str__(self):
        return self.name

class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(Person, through='Membership')

    def __str__(self):
        return self.name

class Membership(models.Model):
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    date_joined = models.DateField()
    invite_reason = models.CharField(max_length=64)

ContentTypes

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

Документация тут.

Подробно мы не будем рассматривать этот функционал, но я бы очень рекомендовал ознакомиться.

Работает основываясь на приложении django.conrib.contenttype, добавленное в проект по умолчанию.

Интерфейс администратора (админка)

Одна из самых мощных частей Django - это автоматический интерфейс администратора. Он считывает метаданные из ваших моделей, чтобы обеспечить быстрый, ориентированный на модели интерфейс, в котором доверенные пользователи могут управлять содержимым вашего сайта. Рекомендуемое использование администратора ограничено внутренним инструментом управления организации. Он не предназначен для создания всего фронт-энда.

Для того, чтобы пользоваться админкой, нужно сделать две вещи:

  • в urls добавить встроенный url админки
  • создать суперпользователя.

Для создания пользователя нам поможет команда

python manage.py createsuperuser

Вводим всё, что от нас требует консоль, и юзер будет создан:

Если вы не стирали url для админки, то он уже у вас есть, если стирали, то допишите в myproject/urls.py

from django.contrib import admin

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

Перезапускаем сервер, и заходим в админку


Поскольку перевод включен по умолчанию, если вы установите LANGUAGE_CODE, экран входа в систему будет отображаться на заданном языке (если у Django есть соответствующие переводы).

Вбиваем credentials (логин и пароль), и видим:

По дефолту, у Django сразу есть две модели из “коробки”, User и Group,

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

Для этого нам нужно в файле admin.py в приложении импортировать модель и зарегистрировать её

from django.contrib import admin
from .models import Article

admin.site.register(Article)

Открываем админку еще раз:

Появилась наша модель, через админку мы можем добавлять, удалять, смотреть, редактировать наши модели.

Кастомная админка

Возможности встроенной админки очень велики, но её можно дописывать, видоизменять, добавлять любые кастомные действия и т.д. Подробно разберем на одном из следующих занятий, сейчас попробуйте создать/изменить/удалить несколько объектов.

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

Весь функционал, на самом деле, описан в приложении django.contrib.admin, о котором мы говорили выше, оно добавлено в наш проект по умолчанию.

Чтобы показать в таблице необхожимые поля модели, необхожимо создать свой класс, наследуемый от admin.ModelAdmin, в котором указать в поле list_display кортеж полей.

from django.contrib import admin  
  
from .models import Article  
  
  
class ArticleAdmin(admin.ModelAdmin):  
    list_display = ('name', 'text')  
  
admin.site.register(Article, ArticleAdmin)

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

from django.db import models  
  
  
class Article(models.Model):  
      
    name = models.CharField(max_length=100)  
    text = models.TextField(null=True, blank=True)  
  
    def __str__(self):  
        return self.name

enter image description here

Практика / Домашнее задание:

  1. Разрабатываем каталог книг, у каждой книги обязательно есть автор, и он может быть только один.

  2. Разработать книжную библиотеку. Храним книги, храним авторов, книгу могут написать несколько соавторов. Храним кто брал книги, и доступна ли книга сейчас.

  3. Разработать набор моделей, для сайта-блога, на котором можно выставлять свои статьи, комментировать чужие, ставить лайк и дизлайк статье, и комментарию.

3.1* Доделать так, чтобы связи позволяли комментировать комментарии.

3.2* Сделать лайки через GenericForeignKey

Литература (что почитать)

  1. 📖 Документация по моделям
  2. 📖 Избегайте использования GenericForeignKey
  3. 📖 Интерфейс администратора