Модели и миграции веб-приложения Django

Все исходники / Язык программирования Python / OS Windows / Веб программирование / Django - веб-фреймворк на Python / Модели и миграции веб-приложения Django
Оглавление:
  1. Модели и миграции Django MySQL
  2. Установка драйвера MySQL для Python
  3. Настройка проекта для работы с MySQL
  4. Модели Django
  5. Создание моделей для приложения Django
  6. Доступ к данным таблиц
  7. Миграции баз данных Django
  8. Создание миграций
  9. Миграции данных
  10. Выполнение миграций
  11. Удаление миграций
  12. Django динамические url-адреса
  13. Индексы значений в html-шаблонах
  14. Исходный код веб-приложения Django

Модели и миграции Django MySQL

Описание исходного кода веб-приложения electronics на фреймворке Django 4.2. На этом этапе к приложению добавляются миграции и модели для базы данных MySQL, корректируются html-шаблоны под динамические веб-адреса.

Данное описание исходника является эволюционным продолжением веб-приложения, описанного на страницах сайта interestprograms.ru: Django - создание проекта в Visual Studio и Приложение на фреймворке Django.

Исходный код приложения на языке Python написан в среде программирования MS Visual Studio. Visual Studio обеспечивает полноценную поддержку языка Python с интеллектуальным IntelliSense и подсветкой синтаксиса программного кода. Поддержка Python доступна в версиях Visual Studio 2017 и выше.

Ознакомится с первоисточником документации Django можно на их официальном сайте: The web framework Django.

Установка драйвера MySQL для Python

Описываемый исходник приложения использует базу данных MySQL. Для работы веб-приложения Python с базой MySQL необходимо установить драйвер mysqlclient. Visual Studio упрощает установку необходимых пакетов, при этом, стандартно используя систему управления пакетами pip.

Установка драйвера MySQL для Python начинается из контекстного меню строки с названием виртуальной среды нажатием на пункт Открыть пакеты Python…. На открывшейся вкладке в окно редактирования необходимо напечатать mysqlclient и щелкнуть мышью на исполнительную строку:
Выполните команду: pip install mysqlclient

После данных манипуляций в составе виртуальной среды появится пакет mysqlclient. Прикрепленный исходник имеет версию драйвера mysqlclient 2.2 для работы с интерпретатором Python до последних 3.9 версий.

Установка драйвера mysqlclient

Настройка проекта для работы с MySQL

По умолчанию Django имеет настройки для работы с базой SQLite, для работы приложения с базой MySQL требуются небольшие настройки в файле проекта settings.py. Настройки сводятся к указанию драйвера для работы с MySQL и параметров доступа.

Настройка константы DATABASES для базы MySQL в файле конфигурации проекта для локальной работы:
# mydjango/settings.py
...
# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'shop_electronics',
        'USER': 'root',
        'PASSWORD': '1234',
        'HOST': 'localhost',  
        'PORT': '3306',         
    }
}
...

Модели Django

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

Скрупулезное составление SQL-запросов к базе данных посредством моделей заменяются простым получением и изменением значений полей класса. Для моделей Django автоматически предоставляет API абстракции создания, извлечения, обновления и удаление объектов базы данных.

Модели фреймворка Django – это классы на языке программирования Python, наследуемые от класса Model(**kwargs). Атрибуты моделей (они же поля, переменные класса) соответствуют столбцам в таблицах базы данных. Совокупность моделей позволяет максимально точно создать требуемую схему базы.

Создание моделей для приложения Django

Модели базы данных приложения electronics это два класса создающих таблицы: TypeComponent и Component, и один абстрактный класс с общими полями для всех таблиц: CommonFields. При большом количестве таблиц общие поля абстрактного класса значительно сокращают объем требуемого программного кода.

По умолчанию названия столбцов таблиц баз данных равны названиям полей (они же атрибуты, переменные) класса модели. Например, атрибут класса, определенный как:
name = models.CharField()
создаст столбец в таблице базы данных с названием:
name

Для столбцов внешних ключей, поскольку они ссылаются на первичные ключи родительских таблиц, название по умолчанию образуется специфически: к названию атрибута (поля, переменной) класса модели добавляется постфикс _id. Например, атрибут класса модели для внешнего ключа, определенный как:
type = models.ForeignKey("TypeComponent")
создаст столбец в таблице базы данных с названием:
type_id.

Желаемое имя столбца можно зафиксировать параметре db_column="" конструкторов классов определяющих типы полей, например:
name_component = models.CharField(db_column="name")
в таблице базы данных будет создан столбец с названием name,
type_component = models.ForeignKey("TypeComponent", db_column="type_id")
в таблице базы данных будет создан столбец внешнего ключа с названием type_id.

