Fix DoesNotExist: Product matching query does not exist. in Django
This error means .get() was called on a queryset but no row matched the filter criteria. Fix it by using get_object_or_404() for views that should return a 404 page, wrapping .get() in a try/except block, or using .filter().first() which returns None instead of raising an exception when no match is found.
Reading the Stack Trace
Here's what each line means:
- File "/app/products/views.py", line 11, in product_detail: The view calls Product.objects.get(slug=slug) with a slug value that does not match any row in the database.
- File "/venv/lib/python3.11/site-packages/django/db/models/query.py", line 637, in get: Django's QuerySet.get() raises DoesNotExist when zero rows match the filter. It also raises MultipleObjectsReturned if more than one row matches.
- products.models.Product.DoesNotExist: Product matching query does not exist.: No Product row in the database has the requested slug value.
Common Causes
1. Using .get() without error handling
The view uses .get() which raises DoesNotExist if no matching record is found, instead of returning a 404 or handling the case gracefully.
def product_detail(request, slug):
product = Product.objects.get(slug=slug) # Crashes if slug not found
return render(request, 'products/detail.html', {'product': product})
2. Deleted or unpublished record
The record existed previously but was deleted or marked as unpublished, and the URL or link still references it.
def product_detail(request, slug):
# Does not filter by is_published, so published URLs may break
product = Product.objects.get(slug=slug, is_published=True)
3. Case-sensitive slug mismatch
The slug in the URL uses different casing than the database value, and the default lookup is case-sensitive.
# URL: /products/My-Product/
# Database slug: my-product
product = Product.objects.get(slug='My-Product') # Case mismatch
The Fix
Use get_object_or_404() which catches DoesNotExist and returns a proper 404 response. This is Django's idiomatic pattern for detail views. For non-view code, use try/except or .filter().first() depending on whether the absence is expected.
def product_detail(request, slug):
product = Product.objects.get(slug=slug)
return render(request, 'products/detail.html', {'product': product})
from django.shortcuts import get_object_or_404
def product_detail(request, slug):
product = get_object_or_404(Product, slug=slug)
return render(request, 'products/detail.html', {'product': product})
# Alternative: try/except for custom handling
# from django.http import Http404
# def product_detail(request, slug):
# try:
# product = Product.objects.get(slug=slug)
# except Product.DoesNotExist:
# raise Http404('Product not found')
# return render(request, 'products/detail.html', {'product': product})
Testing the Fix
import pytest
from django.test import TestCase, Client
from products.models import Product
class TestProductDetail(TestCase):
def setUp(self):
self.client = Client()
self.product = Product.objects.create(
name='Test Widget',
slug='test-widget',
price=9.99,
)
def test_existing_product_returns_200(self):
response = self.client.get('/products/test-widget/')
assert response.status_code == 200
self.assertContains(response, 'Test Widget')
def test_nonexistent_product_returns_404(self):
response = self.client.get('/products/does-not-exist/')
assert response.status_code == 404
def test_deleted_product_returns_404(self):
self.product.delete()
response = self.client.get('/products/test-widget/')
assert response.status_code == 404
Run your tests:
pytest
Pushing Through CI/CD
git checkout -b fix/doesnotexist-product-detail,git add products/views.py,git commit -m "fix: use get_object_or_404 to return 404 for missing products",git push origin fix/doesnotexist-product-detail
Your CI config should look something like this:
name: CI
on:
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_DB: test_db
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
- run: pip install -r requirements.txt
- run: pytest --tb=short -q
The Full Manual Process: 18 Steps
Here's every step you just went through to fix this one bug:
- Notice the error alert or see it in your monitoring tool
- Open the error dashboard and read the stack trace
- Identify the file and line number from the stack trace
- Open your IDE and navigate to the file
- Read the surrounding code to understand context
- Reproduce the error locally
- Identify the root cause
- Write the fix
- Run the test suite locally
- Fix any failing tests
- Write new tests covering the edge case
- Run the full test suite again
- Create a new git branch
- Commit and push your changes
- Open a pull request
- Wait for code review
- Merge and deploy to production
- Monitor production to confirm the error is resolved
Total time: 30-60 minutes. For one bug.
Or Let bugstack Fix It in Under 2 minutes
Every step above? bugstack does it automatically.
Step 1: Install the SDK
pip install bugstack
Step 2: Initialize
import bugstack
bugstack.init(api_key=os.environ["BUGSTACK_API_KEY"])
Step 3: There is no step 3.
bugstack handles everything from here:
- Captures the stack trace and request context
- Pulls the relevant source files from your GitHub repo
- Analyzes the error and understands the code context
- Generates a minimal, verified fix
- Runs your existing test suite
- Pushes through your CI/CD pipeline
- Deploys to production (or opens a PR for review)
Time from error to fix deployed: Under 2 minutes.
Human involvement: zero.
Try bugstack Free →No credit card. 5-minute setup. Cancel anytime.
Deploying the Fix (Manual Path)
- Run the full test suite locally to confirm 404 handling works.
- Open a pull request with the view fix.
- Wait for CI checks to pass on the PR.
- Have a teammate review and approve the PR.
- Merge to main and verify 404 pages render correctly in staging.
Frequently Asked Questions
BugStack runs the fix through your existing test suite, generates tests for both existing and missing records, and validates that 404 pages render correctly before marking it safe to deploy.
BugStack never pushes directly to production. Every fix goes through a pull request with full CI checks, so your team can review it before merging.
Use .filter().first() when the absence of a record is a normal case (returns None), and get_object_or_404 when the record should exist and its absence means a bad URL.
If .get() can match multiple rows, either add a unique constraint to the field, or use .filter().first() to return just one. get_object_or_404 also raises MultipleObjectsReturned if the lookup is not unique.