Expiry Alerts
Food Tracker (Django)
Chapter 7 · Expiry Alerts
With items actually being added, this chapter builds the first payoff feature: surfacing what's about to go to waste.
The QuerySet
expiry_date__lte is Django's own field-lookup syntax — a double-underscore suffix mapping directly to a SQL comparison (__lte → <=, __gte → >=, __contains → LIKE, and so on). SQLAlchemy, Python's other major ORM, takes a genuinely different syntactic approach — operator overloading, so the identical comparison reads as Item.expiry_date <= threshold directly. Both are real, working styles for the same underlying idea; recognizing Django's own double-underscore convention on sight is worth having, since it looks unusual coming from any other ORM.
timezone.now(), Not datetime.now()
Django's modern default, USE_TZ=True, makes the project timezone-aware. Using plain datetime.now() in a timezone-aware project produces a naive datetime with no timezone attached at all — comparisons against Django's own timezone-aware stored values can then silently be wrong, especially once the deployed server and its users don't share a timezone. django.utils.timezone.now() is Django's own answer to exactly the same category of problem every Food Tracker course has hit in its own way — never trust the wrong clock.
No Composite Index Drama Here — a Genuinely Fair Point for This Course
Food Tracker (React + Firebase)'s own Chapter 7 needed an entire section on Firestore's composite-index requirement — a query combining an equality filter and a range filter on two different fields simply refuses to run at all until an index is explicitly declared. Django's ORM, running on a real relational database, has no equivalent hard requirement: the identical two-field filter here just runs. It might run slowly without an index as the table grows — ordinary SQL's own honest tradeoff, matching that same comparison the Firebase course already drew — but it never refuses outright the way Firestore does. This is one of the few places in the whole quartet where the relational side genuinely has the easier time, worth saying plainly rather than only ever finding fault with it.
The Template
{% empty %} is Django's own built-in "if the loop had nothing to iterate" branch, right inside the {% for %} tag — no separate {% if items %} wrapper needed around it.
python manage.py shell, then Item.objects.filter(status="active", expiry_date__lte=timezone.now().date() + timedelta(days=3)) — the same isolate-and-verify habit already established for the model (Chapter 3) and the barcode view (Chapter 4).
USE_TZ=True (Django's modern default), mixing a naive datetime.now() value into a comparison against timezone-aware stored values doesn't necessarily raise an error — it can just as easily produce a subtly wrong result, especially once the deployed server and its users span different timezones. timezone.now() avoids the ambiguity entirely by staying timezone-aware throughout.
Where This Course Is Headed
Item history and search, marking items used, recipe lookup, Django REST Framework, deployment, and a capstone.
Hands-On Exercises
Explain Django's expiry_date__lte field-lookup syntax, and show how SQLAlchemy would express the identical comparison using its own operator-overloading style.
📄 View solutionExplain why this chapter's own two-field filter query doesn't need anything like the Firebase sibling course's composite-index requirement, and what tradeoff still exists if no database index is ever added.
📄 View solutionExplain concretely what could go wrong if datetime.now() were used instead of timezone.now() in this chapter's own dashboard view, given USE_TZ=True.
📄 View solutionChapter 7 Quick Reference
__lte— Django's own double-underscore field-lookup syntax, vs. SQLAlchemy's operator overloadingtimezone.now()— always, notdatetime.now(), onceUSE_TZ=True- No composite index required — a genuine, fair SQL advantage over the Firebase sibling course's own Chapter 7
Meta.indexes— an optional performance optimization here, not a hard functional requirement{% empty %}— a built-in empty-loop branch inside{% for %}- Next chapter: Item History & Live Search-as-You-Type