djangodjango-querysetdjango-signals

Django : DoesNotExist Error raised in post_signal handler


Trying to delete a Task instance with its subtasks, I encounter DoesNotExist: Task matching query does not exist.

The following is the error message:

Traceback (most recent call last):
  File "/Users/thebjko/dev/TrackCracker/venv/lib/python3.11/site-packages/django/db/models/fields/related_descriptors.py", line 218, in __get__
    rel_obj = self.field.get_cached_value(instance)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/thebjko/dev/TrackCracker/venv/lib/python3.11/site-packages/django/db/models/fields/mixins.py", line 15, in get_cached_value
    return instance._state.fields_cache[cache_name]
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^
KeyError: 'supertask'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/thebjko/dev/TrackCracker/venv/lib/python3.11/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/thebjko/dev/TrackCracker/venv/lib/python3.11/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/thebjko/dev/TrackCracker/crackers/views.py", line 73, in delete
    print(task.delete())
          ^^^^^^^^^^^^^
  File "/Users/thebjko/dev/TrackCracker/venv/lib/python3.11/site-packages/django/db/models/base.py", line 1132, in delete
    return collector.delete()
           ^^^^^^^^^^^^^^^^^^
  File "/Users/thebjko/dev/TrackCracker/venv/lib/python3.11/site-packages/django/db/models/deletion.py", line 512, in delete
    signals.post_delete.send(
  File "/Users/thebjko/dev/TrackCracker/venv/lib/python3.11/site-packages/django/dispatch/dispatcher.py", line 176, in send
    return [
           ^
  File "/Users/thebjko/dev/TrackCracker/venv/lib/python3.11/site-packages/django/dispatch/dispatcher.py", line 177, in <listcomp>
    (receiver, receiver(signal=self, sender=sender, **named))
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/thebjko/dev/TrackCracker/crackers/signals.py", line 10, in reassess_achievement
    if instance.supertask is not None and instance.supertask.completed == False:
       ^^^^^^^^^^^^^^^^^^
  File "/Users/thebjko/dev/TrackCracker/venv/lib/python3.11/site-packages/django/db/models/fields/related_descriptors.py", line 236, in __get__
    rel_obj = self.get_object(instance)
              ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/thebjko/dev/TrackCracker/venv/lib/python3.11/site-packages/django/db/models/fields/related_descriptors.py", line 199, in get_object
    return qs.get(self.field.get_reverse_related_filter(instance))
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/thebjko/dev/TrackCracker/venv/lib/python3.11/site-packages/django/db/models/query.py", line 637, in get
    raise self.model.DoesNotExist(
crackers.models.Task.DoesNotExist: Task matching query does not exist.

So far, I have figured out that the error is related to signal handler, since the same error does not occur when

  1. the handler does not handle post_delete signal. That is when I remove post_delete signal from this handler,
  2. instance.supertask is not None
  3. there does not exist instance.subtasks.

These cases are mutually exclusive. If the handler does not handle post_delete, it does not matter if instance.supertask is None or not. If instance.supertask is not None, it does not matter if the instance has subtasks or not. If the instance has subtasks, it does not matter if instance.supertask is None or not.

For the above cases, the error is not raised and delete() method does its job successfully. The error is only raised when the handler handles post_delete signal, and there is no supertask related with the instance if it has subtasks.

The error is raised as it enters if statement.

The following is the handler:

@receiver([post_save, post_delete], sender=Task)
def reassess_achievement(sender, instance, **kwargs):
    print('*'*100)   # executed
    if instance.supertask is not None and instance.supertask.completed == False:
        print('#'*100)    # not executed
        ...

    # No job for other cases.

But I still have to execute this code whenever I delete task. Why is this error raised and how can I debug it?


UPDATE:

If I print dir(instance) inside the handler, it does have 'supertask'. Why can't I use instance.supertask?


Solution

  • From documentation

    Note that the object will no longer be in the database, so be very careful what you do with this instance.

    So I have to use pre_delete instead.