8

Well, now I'm using Django 1.6+

And I have a model:

class FileReference(models.Model):
    # some data fields
    # ...
    pass

class Person(models.Model):
    avatar = models.ForeignKey(FileReference, related_name='people_with_avatar')

class House(models.Model):
    images = models.ManyToManyField(FileReference, related_name='houses_with_images')

class Document(model.Model):
    attachment = models.OneToOneField(FileReference, related_name='document_with_attachment')

So, many other model will have a foreign key referring to the FileReference model.

But sometimes, the referring models is deleted, with the FileReference object left.

I want to delete the FileReference objects with no foreign key referencing.

But so many other places will have foreign keys.

Is there any efficient way to find all the references? i.e. get the reference count of some model object?

Alfred Huang
  • 17,654
  • 32
  • 118
  • 189
  • 2
    Use the `Collector`: http://stackoverflow.com/a/12162619/548165 – catavaran Mar 20 '15 at 03:03
  • @catavaran I tried that, when I call `collector.collect(file_ref_obj)`, it raises: `TypeError: 'FileReference' object does not support indexing` – Alfred Huang Mar 20 '15 at 03:43
  • Pass a list with the single instance: `collector.collect([file_ref_obj])` – catavaran Mar 20 '15 at 03:44
  • possible duplicate of [Get all related Django model objects](http://stackoverflow.com/questions/2233883/get-all-related-django-model-objects) – djangonaut Mar 20 '15 at 12:58
  • possible duplicate of [How to show related items using DeleteView in Django?](http://stackoverflow.com/questions/12158714/how-to-show-related-items-using-deleteview-in-django) – Cerin Sep 18 '15 at 18:42
  • If you find any answer useful you should consider up-vote and select the correct answer. Thanks! – Gal Silberman Mar 25 '18 at 12:08

1 Answers1

2

I stumbled upon this question and I got a solution for you. Note, that django==1.6 is not supported any more, so this solution will probably work on django>=1.9

Lets say we are talking about 2 of the objects for now:

class FileReference(models.Model):
    pass

class Person(models.Model):
    avatar = models.ForeignKey(FileReference, related_name='people_with_avatar', on_delete=models.CASCADE)

As you can see in ForeignKey.on_delete documentation, when you delete the related FileReference object, the referenced object Person is deleted as well.

Now for your question. How do we do the revered? We want upon Person deletion that FileReference object will be removed as well.

We will do that using post_delete signal:

def delete_reverse(sender, **kwargs):
    try:
        if kwargs['instance'].avatar:
            kwargs['instance'].avatar.delete()
    except:
        pass

post_delete.connect(delete_reverse, sender=Person)

What we did there was deleting the reference in avatar field on Person deletion. Notice that the try: except: block is to prevent looping exceptions.

Extra:

The above solution will work on all future objects. If you want to remove all of the past objects without a reference do the following:

In your package add the following file and directories: management/commands/remove_unused_file_reference.py

from django.core.management.base import BaseCommand, CommandError


class Command(BaseCommand):

    def handle(self, *args, **options):

        file_references = FileReference.objects.all()
        file_reference_mapping = {file_reference.id: file_reference for file_reference in file_references}

        persons = Person.objects.all()
        person_avatar_mapping = {person.avatar.id: person for person in persons}


        for file_reference_id, file_reference in file_reference_mapping.items():
            if file_reference_id not in person_avatar_mapping:
                file_reference.delete()

When you done, call: python manage.py remove_unused_file_reference This is the base idea, you can change it to bulk delete etc...

I hope this will help to someone out there. Good Luck!

Gal Silberman
  • 3,756
  • 4
  • 31
  • 58