Программный код модуля моделей приложения electronics:
from django.db import models

# Общие поля для моделей приложения
class CommonFields(models.Model):
    id = models.BigAutoField(primary_key=True, auto_created=True)
    name = models.CharField(max_length=300)
    description = models.TextField(null=True)
    # Данный класс не будет иметь таблицы в базе данных
    class Meta:
        abstract = True


# Типы электронных компонентов
class TypeComponent(CommonFields):
    pass

# Электронный компонент
class Component(CommonFields):
    # Определение значения свойства db_column="type_id" фиксирует название 
    # поля (столбца) для внешнего ключа. 
    # По умолчанию имя столбца определяется атрибутом класса + постфикс _id,
    # т.е. без определения db_column название столбца было бы type_foreignkey_id.
    # Различность названия атрибута модели и названия столбца созданы 
    # для демонстрации ручного определения названия столбца.
    type_foreignkey = models.ForeignKey(
        "TypeComponent", db_column="type_id", on_delete=models.CASCADE
    )
    price = models.IntegerField()

Доступ к данным таблиц

После того, как вы создали свои модели данных, Django автоматически предоставляет API абстракции базы данных для создания, извлечения, обновления и удаления объектов.

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

Листинг кода модуля views.py:
# electronics/views.py

from django.shortcuts import get_object_or_404, render
from electronics.models import Component, TypeComponent


# ===== Контроллеры =====

# Контроллеры для главной страницы
def index(request):
    return func(request, "Здравствуйте! Рады вас видеть в магазине Электроники!")


def catalog(request):
    tc = TypeComponent.objects.all()
    context = {"list_typecomponent": tc, "title": "Каталог", "color": "black"}
    return render(request, "electronics/catalog.html", context)


def catalog_section(request, type_id):
    # Проверка существования элемента
    tc = get_object_or_404(TypeComponent, id=type_id)
    # Получение списка объектов по указанному ключу:
    list_components = Component.objects.all().filter(type_foreignkey=tc.id)
    context = {
        # Отправка в html шаблон кортежа из индексов и значений элементов
        # класс enumerate дает возможность получать
        # индексы и значения элементов списка
        "list_typecomponent": enumerate(list_components, 1),
        "title": tc.name,
        "color": "black",
    }
    return render(request, "electronics/catalog_section.html", context)


def component(request, component_id):
    # Проверка существования элемента
    component = get_object_or_404(Component, id=component_id)
    # Получение объекта по первичному ключу:
    component = Component.objects.get(id=component.id)
    context = {
        # Отправка в html шаблон кортежа из индексов и значений элементов
        "component": component,
        "title": component.name,
        "color": "black",
    }
    return render(request, "electronics/page_component.html", context)


def contacts(request):
    context = {
        "title": "Контакты",
    }
    # render - функция быстрого доступа фреймворка Django,
    # возвращает  HttpResponse
    return render(request, "electronics/contacts.html", context)


# Функция-контроллер, переопределяющая вызов шаблона при ошибке 404
def error404(request, exception):
    context = {"title": "Страница не найдена!"}
    return render(request, "electronics/error404.html", context)

# ===== /Контроллеры =====

# ===== Вспомогательные функции =====


# Вспомогательная функция, группирующая одинаковый код для некоторых контроллеров
def func(request, text="", color="black"):
    context = {"content": text, "title": text, "color": color}
    return render(request, "electronics/page.html", context)

# ===== /Вспомогательные функции =====

Миграции баз данных Django

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

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

Миграции Django следует рассматривать как систему управления версиями схем баз данных. Они отвечают за упаковку изменений моделей в отдельные файлы и их применение к определенной базе данных.

Файлы миграций для каждого веб-приложения находятся в подкаталоге (папке) migrations корневой папки этого приложения. В Django миграции поддерживаются для различных видов баз данных: SQLite, MySQL, PostgreSQL, MSSQL. При установке соответствующего драйвера можно работать с любой базой данных.

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

Команды управления миграциями:
python manage.py makemigrations electronics – создание файлов миграций
python manage.py migrate electronics – выполнение миграций
python manage.py migrate electronics zero – отмена миграций.
Указание названия приложения ограничивает создание миграций только для указанного приложения, без указания имени приложения миграции будут созданы или удалены для всех зарегистрированных в проекте приложений.

Создание миграций

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

В окне командной строки печатается и запускается команда:
python manage.py makemigrations electronics
Директивы makemigrations electronics создают файлы миграции только для приложения electronics.

