#30. Django Models

Models (Моделі)

План

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, укр. об’єктно-реляційне відображення, або перетворення) - технологія програмування, що пов’язує бази даних із концепціями об’єктно-орієнтованих мов програмування, створюючи “віртуальну об’єктну базу даних”.


Встановлення та налаштування PostgreSQL

Перш, ніж почати

За замовчуванням у Django використовується SQLite як база даних. І загалом, для початку її цілком вистачає. Головна її особливість - все зберігається в одному файлі і нічого більше не треба встановлювати.
Але ми ж не шукаємо легких шляхів.
Традиційно у зв’язці з Django використовують PostgreSQL, яку треба встановити.

Завантажити та встановити можна [тут] (https://www.postgresql.org/download/).

Створення бази та користувача в PostgreSQL

Прекрасна стаття з цього приводу під Linux

Припустимо, що база у вас встановлена, і пароль для користувача postgres створений. (відповідно до модуля по PostgreSQL)

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

Під Windows:

psql -U postgres

Під Linux:

sudo -u postgres psql

Створюємо базу з кодуванням 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

Зверніть увагу на ось цей напис:
введіть тут опис зображення

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

Для застосування потрібно виконати команду

python manage.py migrate

Якщо все ок, то результат виконання має виглядати приблизно так:
введіть тут опис зображення

Для того щоб 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

І побачимо щось схоже на:

введіть тут опис зображення

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

Виглядати він буде ось так:
введіть тут опис зображення

Де ми можемо переконатися, що Django дійсно створила за нас поле id

Команда showmigrations

Щоб переконатися, що міграція застосована чи ні, використовується команда

python manage.py showmigrations

Результат:
введіть тут опис зображення

Як ми можемо бачити, наша міграція існує, але не застосована, давайте застосуємо її, за допомогою
python manage.py migrate

І порівняємо showmigrations тепер:

Основні типи полів (детальніше)

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.

DateField і DateTimeField

Поля для зберігання дати, і дати та часу

Приймають аргументи auto_now і auto_now_add.

  • auto_now_add означає, що при створенні об’єкта це поле буде автоматично заповнене поточною датою і часом.

  • auto_now буде оновлюватися щоразу, коли об’єкт зберігається (корисно для часових позначок «останньої зміни»).

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

IntegerField

Зберігання цілих чисел від -2147483648 до 2147483647

int_number = models.IntegerField()

DecimalField

Зберігання 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()

ImageField

Те саме що і FileField, з валідацією для картинок

URLField

Текстовий тип, для зберігання урлів.

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 типи зв’язків

  • One To One
  • Foreign Key
  • Many To Many

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(max_length=255)  
    customer = models.OneToOneField(  
        Customer,  
        on_delete=models.CASCADE,  
        related_name='settings',  
    )

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.PROTECT, 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

Практика / Домашнє завдання:

Чим далі, тим важче:)

  1. Розробляємо каталог книжок, у кожної книжки обов’язково є автор, і він може бути тільки один.

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

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

3.1* Доробити так, щоб зв’язки давали змогу коментувати коментарі.

3.2* Зробити лайки через GenericForeignKey

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

  1. 📖 Документація по моделях
  2. 📖 Уникайте використання GenericForeignKey
  3. 📖 Інтерфейс адміністратора