Используем CRUD запросы в Django 3 на примере приложения


26 марта 2021


Создаём CRUD запросы в Django 3 на примере приложения

Для работы с базой данных в Django существует понятие ORM. Само понятие ORM обозначает возможность преобразования и взаимодействия прикладного языка (в нашем случае Python) и базы данных (обычно это SQL). В любой реализации ORM есть 4 ключевых понятия, которые складываются в аббревиатуру CRUD:

  • Create - создание или добавление данных в базу;
  • Read - получение и чтение данных из базы;
  • Update - обновление данных в базе;
  • Delete - удаление данных.

Если совсем упрощать, то ORM - это программа, которая снимает с нас обязанность знать язык запросов (например SQL). В этой статье, мы рассмотрим как делается CRUD в Django и создадим простое приложение.

 

Подготовка проекта

У вас уже должен быть установлен Django 2-ой или 3-ей версии. Создадим тестовый проект:

django-admin startproject crud_project
cd crud_project/

Создадим приложение на котором будут показаны примеры с CRUD:

python manage.py startapp crud_app

Добавим в файл конфигурации 'settings.py', в массив 'INSTALLED_APPS', следующую строку:

vim crud_project/settings.py
'crud_app.apps.CrudAppConfig'

Добавление приложения в Django settings.py

 

Работа с моделью

Работа ORM, в Django, частично реализована в моделях. Модель - это общее название классов созданных вами и наследуемых от django.db.models.Model. Класс 'django.db.models.Model' имеет некоторые методы работы с базой и их вы наследуете. Каждая модель, обычно, соответствует одной таблице в базе данных. Модели прописываются в файле 'models.py' у каждого из приложений отдельно. Откроем этот файл в нашем приложении:

vim crud_app/models.py

В этом файле мы создадим два класса-модели, которые опишут таблицу со студентами их группами:

class StudentModel(models.Model):
    name = models.CharField(max_length=80)
    sex = models.CharField(max_length=15)
    age = models.IntegerField()
    group = models.ForeignKey(to='GroupModel', on_delete=models.CASCADE)
 
    def __str__(self):
        return self.name

class GroupModel(models.Model):
    name = models.CharField(max_length=10)

    def __str__(self):
        return self.name

Создание модели в Django

В модели есть понятие полей (Field), которое определяется в переменной (например name). Каждое такое поле соответствует колонке в базе данных. Поле определяет какой тип данных в нем может находится, в нашем случае:

  • CharField - строка ограниченной длины;
  • IntegerField - целое число;
  • ForeignKey - колонка для связи двух таблиц.

Кроме этого, в SQL базах данных вы обязаны определять дополнительные параметры для разных типов данных. Это может быть максимальная длина строки (max_length) или что случится с данными, если родительский объект будет удален (on_delete). Таких типов данных множество и какие-то автоматических заполняются ORM.

Что бы созданная нами модель появилась в базе - мы должны выполнить 2 действия:

  • Создать SQL код;
  • Применить SQL код на базе.

Это делается самим ORM при выполнении следующих команд:

python manage.py makemigrations
python manage.py migrate

Миграция данных в Django

Попробуем добавить данные в базу через ORM. Один из способов сделать это - использовать скрипт, который идет вместе с Django, под названием shell. Он работает как обычный IDE Python, но дополнительно подтягивает зависимости Django. Что бы его использовать выполните следующую команду:

python manage.py shell

Импортируйте созданные модели:

from crud_app.models import StudentModel, GroupModel

Работа с shell в Django

Create

Есть два способа добавить данные в базу. Первый - это использовать метод .save(). В примере ниже мы создадим группу для студентов:

# создаем объект (новую строку в базе)
group = GroupModel()
# присваиваем значение полю (колонке)
group.name = "A101"
# формируем и выполняем SQL запрос
group.save()

Если вы не выполните метод save() в примере выше, то данные не будут добавлены в базу. Из-за этого я чаще использую метод create(), который делает все сам:

group = GroupModel.objects.create(name="A102")

Так как переменная group, в обоих случаях, хранит ссылку на какой-то объект - мы можем продолжать изменять у него значения:

group.name = "A103"

Мы должны вызвать метод .save() еще раз, что бы это новое значение сохранилось:

group.save()

Read

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

GroupModel.objects.get(name="A102")

Получение данных из базы в Django

