Personal Catalogue: Django & PostgreSQL — Chapter 10, Exercise 3 ==================================================== TASK Build the api_search JSON view from this chapter, confirm it returns real JSON via a browser or curl request, and explain why it deliberately reuses the exact same Q-object query already built in Chapter 7 rather than writing new search logic. SOLUTION 1. In catalogue/views.py: from django.http import JsonResponse from django.db.models import Q def api_search(request): query = request.GET.get('q', '').strip() items = Item.objects.filter( Q(title__icontains=query) | Q(creator__icontains=query) ).values('id', 'title', 'creator')[:20] return JsonResponse(list(items), safe=False) 2. Wire it into catalogue/urls.py: path('api/search/', views.api_search, name='api_search'), 3. Test it: curl "http://127.0.0.1:8000/api/search/?q=python" EXPECTED RESULT A real JSON array response, e.g.: [ {"id": 4, "title": "Fluent Python", "creator": "Luciano Ramalho"}, {"id": 11, "title": "Python Crash Course", "creator": "Eric Matthes"} ] Note that `safe=False` is required here specifically because JsonResponse defaults to only accepting a dict as its top-level object (a real, deliberate Django security default guarding against a historical JSON-array-response vulnerability) - passing a list needs that default explicitly overridden. WHY IT REUSES CHAPTER 7'S QUERY The Q(title__icontains=query) | Q(creator__icontains=query) filter is exactly the same search logic Chapter 7 already built, tested, and established as correct for this project. Writing a second, separate version of that same query here would mean two places in the codebase could drift out of sync with each other over time - if the search behavior is ever changed (e.g. adding a third searchable field), only one of the two copies might get updated, silently producing inconsistent results between the HTML search page and this JSON endpoint. ANSWER: the api_search view returns a real JSON array of up to 20 matching items using JsonResponse(..., safe=False), and it deliberately reuses Chapter 7's own Q-object query rather than writing new logic, since duplicating the same search rule in two places would risk the two copies silently drifting apart over time. WHY THIS WORKS AS AN ANSWER ---------------------------- It builds and tests the actual view rather than describing it abstractly, correctly identifies and explains the safe=False requirement as a real Django security default (not an arbitrary flag), and gives a concrete, specific reason (drift risk) for the code-reuse principle rather than a vague "good practice" claim.