Модуль 14

QuerySet та робота з даними

Ось готовий урок, згенерований у стилі Девіда Малана (CS50), спеціально для теми QuerySet у Django.


🎓 CS50-Style: Магія QuerySet та робота з даними

Привіт усім! 👋 Ласкаво просимо назад.

Сьогодні ми не просто пишемо код. Сьогодні ми вчимося бути володарями даних.

1. 🔥 Вступ: Проблема та мотивація

Уявіть, що ви — власник величезного супермаркету. У вас на складі лежить мільйон товарів. 📦

До вас підходить клієнт і каже: "Я хочу купити червоні кросівки 42-го розміру".

Що ви зробите? 1. Підете на склад і будете перебирати кожен з мільйона товарів у руках, перевіряючи: "Це кросівки? Ні. Це кросівки? Так. А вони червоні? Ні..."? 2. Чи, можливо, ви заглянете в комп’ютерну базу, відфільтруєте список і підете до конкретної полиці?

Очевидно, другий варіант, правда?

Але в програмуванні новачки часто роблять саме перший варіант! Вони витягують з бази даних ВСЕ (усіх користувачів, усі товари), а потім у циклі Python намагаються знайти потрібне. Це повільно. Це вбиває пам'ять вашого сервера. Це — катастрофа для стартапу, який росте. 📉

Ось тут на сцену виходить QuerySet.

Нам потрібен спосіб сказати базі даних: "Гей, дай мені тільки те, що мені треба, і відсортуй це, будь ласка, за ціною", ще до того, як дані потраплять у нашу програму.

Сьогодні ми розберемося, як це робити елегантно, швидко і професійно. Поїхали! 🚀


2. 🧠 Теоретична база: Що там "під капотом"?

Давайте спростимо.

У Django є Model (опис таблиці) і є Manager (зазвичай це .objects). Коли ви пишете Product.objects..., ви звертаєтесь до "менеджера", який керує доступом до даних.

А що повертає менеджер? Він повертає QuerySet.

📝 Визначення: QuerySet — це колекція об'єктів із вашої бази даних. Але це не просто список (list). Це — лінивий запит.

🐢 Магія "Лінивості" (Lazy Execution)

Це найважливіше, що треба зрозуміти сьогодні.

Уявіть, що ви в ресторані. 🍽️ Ви читаєте меню. Ви думаєте: "Може, замовити суп? А може, салат? Ні, давайте стейк". Поки ви це думаєте і навіть коли кажете офіціанту "Я буду стейк..." — кухар ще нічого не готує. Кухня починає працювати тільки тоді, коли офіціант відносить замовлення і каже: "ГОТУЙ!".

Так само працює QuerySet: 1. Ви створюєте фільтр (filter). 2. Додаєте сортування (order_by). 3. Виключаєте зайве (exclude).

У цей момент Django НЕ чіпає базу даних. Він просто формує "замовлення" (SQL-запит) у пам'яті. Запит полетить у базу тільки тоді, коли ви реально захочете побачити дані (наприклад, виведете їх у шаблоні або у print()).

Чому це круто? Це економить ресурси. Ви можете будувати складні ланцюжки фільтрів, не навантажуючи базу на кожному кроці.


3. 🧪 Приклади: Від простого до реального

Давайте працювати з моделлю Student. У студента є ім'я (name), вік (age) і середній бал (gpa).

Приклад 1: Отримати всіх

Найпростіше.

students = Student.objects.all()

Питання до вас: Чи зробив Django запит до бази в цьому рядку? Ні! Він просто підготував "намір" отримати всіх.

Приклад 2: Фільтрація (The Filter)

Нам потрібні тільки відмінники (GPA > 90).

smart_students = Student.objects.filter(gpa__gt=90)

Зверніть увагу: gpa__gt означає "gpa greater than" (більше ніж). Це спеціальний синтаксис Django.

Приклад 3: Ланцюжки (Chaining) ⛓️

