mtazzari
mtazzari

Reputation: 451

Django: how to save model instance after deleting a ForeignKey-related instance?

I am using Django 2.1.1.

I have a model Analysis that, among other fields, contains a ForeignKey to a MyFile model (a model I wrote to handle files):

from polymorphic.models import PolymorphicModel
from django.db.models import Model, DateTimeField, FileField, SET_NULL
from django.db.models.signals import pre_delete

class MyFile(Model):
    file = FileField(upload_to='./', null=False, blank=False)
    description = CharField(max_length=255, null=True, blank=True)
    date_added = DateTimeField(auto_now_add=True)


@receiver(pre_delete, sender=MyFile)
def mymodel_delete(sender, instance, **kwargs):
    """
    To delete the file connected to the `sender` class: receive the pre_delete signal
    and delete the file associated with the model instance.
    """
    instance.file.delete(False)

class Analysis(PolymorphicModel):
        # ... other fields ...
        file_results = ForeignKey(MyFile, on_delete=SET_NULL,
                                  related_name='file_results', 
                                  null=True, blank=True)

Analysis is a PolymorphicModel for reasons related to the bigger project.

In Analysis.file_results I set on_delete=SET_NULL because I want to allow an Analysis instance to exist even without a file_result, which can be populated later.

Let's suppose I have added a few files (the MyFile table has a few rows) and a few Analysis instances. Now, if I want to delete the file related to one of the instances of Analysis I do:

a = Analysis.objects.get(pk=0)
a.file_results.delete()
a.save()

but I get the following error:

File "/Users/mtazzari/djangos/views.py" in update_job_refs
  377.             a.save()

File "/Users/mtazzari/anaconda/envs/djangos/lib/python3.6/site-packages/polymorphic/models.py" in save
  83.         return super(PolymorphicModel, self).save(*args, **kwargs)

File "/Users/mtazzari/anaconda/envs/djangos/lib/python3.6/site-packages/django/db/models/base.py" in save
  670.                         "unsaved related object '%s'." % field.name
ValueError: save() prohibited to prevent data loss due to unsaved
            related object 'file_results'.

The mymodel_delete function that is called on pre_delete signal works correctly as the file gets actually deleted from the file system. However, I really don't understand how to solve the ValueError.

Interestingly, I notice that the following lines work fine, i.e. do not raise any ValueError, get the file deleted from the file system, and the FK in a.file_results set to Null:

a = Analysis.objects.get(pk=0)
tmp = a.file_results
a.file_results = None    
tmp.file_results.delete()
a.save()

But, is this a proper way of doing this? What is the best practice for deleting a related object?

Thanks!

Upvotes: 3

Views: 5813

Answers (1)

Kevin Christopher Henry
Kevin Christopher Henry

Reputation: 48952

First, note that you don't need to save() just because of the delete(). The delete() will update the database as required.

That said, it's reasonable to want to continue using the instance to do other operations, leading to a save(). The reason you're getting the error is that the a.file_results Python object still exists, and references a database row that is now missing. The documentation for delete() mentions this:

This only deletes the object in the database; the Python instance will still exist and will still have data in its fields.

So if you want to continue to work with the instance object, just set the attribute to None yourself. Similar to your code above, except you don't need the temp object.

a = Analysis.objects.get(pk=0)
a.file_results.delete()
a.file_results = None

# ... more operations on a

a.save()  # no error

Upvotes: 10

Related Questions