Первое выполнение команды makemigrations создает начальные версии таблиц указанного приложения. Класс миграции в таком случае помечается атрибутом initial = True.

Если миграционная логика находит какие-либо несоответствия между модулями предлагается дополнительная опция для команды создания миграций --merge:
makemigrations electronics --merge
опция помогает устранить некоторые миграционные конфликты.

Программный код класса миграции для начального создания таблиц баз данных приложения electronics:
# Файл 0001_initial.py
# Generated by Django 4.2.2 on 2023-07-18 05:05

from email.policy import default
from django.db import migrations, models
import django.db.models.deletion

class Migration(migrations.Migration):

    initial = True

    dependencies = [
    ]

    operations = [
        migrations.CreateModel(
            name='TypeComponent',
            fields=[
                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
                ('name', models.CharField(max_length=300)),
                ('description', models.TextField(null=True)),
            ],
            options={
                'abstract': False,
            },
        ),
        migrations.CreateModel(
            name='Component',
            fields=[
                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
                ('name', models.CharField(max_length=300)),
                ('description', models.TextField(null=True)),
                ('price', models.IntegerField()),
                ('type_foreignkey', models.ForeignKey(db_column='type_id', on_delete=django.db.models.deletion.CASCADE, to='electronics.typecomponent')),
            ],
            options={
                'abstract': False,
            },
        ),
    ]

Миграции данных

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

Миграции, изменяющие данные, называются «миграциями данных». Django не может автоматически генерировать миграции данных, но предоставляет простую возможность создавать их вручную.

Для запуска пользовательского кода в операциях миграции предназначен класс
RunPython(code, reverse_code=None, ...)
Параметр конструктора code принимает ссылку на функцию создания вставки данных в таблицы базы данных. Для возможности отката миграции дополнительно определяется функция для второго аргумента параметра reverse_code.

Поскольку в приложении electronics, при откате миграций, таблицы удаляются вместе данными согласно коду модуля 0001_initial.py, то в качестве параметра reverse_code применена пустая функция-заглушка:
populate_zero(apps, schema_editor)
смотрите код ниже.

Для нужд тестирования в описываемом исходном коде создана миграция данных для типов компонентов и самих компонентов. После выполнения миграций приложения electronics в базе MySQL создаются заполненные таблицы. Миграции данных должны выполняться после выполнения миграции таблиц.

Программный код модуля миграции данных для приложения electronics, укороченный вариант:
# Файл populate_data.py
from random import randrange
from django.db import migrations, models
import django.db.models.deletion

# from electronics import models
import electronics

# ----- Определение констант в качестве первоисточников -----
# Такое позволяет определять названия в одном месте,
# но использовать их во многих местах кода.

# Название модели типов компонентов
TABLE_TYPECOMPONENT = electronics.models.TypeComponent.__name__

# Определение имен разделов для типов электронных компонентов
SECTION_TRANSISTORS = "Транзисторы"
SECTION_DIODES = "Диоды"
SECTION_CAPACITORS = "Конденсаторы"
SECTION_RESISTORS = "Резисторы"

# ----- /Определение констант в качестве первоисточников -----


# Вставка данных типов компонентов
def populate_type_components(apps, schema_editor):
    # We get the model from the versioned app registry;
    # if we directly import it, it'll be the wrong version
    Type = apps.get_model("electronics", TABLE_TYPECOMPONENT)
    db_alias = schema_editor.connection.alias
    Type.objects.using(db_alias).bulk_create(
        [
            Type(
                # name="Транзисторы",
                name=SECTION_TRANSISTORS,
                description="Транзисторы — это полупроводниковые приборы, для усиления, \
                переключения и преобразования электрических сигналов.",
            ),
            Type(
                # name="Диоды",
                name=SECTION_DIODES,
                description="Диоды - это электронные приборы, пропускающие электрический \
                ток только в одном направлении – от плюса к минусу.",
            ),
            Type(
                # name="Конденсаторы",
                name=SECTION_CAPACITORS,
                description="Конденсаторы - электронные компоненты \
                для накопления заряда и энергии электрического поля.",
            ),
            Type(
                # name="Резисторы",
                name=SECTION_RESISTORS,
                description="Резисторы - пассивные элементы для ограничения силы тока.",
            ),
        ]
    )

