Файлы
posts
migrations
0002_auto_20200124_1059.py
__init__.py
0001_initial.py
templates
index.html
models.py
__init__.py
apps.py
admin.py
tests.py
urls.py
views.py
task4orm
__init__.py
settings.py
urls.py
wsgi.py
manage.py
views.py
Урок 1: CRUD и фильтрация через ORM
Напишите код, который позволяет найти посты с ключевым словом «утро», при условии, что посты созданы автором leo в период с 7 по 21 июля 1854 года.
Сначала напишите и проверьте код у себя локально в Django Python shell. А потом вставьте результат в posts/view.py.
Первичный ключ автора необязателен, если известно значение username
Импортируйте модуль datetime.
Диапазон дат для поля pub_date определяется суффиксом range
Браузер1023x977
Браузер1023x977

CRUD и фильтрация через ORM

Для работы с БД у моделей в Django есть встроенный набор методов. Они наследуются от класса models.Model и поддерживают основные операции по обработке данных в БД: CRUD. Вы знакомы с этой аббревиатурой из урока по SQL

CRUD-операции

  • Create: Model.objects.create() — создание объекта в базе
  • Read: Model.objects.get(id=N) — чтение объекта по его ключу
  • Update: object.property= 'new value' и потом object.save() — изменение объекта
  • Delete: object.delete() — удаление объекта из базы
Сейчас мы разберёмся с основными задачами, которые решает Django ORM. Но перед этим познакомимся с инструментом, который упростит нам тестирование кода.

Python shell

С интерпретатором кода Python, как и со многими другими программами, можно работать через командную строку. Если в консоли выполнить команду $python3 без параметров, то интерпретатор Python запустится в «интерактивном режиме». Теперь можно ввести в командную строку любые скрипты python — и они будут выполняться прямо в терминале. Это похоже на работу командной строки, но вместо команд для работы с файлами выполняется программный код, строчка за строчкой.
Откройте новое окно терминала и посмотрите, как работает python shell.
Символы >>> — это приглашение для ввода команд, то же, что и знак $ в командной строке.
Скопировать кодBASH
# запускаем интерпретатор без параметров $ python3 # дальше пишем на python # создаём переменную >>> best_slogan = "Mischief Managed!" # вызываем функцию print() >>> print(best_slogan) # и получаем результат Mischief Managed! # арифметика тоже работает >>> 2 + 2 * 2 6
Прелесть этого режима в том, что он тут же выводит результат выполнения скрипта. Например, создадим и выведем переменную:
Скопировать кодSHELL
>>> x = 5 >>> x 5
Обратите внимание: переменные, которые вы создали во время работы в python shell, будут доступны до тех пор, пока вы не закроете окно терминала.

Django Python shell

В таком же интерактивном режиме можно работать и с Django-проектами. Для этого python shell надо запустить в виртуальном окружении проекта.
Откройте терминал, убедитесь, что запущено виртуальное окружение проекта Yatube и выполните команду:
Скопировать кодBASH
(venv) $ python manage.py shell
Вы увидите примерно такой результат:
Скопировать кодBASH
(venv) $ python manage.py shell Python 3.8.0 (default, Nov 22 2019, 23:37:58) [Clang 11.0.0 (clang-1100.0.33.12)] on darwin Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) >>>
Всё, что вы хотите узнать, но стесняетесь спросить — выясняйте через команду help.
При работе в интерактивном режиме в Django вам становятся доступны все данные проекта. Можно создавать объекты, управлять базой данных, тестировать функции проекта.
Скопировать кодSHELL
(venv) $ python manage.py shell Python 3.8.0 (default, Nov 22 2019, 23:37:58) [Clang 11.0.0 (clang-1100.0.33.12)] on darwin Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) # чтобы убедиться, что мы работаем именно с нашим проектом Yatube # импортируем модели из проекта и заглянем в базу данных: # запросим все объекты модели Post >>> from posts.models import Post, User >>> Post.objects.all()
Основы работы с shell разобрали, теперь можно и делами заняться. Все следующие команды выполняйте в shell.

Работаем с проектом

