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
Фильтр | Пример | Описание |
---|---|---|
gt | Entry.objects.filter(id__gt=4) | Больше чем 4 |
gte | Entry.objects.filter(id__gte=4) | Больше или равно 4 |
lt | Entry.objects.filter(id__lt=4) | Меньше чем 4 |
lte | Entry.objects.filter(id__lte=4) | Меньше или равно 4 |
contains | Entry.objects.get(headline__contains='Lennon') | SELECT ... WHERE headline LIKE '%Lennon%'; (регистрозависимый поиск) |
icontains | Entry.objects.get(headline__icontains='Lennon') | SELECT ... WHERE headline ILIKE '%Lennon%'; (регистронезависимый поиск) |
in | Entry.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'