Skip to content
This repository
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 156 lines (113 sloc) 5.128 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
"""
Mptt registering (modified)
==========================

Registering now takes place in metaclass. Mptt is registered if model contains
attribute Meta, which is subclass of Mptt.

Basic usage::

class MyModel(models.Model):
...
class Mptt(Mptt):
pass

Requirements:
- requires mptt installed on pythonpath
"""

from django.db.models import signals
from django.db import models


class Mptt(models.Model):
    """Abstract class which have to be extended for putting model under mptt.
For changing attributes see MpttMeta
"""
    class Meta:
        abstract = True


class MpttMeta:
    """Basic mptt configuration class - something like Meta in model
"""
    
    META_ATTRIBUTES = ('parent_attr', 'left_attr', 'right_attr',
        'tree_id_attr', 'level_attr', 'tree_manager_attr', 'order_insertion_by')
    
    parent_attr = 'parent'
    left_attr = 'lft'
    right_attr = 'rght'
    tree_id_attr = 'tree_id'
    level_attr = 'level'
    tree_manager_attr = 'tree'
    order_insertion_by = None
    
    @classmethod
    def contribute_to_class(cls, main_cls, name):
        # install rest of mptt, class was build
        
        signals.class_prepared.connect(cls.finish_mptt_class,
            sender=main_cls, weak=False)
        
        from mptt.signals import pre_save
        
        # Set up signal receiver to manage the tree when instances of the
        # model are about to be saved.
        signals.pre_save.connect(pre_save, sender=main_cls)
    
    @classmethod
    def finish_mptt_class(cls, *args, **kwargs):
        main_cls = kwargs['sender']
        try:
            from functools import wraps
        except ImportError:
            from django.utils.functional import wraps # Python 2.3, 2.4 fallback
        
        from mptt.managers import TreeManager
        
        # jsut copy attributes to meta
        for attr in MpttMeta.META_ATTRIBUTES:
            setattr(main_cls._meta, attr, getattr(cls, attr))
        
        meta = main_cls._meta
        
        # Instanciate tree manager
        TreeManager(meta.parent_attr, meta.left_attr, meta.right_attr,
            meta.tree_id_attr, meta.level_attr).contribute_to_class(main_cls, meta.tree_manager_attr)
        
        # Add a custom tree manager
        #setattr(main_cls, meta.tree_manager_attr, tree_manager)
        setattr(main_cls, '_tree_manager', getattr(main_cls, meta.tree_manager_attr))
        
        # Wrap the model's delete method to manage the tree structure before
        # deletion. This is icky, but the pre_delete signal doesn't currently
        # provide a way to identify which model delete was called on and we
        # only want to manage the tree based on the topmost node which is
        # being deleted.
        def wrap_delete(delete):
            def _wrapped_delete(self):
                opts = self._meta
                tree_width = (getattr(self, opts.right_attr) -
                              getattr(self, opts.left_attr) + 1)
                target_right = getattr(self, opts.right_attr)
                tree_id = getattr(self, opts.tree_id_attr)
                self._tree_manager._close_gap(tree_width, target_right, tree_id)
                delete(self)
            return wraps(delete)(_wrapped_delete)
        main_cls.delete = wrap_delete(main_cls.delete)
    
    
def install_mptt(cls, name, bases, attrs):
    """Installs mptt - modifies class attrs, and adds required stuff to them.
"""
    from publisher.models import MpttPublisher
    
    if not Mptt in bases and not MpttPublisher in bases:
        return attrs
    
    if 'MpttMeta' in attrs and not issubclass(attrs['MpttMeta'], MpttMeta):
        raise ValueError, ("%s.Mptt must be a subclass "
                           + " of publisher.Mptt.") % (name,)
    
    attrs['MpttMeta'] = MpttMeta
    
    # import required stuff here, so we will have import errors only when mptt
    # is really in use
    from mptt import models as mptt_models
        
    attrs['_is_mptt_model'] = lambda self: True
    
    mptt_meta = attrs['MpttMeta']
    
    assert mptt_meta.parent_attr in attrs, ("Mppt model must define parent "
        "field!")
    
    # add mptt fields
    fields = (mptt_meta.left_attr, mptt_meta.right_attr,
        mptt_meta.tree_id_attr, mptt_meta.level_attr)
    
    for attr in fields:
        if not attr in attrs:
            attrs[attr] = models.PositiveIntegerField(db_index=True, editable=False)
    
    methods = ('get_ancestors', 'get_children', 'get_descendants',
        'get_descendant_count', 'get_next_sibling',
        'get_previous_sibling', 'get_root', 'get_siblings', 'insert_at',
        'is_child_node', 'is_leaf_node', 'is_root_node', 'move_to')
    
    # Add tree methods for model instances
    for method_name in methods:
        attrs[method_name] = getattr(mptt_models, method_name)
          
    return attrs


def finish_mptt(cls):
    if not hasattr(cls, '_is_mptt_model'):
        return
    
    from mptt import registry
    if not cls in registry:
        registry.append(cls)
    

Something went wrong with that request. Please try again.