Метод get сработает, если будет найден только один экземпляр строки по соответствующему значению. Если таких результатов будет найдено больше одного - вы увидите ошибку:

  • crud_app.models.GroupModel.MultipleObjectsReturned: get() returned more than one GroupModel -- it returned 2!

Кроме колонок, которые вы создавали собственноручно Django автоматически создает колонку с идентификаторами (ID). Эта колонка, если не указано иное, всегда является первичным ключом (PK) (уникально идентифицирует строку) и по ней создается кластерный индекс. Получить значение этой строки (если вы не меняли первичный ключ сами) можно двумя способами:

group = GroupModel.objects.get(name="A102")
# Получаем идентификатор строки базы данных
group.id
# Получаем первичный ключ строки
group.pk

Вывод PK и ID в Django

В Django можно использовать дополнительные операторы, которые будут использоваться для поиска и получения данных. Среди таких операторов:

  • gt - значение слева больше чем справа;
  • lt - значение слева меньше чем справа;
  • gte - значение слева больше или равно правому;
  • lte - значение слева меньше или равно правому;
  • in - поиск в массиве или другом итерируемом объекте;
  • startswith - строка начинается с указанных символов.

Это не все операторы, которые вы можете использовать. Все операторы вы можете посмотреть в документации Django. Каждый такой оператор сопровождается двумя нижними подчеркиваниями "__". Пример такого поиска:

# Ищем значения где PK меньше чем 2
GroupModel.objects.get(pk__lt=2)
# Ищем значения, которые начинаются со строки A102
GroupModel.objects.get(name__startswith="A102")

Дополнительные условия для получения данных в Django

Можно искать строку по нескольким полям сразу:

GroupModel.objects.get(pk__lt=2, name="A101")

Использование условий в запросах Django

Кроме поиска значения в единичном экземпляре - мы так же можем искать группу значений из базы используя метод .filter():

GroupModel.objects.filter(pk__gt=0, pk__lt=5)

Возвращаемы тип данных называется QuerySet. Этот итерируемый объект и мы можем использовать индексы для вывода отдельных записей:

for el in GroupModel.objects.filter(pk__gt=0, pk__lt=5):
   print(el.name)

Работа с QuerySet и filter в Django

Вывод всех значений в таблице делается через метод all():

GroupModel.objects.all()

Update

Мы можем обновить колонку у одной строки используя раннее описанные методы:

group = GroupModel.objects.get(name="A102")
# Выводим текущее значение
group.name
# Присваиваем новое значение
group.name = "A103"
# Сохраняем в базу
group.save()
# Выводим новое значение
group.name

Получение и сохранение данных в Django

Можно обновить значение у множества объектов используя update(). В следующем примере мы поменяем name у всех найденных объектов:

groups = GroupModel.objects.filter(id__gte=2)
groups.update(name="103A")

Обновление данных с методом update в Django

Еще одна разница между save() и update() в том, что в первом случае будут обновлены все поля (не важно меняли ли вы их).  Для метода save() существует дополнительный параметр update_fields, который помогает избежать этого.

Существуют и другие методы обновления и создания записей, таких как update_or_create (обновляем запись, если она существует или создаем новую) или get_or_create (если записи нет, то она будет создана).

Delete

Метод delete() удаляет запись в базе данных. Он работает как для одного объекта так и для множества:

GroupModel.objects.get(name="A101").delete()
GroupModel.objects.filter(name__startswith="A").delete()

Удаление данных в Django

Создание и получение записи со связью ForeignKey

Наша таблица 'StudentModel' связана с таблицей 'GroupModel'. В качестве связующей колонки выступает 'group'. Что бы добавить студента и связать его с группой мы должны выполнить несколько действий:

  1. Получить нужный объект из GroupModel;
  2. Добавить студента, где поле 'group' будет ссылаться на найденный объект.

Предыдущие шаги на примере:

# Получили объект группы
group = GroupModel.objects.get(name="A101")
# создали студента
student = StudentModel.objects.create(
    name="Alexa Pirojkova",
    sex="Female",
    age="19",
    # указали в какой группе он находится
    group=group
)

Создание записи со связью ForeignKey в Django

Что бы найти студентов, которые находятся в определенной группе так же используется два нижних подчеркивания. В примере ниже мы указываем 'group' как часть 'StudentModel' и 'name', как поле в таблице 'GroupModel':

StudentModel.objects.get(group__name="A101")
StudentModel.objects.filter(group__name="A101")
StudentModel.objects.get(group_id__lt=10, id__gt=0)

