Commits

Rajeesh Nair committed 48d086a

Django-monitor: New revision

* We use the status strings, "IP", "CH", "AP", "In Pending", "Approved", and
"Challenged". These strings are hard-coded all over the place. What if
someone wanted to name them in some other way? Now we have moved definitions
of all such status strings to a separate module, ``monitor.conf``. Other
modules import them from there.

* As we started the project as a clone of ``django-gatekeeper``, the monitor.init
module contained many functions and this made it bulky and cluttered. Cleared up
the module by moving most lines of code to other modules like monitor.util.

* Two utility functions, ``model_from_queue`` and ``queued_models`` are defined
in init to avoid importing the private variable, _queue, to other modules.

* Removed the function dq from init since it is not used as of now.

* Now monitor stores in queue additional paramateres, manager_name, status_name and
monitor_name for each model enqueued for moderation.

* In ``moderate_rel_objects``, we had referred to the model of given object as
``given._meta.model``. This is now corrected as ``given.__class__``.

Comments (0)

Files changed (8)

monitor/__init__.py

 __copyright__ = "Copyright (c) 2011 Rajeesh"
 __license__ = "BSD"
 
-from datetime import datetime
+from django.db.models import signals
 
-from django.contrib.contenttypes.models import ContentType
-from django.db.models import Manager, signals
-
-from monitor.middleware import get_current_user
-from monitor.models import MonitorEntry
-from monitor.util import moderate_rel_objects
+from monitor.util import create_moderate_perms, add_fields, save_handler
 
 _queue = {}
 
-def is_in_queue(model):
-    """ Whether the given model is put in queue or not."""
-    return _queue.has_key(model)
+def model_from_queue(model):
+    """ Returns the model dict if model is enqueued, else None."""
+    return _queue.get(model, None)
 
-PENDING_STATUS = 'IP'
-APPROVED_STATUS = 'AP'
-CHALLENGED_STATUS = 'CH'
-
-MONITOR_TABLE = MonitorEntry._meta.db_table
+def queued_models():
+    """ Return the models enqueued for moderation"""
+    return _queue.keys()
 
 def nq(
     model, rel_fields = [], manager_name = 'objects',
     status_name = 'status', monitor_name = 'monitor_entry', base_manager = None
 ):
     """ Register(enqueue) the model for moderation."""
-    if not is_in_queue(model):
+    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)
-        _queue[model] = {'model': model, 'rel_fields': rel_fields}
-
-def dq(model):
-    """ Unregister (dequeue) the registered model."""
-    return _queue.pop(model, None)
-
-def create_moderate_perms(app, created_models, verbosity, **kwargs):
-    """ This will create moderate permissions for all registered models"""
-    from django.contrib.contenttypes.models import ContentType
-    from django.contrib.auth.models import Permission
-    mod_models = _queue.keys()
-    for model in mod_models:
-        ctype = ContentType.objects.get_for_model(model)
-        codename = 'moderate_%s' % model._meta.object_name.lower()
-        name = u'Can moderate %s' % model._meta.verbose_name_raw
-        p, created = Permission.objects.get_or_create(
-            codename = codename,
-            content_type__pk = ctype.id,
-            defaults = {'name': name, 'content_type': ctype}
-        )
-        if created and verbosity >= 2:
-            print "Adding permission '%s'" % p
+        _queue[model] = {
+            'rel_fields': rel_fields,
+            'manager_name': manager_name,
+            'status_name': status_name,
+            'monitor_name': monitor_name
+        }
 
 signals.post_syncdb.connect(
     create_moderate_perms,
     dispatch_uid = "django-monitor.create_moderate_perms"
 )
 
