Django Generic Foreign Key Filtering (difference between v1.5 & v1.6)
Asked Answered
I

2

7

I have the following conceptual models:

class GenericAbstractBase(models.Model):
    name = models.CharField(max_length=255)
    staff = generic.GenericRelation(
        "Staff",
        content_type_field="content_type",
        object_id_field="object_id",
    )

    class Meta:
        abstract = True


class GenericModelA(GenericAbstractBase):
    extra_a = models.CharField(max_length=255)


class GenericModelB(GenericAbstractBase):
    extra_b = models.CharField(max_lenth=10)


class Staff(models.Model):

    first_name = models.CharField(max_length=255)
    last_name = models.CharField(max_length=255)
    active = models.CharField(max_length=10, choices = ACTIVE_CHOICES)

    limit = models.Q(app_label='staff', model='genericmodela') | models.Q(app_label='staff', model='genericmodelb')
    content_type = models.ForeignKey(ContentType, limit_choices_to=limit)
    object_id = models.PositiveIntegerField()
    generic_object = generic.GenericForeignKey("content_type", "object_id")

In Django v1.4 & Django v1.5 the following query works fine:

>>> ctype = ContentType.objects.get_for_model(GenericModelA)
>>> Staff.objects.filter(
        content_type=ctype,
        genericmodela__name__icontains="a"
    )
>>> [<Staff: Abbott, Kaylee>, <Staff: Adams, Kay>, ... ]

and the SQL (sqlite) it produces looks like:

SELECT 
    "staff_staff"."id", "staff_staff"."first_name","staff_staff"."last_name",
"staff_staff"."active","staff_staff"."content_type_id" ,"staff_staff"."object_id"
FROM "staff_staff"
INNER JOIN "staff_staff" T2 ON ("staff_staff"."id" = T2."id")
INNER JOIN "staff_genericmodela" ON (T2."object_id" = "staff_genericmodela"."id")
WHERE (
"staff_genericmodela"."name" LIKE % a % ESCAPE \ '\\\'
AND "staff_staff"."content_type_id" = 11
)

However in Django 1.6 the query fails with a FieldError:

FieldError: Cannot resolve keyword 'genericmodela' into field. Choices are: active, content_type, department, first_name, id, last_name, object_id, position

The following statement in the release notes may be relevant:

Django 1.6 contains many changes to the ORM. These changes fall mostly in three categories:

  1. Bug fixes (e.g. proper join clauses for generic relations, query combining, join promotion, and join trimming fixes)

My question is, what changed in Django 1.6 that caused this to break? Am I stuck with using extra or doing this type of filtering in Python?

Isaacisaacs answered 5/3, 2014 at 14:37 Comment(1)
Did you find any clean solution to this issue since then?Klingel
K
1

I've found some interesting information here.

As a workaround you can do something like this:

ctype = ContentType.objects.get_for_model(GenericModelA)

pk_list = Staff.objects.filter(
    content_type=ctype
).values_list('object_id', flat=True)

GenericModelA.objects.filter(pk__in=pk_list, name__icontains="a")
Klingel answered 17/6, 2014 at 9:50 Comment(1)
Thanks for the link (I have edited it to point to the actual ticket: code.djangoproject.com/ticket/21559). It looks like I was relying on undocumented behaviour and will need to use a workaround similar to the one you have suggested. I'll accept this as the correct answer.Isaacisaacs
I
1

It appears that this only worked in Django 1.4 due to undocumented behaviour so I decided to use a CASE statement in an extra queries to do the queries I wanted. For example:

cta = ContentType.objects.get_for_model(models.GenericModelA)
ctb = ContentType.objects.get_for_model(models.GenericModelB)

extraq = """
CASE
    WHEN content_type_id = {0}
        THEN (SELECT extra_a from staff_genericmodela WHERE object_id = staff_genericmodela.id)
    WHEN content_type_id = {1}
        THEN (SELECT extra_b from staff_genericmodelb WHERE object_id = staff_genericmodelb.id)
 END
 """.format(cta.pk, ctb.pk)

 Staff.objects.extra(select={'genericname': extraq}).extra(where=["genericname LIKE %s", params=["%{0}%".format("foobar")])

This has worked well for me so far and should be easily extendable to other similar cases.

Isaacisaacs answered 5/11, 2014 at 1:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.