Поиск записей по связи ForeignKey в Django

Всех студентов, принадлежащих определенной группе, можно получить и обратным путем. Имея объект принадлежащий модели GroupModel, мы можем увидеть метод "_set" возвращающий студентов:

group = GroupModel.objects.get(id=1)
dir(group)
group.studentmodel_set.all()

Получение данных со связью ForeignKey в Django используя set

Вывод SQL запросов

При необходимости вы можете вывести SQL запрос применяемый на базе данных. Это можно сделать использовав .query и только для типов данных QuerySet (массивов с объектами):

print(GroupModel.objects.all().query)
print(GroupModel.objects.filter(id__gt=0).query)

Вывод плана SQL запроса в Django

Следующий способ, соответственно, вернет ошибку т.к. возвращает не QuerySet:

group = GroupModel.objects.get(id=1)
group.query
  • AttributeError: 'GroupModel' object has no attribute 'query'

Что бы выводить SQL запрос для всех случаев можно использовать логирование, debug toolbar и другие способы.

 

Создание CRUD приложения

Наше приложение будет состоять нескольких частей:

  1. Формы, с помощью которой можно добавлять студентов и обновлять данные уже у существующих;
  2. Страницы для создания студентов, на основе формы;
  3. Страницы для просмотра полной информации об одном студенте;
  4. Страницы с выводом всех студентов
  5. Страницы с возможностью обновления данных об одном студенте на основе формы;
  6. Страницы удаления.

Реализуем эти задачи за счет простых функций ( function based views). В Django так же существуют "class based views", которые еще больше сокращают шаги описанные ниже, но они рассматриваться не будут.

Форма

В Django есть несколько способов создать форму. Если вам нужна форма, которая будет соответствовать полям модели, то можно использовать класс ModelForm. В этом случае мы должны указать только название модели и поля, которые хотим выводить конечным пользователям.

Для создания форм принято создавать новый файл под названием 'forms.py' в папке того приложения, над которым вы работаете. В нашем случае это будет путь:

crud_app\forms.py

В этом файле мы импортируем класс модели и формы и напишем код:

from .models import StudentModel
from django.forms import ModelForm


class StudentForm(ModelForm):

    class Meta:
        # Названием модели на основе которой
        # нужно создать форму.
        model = StudentModel
        # Поля модели, которые нужно вывести
        fields = ('name', 'sex', 'age', 'group')

Создание формы ModelForm в Django

Страница создания студентов

Страницы сайта реализуются за счет функции или класса (возвращает данные пользователю), адреса (по которому можно обратится пользователю) и шаблона (разметка HTML). Функции и классы определяются в views.py, адреса в urls.py, а шаблоны в папке templates.

Функцию, которая будет создавать студентов, назовем create_view. Эта функция будет использовать следующие возможности:

  1. request - объект, который возвращается от пользователя. В этом объекте хранится введенные пользователем данные, тип запроса и другая информация;
  2. form - хранит в себе объект формы или распарсенные данные из request;
  3. is_valid() - функция, которая проверяет что все данные, введенными пользователями, соответствуют тому, что находится в модели (базе);
  4. .save() - сохранение нового студента.

Сама функция будет выглядеть так:

from .models import StudentModel
from .forms import StudentForm
from django.shortcuts import render, redirect
 
def create_view(request):
    # Проверяем, что запрос на 
    # добавление студента (POST)
    # или просто на получение формы
    if request.method == 'POST':
        # Получаем из запроса только те данные
        # которые использует форма
        form = StudentForm(request.POST)
        # Проверяем правильность введенных данных
        if form.is_valid():
            # сохраняем в базу
            form.save()
            # переадресуем на главную страницу
            return redirect('/')
    else:
        form =StudentForm()
        context = {
            'form': form
        }
        return render(request, 'create.html', context)

Создание функции во views.py для создания записи в Django

Создадим шаблон, который будет отображать данные из функции по следующему пути:

vim crud_app/templates/create.html

В этот файл поместите следующую разметку:

<form method = "post">
    {% csrf_token %}
    {{ form.as_p }}
    <input type = "submit" value = "submit">
</form>

Шаблон вывода формы в Django

Определить адрес, по которому пользователь сможет открыть эту форму, можно в urls.py:

vim crud_project/urls.py

В этот файл добавьте следующий код:

