# Signal does not pick up update\_field if the model's Manager filters objects

**URL:** <https://forum.djangoproject.com/t/signal-does-not-pick-up-update-field-if-the-models-manager-filters-objects/37958>\
**Category:** Mystery Errors\
**Created:** [January 14, 2025, 9:20pm UTC](https://forum.djangoproject.com/t/signal-does-not-pick-up-update-field-if-the-models-manager-filters-objects/37958 "2025-01-14T21:20:59Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![NicoJJohnson](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/nicojjohnson/32/26306_2.png) [@NicoJJohnson](https://forum.djangoproject.com/u/NicoJJohnson)\
**Post date:** [January 14, 2025, 9:20pm UTC](https://forum.djangoproject.com/t/signal-does-not-pick-up-update-field-if-the-models-manager-filters-objects/37958/1 "2025-01-14T21:20:59Z")

</div>

So I have a Model that has some sensitive objects labeled with is\_private=True.  
Furthermore, all private objects should be filtered out by default, unless there is a User, in which we can check if they own the object.

```auto
class SensitiveObjectManager(models.Manager):
    def __init__ (self):
        super(). __init__ ()
        self.user = None

    def for_user(self, user):
        """Create a new manager instance with the user context"""
        manager = SensitiveObjectManager()
        manager.model = self.model
        manager.user = user
        if hasattr(self, "core_filters"):
            manager.core_filters = self.core_filters
        return manager

    def get_queryset(self):
        qs = super().get_queryset()

        if hasattr(self, "core_filters"):
            qs = qs.filter(**self.core_filters)

        if self.user is None:
            return qs.filter(is_private=False)
        return qs.filter(Q(is_private=False) | Q(owner=self.user.id))

class SensitiveObject(models.Model):
  objects = SensitiveObjectManager()
  all_objects = models.Manager()

  is_private = models.BooleanField(default=True)
  owner = models.ForeignKey(User)
  is_leaked = models.BooleanField(default=False)

```

This is designed because SensitiveObject.objects.all() is commonly used throughout our code, but with this Manager, we can always filter out the private objects. To include objects that the user owns, we can use SensitiveObject.objects.for\_user(User).all().

Everything works fine with it so far except for a very odd bug using django.db.models.signals.post\_save. We want to catch when is\_leaked is updated so that we can update a few other Models. So we have

```auto
post_save.connect(sensitive_object_updated, sender=SensitiveObject)

```

And then finally:

```auto
def sensitive_object_updated(sender, instance, created, update_fields, **kwargs):
  print(instance)
  print(instance.is_leaked)
  print(update_fields)

```

If the SensitiveObject.is\_private=True, then the update\_fields will NOT be included which is very annoying. But when SensitiveObject.is\_private=False, the update\_fields work fine. I tested this, and I know it is because of the SensitiveObjectManager. Furthermore, if you test it, you will see the instance and instance.is\_leaked have the correct values inside sensitive\_object\_updated always, (whether if is\_private is True or False). I’m pretty confident that this is a bug with Django.

---

<div class="post-metadata">

**Author:** ![anefta](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/anefta/32/10649_2.png) [@anefta](https://forum.djangoproject.com/u/anefta)\
**Post date:** [January 15, 2025, 11:21am UTC](https://forum.djangoproject.com/t/signal-does-not-pick-up-update-field-if-the-models-manager-filters-objects/37958/2 "2025-01-15T11:21:58Z")

</div>

Perhaps you could try: `post_save.connect(sensitive_object_updated,sender=SensitiveObject._meta.get_field('all_objects').model)`

I am not sure if it is the best solution but all the fields could be updated this way - at least on theory 🙂

---

<div class="post-metadata">

**Author:** ![NicoJJohnson](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/nicojjohnson/32/26306_2.png) [@NicoJJohnson](https://forum.djangoproject.com/u/NicoJJohnson)\
**Post date:** [January 15, 2025, 3:20pm UTC](https://forum.djangoproject.com/t/signal-does-not-pick-up-update-field-if-the-models-manager-filters-objects/37958/3 "2025-01-15T15:20:15Z")

</div>

Thanks for the reply. That’s a pretty good idea but unfortunately does not work. I like the idea of trying to force the Signal to make sure it checks SensitiveObject.all\_objects instead of the SensitiveObject.objects, so I tried changing the signal implementation to:

```auto
post_save.connect(sensitive_object_updated, sender=SensitiveObject.all_objects.model)

```

But the result is still the same where `update_fields` is still None if `SensitiveObject.is_private=True`.

(Note that `SensitiveObject._meta.get_field('all_objects')` and `SensitiveObject._meta.get_field('objects')` doesn’t work as it will raise the error:

```auto
django.core.exceptions.FieldDoesNotExist: SensitiveObject has no field named 'all_objects'

```

)

---

<div class="post-metadata">

**Author:** ![anefta](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/anefta/32/10649_2.png) [@anefta](https://forum.djangoproject.com/u/anefta)\
**Post date:** [January 16, 2025, 12:00am UTC](https://forum.djangoproject.com/t/signal-does-not-pick-up-update-field-if-the-models-manager-filters-objects/37958/4 "2025-01-16T00:00:56Z")

</div>

hmm…  
Perhaps you could try to change the default manager:

```auto
class SensitiveObject(models.Model):
    # Define all_objects first to make it the default
    all_objects = models.Manager()
    objects = SensitiveObjectManager()

    is_private = models.BooleanField(default=True)
    owner = models.ForeignKey(User)
    is_leaked = models.BooleanField(default=False)

    class Meta:
        default_manager_name = 'all_objects' # Explicitly set default manager

```

```auto
# Then simple signal connection
post_save.connect(sensitive_object_updated, sender=SensitiveObject)

```

---

<div class="post-metadata">

**Author:** ![NicoJJohnson](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/nicojjohnson/32/26306_2.png) [@NicoJJohnson](https://forum.djangoproject.com/u/NicoJJohnson)\
**Post date:** [January 17, 2025, 5:09pm UTC](https://forum.djangoproject.com/t/signal-does-not-pick-up-update-field-if-the-models-manager-filters-objects/37958/5 "2025-01-17T17:09:04Z")

</div>

Surprisingly, this still did not work. After testing, the result is still the same (where saving an object with `SensitiveObject.is_private=True` will not have the update\_fields in `sensitive_object_updated`, but when changing it to `SensitiveObject.is_private=False`, the update\_fields work fine. )

To make things more difficult, another perk of the SensitiveObjectManager is that it will also automatically filter sensitive\_objects from related models. For example:

```auto
class Groups(models.Model):
    sensitive_objects=models.ManyToManyField(
        SensitiveObject, related_name="sensitive_object_groups", blank=True
    )

```

If I have a group object

```auto
group = Groups.objects.create()
group.sensitive_objects.set(sensitive_objects_list)
group.sensitive_objects.all() # Returns only sensitive_objects where is_private is false
group.sensitive_objects.for_user(User).all() # Returns sensitive_objects the user has access to

```

The flexibility of this is great and already works with the current set-up. But I just wanted to mention this because when using

```auto
class SensitiveObject(models.Model):
    class Meta:
        default_manager_name = 'all_objects'

```

Then `group.sensitive_objects.for_user(User)` will actually raise the error: `AttributeError: 'ManyRelatedManager' object has no attribute 'for_user'`

---

<div class="post-metadata">

**Author:** ![NicoJJohnson](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/nicojjohnson/32/26306_2.png) [@NicoJJohnson](https://forum.djangoproject.com/u/NicoJJohnson)\
**Post date:** [January 17, 2025, 5:45pm UTC](https://forum.djangoproject.com/t/signal-does-not-pick-up-update-field-if-the-models-manager-filters-objects/37958/6 "2025-01-17T17:45:26Z")

</div>

I’m about to create a ticket for this as it does seem like a bug with Django. I just wanted to add to this forum the test case I am using.

```auto
# tests.py
from django.test import TestCase
def SensitiveObjectTest(TestCase):
   def example_test(self):
      private_object = SensitiveObject.objects.create(
         owner=self.user,
         is_private=True,
         is_leaked=False
      )

      self.assertTrue(SensitiveObject.all_objects.filter(id=private_object.id).exists())
      # Thanks to the SensitiveObjectManager, private objects are filtered out of objects by default.
      self.assertFalse(SensitiveObject.objects.filter(id=private_object.id).exists())
      self.assertTrue(SensitiveObject.objects.for_user(self.user).filter(id=private_object.id).exists())

      private_object.is_leaked=True
      private_object.save()
      # Should see the print statements from `senstive_object_updated`.
      # update_fields will be None 

      public_object = SensitiveObject.objects.create(
         owner=self.user,
         is_private=False,
         is_leaked=False
      )
      self.assertTrue(SensitiveObject.all_objects.filter(id=public_object.id).exists())
      self.assertTrue(SensitiveObject.objects.filter(id=public_object.id).exists())

      public_object.is_leaked=True
      public_object.save()
      # Should see the print statements from `senstive_object_updated`.
      # update_fields will be correct 

      

```

```auto
# apps.py

def sensitive_object_updated(sender, instance, created, update_fields, **kwargs):
  print("Instance:")
  print(instance)
  print("Instance.is_private:")
  print(instance.is_private)
  print("Instance.is_leaked:")
  print(instance.is_leaked)
  print("update_fields:")
  print(update_fields)

from django.apps import AppConfig

class SensitiveObjectConfig(AppConfig):
    default_auto_field = "django.db.models.BigAutoField"
    name = "sensitive_object"

    def ready(self):
        from django.db.models.signals import post_save
        from sensitive_object.models import SensitiveObject
        post_save.connect(sensitive_object_updated, sender=SensitiveObject)

```

---

<div class="post-metadata">

**Author:** ![charettes](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/charettes/32/27_2.png) [@charettes](https://forum.djangoproject.com/u/charettes)\
**Post date:** [January 17, 2025, 7:20pm UTC](https://forum.djangoproject.com/t/signal-does-not-pick-up-update-field-if-the-models-manager-filters-objects/37958/7 "2025-01-17T19:20:26Z")

</div>

As [pointed out on Trac](https://code.djangoproject.com/ticket/36108#comment:1) there is a misunderstanding of what `update_fields` is here.

It’s not the set of model fields that were set to a different value since the last `save` call, Django doesn’t keep track of that, but simply the value passed to `Model.save(update_fields)` to denote which fields should be part of the resulting `UPDATE` query.

Since no explicit `update_fields` is passed to `save` calls in the above code it will always be `None` as expected.

---

<div class="post-metadata">

**Author:** ![NicoJJohnson](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/nicojjohnson/32/26306_2.png) [@NicoJJohnson](https://forum.djangoproject.com/u/NicoJJohnson)\
**Post date:** [January 17, 2025, 8:08pm UTC](https://forum.djangoproject.com/t/signal-does-not-pick-up-update-field-if-the-models-manager-filters-objects/37958/8 "2025-01-17T20:08:08Z")

</div>

Ah, thanks for that. [charettes](https://forum.djangoproject.com/u/charettes) was spot on, all it needed was `Model.save(update\_fields=[“is\_leaked”]).

For those wondering (or for myself in case I forget in the future), there is an unfinished explanation for how this doesn’t really add up here:

> Since no explicit `update_fields` is passed to `save` calls in the above code it will always be `None` as expected.

If you fully read this thread, this might not seem accurate as the `update_fields` are somehow tracked when `Model.is_private=False`. If you take a look at the example test file (full example is above):

```auto
      public_object = SensitiveObject.objects.create(
         owner=self.user,
         is_private=False,
         is_leaked=False
      )
      self.assertTrue(SensitiveObject.all_objects.filter(id=public_object.id).exists())
      self.assertTrue(SensitiveObject.objects.filter(id=public_object.id).exists())

      public_object.is_leaked=True
      public_object.save()
      # Should see the print statements from `senstive_object_updated`.
      # update_fields:
      # frozenset({'is_leaked'})

```

The issue here is that I was not fully accurate in describing the code. To be fully accurate, “since no explicit `update_fields` is passed to `save` calls in the above code it will always be `None` as expected” _ **that is assuming** _ this is the django’s default `save` method, which could be overridden by models inheriting the `models.Model`.

I oversimplified the definition of `class SensitiveObject(models.Model)`. In our code, it’s actually more like

```auto
class SensitiveObject(CustomModel)
...

class CustomModel(models.Model):
   def save(self, *args, **kwargs)
     # ... Tracks the updates to the object and appends it to update_fields 
     old_object = SensitiveObject.objects.filter(pk=self.pk) # This had to be updated to SensitiveObjects.all_objects.filter...
     # ...
     super().save(*args_with_tracked_update_fields)

```
