Barcode Lookup: Integrating Open Food Facts

Food Tracker (Django)

Chapter 4 · Barcode Lookup: Integrating Open Food Facts

The admin gave a working CRUD interface for free. This chapter writes the first genuinely custom code in this course: a view that turns a scanned barcode into a real product name.

URLs and Views: Two Separate Files, on Purpose

Django deliberately keeps "which URL triggers this code" (urls.py) separate from "what that code actually does" (views.py) — two files, wired together explicitly. FastAPI's own decorator-based routing (@app.get("/path")) puts both in the same place, directly above the function it decorates. Neither is objectively better; it's a real, structural difference in where that one piece of wiring lives, worth knowing rather than assuming every framework does it the same way.

A Second Model: BarcodeCache

Open Food Facts needs no API key here either, so the same reasoning that justified a Cloud Function in this app's own Firebase sibling course applies again: caching, consistent error handling, future-proofing — nothing about secrecy.

class BarcodeCache(models.Model): barcode = models.CharField(max_length=64, primary_key=True) name = models.CharField(max_length=200) category = models.CharField(max_length=100, blank=True)

Note primary_key=True on barcode here — the exact opposite of Chapter 2's own Item model, where barcode is deliberately an ordinary field, never the primary key, because the same product can be purchased and tracked more than once. BarcodeCache genuinely is one record per product, so keying it directly by barcode is correct here.

The View

# pantry/views.py import requests from django.http import JsonResponse from .models import BarcodeCache def lookup_barcode(request, barcode): cached = BarcodeCache.objects.filter(barcode=barcode).first() if cached: return JsonResponse({"name": cached.name, "category": cached.category}) response = requests.get(f"https://world.openfoodfacts.org/api/v2/product/{barcode}.json") data = response.json() if data.get("status") != 1: return JsonResponse({"error": "Product not found"}, status=404) product = data["product"] name = product.get("product_name", "Unknown item") category = (product.get("categories_tags") or [""])[0].replace("en:", "") BarcodeCache.objects.create(barcode=barcode, name=name, category=category) return JsonResponse({"name": name, "category": category})

Wiring the URL

# pantry/urls.py from django.urls import path from . import views urlpatterns = [ path("lookup/<str:barcode>/", views.lookup_barcode, name="lookup_barcode"), ]

<str:barcode> is Django's own path converter — it types and captures the URL segment directly inside the route pattern string, rather than as a Python function type annotation the way FastAPI declares a path parameter's type.

Migrating the New Model

python manage.py makemigrations pantry python manage.py migrate

requests Isn't a Django Battery

Worth being precise about what "batteries included" actually covers: Django ships an ORM, an admin, forms, and templating — everything for building the app's own request/response cycle. Making an outbound call to someone else's API is a different job entirely, and needs a separate library (pip install requests) regardless of how much Django itself provides natively.

The same design question, resolved identically
Whether to use a barcode as a primary key isn't answered once for the whole app — it depends entirely on what the collection or table actually represents. Item tracks purchased instances (many can share a barcode); BarcodeCache tracks canonical products (exactly one per barcode). This app's own Firebase sibling course faced the identical question for its items vs. barcodeCache collections, and landed on the same answer for the same underlying reason — proof this is a genuine data-modeling principle, not a coincidence of one particular database technology.
Test the view before building anything else
Visiting /lookup/<a-real-barcode>/ directly in a browser returns the raw JSON response — a genuinely useful way to confirm this view works correctly before any frontend exists at all, the same "test one layer in isolation" habit Chapter 3's manage.py shell tip already established.
No error handling yet for a failing external request
This view has no try/except around the requests.get() call at all. If Open Food Facts is slow, times out, or is simply down, this raises an unhandled exception, and whoever called this endpoint gets a raw Django 500 error page instead of a clean, meaningful response. This is a genuine, known gap in this simple version — a production-ready version would wrap the request in error handling and return a proper JSON error response instead of letting the exception propagate.

Where This Course Is Headed

Camera-based barcode scanning, the add-item flow via Django Forms, expiry alerts, item history and search, marking items used, recipe lookup, Django REST Framework, deployment, and a capstone.

Hands-On Exercises

Exercise 1

Explain the structural difference between Django's separated urls.py/views.py routing and FastAPI's decorator-based routing. Where does "this URL maps to this code" actually live in each?

📄 View solution
Exercise 2

Explain why BarcodeCache uses barcode as its primary key while Item does not, and connect this to how the Firebase sibling course resolved the identical design question.

📄 View solution
Exercise 3

Explain what actually happens right now if Open Food Facts is down or times out while this view is running, and why this is described as a genuine, known gap rather than an acceptable final state.

📄 View solution

Chapter 4 Quick Reference

  • urls.py / views.py — routing and logic deliberately kept separate, unlike FastAPI's combined decorator approach
  • BarcodeCachebarcode as primary key, correctly, since this collection is one-per-product
  • <str:barcode> — Django's own URL path converter/typing system
  • requests — not a Django battery; outbound API calls still need a separate library
  • Known gap — no error handling yet for a failing Open Food Facts request; a real 500 error results today
  • Next chapter: Camera-Based Barcode Scanning (Frontend)