ModelMiddleware: model_utils.py

File model_utils.py, 8.2 KB (added by Gevara, 18 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)
Back to Top