Source

Django-monitor / django_monitor / util.py

Full commit
Rajeesh Nair abac1b1 






























Rajeesh Nair 88103bf 
Rajeesh Nair abac1b1 

























































































































































































Rajeesh Nair 11eb8a0 

Rajeesh Nair abac1b1 


Rajeesh Nair 11eb8a0 
Rajeesh Nair abac1b1 

















































































from datetime import datetime

from django.contrib.contenttypes.models import ContentType
from django.db.models import Manager

from django_monitor.middleware import get_current_user
from django_monitor.models import MonitorEntry, MONITOR_TABLE
from django_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 django_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 __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_status(self):
        """
        Accessor for monitor_status.
        To be added to the model as a property, ``monitor_status``.
        """
        if not hasattr(self, '_status'):
            return getattr(self, monitor_name).status
        return self._status

    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_for_instance(self)
        return self._monitor_entry

    def _get_status_display(self):
        """ to display the moderation status in verbose """
        return STATUS_DICT[self.monitor_status]
    _get_status_display.short_description = status_name

    def moderate(self, status, user = None, notes = ''):
        """ developers may use this to moderate objects """
        import django_monitor
        getattr(self, monitor_name).moderate(status, user, notes)
        # Auto-Moderate parents also
        monitored_parents = filter(
            lambda x: django_monitor.model_from_queue(x),
            self._meta.parents.keys()
        )
        for parent in monitored_parents:
            parent_ct = ContentType.objects.get_for_model(parent)
            parent_pk_field = self._meta.get_ancestor_link(parent)
            parent_pk = getattr(self, parent_pk_field.attname)
            me = MonitorEntry.objects.get(
                content_type = parent_ct, object_id = parent_pk
            )
            me.moderate(status, user)

    def approve(self, user = None, notes = ''):
        """ Approve the object & its parents."""
        self.moderate(APPROVED_STATUS, user, notes)

    def challenge(self, user = None, notes = ''):
        """Challenge"""
        self.moderate(CHALLENGED_STATUS, user, notes)

    def reset_to_pending(self, user = None, notes = ''):
        """Reset"""
        self.moderate(PENDING_STATUS, user, notes)

    def is_approved(self):
        return self.monitor_status == APPROVED_STATUS

    def is_pending(self):
        return self.monitor_status == PENDING_STATUS

    def is_challenged(self):
        return self.monitor_status == CHALLENGED_STATUS

    # 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('monitor_status', property(_get_monitor_status)) 
    cls.add_to_class(status_name, lambda self: self.monitor_status)
    cls.add_to_class(
        'get_monitor_status_display', _get_status_display
    )
    cls.add_to_class('moderate', moderate)
    cls.add_to_class('approve', approve)
    cls.add_to_class('challenge', challenge)
    cls.add_to_class('reset_to_pending', reset_to_pending)
    cls.add_to_class('is_approved', property(is_approved))
    cls.add_to_class('is_challenged', property(is_challenged))
    cls.add_to_class('is_pending', property(is_pending))
    # We have a custom filter defined in django_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``.

    # 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

def save_handler(sender, instance, **kwargs):
    """
    The following things are done after creating an object in moderated class:
    1. Creates monitor entries for object and its parents.
    2. Auto-approves object, its parents & specified related objects if user 
       has ``moderate`` permission. Otherwise, they are put in pending.
    """
    import django_monitor
    # 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.objects.create(
            status = status,
            content_object = instance,
            timestamp = datetime.now()
        )
        me.moderate(status, user)

        # Create one monitor_entry per moderated parent.
        monitored_parents = filter(
            lambda x: django_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... 
        model = django_monitor.model_from_queue(instance.__class__)
        if model:
            for rel_name in model['rel_fields']:
                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):
    """
    `given` can either be any model object or a queryset. Moderate given
    object(s) and all specified related objects.
    TODO: Permissions must be checked before each iteration.
    """
    from django_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:
        # given may become None. Stop there.
        return
    if hasattr(given, 'all'):
        qset = given.all()
        for obj in qset:
            obj.moderate(status, user)
            model = model_from_queue(qset.model)
            if model:
                for rel_name in model['rel_fields']:
                    rel_obj = getattr(obj, rel_name, None)
                    if rel_obj:
                        moderate_rel_objects(rel_obj, status, user)
    else:
        given.moderate(status, user)
        model = model_from_queue(given.__class__)
        if model:
            for rel_name in model['rel_fields']:
                rel_obj = getattr(given, rel_name, None)
                if rel_obj:
                    moderate_rel_objects(rel_obj, status, user)

def delete_handler(sender, instance, **kwargs):
    """ When an instance is deleted, delete corresponding monitor_entries too"""
    from django_monitor import model_from_queue
    if model_from_queue(sender):
        me = MonitorEntry.objects.get_for_instance(instance)
        if me:
            me.delete()
        # Delete monitor_entries of parents too
        monitored_parents = filter(
            lambda x: 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
                )
                me.delete()
            except MonitorEntry.DoesNotExist:
                pass