-def add_fields(cls, manager_name, status_name, monitor_name, base_manager):
-    """ Add additional fields like status to moderated models"""
-    # Inheriting from old manager
-    if base_manager is None:
-        if hasattr(cls, manager_name):
-            base_manager = getattr(cls, manager_name).__class__
-        else:
-            base_manager = Manager
-    # Queryset inheriting from manager's Queryset
-    base_queryset = base_manager().get_query_set().__class__
-
-    class CustomQuerySet(base_queryset):
-        """ Chainable queryset for checking status """
-       
-        def _by_status(self, field_name, status):
-            """ Filter queryset by given status"""
-            where_clause = '%s = %%s' % (field_name)
-            return self.extra(where = [where_clause], params = [status])
-
-        def approved(self):
-            """ All approved objects"""
-            return self._by_status(status_name, APPROVED_STATUS)
-
-        def exclude_approved(self):
-            """ All not-approved objects"""
-            where_clause = '%s != %%s' % (status_name)
-            return self.extra(
-                where = [where_clause], params = [APPROVED_STATUS]
-            )
-
-        def pending(self):
-            """ All pending objects """
-            return self._by_status(status_name, PENDING_STATUS)
-
-        def challenged(self):
-            """ All challenged objects """
-            return self._by_status(status_name, CHALLENGED_STATUS)
-
-    class CustomManager(base_manager):
-        """ custom manager that adds parameters and uses custom QuerySet """
-
-        # use_for_related_fields is read when the model class is prepared
-        # because CustomManager isn't set on the class at the time
-        # this really has no effect, but is set to True because we are going
-        # to hijack cls._default_manager later
-        use_for_related_fields = True
-
-        # add monitor_id and status_name attributes to the query
-        def get_query_set(self):
-            # parameters to help with generic SQL
-            db_table = self.model._meta.db_table
-            pk_name = self.model._meta.pk.attname
-            content_type = ContentType.objects.get_for_model(self.model).id
-
-            # extra params - status and id of object (for later access)
-            select = {
-                '_monitor_id': '%s.id' % MONITOR_TABLE,
-                '_status': '%s.status' % MONITOR_TABLE,
-            }
-            where = [
-                '%s.content_type_id=%s' % (MONITOR_TABLE, content_type),
-                '%s.object_id=%s.%s' % (MONITOR_TABLE, db_table, pk_name)
-            ]
-            tables = [MONITOR_TABLE]
-
-            # build extra query then copy model/query to a CustomQuerySet
-            q = super(CustomManager, self).get_query_set().extra(
-                select = select, where = where, tables = tables
-            )
-            return CustomQuerySet(self.model, q.query)
-
-    def _get_monitor_entry(self):
-        """ accessor for monitor_entry that caches the object """
-        if not hasattr(self, '_monitor_entry'):
-            self._monitor_entry = MonitorEntry.objects.get(pk = self._monitor_id)
-        return self._monitor_entry
-
-    def _get_status_display(self):
-        """ to display the moderation status in verbose """
-        return {
-            'IP': 'In Pending', 
-            'CH': 'Challenged',
-            'AP': 'Approved'
-        }[self._status]
-    _get_status_display.short_description = status_name
-
-    # Add custom manager & monitor_entry to class
-    manager = CustomManager()
-    cls.add_to_class(manager_name, manager)
-    cls.add_to_class(monitor_name, property(_get_monitor_entry))
-    cls.add_to_class(status_name, property(lambda self: self._status))
-    cls.add_to_class(
-        'get_status_display', _get_status_display
-    )
-    # We have a custom filter defined in monitor.filter to enable
-    # 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
-
-    # Copy manager to default_class
-    cls._default_manager = manager
-
-def save_handler(sender, instance, **kwargs):
-    """
-    After saving an object in moderated class, do the following:
-    1. Create a corresponding monitor entry.
-    2. Auto-moderate objects if enough permissions are available.
-    3. Moderate specified related objects too.
-    """
-    # Auto-moderation
-    user = get_current_user()
-    opts = instance.__class__._meta
-    mod_perm = '%s.moderate_%s' % (
-        opts.app_label.lower(), opts.object_name.lower()
-    )
-    if user and user.has_perm(mod_perm):
-        status = APPROVED_STATUS
-    else:
-        status = PENDING_STATUS
-
-    # Create corresponding monitor entry
-    if kwargs.get('created', None):
-        me = MonitorEntry(
-            status = status, content_object = instance,
-            timestamp = datetime.now()
-        )
-        me.save()
-
-    # Moderate related objects too...
-    moderate_rel_objects(instance, status, user)
-

monitor/actions.py

 from django.utils.translation import ugettext_lazy, ugettext as _
 
 from monitor.util import moderate_rel_objects
