skip to content

In Django, why does Like.objects.filter(content_object=photo) raise FieldError, and how do you query generic relations instead?

level: middleimportance: should knowfreq 38%

answer

  1. virtual field, no column
  2. no automatic reverse relation
  3. filter the two real columns
  4. related_query_name opens the join

basics

~20 s

A GenericForeignKey has no column and no reverse relation, so the ORM cannot build a lookup on it. Filter on content_type and object_id directly, or declare a GenericRelation, optionally with related_query_name, to filter and aggregate across the link.

solid answer

~30 s

The ORM raises `FieldError` saying the field 'does not generate an automatic reverse relation' and suggesting a `GenericRelation`. There are three ways round it. From the Like side, filter the two concrete columns: `Like.objects.filter(content_type=ContentType.objects.get_for_model(photo), object_id=photo.pk)`. From the target side, a `likes = GenericRelation(Like)` field gives `photo.likes.all()`, `Photo.objects.filter(likes__user=u)` and `Photo.objects.annotate(n=Count('likes'))`. And `GenericRelation(Like, related_query_name='photo')` lets you write `Like.objects.filter(photo__title__icontains='sunset')`, with Django adding the content-type condition to the join. `select_related('content_object')` fails the same way; use `prefetch_related`.

code

python · 20 lines
python
from django.contrib.contenttypes.fields import GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models import Count


class Photo(models.Model):
    title = models.CharField(max_length=200)
    likes = GenericRelation('likes.Like', related_query_name='photo')


# Like.objects.filter(content_object=photo)  -> FieldError

ct = ContentType.objects.get_for_model(photo)
Like.objects.filter(content_type=ct, object_id=photo.pk)

photo.likes.filter(user=request.user).exists()
Photo.objects.filter(likes__user=request.user)
Photo.objects.annotate(like_count=Count('likes')).order_by('-like_count')
Like.objects.filter(photo__title__icontains='sunset')   # needs related_query_name

go deeper

for a junior

Remember that the generic attribute cannot appear in filter(); you filter content_type and object_id instead.

for a middle

Explain why the ORM has no table to join for a GenericForeignKey and how a GenericRelation supplies both join conditions, including related_query_name.

for a senior

Spot the object_id-only filter that mixes targets, and know when a cross-type query must become one query per content type.

for a principal

Judge whether reporting needs across liked content justify concrete foreign keys, since generic links make every analytical join a hand-assembled one.

## Why the lookup fails A `GenericForeignKey` is registered on the model as a **private field without a column**. When the ORM resolves `filter(content_object=photo)`, it finds a relation field whose `related_model` is `None`, because the target model varies row by row. With no single table to join and no column to compare, the ORM refuses with a `FieldError`: the field "does not generate an automatic reverse relation and therefore cannot be used for reverse querying. If it is a GenericForeignKey, consider adding a GenericRelation." The same applies to `get(content_object=...)`, `exclude(...)` and `Q(content_object=...)`. ## Querying from the Like side The two columns underneath are ordinary fields, so query them directly: - `ct = ContentType.objects.get_for_model(photo)` returns Photo's registry row, cached after the first call. - `Like.objects.filter(content_type=ct, object_id=photo.pk)` finds the photo's likes. - For several targets of one type: `Like.objects.filter(content_type=ct, object_id__in=[p.pk for p in photos])`. Never filter on `object_id` alone. Comment 5 and photo 5 share the same `object_id`, and only `content_type` tells them apart. ## Querying from the target side with GenericRelation A `GenericRelation` on the target turns the link into something the ORM can join. Its join condition includes both `object_id = target.pk` **and** `content_type_id = <target's content type>`, which Django adds for you. 1. `photo.likes.all()` and `photo.likes.filter(user=u)` go through the related manager. 2. `Photo.objects.filter(likes__user=u)` returns the photos a user liked, joining through `likes`. 3. `Photo.objects.annotate(like_count=Count('likes')).order_by('-like_count')` counts likes per photo; the aggregation API works with a `GenericRelation`. ## related_query_name: filtering from Like to one target type By default the relation from Like back to Photo **does not exist** for queries. Setting `related_query_name` on the `GenericRelation` creates it: - `likes = GenericRelation(Like, related_query_name='photo')` on Photo; - then `Like.objects.filter(photo__title__icontains='sunset')` joins Like to Photo with the content-type condition included, so only likes of photos match. Without it, the equivalent is two steps: fetch the matching photo ids, then filter likes on `content_type` and `object_id__in`, which the docs show as the fallback. ## A worked example Suppose the product page must show a user's liked photos, newest first, with a like count on each. Without a `GenericRelation`, the code needs a content-type lookup, a query for the user's photo likes, a list of ids, a query for the photos, and a separate count query or a manual aggregation. With `likes = GenericRelation(Like, related_query_name='photo')` on Photo it becomes one expression: - `Photo.objects.filter(likes__user=request.user)` selects the photos the user liked; - `Photo.objects.annotate(like_count=Count('likes'))` adds each photo's total like count; - `.order_by('-like_count')` sorts by it. Combining the first two needs care. If the `likes__user` filter comes before the annotation on the same relation, the count reuses the filtered join and counts only that user's like, giving 1 per photo. Restricting the photos with a `pk__in` subquery over the user's likes (`content_type` plus `object_id`) and then annotating keeps the totals honest. This is standard multi-valued relation behaviour, but generic relations make it easy to miss because the join is invisible in the model code. ## What works and what does not | Operation | Works? | Instead | |---|---|---| | `Like.objects.filter(content_object=photo)` | no, `FieldError` | filter `content_type` and `object_id` | | `Like.objects.select_related('content_object')` | no, `FieldError` on evaluation | `prefetch_related('content_object')` | | `photo.likes.count()` | yes, with `GenericRelation` | not available without one | | `Photo.objects.filter(likes__user=u)` | yes, with `GenericRelation` | two queries without one | | `Like.objects.filter(photo__title=...)` | only with `related_query_name` | two queries without it | | `Like.objects.values('content_object')` | no | `values('content_type', 'object_id')` | ## Pitfalls - **Missing content type condition.** Hand-written SQL that joins on `object_id` only will mix targets; the ORM's `GenericRelation` join does not make that mistake. - **Multi-valued joins.** Filtering `Photo.objects.filter(likes__user__in=team)` can return a photo once per matching like; add `.distinct()` when the join can match several rows. - **Cross-type queries stay awkward.** "All likes by this user, with their targets" cannot be a single join across photos, comments and posts; load the likes, then use `prefetch_related` to fetch targets with one query per type.

  • Why is filtering likes by object_id alone a bug even when it seems to work in development?
    `object_id` values collide across targets: photo 5, comment 5 and post 5 all store `object_id=5`. In development with a few rows the ids may not overlap, so the bug hides; in production a photo's like count silently includes likes of comments with the same id. Always pair `object_id` with `content_type`, or go through a `GenericRelation`, whose join adds the content-type condition automatically.
  • Does adding related_query_name change the database schema?
    No. A `GenericRelation` adds no column to either table, and `related_query_name` only registers a query name on the Like model so the ORM can build the join `like.object_id = photo.id AND like.content_type_id = <Photo's id>`. `makemigrations` may record the field in model state, but no schema operation runs.

saying these in an interview costs you the question

  • filter(content_object=obj) works just like filtering on a ForeignKey
  • select_related('content_object') joins every possible target table
  • Filtering on object_id alone is enough to find one photo's likes
  • GenericRelation queries from Like work without setting related_query_name
  • related_query_name adds a column to the Like table