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()]))
154 cache.set_many(to_cache, 60 * 60)
157 for row in to_return:
158 if hasattr(row, 'leaf'):
163 def _get_query_hash(self):
164 return md5(str(self.query)).hexdigest()
168 class CachedManager(models.Manager):
169 use_for_related_fields = True
171 def get_query_set(self):
172 return CachedQuerySet(self.model)
174 def get_or_create(self, *args, **kwargs):
176 return self.get(*args, **kwargs)
178 return super(CachedManager, self).get_or_create(*args, **kwargs)
181 class DenormalizedField(object):
182 def __init__(self, manager, *args, **kwargs):
183 self.manager = manager
184 self.filter = (args, kwargs)
186 def setup_class(self, cls, name):
187 dict_name = '_%s_dencache_' % name
190 val = inst.__dict__.get(dict_name, None)
193 val = getattr(inst, self.manager).filter(*self.filter[0], **self.filter[1]).count()
194 inst.__dict__[dict_name] = val
199 def reset_cache(inst):
200 inst.__dict__.pop(dict_name, None)
203 cls.add_to_class(name, property(getter))
204 cls.add_to_class("reset_%s_cache" % name, reset_cache)
207 class BaseMetaClass(models.Model.__metaclass__):
210 def __new__(cls, *args, **kwargs):
211 new_cls = super(BaseMetaClass, cls).__new__(cls, *args, **kwargs)
213 BaseMetaClass.to_denormalize.extend(
214 [(new_cls, name, field) for name, field in new_cls.__dict__.items() if isinstance(field, DenormalizedField)]
220 def setup_denormalizes(cls):
221 for new_cls, name, field in BaseMetaClass.to_denormalize:
222 field.setup_class(new_cls, name)
225 class BaseModel(models.Model):
226 __metaclass__ = BaseMetaClass
228 objects = CachedManager()
234 def __init__(self, *args, **kwargs):
235 super(BaseModel, self).__init__(*args, **kwargs)
236 self.reset_original_state(kwargs.keys())
238 def reset_original_state(self, reset_fields=None):
239 self._original_state = self._as_dict()
242 self._original_state.update(dict([(f, None) for f in reset_fields]))
244 def get_dirty_fields(self):
245 return [f.name for f in self._meta.fields if self._original_state[f.attname] != self.__dict__[f.attname]]
248 return dict([(name, getattr(self, name)) for name in
249 ([f.attname for f in self._meta.fields] + [k for k in self.__dict__.keys() if k.endswith('_dencache_')])
252 def _get_update_kwargs(self):
254 (f.name, getattr(self, f.name)) for f in self._meta.fields if self._original_state[f.attname] != self.__dict__[f.attname]
257 def save(self, full_save=False, *args, **kwargs):
258 put_back = [k for k, v in self.__dict__.items() if isinstance(v, models.expressions.ExpressionNode)]
260 if self.id and not full_save:
261 self.__class__.objects.filter(id=self.id).update(**self._get_update_kwargs())
263 super(BaseModel, self).save()
267 self.__dict__.update(
268 self.__class__.objects.filter(id=self.id).values(*put_back)[0]
271 logging.error("Unable to read %s from %s" % (", ".join(put_back), self.__class__.__name__))
274 self.reset_original_state()
275 self._set_query_cache_invalidation_timestamp()
279 def _get_cache_query_invalidation_key(cls):
280 return cls._generate_cache_key("INV_TS")
283 def _set_query_cache_invalidation_timestamp(cls):
284 cache.set(cls._get_cache_query_invalidation_key(), datetime.datetime.now(), 60 * 60 * 24)
286 for base in filter(lambda c: issubclass(c, BaseModel) and (not c is BaseModel), cls.__bases__):
287 base._set_query_cache_invalidation_timestamp()
290 def _generate_cache_key(cls, key, group=None):
294 return '%s:%s:%s' % (settings.APP_URL, group, key)
297 return self._generate_cache_key(self.id)
300 def value_to_list_on_cache_query(cls):
304 def infer_cache_key(cls, querydict):
306 pk = [v for (k,v) in querydict.items() if k in ('pk', 'pk__exact', 'id', 'id__exact'
307 ) or k.endswith('_ptr__pk') or k.endswith('_ptr__id')][0]
309 return cls._generate_cache_key(pk)
314 cache.set(self.cache_key(), self._as_dict(), 60 * 60)
317 cache.delete(self.cache_key())
321 self._set_query_cache_invalidation_timestamp()
322 super(BaseModel, self).delete()
325 from user import User
326 from node import Node, NodeRevision, NodeManager
327 from action import Action