from django.contrib import admin
from django.urls import path
from crud_app.views import *

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

Создание ссылки для вывода формы создания в Django

Теперь можно запустить сервер и открыть страницу для проверки работы формы:

python manage.py runserver

Форма создания в Django

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

Страницы с выводом студентов

Мы создадим 2 страницы, которые будут выводить студентов. Первая страница, с названием student_detail_view, вернет информацию об одном студенте. Функция student_view- вернет список студентов.

Метод Http404, который используется ниже, вернет ошибку если указанный id не будет найден:

from django.http import Http404

# ...
 
def student_view(request):
    # Получаем всех студентов
    dataset = StudentModel.objects.all()
    return render(request, 'listview.html', {'dataset': dataset})
 
def student_detail_view(request, id):
    try:
        # Получаем студента по-определенному id
        data = StudentModel.objects.get(id=id)
    except StudentModel.DoesNotExist:
        raise Http404('Такого студента не существует')
 
    return render(request, 'detailview.html', {'data': data})

Функция вывода данных в Django

Создадим файл шаблона, для функции student_detail_view, по следующему пути:

vim crud_app/templates/detailview.html

С содержимым:

<h3>Name: {{ data.name }}</h3><br>
<h3>Sex: {{ data.sex}}</h3><br>
<h3>Age: {{ data.age }}</h3><br>
<h3>Group: {{ data.group.name }}</h3><br>
<hr/>

И еще один файл, для функции student_view, по пути:

vim crud_app/templates/listview.html

С содержимым: 

{% for data in dataset %}
{{ data }}
<hr>
{% endfor %}

Добавим 2 ссылки в следующий файл:

vim crud_project/urls.py
path('', student_view),
path('<int:id>/', student_detail_view),

Создание ссылки в urls.py для вывода данных в Django

Теперь, открывая главную страницу, вы увидите всех студентов:

Вывод данных списком в Django

Если добавить к странице идентификатор, откроем конкретного студента:

Детальный вывод в Django

Страница обновления студента

Функцию, с помощью которой сможем обновить данные студента назовем update_view. Если от пользователя приходит запрос GET, то значит ему нужно вернуть текущую информацию о студенте. Если запрос типа POST, то пользователь прислал уже обновленные данные и их нужно сохранить:

from django.shortcuts import render, redirect, get_object_or_404
...

def update_view(request, id):
    try:
        old_data = get_object_or_404(StudentModel, id=id)
    except Exception:
        raise Http404('Такого студента не существует')
    # Если метод POST, то это обновленные данные студента
    # Остальные методы - возврат данных для изменения
    if request.method =='POST':
        form = StudentForm(request.POST, instance=old_data)
        if form.is_valid():
            form.save()
            return redirect(f'/{id}')
    else:
        form = StudentForm(instance = old_data)
        context ={
            'form':form
        }
        return render(request, 'update.html', context)

Создание формы обновления данных в Django

Мы так же должны создать файл шаблона по следующему пути:

vim crud_app/templates/update.html

Со следующим содержимым: 

<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="update">
</form>

В файл urls.py поместим:

vim crud_project/urls.py
path('update/<int:id>/', update_view),

Создание ссылки обновления данных в Django

Запустив сервер и открыв страницу обновления можно будет увидеть заполненную форму с возможностью обновления данных:

Страница обновления данных в Django

Страница удаления

Функция удаления пользователя будет выглядеть следующим образом:

def delete_view(request, id):
    try:
        data = get_object_or_404(StudentModel, id=id)
    except Exception:
        raise Http404('Такого студента не существует')
 
    if request.method == 'POST':
        data.delete()
        return redirect('/')
    else:
        return render(request, 'delete.html')

Создание функции удаления данных в Django

Создадим файл шаблона:

vim crud_app/templates/delete.html

С содержимым:

<form method="post">
    {% csrf_token %}
    Подтвердите удаление студента
    <input type = "submit" value="Да">
    <a href='/'>Нет</a>
</form>

В файл urls добавьте:

path('delete/<int:id>/', delete_view),

Создание ссылки удаления данных в Django

При запуске сервера эта страница будет выглядеть так:

Страница удаления данных в Django

 

...

Теги: #python #django


Каналы
Telegram FixMyPc Telegram Лента FixMyPC RSS Rss
Популярные тэги
О блоге
Этот блог представляет собой конспекты выученного материала, преобретенного опыта и лучшие практики в системном администрировании и программировании.