8

In my Django project I have a model named Value that has a GenericForeignKey as such:

class Value(models.Model):
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, blank=True, null=True)
    val_id = models.PositiveIntegerField(blank=True, null=True)
    data_obj = GenericForeignKey('content_type', 'val_id')

Value is meant to be a sort of polymorphic table that uses ContentType and GenericForeignKey to be able to point to an arbitrary table that contains the actual data. For example, there is a model Int that a Value can point to:

class Int(models.Model):
    data = models.IntegerField(blank=True, null=True)

So, after creating an instance of Int:

myint = Int.objects.create(data=1)

I can create a Value that points to it:

myval = Value.objects.create(data_obj=myint)

There are other similar models that a Value would point to, such as UInt, String, and Float; they all only have the one field data. However, I was wondering how to query/filter through instances of Value based on the data contained by the model pointed to by the field data_obj. I.e., I want to be able to do something like this:

Value.objects.filter(data_obj__data=1)

But this results in an error:

Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/usr/local/lib/python2.7/site-packages/django/db/models/manager.py", line 122, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/usr/local/lib/python2.7/site-packages/django/db/models/query.py", line 790, in filter
    return self._filter_or_exclude(False, *args, **kwargs)
  File "/usr/local/lib/python2.7/site-packages/django/db/models/query.py", line 808, in _filter_or_exclude
    clone.query.add_q(Q(*args, **kwargs))
  File "/usr/local/lib/python2.7/site-packages/django/db/models/sql/query.py", line 1243, in add_q
    clause, _ = self._add_q(q_object, self.used_aliases)
  File "/usr/local/lib/python2.7/site-packages/django/db/models/sql/query.py", line 1269, in _add_q
    allow_joins=allow_joins, split_subq=split_subq,
  File "/usr/local/lib/python2.7/site-packages/django/db/models/sql/query.py", line 1149, in build_filter
    lookups, parts, reffed_expression = self.solve_lookup_type(arg)
  File "/usr/local/lib/python2.7/site-packages/django/db/models/sql/query.py", line 1035, in solve_lookup_type
    _, field, _, lookup_parts = self.names_to_path(lookup_splitted, self.get_meta())
  File "/usr/local/lib/python2.7/site-packages/django/db/models/sql/query.py", line 1316, in names_to_path
    "adding a GenericRelation." % name
FieldError: Field 'data_obj' 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 problem is that even though I've updated each data model to have a GenericRelation field back to the Value model:

class Int(models.Model):
    data = models.IntegerField(blank=True, null=True)
    val_obj = GenericRelation(Value, object_id_field="val_id")

class UInt(models.Model):
    data = models.PositiveIntegerField(blank=True, null=True)
    val_obj = GenericRelation(Value, object_id_field="val_id")

class String(models.Model):
    data = models.CharField(max_length=2048)
    val_obj = GenericRelation(Value, object_id_field="val_id")

# etc.

I still can't get reverse querying to work and get the same error from above. What am I doing wrong here that adding GenericRelation still does not allow for reverse querying or seem to change anything at all? I would definitely like the reverse querying to work because the project has a filtering script that would be much easier to make work on Values if I could only just reverse query successfully.

UPDATE: I figured out how to not get a FieldError, but still cannot get reverse querying to work the way I want. What I did was add a related_query_name to each of the Int, UInt, Float, etc. data models' GenericRelation fields:

val_obj = GenericRelation(Value, object_id_field="val_id", related_query_name="val")

And according to the Django documentation, I believe I would then reverse query like this:

Value.objects.filter(val__data=1)

Yet, even though I definitely have data models whose data field contains the int 1, this and any other reverse querying returns an empty list. Is there something else I'm doing wrong/missing?

Dan K
  • 803
  • 8
  • 26

3 Answers3

10

I had the same problem and found I had to define a unique related_query_name for each related model, as described here:

https://stackoverflow.com/a/36166644/1143466

For example:

class Int(models.Model):
    data = models.IntegerField(blank=True, null=True)
    val_obj = GenericRelation(Value, object_id_field="val_id",
                              related_query_name="int")

class UInt(models.Model):
    data = models.PositiveIntegerField(blank=True, null=True)
    val_obj = GenericRelation(Value, object_id_field="val_id",
                              related_query_name="uint")

class String(models.Model):
    data = models.CharField(max_length=2048)
    val_obj = GenericRelation(Value, object_id_field="val_id",
                              related_query_name="string")

Then you would create a filter by querying each model separately, e.g.:

from django.db.models import Q

val = 1
val_filter = Q(int__data=val) | Q(uint__data=val) | Q(string__data=val)
filtered_values = Value.objects.filter(val_filter)
Community
  • 1
  • 1
Leila Hadj-Chikh
  • 1,653
  • 17
  • 16
0

A GenericRelation is not an actual field; nothing needs to be added to the database; that is not the cause of your error.

You probably need to add the app that defines your models to the INSTALLED_APPS setting.

Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895
  • The first part of your answer helps clear things up. However I definitely already added the app that defines my models in my INSTALLED_APPS setting, so I don't think that's the issue either. – Dan K Apr 10 '16 at 20:55
-3

There may be a parameter in the call to GenericForeignKey or GenericRelation as noted in the answer by @LeilaHC that allows you to change the names of the generic FK, but failing that, you will get issues if you change the names of the fields that describe the id and models of the polymorphic relation (at least up till Django 1.9). That is content_type an object_id. Thus

class Value(models.Model):
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, blank=True, null=True)
    val_id = models.PositiveIntegerField(blank=True, null=True)
    data_obj = GenericForeignKey('content_type', 'val_id')

should be

class Value(models.Model):
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, blank=True, null=True)
    object_id = models.PositiveIntegerField(blank=True, null=True)
    data_obj = GenericForeignKey('content_type', 'object_id')

The issues are sometimes of the nature that the reverse relation does not exist, and sometimes you will get a "The field 'XXX' does not exist. valid choices are... , object_id, content_type, ..." error (where XXX is what you changed the name to) in django.contrib.contenttypes.

MagicLAMP
  • 1,032
  • 11
  • 26