Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 997
randlet Avatar asked Mar 05 '14 14:03

randlet


2 Answers

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")
like image 172
Germano Avatar answered Nov 15 '22 09:11

Germano


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.

like image 36
randlet Avatar answered Nov 15 '22 10:11

randlet