Link Search Menu Expand Document

Django ORM

Команды будем выполнять через Django shell python3 manage.py shell

Для начала импортируем необходимые модули:
>>> from news.models import News, Category

Вся информация по методам

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

News.objects.get(pk=5).category_id

и получить цифру (id) категори.
Либо мы можем обратиться так:

News.objects.get(pk=5).category

и получить ссылку на объект категории.

Также мы можем получить по категории новости (которые ссылаются на эту категорию) при помощи <имя_связанной_модели>_set:

cat4 = Category.objects.get(pk=4)
cat4.news_set.all()

Вывод всех новостей 2-й категории:

>>> for item in Category.objects.get(pk=2).news_set.all():
...     print(item.title, item.is_published)
... 

Обратную связь (<имя_связанной_модели>_set) мы можем перенезначить при помощи атрибута related_name (например related_name='get_news')

Фильтры полей

Для использования фильтра полей необходимо использовать следующую структуру:
<имя поля>__<фильтр>
Всю информацию можно посмотреть тут
Фильтры можно комбинироватьследующим образом:

  • либо так: Entry.objects.filter(id__gt=4, id__lte=4)
  • либо так: Entry.objects.filter(id__gt=4).filter(id__lte=4)

По умолчанию использется логическое И. Для логического ИЛИ необходимо использовать класс Q

ФильтрПримерОписание
gtEntry.objects.filter(id__gt=4)Больше чем 4
gteEntry.objects.filter(id__gte=4)Больше или равно 4
ltEntry.objects.filter(id__lt=4)Меньше чем 4
lteEntry.objects.filter(id__lte=4)Меньше или равно 4
containsEntry.objects.get(headline__contains='Lennon')SELECT ... WHERE headline LIKE '%Lennon%'; (регистрозависимый поиск)
icontainsEntry.objects.get(headline__icontains='Lennon')SELECT ... WHERE headline ILIKE '%Lennon%'; (регистронезависимый поиск)
inEntry.objects.filter(id__in=[1, 3, 4]) или Entry.objects.filter(headline__in='abc')SELECT ... WHERE id IN (1, 3, 4); Ищем что-то в указанном диапазоне

В фильтрах мы можем передавать не только значение для списка но и QUERY SET для получения другого списка значений.

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

>>> cats = Category.objects.filter(pk__in=[1,3])
>>> News.objects.filter(category__in=cats)

В этом случае мы получим список новостей принадлежащих 1 и 3 категории.

Методы получения записей

МетодПримерОписание
first()News.objects.first()Получение первой записи
last()News.objects.last()Получение последней записи
latest()News.objects.latest('updated_at')Получение самой поздней записи (в случае когда есть поле с датами)
earliest()News.objects.earliest('updated_at')Получение самой ранней записи (в случае когда есть поле с датами)
exists()Category.objects.get(pk=1).news_set.exists()Данный метод возвращает True или False в зависимочти от наличия записей по запросу
count()Category.objects.get(pk=1).news_set.count()Возвращает количество записей по запросу
get_previous_by_<имя поля>()News.objects.get(pk=5).get_previous_by_created_at()Возвращает предыдущую запись (только для полей с датами или датой и временем)
get_next_by_<имя поля>()News.objects.get(pk=5).get_next_by_created_at()Возвращает следующую запись (только для полей с датами или датой и временем)
distinct() получение только уникальных записей

Фильтрация по значения полей связанных записей

<имя поля внешнего ключа>__<имя поля первичной модели>

>>> News.objects.filter(category__title='Политика')

Таким образом мы получим все новости у которых категория “Политика”.

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

>>> Category.objects.filter(news__title__contains='формы')

Класс Q

Класс Q необходим для реализации логического ИЛИ или НЕ

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

from django.db.models import Q
``````sh
>>> News.objects.filter(Q(pk__in=[5,6]) | Q(title__contains='2'))

Срез данных

Чтобы получить срез данных в Django можно воспользоваться такой конструкцией:

>>> News.objects.all()[:3]

Таким образом мы получим первые три записи.

Агрегатные функции

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

Для начала добавить еще одно поле к нашей модели – “Количество просмотров”

views = models.IntegerField(default=0)

И импортируем необходимый модуль:

from django.db.models import *
``````sh
>>> News.objects.aggregate(Min('views'), Max('views'))
{'views__min': 0, 'views__max': 1000}

Или так (задаем имена результатам):

>>> News.objects.aggregate(min_views=Min('views'), max_views=Max('views'))
{'min_views': 0, 'max_views': 1000}

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

>>> News.objects.aggregate(dif=Min('views')-Max('views'))
{'dif': -1000}

Также мы можем получить:

Агрегатная функцияЗначение
Sum('views')Сумма всех значений по колонке ‘views’
Avg('views')Среднее значение по колонке ‘views’
Count('views', distinct=True)Получение количества записей с уникальным полем views
Count('news', filter=Q(news__is_published__gt=0))Получение количества записей с применением фильтра