# Вставка данных электронных компонентов
def populate_components(apps, schema_editor):
    # We can't import the Person model directly as it may be a newer
    # version than this migration expects. We use the historical version.
    Type = apps.get_model("electronics", TABLE_TYPECOMPONENT)
    for type in Type.objects.all():
        if type.name == SECTION_TRANSISTORS:
            Component = apps.get_model("electronics", "Component")
            db_alias = schema_editor.connection.alias
            Component.objects.using(db_alias).bulk_create(
                [
                    Component(
                        type_foreignkey=type,
                        name="BC857C",
                        description="Транзисторы общего назначения PNP в небольшом пластиковом корпусе SOT23.",
                        price=randrange(5, 1401),
                    ),
                    ...
                    Component(
                        type_foreignkey=type,
                        name="2SA1015",
                        description="Кремниевый транзистор TOSHIBA PNP для усилителей звуковой частоты общего назначения.",
                        price=randrange(5, 1401),
                    ),
                ]
            )

        if type.name == SECTION_DIODES:
            Component = apps.get_model("electronics", "Component")
            db_alias = schema_editor.connection.alias
            Component.objects.using(db_alias).bulk_create(
                [
                    Component(
                        type_foreignkey=type,
                        name="1N4148",
                        description="Диоды с быстрым переключением для малых токов.",
                        price=randrange(5, 401),
                    ),
                    ...                    
                    Component(
                        type_foreignkey=type,
                        name="10A10",
                        description="Кремниевый выпрямительный диод на напряжения до 1000 вольт ток 10 ампер.",
                        price=randrange(5, 401),
                    ),
                ]
            )

        if type.name == SECTION_CAPACITORS:
            Component = apps.get_model("electronics", "Component")
            db_alias = schema_editor.connection.alias
            Component.objects.using(db_alias).bulk_create(
                [
                    Component(
                        type_foreignkey=type,
                        name="CT8110bB2KV102K",
                        description="1000пф 2000в Y5P 10% дисковый конденсатор (К15-5). Высоковольтные аналог К15-5, К10-62.",
                        price=randrange(5, 401),
                    ),
                    ...
                    Component(
                        type_foreignkey=type,
                        name="К50-35 мини",
                        description="Конденсаторы оксидно-электролитические алюминиевые для работы в цепях различных электрических токов.",
                        price=randrange(5, 401),
                    ),
                ]
            )

        if type.name == SECTION_RESISTORS:
            Component = apps.get_model("electronics", "Component")
            db_alias = schema_editor.connection.alias
            Component.objects.using(db_alias).bulk_create(
                [
                    Component(
                        type_foreignkey=type,
                        name="CF-25 0.25 Вт, 1.8 Ом",
                        description="Резисторы с углеродным проводящим слоем предназначены для работы в цепях постоянного, переменного и импульсного тока.",
                        price=randrange(5, 401),
                    ),
                    ...
                    Component(
                        type_foreignkey=type,
                        name="CF-100 1 Вт, 5.1 МОм",
                        description="Резистор углеродистый CF-100 мощностью 1 Вт, номинал 5,6 кОм 5%.",
                        price=randrange(5, 401),
                    ),
                ]
            )

# Заглушка для отмены миграции данных
def populate_zero(apps, schema_editor):
    pass

class Migration(migrations.Migration):
    dependencies = [("electronics", "0001_initial")]

    operations = [
        migrations.RunPython(populate_type_components, populate_zero),
        migrations.RunPython(populate_components, populate_zero),
    ]

Выполнение миграций

Сформированные миграции приложения electronics запускаются в окне командной строки командой:
python manage.py migrate electronics
при этом выполняются все связанные миграции.

Миграция данных должна выполняться строго после создания таблиц, такой порядок обеспечивается атрибутом зависимости в классе миграции данных:
dependencies = [("electronics", "0001_initial")]
Первой выполняется модуль создания таблиц 0001_initial.py и только затем модуль заполнения таблиц данными populate_data.py, смотрите программный код выше.

Удаление миграций

В Django возможно не только выполнение, но и удаление (или, точнее, откат назад) миграций. Для этого в наборе миграционных команд имеется директива zero. Например, удаление созданных таблиц в базе данных приложения electronics происходит после такой команды:
python manage.py migrate electronics zero
при этом удаляются все таблицы, созданные модулями миграций для указанного приложения.

Django динамические url-адреса

Предыдущая версия веб-приложения electronics Приложение на фреймворке Django не имела в своем составе базу данных. Добавление базы в новую версию веб-приложения electronics требует соответствующего изменения кода контроллеров в модуле views.py (смотрите код выше) и маршрутизаторов в модуле urls.py (смотрите код ниже).

