Code

ModelMiddleware: model_utils.py

File model_utils.py, 8.2 KB (added by Gevara, 8 years ago)

ModelMiddleware itself with two example classes

Line 
1# -*- coding: utf-8 -*-
2from django.db.models.base import ModelBase
3from django.db import models
4import types
5
6def _filter_bases(bases, filter_key): 
7    """ Remove all classes descendants of ``filter_key`` from ``bases`` """
8    new_bases = tuple([base for base in bases \
9                       if ((not (base is filter_key)) and (not issubclass(base, filter_key)))])
10   
11    # ensure that we don't end up with an orphan class - it must be
12    # parented by at least models.Model
13    if len(new_bases) == 0: new_bases = (models.Model,)
14    return new_bases
15
16class Hooks(object):
17    _pre_saves = {}
18    _post_saves = {}
19    _pre_deletes = {}
20    _post_deletes = {}
21
22class MetaModelMiddleware(ModelBase):
23   
24    # pre and post hooks for save() will be temporarily stored in these
25    #pre_saves = {}
26    #post_saves = {}
27    # pre and post hooks for delete()
28    #pre_deletes = {}
29    #post_deletes = {}
30    def __new__(cls, name, bases, attrs):
31       
32        # whether base classes should be filtered
33        cls.hide_bases = False
34        # only filter bases if this wasn't invoked by the ModelMiddleware
35        # class, which is a super class for all custom middleware, and the
36        # one we are using as a filter key
37        if not (name == 'ModelMiddleware'):
38            if not (ModelMiddleware in bases):
39                cls.hide_bases = True
40        if cls.hide_bases:
41            # replace the original bases with filtered ones to fool Django's inheritance
42            bases = _filter_bases(bases, ModelMiddleware)
43        # set the middleware options under Klass._middle
44        if attrs.has_key('Middle'):
45            midopts = attrs['Middle']
46            assert type(midopts) == types.ClassType, "Middle attribute of %s model must be a class, not a %s object" % (name, type(midopts))
47            opts = {}
48            opts.update([(k,v) for k,v in midopts.__dict__.items() if not k.startswith('_')])
49            attrs["_middle"] = opts
50            attrs.pop('Middle')
51        return ModelBase.__new__(cls, name, bases, attrs)
52   
53    def __init__(cls,name,bases,attrs):
54        # provide a wrapper func for save()
55        def new_save(func):
56            def wrapper(*args, **kwargs):
57                if hasattr(cls, 'pre_saves'):
58                    [pre(args[0]) for pre in cls.pre_saves]
59                func(*args, **kwargs)
60                if hasattr(cls, 'post_saves'):
61                    [post(args[0]) for post in cls.post_saves]
62            return wrapper
63        # provide a wrapper func for delete()
64        def new_delete(func):
65            def wrapper(*args, **kwargs):
66                if hasattr(cls, 'pre_deletes'):
67                    [pre(args[0]) for pre in cls.pre_deletes]
68                func(*args, **kwargs)
69                if hasattr(cls, 'post_deletes') > 0:
70                    [post(args[0]) for post in cls.post_deletes]
71            return wrapper
72       
73        # if this is a descendant of ModelMiddleware, but not ModelMiddleware itself
74        if name != 'ModelMiddleware':
75            # if this class inherits directly from ModelMiddleware then save its hooks
76            if ModelMiddleware in bases:
77                if attrs.has_key('pre_save'):
78                    Hooks._pre_saves[name] = attrs['pre_save']
79                if attrs.has_key('post_save'):
80                    Hooks._post_saves[name] = attrs['post_save']
81                if attrs.has_key('pre_delete'):
82                    Hooks._pre_deletes[name] = attrs['pre_delete']
83                if attrs.has_key('post_delete'):
84                    Hooks._post_deletes[name] = attrs['post_delete']
85       
86
87            # if this is NOT a direct descendant of ModelMiddleware - not a holder of callbacks
88            if ModelMiddleware not in bases:
89                orig_save = cls.save
90                orig_delete = cls.delete
91                for base in bases:
92                    base_pre_save = Hooks._pre_saves.get(base.__name__, False)
93                    if base_pre_save:
94                        if not hasattr(cls,'pre_saves'):
95                            cls.pre_saves = []
96                        cls.pre_saves.append(base_pre_save)
97                    base_post_save = Hooks._post_saves.get(base.__name__, False)
98                    if base_post_save:
99                        if not hasattr(cls, 'post_saves'):
100                            cls.post_saves = []
101                        cls.post_saves.append(base_post_saves)
102                    base_pre_delete = Hooks._pre_deletes.get(base.__name__, False)
103                    if base_pre_delete:
104                        if not hasattr(cls, 'pre_deletes'):
105                            cls.pre_deletes = []
106                        cls.pre_deletes.append(base_pre_deletes)
107                    base_post_delete = Hooks._post_deletes.get(base.__name__, False)
108                    if base_post_delete:
109                        if not hasattr(cls, 'post_deletes'):
110                            cls.post_deletes = []
111                        cls.post_deletes.append(base_post_deletes)
112                cls.save = new_save(orig_save)
113                cls.delete = new_delete(orig_delete)
114                # replace original bases with filtered ones
115                bases = _filter_bases(bases,ModelMiddleware)
116        new_class = super(ModelBase,cls).__init__(name,bases,attrs)
117        return new_class
118   
119
120class ModelMiddleware(models.Model):
121    """
122    Custom model middleware components should subclass this and never
123    use the MetaModelMiddleware metaclass directly.
124    """
125    __metaclass__ = MetaModelMiddleware
126   
127   
128class ReSTMiddleware(ModelMiddleware):
129    def pre_save(self):
130        try:
131            opts = self.__class__._middle["ReST"] # individual options are saved in a dict
132        except (AttributeError, KeyError):
133            return  # just fail silently, though it might not be a very good idea in practice
134
135        # parse for as many fields as we have options for
136        for opt in opts: 
137            # lets be nice to ourselves and provide a default value for the initial header level
138            if not opt.has_key("init_header"):
139                opt["init_header"] = 1 
140            try:
141                cont = getattr(self, opt["field"]).decode("utf_8")
142                parts = build_document(cont, initial_header_level=opt["init_header"])
143                setattr(self, opt["save_body"], parts["html_body"].encode('utf_8'))
144                setattr(self, opt["save_toc"], parts["toc"].encode('utf_8'))
145            except:
146                pass # another silent fail, needs fixing        d = datetime.now()
147
148from datetime import datetime
149
150class TimestampMiddleware(ModelMiddleware):
151    """
152    This class can record a timestamp (down to one second precision) into any fields you specify.
153    There are two types of timestamps: 'always' and 'once'. 'always' means that record must be
154    made on every save(), while 'once' fields will be timestamped once on the first save() of this
155    object.
156   
157    A default set of options (used if none are provided by the model) is provided, which presume
158    the existance of 'pub_date' and 'last_modified' fields.  The 'pub_date' field is of type "once",
159    and 'last_modified' is of type "always". This lets you timestamp the object's creation and modification
160    times.
161   
162    Example options (also the default ones):
163   
164    class Middle:
165        Timestamp = ({'field' : 'pub_date', 'type' : 'once'},
166            {'field' : 'last_modified', 'type' : 'always'})
167    """
168    def pre_save(self):
169        try:
170            opts = self.__class__._middle["Timestamp"]
171        except (AttributeError, KeyError):
172            opts = ({'field' : 'pub_date', 'type' : 'once'},
173                {'field' : 'last_modified', 'type' : 'always'})
174       
175        for opt in opts:
176            if not opt.has_key('type'):
177                opt['type'] = 'always'
178            d = datetime.now()
179            pdate = datetime(d.year, d.month, d.day, d.hour, d.minute)
180            # if this is a "set once" type of field, then we check whether
181            # it's been filled in and if not - do so
182            if opt['type'] == 'once':
183                if getattr(self, opt['field']) is None:
184                    setattr(self, opt['field'], pdate)
185            elif opt['type'] == 'always':
186                setattr(self, opt['field'], pdate)