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.conf import settings as django_settings
12 from django.core.urlresolvers import reverse
13 from django.contrib.contenttypes import generic
14 from django.contrib.contenttypes.models import ContentType
15 from django.core.cache import cache
16 from django.template.defaultfilters import slugify
17 from django.db.models.signals import post_delete, post_save, pre_save, pre_delete
18 from django.utils.translation import ugettext as _
19 from django.utils.safestring import mark_safe
20 from django.utils.encoding import force_unicode
21 from django.contrib.sitemaps import ping_google
22 import django.dispatch
23 from forum import settings
27 if not hasattr(cache, 'get_many'):
28 #put django 1.2 code here
31 class LazyQueryList(object):
32 def __init__(self, model, items):
36 def __getitem__(self, k):
37 return self.model.objects.get(id=self.items[k][0])
41 yield self.model.objects.get(id=id[0])
44 return len(self.items)
46 class ToFetch(unicode):
49 class CachedQuerySet(models.query.QuerySet):
52 if not len(self.query.aggregates):
55 if len(self.query.extra):
56 extra_keys = self.query.extra.keys()
57 values_list += extra_keys
59 return LazyQueryList(self.model, list(self.values_list(*values_list)))
63 def obj_from_datadict(self, datadict):
65 obj.__dict__.update(datadict)
67 if hasattr(obj, '_state'):
68 obj._state.db = 'default'
72 def _base_clone(self):
73 return self._clone(klass=models.query.QuerySet)
75 def get(self, *args, **kwargs):
76 key = self.model.infer_cache_key(kwargs)
82 obj = self._base_clone().get(*args, **kwargs)
85 obj = self.obj_from_datadict(obj)
87 obj.reset_original_state()
91 return self._base_clone().get(*args, **kwargs)
93 def _fetch_from_query_cache(self, key):
94 invalidation_key = self.model._get_cache_query_invalidation_key()
95 cached_result = cache.get_many([invalidation_key, key])
97 if not invalidation_key in cached_result:
98 self.model._set_query_cache_invalidation_timestamp()
101 if (key in cached_result) and(cached_result[invalidation_key] < cached_result[key][0]):
102 return cached_result[key][1]
107 cache_key = self.model._generate_cache_key("CNT:%s" % self._get_query_hash())
108 result = self._fetch_from_query_cache(cache_key)
110 if result is not None:
113 result = super(CachedQuerySet, self).count()
114 cache.set(cache_key, (datetime.datetime.now(), result), 60 * 60)
118 cache_key = self.model._generate_cache_key("QUERY:%s" % self._get_query_hash())
119 on_cache_query_attr = self.model.value_to_list_on_cache_query()
124 with_aggregates = len(self.query.aggregates) > 0
125 key_list = self._fetch_from_query_cache(cache_key)
128 if not with_aggregates:
129 values_list = [on_cache_query_attr]
131 if len(self.query.extra):
132 values_list += self.query.extra.keys()
134 key_list = [v[0] for v in self.values_list(*values_list)]
135 to_cache[cache_key] = (datetime.datetime.now(), key_list)
137 to_return = list(super(CachedQuerySet, self).iterator())
138 to_cache[cache_key] = (datetime.datetime.now(), [
139 (row.__dict__[on_cache_query_attr], dict([(k, row.__dict__[k]) for k in self.query.aggregates.keys()]))
140 for row in to_return])
141 elif with_aggregates:
143 key_list = [k[0] for k in tmp]
144 with_aggregates = [k[1] for k in tmp]
147 if (not to_return) and key_list:
148 row_keys = [self.model.infer_cache_key({on_cache_query_attr: attr}) for attr in key_list]
149 cached = cache.get_many(row_keys)
152 (ck in cached) and self.obj_from_datadict(cached[ck]) or ToFetch(force_unicode(key_list[i])) for i, ck in enumerate(row_keys)
155 if len(cached) != len(row_keys):
156 to_fetch = [unicode(tr) for tr in to_return if isinstance(tr, ToFetch)]
158 fetched = dict([(force_unicode(r.__dict__[on_cache_query_attr]), r) for r in
159 models.query.QuerySet(self.model).filter(**{"%s__in" % on_cache_query_attr: to_fetch})])
161 to_return = [(isinstance(tr, ToFetch) and fetched[unicode(tr)] or tr) for tr in to_return]
162 to_cache.update(dict([(self.model.infer_cache_key({on_cache_query_attr: attr}), r._as_dict()) for attr, r in fetched.items()]))
165 for i, r in enumerate(to_return):
166 r.__dict__.update(with_aggregates[i])
170 cache.set_many(to_cache, 60 * 60)
173 for row in to_return:
174 if hasattr(row, 'leaf'):
177 row.reset_original_state()
180 def _get_query_hash(self):
182 return md5(unicode(self.query).encode("utf-8")).hexdigest()
185 return md5(self.query).hexdigest()
187 return md5(str(self.query)).hexdigest()
190 class CachedManager(models.Manager):
191 use_for_related_fields = True
193 def get_query_set(self):
194 return CachedQuerySet(self.model)
196 def get_or_create(self, *args, **kwargs):
198 return self.get(*args, **kwargs)
200 return super(CachedManager, self).get_or_create(*args, **kwargs)
203 class DenormalizedField(object):
204 def __init__(self, manager, *args, **kwargs):
205 self.manager = manager
206 self.filter = (args, kwargs)
208 def setup_class(self, cls, name):
209 dict_name = '_%s_dencache_' % name
212 val = inst.__dict__.get(dict_name, None)
215 val = getattr(inst, self.manager).filter(*self.filter[0], **self.filter[1]).count()
216 inst.__dict__[dict_name] = val
221 def reset_cache(inst):
222 inst.__dict__.pop(dict_name, None)
225 cls.add_to_class(name, property(getter))
226 cls.add_to_class("reset_%s_cache" % name, reset_cache)
229 class BaseMetaClass(models.Model.__metaclass__):
232 def __new__(cls, *args, **kwargs):
233 new_cls = super(BaseMetaClass, cls).__new__(cls, *args, **kwargs)
235 BaseMetaClass.to_denormalize.extend(
236 [(new_cls, name, field) for name, field in new_cls.__dict__.items() if isinstance(field, DenormalizedField)]
242 def setup_denormalizes(cls):
243 for new_cls, name, field in BaseMetaClass.to_denormalize:
244 field.setup_class(new_cls, name)
247 class BaseModel(models.Model):
248 __metaclass__ = BaseMetaClass
250 objects = CachedManager()
256 def __init__(self, *args, **kwargs):
257 super(BaseModel, self).__init__(*args, **kwargs)
258 self.reset_original_state(kwargs.keys())
260 def reset_original_state(self, reset_fields=None):
261 self._original_state = self._as_dict()
264 self._original_state.update(dict([(f, None) for f in reset_fields]))
266 def get_dirty_fields(self):
267 return [f.name for f in self._meta.fields if self._original_state[f.attname] != self.__dict__[f.attname]]
270 return dict([(name, getattr(self, name)) for name in
271 ([f.attname for f in self._meta.fields] + [k for k in self.__dict__.keys() if k.endswith('_dencache_')])
274 def _get_update_kwargs(self):
276 (f.name, getattr(self, f.name)) for f in self._meta.fields if self._original_state[f.attname] != self.__dict__[f.attname]
279 def save(self, full_save=False, *args, **kwargs):
280 put_back = [k for k, v in self.__dict__.items() if isinstance(v, models.expressions.ExpressionNode)]
282 if hasattr(self, '_state'):
283 self._state.db = 'default'
285 if self.id and not full_save:
286 self.__class__.objects.filter(id=self.id).update(**self._get_update_kwargs())
288 super(BaseModel, self).save()
292 self.__dict__.update(
293 self.__class__.objects.filter(id=self.id).values(*put_back)[0]
296 logging.error("Unable to read %s from %s" % (", ".join(put_back), self.__class__.__name__))
299 self.reset_original_state()
300 self._set_query_cache_invalidation_timestamp()
304 def _get_cache_query_invalidation_key(cls):
305 return cls._generate_cache_key("INV_TS")
308 def _set_query_cache_invalidation_timestamp(cls):
309 cache.set(cls._get_cache_query_invalidation_key(), datetime.datetime.now(), 60 * 60 * 24)
311 for base in filter(lambda c: issubclass(c, BaseModel) and (not c is BaseModel), cls.__bases__):
312 base._set_query_cache_invalidation_timestamp()
315 def _generate_cache_key(cls, key, group=None):
319 return '%s:%s:%s' % (settings.APP_URL, group, key)
322 return self._generate_cache_key(self.id)
325 def value_to_list_on_cache_query(cls):
329 def infer_cache_key(cls, querydict):
331 pk = [v for (k,v) in querydict.items() if k in ('pk', 'pk__exact', 'id', 'id__exact'
332 ) or k.endswith('_ptr__pk') or k.endswith('_ptr__id')][0]
334 cache_key = cls._generate_cache_key(pk)
336 if len(cache_key) > django_settings.CACHE_MAX_KEY_LENGTH:
337 cache_key = cache_key[:django_settings.CACHE_MAX_KEY_LENGTH]
344 cache.set(self.cache_key(), self._as_dict(), 60 * 60)
347 cache.delete(self.cache_key())
351 self._set_query_cache_invalidation_timestamp()
352 super(BaseModel, self).delete()
355 from user import User
356 from node import Node, NodeRevision, NodeManager
357 from action import Action