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.utils.encoding import force_unicode
20 from django.contrib.sitemaps import ping_google
21 import django.dispatch
22 from forum import settings
26 if not hasattr(cache, 'get_many'):
27 #put django 1.2 code here
30 class LazyQueryList(object):
31 def __init__(self, model, items):
35 def __getitem__(self, k):
36 return self.model.objects.get(id=self.items[k][0])
40 yield self.model.objects.get(id=id[0])
43 return len(self.items)
45 class ToFetch(unicode):
48 class CachedQuerySet(models.query.QuerySet):
51 if not len(self.query.aggregates):
54 if len(self.query.extra):
55 extra_keys = self.query.extra.keys()
56 values_list += extra_keys
58 return LazyQueryList(self.model, list(self.values_list(*values_list)))
62 def obj_from_datadict(self, datadict):
64 obj.__dict__.update(datadict)
66 if hasattr(obj, '_state'):
67 obj._state.db = 'default'
71 def _base_clone(self):
72 return self._clone(klass=models.query.QuerySet)
74 def get(self, *args, **kwargs):
75 key = self.model.infer_cache_key(kwargs)
81 obj = self._base_clone().get(*args, **kwargs)
84 obj = self.obj_from_datadict(obj)
85 obj.reset_original_state()
89 return self._base_clone().get(*args, **kwargs)
91 def _fetch_from_query_cache(self, key):
92 invalidation_key = self.model._get_cache_query_invalidation_key()
93 cached_result = cache.get_many([invalidation_key, key])
95 if not invalidation_key in cached_result:
96 self.model._set_query_cache_invalidation_timestamp()
99 if (key in cached_result) and(cached_result[invalidation_key] < cached_result[key][0]):
100 return cached_result[key][1]
105 cache_key = self.model._generate_cache_key("CNT:%s" % self._get_query_hash())
106 result = self._fetch_from_query_cache(cache_key)
108 if result is not None:
111 result = super(CachedQuerySet, self).count()
112 cache.set(cache_key, (datetime.datetime.now(), result), 60 * 60)
116 cache_key = self.model._generate_cache_key("QUERY:%s" % self._get_query_hash())
117 on_cache_query_attr = self.model.value_to_list_on_cache_query()
122 with_aggregates = len(self.query.aggregates) > 0
123 key_list = self._fetch_from_query_cache(cache_key)
126 if not with_aggregates:
127 values_list = [on_cache_query_attr]
129 if len(self.query.extra):
130 values_list += self.query.extra.keys()
132 key_list = [v[0] for v in self.values_list(*values_list)]
133 to_cache[cache_key] = (datetime.datetime.now(), key_list)
135 to_return = list(super(CachedQuerySet, self).iterator())
136 to_cache[cache_key] = (datetime.datetime.now(), [
137 (row.__dict__[on_cache_query_attr], dict([(k, row.__dict__[k]) for k in self.query.aggregates.keys()]))
138 for row in to_return])
139 elif with_aggregates:
141 key_list = [k[0] for k in tmp]
142 with_aggregates = [k[1] for k in tmp]
145 if (not to_return) and key_list:
146 row_keys = [self.model.infer_cache_key({on_cache_query_attr: attr}) for attr in key_list]
147 cached = cache.get_many(row_keys)
150 (ck in cached) and self.obj_from_datadict(cached[ck]) or ToFetch(force_unicode(key_list[i])) for i, ck in enumerate(row_keys)
153 if len(cached) != len(row_keys):
154 to_fetch = [unicode(tr) for tr in to_return if isinstance(tr, ToFetch)]
156 fetched = dict([(force_unicode(r.__dict__[on_cache_query_attr]), r) for r in
157 models.query.QuerySet(self.model).filter(**{"%s__in" % on_cache_query_attr: to_fetch})])
159 to_return = [(isinstance(tr, ToFetch) and fetched[unicode(tr)] or tr) for tr in to_return]
160 to_cache.update(dict([(self.model.infer_cache_key({on_cache_query_attr: attr}), r._as_dict()) for attr, r in fetched.items()]))
163 for i, r in enumerate(to_return):
164 r.__dict__.update(with_aggregates[i])
168 cache.set_many(to_cache, 60 * 60)
171 for row in to_return:
172 if hasattr(row, 'leaf'):
177 def _get_query_hash(self):
178 return md5(unicode(self.query).encode("utf-8")).hexdigest()
182 class CachedManager(models.Manager):
183 use_for_related_fields = True
185 def get_query_set(self):
186 return CachedQuerySet(self.model)
188 def get_or_create(self, *args, **kwargs):
190 return self.get(*args, **kwargs)
192 return super(CachedManager, self).get_or_create(*args, **kwargs)
195 class DenormalizedField(object):
196 def __init__(self, manager, *args, **kwargs):
197 self.manager = manager
198 self.filter = (args, kwargs)
200 def setup_class(self, cls, name):
201 dict_name = '_%s_dencache_' % name
204 val = inst.__dict__.get(dict_name, None)
207 val = getattr(inst, self.manager).filter(*self.filter[0], **self.filter[1]).count()
208 inst.__dict__[dict_name] = val
213 def reset_cache(inst):
214 inst.__dict__.pop(dict_name, None)
217 cls.add_to_class(name, property(getter))
218 cls.add_to_class("reset_%s_cache" % name, reset_cache)
221 class BaseMetaClass(models.Model.__metaclass__):
224 def __new__(cls, *args, **kwargs):
225 new_cls = super(BaseMetaClass, cls).__new__(cls, *args, **kwargs)
227 BaseMetaClass.to_denormalize.extend(
228 [(new_cls, name, field) for name, field in new_cls.__dict__.items() if isinstance(field, DenormalizedField)]
234 def setup_denormalizes(cls):
235 for new_cls, name, field in BaseMetaClass.to_denormalize:
236 field.setup_class(new_cls, name)
239 class BaseModel(models.Model):
240 __metaclass__ = BaseMetaClass
242 objects = CachedManager()
248 def __init__(self, *args, **kwargs):
249 super(BaseModel, self).__init__(*args, **kwargs)
250 self.reset_original_state(kwargs.keys())
252 def reset_original_state(self, reset_fields=None):
253 self._original_state = self._as_dict()
256 self._original_state.update(dict([(f, None) for f in reset_fields]))
258 def get_dirty_fields(self):
259 return [f.name for f in self._meta.fields if self._original_state[f.attname] != self.__dict__[f.attname]]
262 return dict([(name, getattr(self, name)) for name in
263 ([f.attname for f in self._meta.fields] + [k for k in self.__dict__.keys() if k.endswith('_dencache_')])
266 def _get_update_kwargs(self):
268 (f.name, getattr(self, f.name)) for f in self._meta.fields if self._original_state[f.attname] != self.__dict__[f.attname]
271 def save(self, full_save=False, *args, **kwargs):
272 put_back = [k for k, v in self.__dict__.items() if isinstance(v, models.expressions.ExpressionNode)]
274 if hasattr(self, '_state'):
275 self._state.db = 'default'
277 if self.id and not full_save:
278 self.__class__.objects.filter(id=self.id).update(**self._get_update_kwargs())
280 super(BaseModel, self).save()
284 self.__dict__.update(
285 self.__class__.objects.filter(id=self.id).values(*put_back)[0]
288 logging.error("Unable to read %s from %s" % (", ".join(put_back), self.__class__.__name__))
291 self.reset_original_state()
292 self._set_query_cache_invalidation_timestamp()
296 def _get_cache_query_invalidation_key(cls):
297 return cls._generate_cache_key("INV_TS")
300 def _set_query_cache_invalidation_timestamp(cls):
301 cache.set(cls._get_cache_query_invalidation_key(), datetime.datetime.now(), 60 * 60 * 24)
303 for base in filter(lambda c: issubclass(c, BaseModel) and (not c is BaseModel), cls.__bases__):
304 base._set_query_cache_invalidation_timestamp()
307 def _generate_cache_key(cls, key, group=None):
311 return '%s:%s:%s' % (settings.APP_URL, group, key)
314 return self._generate_cache_key(self.id)
317 def value_to_list_on_cache_query(cls):
321 def infer_cache_key(cls, querydict):
323 pk = [v for (k,v) in querydict.items() if k in ('pk', 'pk__exact', 'id', 'id__exact'
324 ) or k.endswith('_ptr__pk') or k.endswith('_ptr__id')][0]
326 return cls._generate_cache_key(pk)
331 cache.set(self.cache_key(), self._as_dict(), 60 * 60)
334 cache.delete(self.cache_key())
338 self._set_query_cache_invalidation_timestamp()
339 super(BaseModel, self).delete()
342 from user import User
343 from node import Node, NodeRevision, NodeManager
344 from action import Action