#36108: The `update_field` when using `django.db.models.signals` does not work 
with
a custom Model Manager
-------------------------------------+-------------------------------------
     Reporter:  NicoJJohnson         |                     Type:  Bug
       Status:  new                  |                Component:  Database
                                     |  layer (models, ORM)
      Version:  4.2                  |                 Severity:  Normal
     Keywords:                       |             Triage Stage:
  signal,model,manager,filter        |  Unreviewed
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
 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.

 {{{
 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()

   id = models.UUIDField(primary_key=True, default=uuid.uuid4)
   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

 {{{
 #apps.py
 def sensitive_object_updated(sender, instance, created, update_fields,
 **kwargs):
   # ( Would perform additional ORM logic, but for testing, the print
 statements are all that's needed)
   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)
 }}}
 If the `SensitiveObject.is_private=True`, then the `update_fields` will
 NOT be included in `sensitive_object_updated` which is very annoying
 (`update_fields` will equal `None`). 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.

 For more information about trying to find a patch, this is also posted on
 the django forum under the name "Signal does not pick up update_field if
 the model’s Manager filters objects"

 Example Test Case:
 {{{
 # 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_dataset.id).exists())
       # Thanks to the SensitiveObjectManager, private datasets are
 filtered out of objects by default.
 
self.assertFalse(SensitiveObject.objects.filter(id=private_dataset.id).exists())
 
self.assertTrue(SensitiveObject.objects.for_user(self.user).filter(id=private_dataset.id).exists())

       print("Observe print logs from sensitive_object_updated for private
 object")
       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_dataset.id).exists())
 self.assertTrue(SensitiveObject.objects.filter(id=public_dataset.id).exists())

       print("Observe print logs from sensitive_object_updated for public
 object")
       public_object.is_leaked=True
       public_object.save()
       # Should see the print statements from `senstive_object_updated`.
       # update_fields will be correct
 }}}
-- 
Ticket URL: <https://code.djangoproject.com/ticket/36108>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/django-updates/01070194757a7e39-3cbee9f5-3a81-4fdc-8cd6-d1d7d58dfa68-000000%40eu-central-1.amazonses.com.

Reply via email to