А тепер реальний кейс. Нам потрібні студенти: 1. Старші 18 років. 2. Які НЕ звуться "Ivan" (ну, наприклад). 3. Відсортовані за прізвищем.

Як це напише новачок? Можливо, трьома різними командами. Як це напише профі?

result = Student.objects.filter(age__gte=18).exclude(name="Ivan").order_by("name")

👀 Що тут відбувається? Ми беремо QuerySet, застосовуємо фільтр — отримуємо новий QuerySet. Застосовуємо виключення — знову новий QuerySet. Це як конвеєр на заводі.

І тільки коли ми напишемо:

for s in result:
    print(s.name)

...ось у цей момент Django скаже базі даних: "SELECT * FROM students WHERE..." і принесе дані.


4. 🛠 Практична частина

Час розім'яти пальці! Уявіть, що ви працюєте над системою для інтернет-магазину. У вас є модель Order (Замовлення) з полями: * status ('new', 'paid', 'shipped') * amount (сума замовлення) * created_at (дата створення)

Завдання 1: Розігрів

Напишіть запит, щоб отримати всі замовлення зі статусом 'paid'.

Завдання 2: Знайди "китів" 🐳

Напишіть запит, щоб знайти замовлення, які є 'paid' І сума яких перевищує 1000.

Завдання 3: Виправ помилку 🐛

Студент-стажер написав код, щоб знайти конкретне замовлення за ID=1, але код падає з помилкою, якщо замовлення не існує.

# Код стажера:
order = Order.objects.get(id=1) 

Як зробити це безпечніше або обробити помилку? (Підказка: згадайте try/except або filter + first()).

Завдання 4: Міні-кейс

Менеджер каже: "Дай мені 5 найсвіжіших замовлень, які ще не відправлені (статус 'new')". Як ви це реалізуєте, використовуючи filter, order_by (по спаданню дати) і зрізи Python [:5]?

Завдання 5: А що, якщо...

Ви написали orders = Order.objects.all(). А потім у коді зробили print(orders[0]). Скільки записів витягне Django з бази? Один чи всі? (Це питання з зірочкою ⭐).


5. 💡 Мислення як у розробника

Як відрізнити код джуніора від коду сеньйора в цій темі?

🚫 Помилка №1: Фільтрація Python-ом

Погано:

users = User.objects.all() # Витягли 10 000 користувачів у пам'ять
active_users = []
for u in users:
    if u.is_active:        # Перевіряємо в циклі
        active_users.append(u)

Це повільно і з'їдає всю оперативну пам'ять (RAM).

Добре:

active_users = User.objects.filter(is_active=True) # База сама все відфільтрувала

🚫 Помилка №2: len() проти count()

Якщо вам треба просто знати кількість, не робіть len(Model.objects.all()). Це завантажить усі об'єкти, щоб просто їх порахувати. Використовуйте Model.objects.count(). Це перетвориться на швидкий SQL-запит SELECT COUNT(*).

Порада від профі:

QuerySet'и можна зберігати у змінних і передавати у функції. Вони не "псуються". Ви можете створити "базовий запит" (наприклад, тільки активні товари) і використовувати його в різних частинах сайту, дофільтровуючи за потребою.


6. 🧩 Підсумок

Отже, що ми сьогодні поклали в нашу "скарбничку знань"?

  1. QuerySet — це розумний список об'єктів.
  2. Він лінивий 🦥. Він не біжить у базу, поки ви його не змусите.
  3. Ми використовуємо Chaining (ланцюжки), щоб писати красиві та складні запити: filter().exclude().order_by().
  4. Ми перекладаємо важку роботу на Базу Даних, а не робимо її в Python.

Ви тепер вмієте: Отримувати саме ті дані, які вам потрібні, не навантажуючи сервер зайвою роботою.

🔎 Тизер наступного уроку: А що робити, якщо дані розкидані по різних таблицях? Як отримати всі замовлення конкретного користувача? На наступному уроці ми розберемо Relational Fields (Зв'язки) та магічний select_related. Це буде як збирати LEGO з різних коробок!

А поки що — це був CS50. Побачимось! 👋