Запрос к базе возвращает специальный объект QuerySet, который содержит список объектов, соответствующих условиям запроса. По запросу .all() мы получили все объекты модели Post.
Чтобы получить определённый объект, можно обратиться к нему по его primary key:
Скопировать кодSHELL
# можно использовать User.objects.get(id=1) >>> me = User.objects.get(pk=3) >>> me # python shell сообщает, что переменная me содержит <Объект> класса User, # а поле username этого объекта равно "admin" <User: admin>
Класс User предустановлен в Django, и для него настроен вывод на экран именно в таком виде. При создании любого класса можно описать, каким образом объекты этого класса будут выводиться на экран (например, в python shell или при вызове print(any_object)). Это описывается в «магическом методе» __str__.
Запросим пользователя с pk=13: у нас в базе такой записи пока что нет. Если объект с запрошенным ключом не найден, то появится сообщение об ошибке:
Скопировать кодSHELL
>>> User.objects.get(pk=13) Traceback (most recent call last): File "<console>", line 1, in <module> File "/Dev/Yatube/venv/lib/python3.8/site-packages/django/db/models/manager.py", line 82, in manager_method return getattr(self.get_queryset(), name)(*args, **kwargs) File "/Dev/Yatube/venv/lib/python3.8/site-packages/django/db/models/query.py", line 415, in get raise self.model.DoesNotExist( django.contrib.auth.models.User.DoesNotExist: User matching query does not exist.
Новый объект в базе можно создать методом create():
Скопировать кодSHELL
# создаём объект, передаём свойства >>> new = Post.objects.create(author=me, text="Смотри, этот пост я создал через shell!") # посмотрим, какой id присвоен этому объекту в базе >>> new.id 39 # а что в поле text? >>> new.text "Смотри, этот пост создан через shell!" # а что в поле author? >>> new.author <User: admin> # смотрим, что записано в поле username того объекта, на который ссылается поле author >>> new.author.username 'admin'
Объект new в момент создания сохранился в базе и получил уникальный id.
Чтобы изменить этот объект, надо присвоить новое значение одному из его полей и вызвать метод save():
Скопировать кодSHELL
# присваиваем новое значение полю text >>> new.text = "Смотри, этот пост обновлён!" # но пока что значение изменено лишь в коде. В БД всё ещё хранится старое значение # чтобы отправить новое значение в базу данных — вызываем метод save() >>> new.save()
Если вы изменили объект в коде — он не изменится в базе до тех пор, пока вы не вызовете метод save().
Теперь обновлённый текст записи можно увидеть в админ-зоне.
Удалить объект из базы можно методом delete(). При вызове этот метод дополнительно удалит и все связанные объекты, для которых был задан параметр on_delete=models.CASCADE.
Скопировать кодSHELL
>>> new.delete()
Для дальнейшей работы нам понадобятся тестовые посты админа, создайте их:
Скопировать кодSHELL
>>> first_post = Post.objects.create(author=me, text="Oops, I did it again!") >>> second_post = Post.objects.create(author=me, text="Утромъ гольдъ Дерсу Узала на повторно заданный вопросъ согласенъ ли онъ поступить проводникомъ изъявилъ свое согласіе и съ этого момента онъ сталъ членом экспедиціи")

Фильтрация объектов

Основная задача при работе с базой — это поиск объектов по заданным признакам. В SQL за это отвечают команды блока WHERE, в Django ORM — метод filter():
Скопировать кодSHELL
# найти все объекты, значение поля author у которых равно me # в этой переменной хранится объект User с pk=3 >>> Post.objects.filter(author=me) <QuerySet [<Post: Oops, I did it again!>, <Post: Утромъ гольдъ Дерсу Узала...>]>
В базе найдены две записи, соответствующие условиям запроса.
Если ваш вывод выглядит так <QuerySet [<Post: Post object(39)>, <Post: Post object(40)>] значит в классе Post не хватает метода __str__ , который нужен для красивого вывода объектов на экран. Добавьте его:
Скопировать кодPYTHON
class Post: ... def __str__(self): # выводим текст поста return self.text
Увидеть SQL-запрос, который будет отправлен к базе, можно с помощью команды .query:
Скопировать кодSHELL
>>> print(Post.objects.filter(author=me).query) SELECT "posts_post"."id", "posts_post"."text", "posts_post"."pub_date", "posts_post"."author_id", "posts_post"."group_id" FROM "posts_post" WHERE "posts_post"."author_id" = 1
В Django ORM аналог команд WHERE выглядит так: указывается имя поля, затем два знака подчеркивания __, название фильтра и его значение:
Скопировать кодSHELL
# найти посты, где поле text__содержит строку "again" >>> Post.objects.filter(text__contains='again') <QuerySet [<Post: Oops, I did it again!>]>
При запросе указываются именованные параметры функции filter(). Имя параметра состоит из имени поля и суффикса, указывающего, какой оператор применять. Доступные операторы:
  • exact — точное совпадение. «Найти пост, где поле id точно равно 1»
ORM: Post.objects.filter(id__exact=1) или Post.objects.filter(id=1)
На SQL это условие выглядит так: SELECT ... WHERE id = 1.
Сравнение работает и с None. Выражение Post.objects.filter(text=None) превратится в SELECT ... WHERE text IS NULL
  • contains — поиск по тексту в поле text. «Найти пост, где в поле text есть слово "oops" именно в таком регистре»
ORM: Post.objects.filter(text__contains="oops")
SQL: SELECT ... WHERE text LIKE '%oops%';
В большинстве баз данных (например, в MySQL или PostgreSQL) ничего не найдётся: регистр символов не совпадает. В посте админа написано "Oops", а в запросе — "oops".
Однако в нашем проекте установлена СУБД SQLite (Django ставит её по умолчанию), и у неё есть неприятная особенность: она не различает регистр символов нигде, кроме как в кодировке ASCII (а мы все давно уже работаем в UTF-8, ведь в ней есть смайлики 😃).
Мануалы формулируют проблему так: SQLite only understands upper/lower case for ASCII characters by default.
Итак: в базе SQLite фильтр Post.objects..filter(text__contains="oops") найдёт записи со словами oops, Oops и OOPS, а в большинстве других баз такой запрос вернёт только запись, где регистр слова совпадает полностью.
  • in — вхождение в множество. «Найти пост, где значение поля id точно равно одному из значений: 1, 3 или 4»
ORM: Post.objects.filter(id__in=[1, 3, 4])
SQL: SELECT ... WHERE id IN (1, 3, 4);
Если вместо списка будет передана строка, она разобьётся на символы: «Найти пост, где значение поля text точно равно "o", "p" или "s"»
ORM: Post.objects.filter(id__in="oops")
SQL: SELECT ... WHERE text IN ('o', 'p', 's');
  • Операторы сравнения
gt> (больше),
gte=> (больше или равно),
lt< (меньше),
lte<= (меньше или равно).
«Найти пост, где значение поля id больше пяти»
ORM: Post.objects.filter(id__gt=5)
SQL: SELECT ... WHERE id>5;
  • Операторы сравнения с началом и концом строки startswith, endswith
«Найти посты, где содержимое поля text начинается со строки "Утромъ"»
ORM: Post.objects.filter(text__startswith="Утромъ")
SQL: SELECT ... WHERE text LIKE Утромъ% ESCAPE
  • range — вхождение в диапазон
Скопировать кодPYTHON
import datetime start_date = datetime.date(1890, 1, 1) end_date = datetime.date(1895, 3, 31) Post.objects.filter(pub_date__range=(start_date, end_date)) # SQL: SELECT ... WHERE pub_date BETWEEN '1890-01-01' and '1895-03-31'; # выберет посты, опубликованные в диапазоне с 1 января 1890 до 31 марта 1895
  • При работе с частями дат можно применять дополнительные суффиксы date, year, month, day, week, week_day, quarter и указывать для них дополнительные условия:
Скопировать кодPYTHON
# условия для конкретной даты Post.objects.filter(pub_date__date=datetime.date(1890, 1, 1)) Post.objects.filter(pub_date__date__lt=datetime.date(1895, 1, 1)) # условия для года и месяца Post.objects.filter(pub_date__year=1890) Post.objects.filter(pub_date__month__gte=6) # условия для квартала Post.objects.filter(pub_date__quarter=1)
Такой же синтаксис применяется и для времени: hour, minute, second.
  • isnull — проверка на пустое значение.
ORM: Post.objects.filter(pub_date__isnull=True)
SQL: SELECT ... WHERE pub_date IS NULL;

Объединение условий

В одном запросе можно указать несколько условий одновременно. Для этого последовательно вызовите методы filter() с различными параметрами. Будет сгенерирован SQL-запрос, в котором все условия объединены оператором AND.
Исключить данные из выборки можно методом exclude():
Скопировать кодSHELL
# выбрать посты, начинающиеся со слова "Утромъ" # исключить из выборки посты автора me # и показать только те посты, которые опубликованы не ранее 30 января 1895 года >>> Post.objects.filter( ... text__startswith='Утромъ' ... ).exclude( ... author=me ... ).filter( ... pub_date__gte=datetime.date(1895, 1, 30) ... )

Сортировка и ограничение количества результатов

Этот синтаксис вам знаком: мы применяли сортировку во view-функции index() и там же ограничили число возвращаемых результатов запроса.
order_by("-pub_date") — сортировать результаты по полю pub_date в обратном порядке (от больших значений к меньшим) [:11] — вернуть не более одиннадцати результатов из найденных.
Скопировать кодSHELL
>>> print(Post.objects.order_by("-pub_date")[:11].query) SELECT "posts_post"."id", "posts_post"."text", "posts_post"."pub_date", "posts_post"."author_id", "posts_post"."group_id" FROM "posts_post" ORDER BY "posts_post"."pub_date" DESC LIMIT 11
Сортировку и ограничение числа возвращаемых результатов можно объединить с фильтрацией:
Скопировать кодSHELL
>>> Post.objects.filter(text__startswith='Утромъ').order_by("-pub_date")[:2]
В Django ORM есть и дополнительный синтаксис, он описан в документации.
Переименовать
Удалить файл
Переименовать
Удалить файл
Переименовать
Удалить файл
Переименовать
Новый файл
Новая папка
Удалить папку
Переименовать
Удалить файл
Переименовать
Новый файл
Новая папка
Удалить папку
Переименовать
Удалить файл
Переименовать
Удалить файл
Переименовать
Удалить файл
Переименовать
Удалить файл
Переименовать
Удалить файл
Переименовать
Удалить файл
Переименовать
Удалить файл
Переименовать
Новый файл
Новая папка
Удалить папку
Переименовать
Удалить файл
Переименовать
Удалить файл
Переименовать
Удалить файл
Переименовать
Удалить файл
Переименовать
Новый файл
Новая папка
Удалить папку
Переименовать
Удалить файл
Новый файл
Новая папка