Commits

Rajeesh Nair committed 14c6ec8

Django-monitor: Bug-fix (Issue #1)

* Fixed the bug (issue #1) by adding code in ``monitor.util.save_handler`` to
create monitor_entries for parent instances as well.

* We used ``model._meta.pk.attname`` as the field to which we hook custom filter.
This raised ``FieldDoesNotExist`` for sub-classed models. Fixed by using ``id``
as the field.

Comments (0)

Files changed (4)

 Django-monitor: CHANGE LOG
 ==========================
 
+0.1.5
+======
+
+* Django-monitor contained a bug that pops up when you subclass a moderated
+  model and enqueue that sub-class also for moderation (Ticket #1). When you
+  create an instance for a sub-class of any non-abstract model, django creates
+  corresponding parent class instances too. But no ``post_save`` signal is
+  emitted for any of those parent instances. Since moderation is invoked on
+  the ``post_save`` signal, no monitor_entry is created for any of those
+  parents. The CustomQueryset we use assumes that a monitor_entry will be there
+  for all moderated model instances. Since it can not find any for the parents,
+  it always return empty rows when you try to access them. Fixed by adding a
+  couple of lines in our ``monitor.util.save_handler`` to create monitor_entries
+  for all moderated parent instances as well.
+
 0.1.4
 ======
 

monitor/__init__.py

 __author__ = "Rajeesh Nair"
-__version__ = "0.1.4a"
+__version__ = "0.1.5a"
 __copyright__ = "Copyright (c) 2011 Rajeesh"
 __license__ = "BSD"
 
 from django.dispatch import Signal
 from django.db.models import signals
+from django.db.models.loading import get_model
 
 from monitor.util import create_moderate_perms, add_fields, save_handler
 
     """ Register(enqueue) the model for moderation."""
     if not model_from_queue(model):
         signals.post_save.connect(save_handler, sender = model)
-        add_fields(model, manager_name, status_name, monitor_name, base_manager)
+        registered_model = get_model(
+            model._meta.app_label, model._meta.object_name, False
+        )
+        add_fields(
+            registered_model, manager_name, status_name,
+            monitor_name, base_manager
+        )
         _queue[model] = {
             'rel_fields': rel_fields,
             'can_delete_approved': can_delete_approved,
     def __init__(self, model, admin_site):
         """ Overridden to add a custom filter to list_filter """
         super(MonitorAdmin, self).__init__(model, admin_site)
-        self.list_filter = [self.opts.pk.attname] + list(self.list_filter)
+        self.list_filter = ['id'] + list(self.list_filter)
         self.list_display = list(self.list_display) + ['get_status_display']
 
     def queryset(self, request):
             )
             return CustomQuerySet(self.model, q.query)
 
+        def __getattr__(self, attr):
+            """ Try to get the rest of attributes from queryset """
+            try:
+                return getattr(self, attr)
+            except AttributeError:
+                return getattr(self.get_query_set(), attr)
+
     def _get_monitor_entry(self):
         """ accessor for monitor_entry that caches the object """
         if not hasattr(self, '_monitor_entry'):
     # filtering of model objects by their moderation status.
     # But `status` is not a real field and Django does not support filters
     # on non-fields as of now. Our way out is to attach the filter to some
-    # other field which the developer may never include in list_filter.
-    # I think, prmary key is the best option.
-    # (Latest Django dev-version has undergone changes to allow non-fields.)
-    cls._meta.get_field(cls._meta.pk.attname).monitor_filter = True
+    # other field which the developer may never include in ``list_filter``.
+
+    # Used ``pk`` before but subclassed models raise FieldDoesNotExist here.
+    # So let's use ``id``. Latest Django dev-version has undergone changes to
+    # allow non-fields. So this hack must be for a short period of time.
+    cls._meta.get_field('id').monitor_filter = True
 
     # Copy manager to default_class
     cls._default_manager = manager
     2. Auto-moderate objects if enough permissions are available.
     3. Moderate specified related objects too.
     """
+    import monitor
     # Auto-moderation
     user = get_current_user()
     opts = instance.__class__._meta
             timestamp = datetime.now()
         )
 
+        # Create one monitor_entry per moderated parent.
+        monitored_parents = filter(
+            lambda x: monitor.model_from_queue(x),
+            instance._meta.parents.keys()
+        )
+        for parent in monitored_parents:
+            parent_ct = ContentType.objects.get_for_model(parent)
+            parent_pk_field = instance._meta.get_ancestor_link(parent)
+            parent_pk = getattr(instance, parent_pk_field.attname)
+            try:
+                me = MonitorEntry.objects.get(
+                    content_type = parent_ct, object_id = parent_pk
+                )
+            except MonitorEntry.DoesNotExist:
+                me = MonitorEntry(
+                    content_type = parent_ct, object_id = parent_pk,
+                )
+            me.moderate(status, user)
+
         # Moderate related objects too... 
-        from monitor import model_from_queue
-        model = model_from_queue(instance.__class__)
+        model = monitor.model_from_queue(instance.__class__)
         if model:
             for rel_name in model['rel_fields']:
-                moderate_rel_objects(getattr(instance, rel_name), status, user)
+                rel_obj = getattr(instance, rel_name, None)
+                if rel_obj:
+                    moderate_rel_objects(rel_obj, status, user)
 
 def moderate_rel_objects(given, status, user = None):
     """
             model = model_from_queue(qset.model)
             if model:
                 for rel_name in model['rel_fields']:
-                    moderate_rel_objects(getattr(obj, rel_name), status, user)
+                    rel_obj = getattr(obj, rel_name, None)
+                    if rel_obj:
+                        moderate_rel_objects(rel_obj, status, user)
     else:
         me = MonitorEntry.objects.get_for_instance(given)
         me.moderate(status, user)
         model = model_from_queue(given.__class__)
         if model:
             for rel_name in model['rel_fields']:
-                moderate_rel_objects(getattr(given, rel_name), status, user)
+                rel_obj = getattr(given, rel_name, None)
+                if rel_obj:
+                    moderate_rel_objects(rel_obj, status, user)