-from monitor import is_in_queue
+from monitor import model_from_queue
+from monitor.conf import (
+    STATUS_DICT, PENDING_STATUS, APPROVED_STATUS, CHALLENGED_STATUS
+)
 from monitor.models import MonitorEntry
 
 def moderate_selected(modeladmin, request, queryset, status):
     opts = modeladmin.model._meta
     
     # If moderation is disabled..
-    if not is_in_queue(modeladmin.model):
+    if not model_from_queue(modeladmin.model):
         return 0
 
-    # Check that the user has required permission for the actual model
+    # Check that the user has required permission for the actual model.
     # To reset to pending status, change_perm is enough. For all else,
     # user need to have moderate_perm.
     if (
-        (status == 'IP' and not modeladmin.has_change_permission(request)) or 
-        (status != 'IP' and not modeladmin.has_moderate_permission(request))
+        (status == PENDING_STATUS and
+            not modeladmin.has_change_permission(request)
+        ) or 
+        (status != PENDING_STATUS and
+            not modeladmin.has_moderate_permission(request)
+        )
     ):
         raise PermissionDenied
 
     q_count = queryset.count()
 
     # We want to use the status display rather than abbreviations in logs.
-    status_display = {
-        'IP': 'Pending', 
-        'CH': 'Challenged',
-        'AP': 'Approved'
-    }[status]
+    status_display = STATUS_DICT[status]
 
     if q_count:
         #for obj in queryset:
         
 def approve_selected(modeladmin, request, queryset):
     """ Default action to approve selected objects """