Вычисление по группам записей

Пример 1

>>> cats = Category.objects.annotate(cnt=Count('news'))
>>> for item in cats:
...     print(item.title, item.cnt)
... 
Культура 4
Наука 1
Политика 6
Спорт 4

Таким образом мы получим вывод всех категорий с суммой всех новостей данной категории.

Пример 2

Получение для каждой рубрики максимального количества просмортов

>>> cats = Category.objects.annotate(max_views=Max('news__views'))
>>> for item in cats:
...     print(item.title, item.max_views)
... 
Культура 700
Наука 1000
Политика 542
Спорт 785

Пример 3

Получение для каждой рубрики сумму просмотров всех новостей

>>> cats = Category.objects.annotate(sum_views=Sum('news__views'))
>>> for item in cats:
...     print(item.title, item.sum_views)
... 
Культура 986
Наука 1000
Политика 1041
Спорт 1107

Метод Values

Данный метод позволяет получить нужные для получения поля и возвращает словари с указанными полями.

>>> news1 = News.objects.values('title', 'views')
>>> news1[0]['title']
'Новость из формы 9'

Получим ‘title’ кажой новости из базы и её категорию. Также посмотрим сколько всего SQL запросов при этом выполняется.

>>> from django.db import reset_queries, queries
>>> reset_queries()  # Очистим историю запросов
>>> news = News.objects.values('title', 'views', 'category__title')
>>> for item in news:
...     print(item['title'], item['category__title'])
... 
Новость из формы 9 Спорт
Новость из формы 6 Спорт
Новость из формы 6 Политика
Новость из формы 5 Политика
Новость из формы 4 Политика
Новость из формы 2 Культура
Новость из формы 3 Политика
Новость из формы Спорт
Новость из формы Политика
Новость из админки Культура
Новость 5 Наука
Новая ность 4 Политика
Новость 3 Спорт
Новость 2 Культура
Новость 1 Культура
>>> connection.queries
[{'sql': 'SELECT "news_news"."title", "news_news"."views", "news_category"."title" FROM "news_news" INNER JOIN "news_category" ON ("news_news"."category_id" = "news_category"."id") ORDER BY "news_news"."created_at" DESC', 'time': '0.000'}]

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

Класс F

Данный класс предназначен для сравнения полей одного объекта

from django.db.models import F

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

>>> news = News.objects.get(pk=1)
>>> news.views = F('views') +1 
>>> news.save()

Тем самым мы избежим коллизий при возможном одновременном использоваии данного приложения.

Пример: найдем новость, название которой (‘title’) есть в поле ‘content’

>>> News.objects.filter(content__icontains=F('title'))

Функции СУБД

Часть вычислений мы можем перенести на сторону СУБД благодаря встроенным в неё функциям

Подробная информация

Пример: получим длину поля ‘title’ каждой новости:

from django.db.models.functions import *
``````sh
>>> news = News.objects.annotate(length = Length('title')).all()
>>> for item in news:
...     print(item.title, item.length)
... 
Новость из формы 9 18
Новость из формы 6 18
Новость из формы 6 18
Новость из формы 5 18
Новость из формы 4 18
Новость из формы 2 18
Новость из формы 3 18
Новость из формы 16
Новость из формы 16
Новость из админки 18
News 5 6
Новая ность 4 13
Новость 3 9
Новость 2 9
Новость 1 9

Выполнение чистых SQL запросов

Подробная информация

При выборе полей мы обязаны указать первичный ключ

>>> News.objects.raw("SELECT * FROM news_news")
<RawQuerySet: SELECT * FROM news_news>
>>> news = _
>>> for item in news:
...     print(item.title, item.pk, item.is_published)
... 
Новость 1 1 True
Новость 2 2 True
Новость 3 3 True
Новая ность 4 4 True
News 5 5 False
Новость из админки 7 True
Новость из формы 8 True
Новость из формы 9 True
Новость из формы 3 10 True
Новость из формы 2 11 True
Новость из формы 4 12 True
Новость из формы 5 13 True
Новость из формы 6 14 False
Новость из формы 6 15 True
Новость из формы 9 16 True
>>>  

Отложенная загрузка полей

Отложенная загрузка означает что мы можем получить поля, которые мы не указывали в запросе.

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

Передача параметров в RAW SQL запросы

Это требуется если нужно отобрать поля по условию

Рассмотрим плохой пример:

>>> news = News.objects.raw("SELECT * FROM news_news WHERE title = 'News 5'")
>>> news[0].title
'News 5'

Данная практика является плохой, так как она ведет к SQL инъекциям. Правильнее будет использовать параметры:

>>> news = News.objects.raw("SELECT * FROM news_news WHERE title = %s", ['News 5'])
>>> news[0].title
'News 5'