4 from hashlib import md5
6 from md5 import new as md5
7 from urllib import quote_plus, urlencode
8 from django.db import models, IntegrityError, connection, transaction
9 from django.utils.http import urlquote as django_urlquote
10 from django.utils.html import strip_tags
11 from django.core.urlresolvers import reverse
12 from django.contrib.contenttypes import generic
13 from django.contrib.contenttypes.models import ContentType
14 from django.core.cache import cache
15 from django.template.defaultfilters import slugify
16 from django.db.models.signals import post_delete, post_save, pre_save, pre_delete
17 from django.utils.translation import ugettext as _
18 from django.utils.safestring import mark_safe
19 from django.contrib.sitemaps import ping_google
20 import django.dispatch
21 from forum import settings
25 if not hasattr(cache, 'get_many'):
26 #put django 1.2 code here
29 class LazyQueryList(object):
30 def __init__(self, model, items):
34 def __getitem__(self, k):
35 return self.model.objects.get(id=self.items[k][0])
39 yield self.model.objects.get(id=id[0])
42 return len(self.items)
47 class CachedQuerySet(models.query.QuerySet):
50 if not len(self.query.aggregates):
53 if len(self.query.extra):
54 extra_keys = self.query.extra.keys()
55 values_list += extra_keys
57 return LazyQueryList(self.model, list(self.values_list(*values_list)))
61 def obj_from_datadict(self, datadict):
63 obj.__dict__.update(datadict)
65 if hasattr(obj, '_state'):
66 obj._state.db = 'default'
70 def _base_clone(self):
71 return self._clone(klass=models.query.QuerySet)
73 def get(self, *args, **kwargs):
74 key = self.model.infer_cache_key(kwargs)
80 obj = self._base_clone().get(*args, **kwargs)
83 obj = self.obj_from_datadict(obj)
84 obj.reset_original_state()
88 return self._base_clone().get(*args, **kwargs)
90 def _fetch_from_query_cache(self, key):
91 invalidation_key = self.model._get_cache_query_invalidation_key()
92 cached_result = cache.get_many([invalidation_key, key])
94 if not invalidation_key in cached_result:
95 self.model._set_query_cache_invalidation_timestamp()
98 if (key in cached_result) and(cached_result[invalidation_key] < cached_result[key][0]):
99 return cached_result[key][1]
104 cache_key = self.model._generate_cache_key("CNT:%s" % self._get_query_hash())
105 result = self._fetch_from_query_cache(cache_key)
107 if result is not None:
110 result = super(CachedQuerySet, self).count()
111 cache.set(cache_key, (datetime.datetime.now(), result), 60 * 60)
115 cache_key = self.model._generate_cache_key("QUERY:%s" % self._get_query_hash())
116 on_cache_query_attr = self.model.value_to_list_on_cache_query()
121 key_list = self._fetch_from_query_cache(cache_key)
124 if not len(self.query.aggregates):
125 values_list = [on_cache_query_attr]
127 if len(self.query.extra):
128 values_list += self.query.extra.keys()
130 key_list = [v[0] for v in self.values_list(*values_list)]
131 to_cache[cache_key] = (datetime.datetime.now(), key_list)
133 to_return = list(super(CachedQuerySet, self).iterator())
134 to_cache[cache_key] = (datetime.datetime.now(), [row.__dict__[on_cache_query_attr] for row in to_return])
136 if (not to_return) and key_list:
137 row_keys = [self.model.infer_cache_key({on_cache_query_attr: attr}) for attr in key_list]
138 cached = cache.get_many(row_keys)
141 (ck in cached) and self.obj_from_datadict(cached[ck]) or ToFetch(key_list[i]) for i, ck in enumerate(row_keys)
144 if len(cached) != len(row_keys):
145 to_fetch = [str(tr) for tr in to_return if isinstance(tr, ToFetch)]
147 fetched = dict([(str(r.__dict__[on_cache_query_attr]), r) for r in
148 models.query.QuerySet(self.model).filter(**{"%s__in" % on_cache_query_attr: to_fetch})])
150 to_return = [(isinstance(tr, ToFetch) and fetched[str(tr)] or tr) for tr in to_return]
151 to_cache.update(dict([(self.model.infer_cache_key({on_cache_query_attr: attr}), r._as_dict()) for attr, r in fetched.items()]))
155 cache.set_many(to_cache, 60 * 60)
158 for row in to_return:
159 if hasattr(row, 'leaf'):
164 def _get_query_hash(self):
165 return md5(str(self.query)).hexdigest()
169 class CachedManager(models.Manager):
170 use_for_related_fields = True
172 def get_query_set(self):
173 return CachedQuerySet(self.model)
175 def get_or_create(self, *args, **kwargs):
177 return self.get(*args, **kwargs)
179 return super(CachedManager, self).get_or_create(*args, **kwargs)
182 class DenormalizedField(object):
183 def __init__(self, manager, *args, **kwargs):
184 self.manager = manager
185 self.filter = (args, kwargs)
187 def setup_class(self, cls, name):
188 dict_name = '_%s_dencache_' % name
191 val = inst.__dict__.get(dict_name, None)
194 val = getattr(inst, self.manager).filter(*self.filter[0], **self.filter[1]).count()
195 inst.__dict__[dict_name] = val
200 def reset_cache(inst):
201 inst.__dict__.pop(dict_name, None)
204 cls.add_to_class(name, property(getter))
205 cls.add_to_class("reset_%s_cache" % name, reset_cache)
208 class BaseMetaClass(models.Model.__metaclass__):
211 def __new__(cls, *args, **kwargs):
212 new_cls = super(BaseMetaClass, cls).__new__(cls, *args, **kwargs)
214 BaseMetaClass.to_denormalize.extend(
215 [(new_cls, name, field) for name, field in new_cls.__dict__.items() if isinstance(field, DenormalizedField)]
221 def setup_denormalizes(cls):
222 for new_cls, name, field in BaseMetaClass.to_denormalize:
223 field.setup_class(new_cls, name)
226 class BaseModel(models.Model):
227 __metaclass__ = BaseMetaClass
229 objects = CachedManager()
235 def __init__(self, *args, **kwargs):
236 super(BaseModel, self).__init__(*args, **kwargs)
237 self.reset_original_state(kwargs.keys())
239 def reset_original_state(self, reset_fields=None):
240 self._original_state = self._as_dict()
243 self._original_state.update(dict([(f, None) for f in reset_fields]))
245 def get_dirty_fields(self):
246 return [f.name for f in self._meta.fields if self._original_state[f.attname] != self.__dict__[f.attname]]
249 return dict([(name, getattr(self, name)) for name in
250 ([f.attname for f in self._meta.fields] + [k for k in self.__dict__.keys() if k.endswith('_dencache_')])
253 def _get_update_kwargs(self):
255 (f.name, getattr(self, f.name)) for f in self._meta.fields if self._original_state[f.attname] != self.__dict__[f.attname]
258 def save(self, full_save=False, *args, **kwargs):
259 put_back = [k for k, v in self.__dict__.items() if isinstance(v, models.expressions.ExpressionNode)]
261 if hasattr(self, '_state'):
262 self._state.db = 'default'
264 if self.id and not full_save:
265 self.__class__.objects.filter(id=self.id).update(**self._get_update_kwargs())
267 super(BaseModel, self).save()
271 self.__dict__.update(
272 self.__class__.objects.filter(id=self.id).values(*put_back)[0]
275 logging.error("Unable to read %s from %s" % (", ".join(put_back), self.__class__.__name__))
278 self.reset_original_state()
279 self._set_query_cache_invalidation_timestamp()
283 def _get_cache_query_invalidation_key(cls):
284 return cls._generate_cache_key("INV_TS")
287 def _set_query_cache_invalidation_timestamp(cls):
288 cache.set(cls._get_cache_query_invalidation_key(), datetime.datetime.now(), 60 * 60 * 24)
290 for base in filter(lambda c: issubclass(c, BaseModel) and (not c is BaseModel), cls.__bases__):
291 base._set_query_cache_invalidation_timestamp()
294 def _generate_cache_key(cls, key, group=None):
298 return '%s:%s:%s' % (settings.APP_URL, group, key)
301 return self._generate_cache_key(self.id)
304 def value_to_list_on_cache_query(cls):
308 def infer_cache_key(cls, querydict):
310 pk = [v for (k,v) in querydict.items() if k in ('pk', 'pk__exact', 'id', 'id__exact'
311 ) or k.endswith('_ptr__pk') or k.endswith('_ptr__id')][0]
313 return cls._generate_cache_key(pk)
318 cache.set(self.cache_key(), self._as_dict(), 60 * 60)
321 cache.delete(self.cache_key())
325 self._set_query_cache_invalidation_timestamp()
326 super(BaseModel, self).delete()
329 from user import User
330 from node import Node, NodeRevision, NodeManager
331 from action import Action