Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Filter non-existing GenericForeignKey objects in Django queryset

I have a simple model with a generic foreign key:

class Generic(models.Model):
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

I would like to filter all entries in this table that have non-null content_object's, i.e. filter out all instances of Generic whose content objects no longer exist:

Generic.objects.filter(~Q(content_object=None))

This doesn't work, giving the exception:

django.core.exceptions.FieldError: Field 'content_object' does not generate an automatic reverse relation and therefore cannot be used for reverse querying. If it is a GenericForeignKey, consider adding a GenericRelation.

Adding GenericRelation to the referenced content type models makes no difference.

Any help on how to achieve this would be appreciated, many thanks.

EDIT: I realise I could cascade the delete, however this is not an option in my situation (I wish to retain the data).

like image 557
Ben Avatar asked Jan 26 '16 15:01

Ben


1 Answers

If you want to filter some records out, it's often better to use the exclude() method:

Generic.objects.exclude(object_id__isnull=True)

Note, though, that your model now doesn't allow empty content_object fields. To change this behaviour, use the null=True argument to both object_id and content_type fields.

Update

Okay, since the question has shifted from filtering out null records to determining broken RDBMS references without help of RDBMS itself, I'd suggest a (rather slow and memory hungry) workaround:

broken_items = []
for ct in ContentType.objects.all():        
    broken_items.extend(
        Generic.objects
        .filter(content_type=ct)
        .exclude(object_id__in=ct.model_class().objects.all())
        .values_list('pk', flat=True))

This would work as a one-time script, but not as a robust solution. If you absolutely want to retain the data, the only fast way I could think out is having a is_deleted boolean flag in your Generic model and setting it in a (post|pre)_delete signal.

like image 62
Alex Morozov Avatar answered Sep 18 '22 14:09

Alex Morozov