-    ap_count = moderate_selected(modeladmin, request, queryset, 'AP')
+    ap_count = moderate_selected(modeladmin, request, queryset, APPROVED_STATUS)
     if ap_count:
         modeladmin.message_user(
             request,
 
 def challenge_selected(modeladmin, request, queryset):
     """ Default action to challenge selected objects """
-    ch_count = moderate_selected(modeladmin, request, queryset, 'CH')
+    ch_count = moderate_selected(modeladmin, request, queryset, CHALLENGED_STATUS)
     if ch_count:
         modeladmin.message_user(
             request,
 
 def reset_to_pending(modeladmin, request, queryset):
     """ Default action to reset selected object's status to pending """
-    ip_count = moderate_selected(modeladmin, request, queryset, 'IP')
+    ip_count = moderate_selected(modeladmin, request, queryset, PENDING_STATUS)
     if ip_count:
         modeladmin.message_user(
             request,
     approve_selected, challenge_selected, reset_to_pending
 )
 from monitor.filter import MonitorFilter
-from monitor import APPROVED_STATUS
+from monitor import model_from_queue
+from monitor.conf import (
+    PENDING_STATUS, CHALLENGED_STATUS, APPROVED_STATUS
+)
 
 # Our objective is to place the custom monitor-filter on top
 FilterSpec.filter_specs.insert(
         Django does not allow using non-fields in list_filter. (As of 1.3).
         Using params not mentioned in list_filter will raise error in changelist.
         We want to enable status based filtering (status is not a db_field).
-        We will check the request.get here and if there's a `status` in it,
+        We will check the request.GET here and if there's a `status` in it,
         Remove that and filter the qs by status.
         """
         qs = super(MonitorAdmin, self).queryset(request)
             del get_dict['status']
             request.GET = get_dict
         # ChangeList will use this custom queryset. So we've done it!
-        if status and status == 'IP':
+        if status and status == PENDING_STATUS:
             qs = qs.pending()
-        elif status and status == 'CH':
+        elif status and status == CHALLENGED_STATUS:
             qs = qs.challenged()
-        elif status and status == 'AP':
+        elif status and status == APPROVED_STATUS:
             qs = qs.approved()
         return qs
 
     def is_monitored(self):
         """Returns whether the underlying model is monitored or not."""
-        from monitor import is_in_queue
-        return is_in_queue(self.model)
+        return bool(model_from_queue(self.model))
 
     def get_readonly_fields(self, request, obj = None):
         """ Overridden to include protected_fields as well."""
+"""
+All status labels defined here.
+"""
+PENDING_STATUS = 'IP'
+APPROVED_STATUS = 'AP'
+CHALLENGED_STATUS = 'CH'
+
+PENDING_DESCR = "In Pending"
+APPROVED_DESCR = "Approved"
+CHALLENGED_DESCR = "Challenged"
+
+STATUS_DICT = {
+    PENDING_STATUS: PENDING_DESCR,
+    APPROVED_STATUS: APPROVED_DESCR,
+    CHALLENGED_STATUS: CHALLENGED_DESCR
+}
+

monitor/filter.py

 from django.contrib.admin.filterspecs import ChoicesFilterSpec
 from django.utils.encoding import smart_unicode
 from django.utils.translation import ugettext_lazy as _
+from monitor.conf import STATUS_DICT
 
 class MonitorFilter(ChoicesFilterSpec):
     """
         )
         self.lookup_kwarg = 'status'
         self.lookup_val = request.GET.get(self.lookup_kwarg)
-        self.lookup_choices = ['IP', 'AP', 'CH']
+        self.lookup_choices = STATUS_DICT.keys()
         
     def choices(self, cl):
         yield {
             yield {
                 'selected': smart_unicode(val) == self.lookup_val,
                 'query_string': cl.get_query_string({self.lookup_kwarg: val}),
-                'display': {
-                    'IP': "In Pending",
-                    'CH': "Challenged",
-                    'AP': "Approved"
-                }[val]
+                'display': STATUS_DICT[val]
             }
 
     def title(self):

monitor/models.py

 from django.contrib.contenttypes import generic
 import datetime
 
-STATUS_CHOICES = [
-    ('IP', "In Pending"),
-    ('AP', "Approved"),
-    ('CH', "Challenged")
-]
+from monitor.conf import STATUS_DICT
+STATUS_CHOICES = STATUS_DICT.items()
 
 class MonitorEntryManager(models.Manager):
     """ Custom Manager for MonitorEntry"""
         """
         self._moderate(status, user, notes)
 
+MONITOR_TABLE = MonitorEntry._meta.db_table
+

monitor/tests/apps/testapp/tests.py

 from django.contrib.auth.models import User, Permission
 from django.contrib.contenttypes.models import ContentType
 
+from monitor.conf import PENDING_STATUS, CHALLENGED_STATUS, APPROVED_STATUS
+
 from monitor.tests.utils.testsettingsmanager import SettingsTestCase
-
 from monitor.tests.apps.testapp.models import Author, Book, Supplement, Publisher
 
 def get_perm(Model, perm):
         self.assertEquals(response.status_code, 200)
         # 2 Author instances added. Both are in pending (IP)
         self.assertEquals(Author.objects.count(), 2)
-        self.assertEquals(Author.objects.get(pk=1).status, 'IP')
-        self.assertEquals(Author.objects.get(pk=2).status, 'IP')
+        self.assertEquals(Author.objects.get(pk=1).status, PENDING_STATUS)
+        self.assertEquals(Author.objects.get(pk=2).status, PENDING_STATUS)
         # Adding 1 book instance...
         url = '/admin/testapp/book/add/'
         data = {
         self.assertEquals(response.status_code, 200)
         # 1 Book instance added. In pending (IP)
         self.assertEquals(Book.objects.count(), 1)
-        self.assertEquals(Book.objects.get(pk=1).status, 'IP')
+        self.assertEquals(Book.objects.get(pk=1).status, PENDING_STATUS)
         # Adding 2 Supplement instances
         url = '/admin/testapp/supplement/add/'
         data = {'serial_num': 1, 'book': 1}
         response = self.client.post(url, data, follow = True)
         # 2 Supplement instances added. In Pending (IP)
         self.assertEquals(Supplement.objects.count(), 2)
-        self.assertEquals(Supplement.objects.get(pk=1).status, 'IP')
-        self.assertEquals(Supplement.objects.get(pk=2).status, 'IP')
+        self.assertEquals(Supplement.objects.get(pk=1).status, PENDING_STATUS)
+        self.assertEquals(Supplement.objects.get(pk=2).status, PENDING_STATUS)
 
         # Adder logs out
         self.client.logout()
         url = '/admin/testapp/author/'
         data = {'action': 'approve_selected', 'index': 0, '_selected_action': 1}
         response = self.client.post(url, data, follow = True)
-        self.assertEquals(Author.objects.get(pk=1).status, 'AP')
+        self.assertEquals(Author.objects.get(pk=1).status, APPROVED_STATUS)
         # Challenge Author 2 (created by adder)
         data = {'action': 'challenge_selected', 'index': 0, '_selected_action': 2}
         response = self.client.post(url, data, follow = True)
-        self.assertEquals(Author.objects.get(pk=2).status, 'CH')
+        self.assertEquals(Author.objects.get(pk=2).status, CHALLENGED_STATUS)
         # Approve Book 1 (created by adder). Supplements also get approved.
         url = '/admin/testapp/book/'
         data = {'action': 'approve_selected', 'index': 0, '_selected_action': 1}
         response = self.client.post(url, data, follow = True)
-        self.assertEquals(Book.objects.get(pk=1).status, 'AP')
-        self.assertEquals(Supplement.objects.get(pk=1).status, 'AP')
-        self.assertEquals(Supplement.objects.get(pk=2).status, 'AP')
+        self.assertEquals(Book.objects.get(pk=1).status, APPROVED_STATUS)
+        self.assertEquals(Supplement.objects.get(pk=1).status, APPROVED_STATUS)
+        self.assertEquals(Supplement.objects.get(pk=2).status, APPROVED_STATUS)
 
         # moderator logs out
         self.client.logout()
         url = '/admin/testapp/author/'
         data = {'action': 'reset_to_pending', 'index': 0, '_selected_action': 2}
         response = self.client.post(url, data, follow = True)
-        self.failUnlessEqual(Author.objects.get(pk=2).status, 'IP')
+        self.failUnlessEqual(Author.objects.get(pk=2).status, PENDING_STATUS)
 
 
-from monitor import _queue
-from monitor.models import MonitorEntry
+from datetime import datetime
+
+from django.contrib.contenttypes.models import ContentType
+from django.db.models import Manager
+
+from monitor.middleware import get_current_user
+from monitor.models import MonitorEntry, MONITOR_TABLE
+from monitor.conf import (
+    STATUS_DICT, PENDING_STATUS, APPROVED_STATUS, CHALLENGED_STATUS
+)
+
+def create_moderate_perms(app, created_models, verbosity, **kwargs):
+    """ This will create moderate permissions for all registered models"""
+    from django.contrib.auth.models import Permission
+
+    from monitor import queued_models
+
+    for model in queued_models():
+        ctype = ContentType.objects.get_for_model(model)
+        codename = 'moderate_%s' % model._meta.object_name.lower()
+        name = u'Can moderate %s' % model._meta.verbose_name_raw
+        p, created = Permission.objects.get_or_create(
+            codename = codename,
+            content_type__pk = ctype.id,
+            defaults = {'name': name, 'content_type': ctype}
+        )
+        if created and verbosity >= 2:
+            print "Adding permission '%s'" % p
+
+def add_fields(cls, manager_name, status_name, monitor_name, base_manager):
+    """ Add additional fields like status to moderated models"""
+    # Inheriting from old manager
+    if base_manager is None:
+        if hasattr(cls, manager_name):
+            base_manager = getattr(cls, manager_name).__class__
+        else:
+            base_manager = Manager
+    # Queryset inheriting from manager's Queryset
+    base_queryset = base_manager().get_query_set().__class__
+
+    class CustomQuerySet(base_queryset):
+        """ Chainable queryset for checking status """
+       
+        def _by_status(self, field_name, status):
+            """ Filter queryset by given status"""
+            where_clause = '%s = %%s' % (field_name)
+            return self.extra(where = [where_clause], params = [status])
+
+        def approved(self):
+            """ All approved objects"""
+            return self._by_status(status_name, APPROVED_STATUS)
+
+        def exclude_approved(self):
+            """ All not-approved objects"""
+            where_clause = '%s != %%s' % (status_name)
+            return self.extra(
+                where = [where_clause], params = [APPROVED_STATUS]
+            )
+
+        def pending(self):
+            """ All pending objects """
+            return self._by_status(status_name, PENDING_STATUS)
+
+        def challenged(self):
+            """ All challenged objects """
+            return self._by_status(status_name, CHALLENGED_STATUS)
+
+    class CustomManager(base_manager):
+        """ custom manager that adds parameters and uses custom QuerySet """
+
+        # use_for_related_fields is read when the model class is prepared
+        # because CustomManager isn't set on the class at the time
+        # this really has no effect, but is set to True because we are going
+        # to hijack cls._default_manager later
+        use_for_related_fields = True
+
+        # add monitor_id and status_name attributes to the query
+        def get_query_set(self):
+            # parameters to help with generic SQL
+            db_table = self.model._meta.db_table
+            pk_name = self.model._meta.pk.attname
+            content_type = ContentType.objects.get_for_model(self.model).id
+
+            # extra params - status and id of object (for later access)
+            select = {
+                '_monitor_id': '%s.id' % MONITOR_TABLE,
+                '_status': '%s.status' % MONITOR_TABLE,
+            }
+            where = [
+                '%s.content_type_id=%s' % (MONITOR_TABLE, content_type),
+                '%s.object_id=%s.%s' % (MONITOR_TABLE, db_table, pk_name)
+            ]
+            tables = [MONITOR_TABLE]
+
+            # build extra query then copy model/query to a CustomQuerySet
+            q = super(CustomManager, self).get_query_set().extra(
+                select = select, where = where, tables = tables
+            )
+            return CustomQuerySet(self.model, q.query)
+
+    def _get_monitor_entry(self):
+        """ accessor for monitor_entry that caches the object """
+        if not hasattr(self, '_monitor_entry'):
+            self._monitor_entry = MonitorEntry.objects.get(pk = self._monitor_id)
+        return self._monitor_entry
+
+    def _get_status_display(self):
+        """ to display the moderation status in verbose """
+        return STATUS_DICT[self._status]
+    _get_status_display.short_description = status_name
+
+    # Add custom manager & monitor_entry to class
+    manager = CustomManager()
+    cls.add_to_class(manager_name, manager)
+    cls.add_to_class(monitor_name, property(_get_monitor_entry))
+    cls.add_to_class(status_name, property(lambda self: self._status))
+    cls.add_to_class(
+        'get_status_display', _get_status_display
+    )
+    # We have a custom filter defined in monitor.filter to enable
+    # 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
+
+    # Copy manager to default_class
+    cls._default_manager = manager
+
+def save_handler(sender, instance, **kwargs):
+    """
+    After saving an object in moderated class, do the following:
+    1. Create a corresponding monitor entry.
+    2. Auto-moderate objects if enough permissions are available.
+    3. Moderate specified related objects too.
+    """
+    # Auto-moderation
+    user = get_current_user()
+    opts = instance.__class__._meta
+    mod_perm = '%s.moderate_%s' % (
+        opts.app_label.lower(), opts.object_name.lower()
+    )
+    if user and user.has_perm(mod_perm):
+        status = APPROVED_STATUS
+    else:
+        status = PENDING_STATUS
+
+    # Create corresponding monitor entry
+    if kwargs.get('created', None):
+        me = MonitorEntry(
+            status = status, content_object = instance,
+            timestamp = datetime.now()
+        )
+        me.save()
+
+    # Moderate related objects too...
+    moderate_rel_objects(instance, status, user)
 
 def moderate_rel_objects(given, status, user = None):
     """
     object(s) and all specified related objects.
     TODO: Permissions must be checked before each iteration.
     """
+    from monitor import model_from_queue
     # Not sure how we can find whether `given` is a queryset or object.
     # Now assume `given` is a queryset/related_manager if it has 'all'
     if not given:
         for obj in qset:
             me = MonitorEntry.objects.get_for_instance(obj)
             me.moderate(status, user)
-            rel_fields = _queue[qset.model]['rel_fields']
-            for rel_name in rel_fields:
-                moderate_rel_objects(getattr(obj, rel_name), status, user)
+            model = model_from_queue(qset.model)
+            if model:
+                for rel_name in model['rel_fields']:
+                    moderate_rel_objects(getattr(obj, rel_name), status, user)
     else:
         me = MonitorEntry.objects.get_for_instance(given)
         me.moderate(status, user)
-        rel_fields = _queue[given._meta.model]['rel_fields']
-        for rel_name in rel_fields:
-            moderate_rel_objects(getattr(given, rel_name), 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)