При работе с базой данных url-маршруты формируются динамически, используя значения, полученные из таблиц, для таких адресов в модуле маршрутов urls.py создаются шаблоны с константами и метками-заполнителями, например шаблон:
path("catalog/<int:type_id>/", ...)
имеет константу catalog и метку-заполнитель , такой шаблон предназначен для веб-адресов вида:
http://localhost:5000/catalog/3/ - где 3 захватывается меткой-заполнителем и вставляется как аргумент type_id функции-контроллера модуля views.py можно получить это значение.

Шаблон path("catalog/component-<int:component_id>/", ...) предназначен для веб-адресов вида:
http://localhost:5000/catalog/component-9/ - где один сегмент пути состоит из константы и метки-заполнителя одновременно. Значение метки можно получить в функции component(request, component_id) модуля views.py.

Программный код модуля urls.py с шаблонами для динамических веб-адресов:
from django.urls import path
from . import views

# Пространство имен для маршрутов данного приложения
# Используются для доступа к маршрутам по имени в шаблонах, 
# например, в menu.html
app_name = "electronics"

# Маршруты для сайта электронного интернет-магазина
urlpatterns = [
    # views.index и др. - контроллеры обрабатывающие данный запрос
    # name хранит имя маршрута
    #  - получение значения из url после получения запроса
    path("", views.index, name="index"),
    path("catalog/", views.catalog, name="catalog"),
    path("catalog/<int:type_id>/", views.catalog_section, name="catalog_section"),
    path("catalog/component-<int:component_id>/", views.component, name="page_component"),
    path("electronics/contacts/", views.contacts, name="electronics_contacts")
]

Индексы значений в html-шаблонах

Порядковые индексы в html шаблонах django

Часто возникает необходимость передавать в html-шаблоны значения и их порядковые номера. Django не предоставляет возможности выполнения кода Python внутри html-шаблонов. Философия фреймворка обязывает подготавливать все отображаемые данные до вывода на веб-страницу.

На веб-странице раздела каталога http://localhost:5000/catalog/4/ список компонентов имеет столбец порядковых номеров.

Для предварительного добавления индексов к элементам списка полученных строк из таблиц баз данных применяется встроенная функция Python:
enumerate(iterable, start=0)
Функция возвращает объект (кортеж) из двух значений: индекса начинающегося от указанного start и элемент списка. Теперь в html-шаблоне мы можем вывести порядковые номера и данные компонентов.

Листинг кода html-шаблона catalog_section.html приложения electronics:
{# catalog_section.html #}
{% extends "electronics/shared/layout.html" %}
{% block content %}
<h1 style="color:{{ color }}">{{ title }}</h1>

<table style="border:1px solid #808080;border-collapse:collapse">
    <thead>
        <tr>
            <th style="border: 1px solid; padding: 5px;">п/п</th>
            <th style="border: 1px solid; padding: 5px;">Название</th>
            <th style="border: 1px solid; padding: 5px;">Цена в руб.</th>
            <th style="border: 1px solid; padding: 5px;">Страница описания</th>
        </tr>
    </thead>
    <tbody>
        {% for i, item in list_typecomponent %}
        <tr>
            <td style="border:1px solid;text-align:center">{{ i }}</td>
            <td style="border:1px solid;padding:5px;">{{item.name}}</td>
            <td style="border:1px solid; text-align: right; padding: 5px;">{{ item.price }}</td>
            <td style="border: 1px solid; text-align: right; padding: 5px; font-size: 2em; font-weight: bold; text-align: center">
                <a href="{% url 'electronics:page_component' item.id %}" style="text-decoration:none;">&#10132;</a>
            </td>
        </tr>
    </tbody>
    {% endfor %}
</table>

{% endblock %}

Исходный код веб-приложения Django

Архивный файл содержит полный исходный код веб-приложения electronics на фреймворке Django вместе с виртуальной средой для запуска данного приложения и установленным драйвером для базы MySQL. Исходник предназначен для разработки в среде MS Visual Studio на языке программирования Python.

Требуемая версия базового интерпретатора Python от 3.8 до последних 3.9. Нижнюю версию определяет фреймворк Django, верхнюю версию интерпретатора определяет пакет mysqlclient (на момент написания статьи не выпущена версия для Python 3.10 и выше).

Для успешного запуска проекта необходимо в файле pyvenv.cfg виртуальной среды прописать версию и путь до вашего базового интерпретатора, например:
home = G:\MyProjects\Programming\Pythons\Python39_4
include-system-site-packages = false
version = 3.9.4

Перед началом работ с приложением создайте базу данных MySQL с названием shop_electronics и выполните миграцию командой:
python manage.py migrate electronics

Скачать исходник

Тема: «Модели и миграции веб-приложения Django» Язык программирования Python DjangoModelsMigrations-vs17.zip Размер:22670 КбайтЗагрузки:64