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):
181 return md5(unicode(self.query).encode("utf-8")).hexdigest()
185 class CachedManager(models.Manager):
186 use_for_related_fields = True
188 def get_query_set(self):
189 return CachedQuerySet(self.model)
191 def get_or_create(self, *args, **kwargs):
193 return self.get(*args, **kwargs)
195 return super(CachedManager, self).get_or_create(*args, **kwargs)
198 class DenormalizedField(object):
199 def __init__(self, manager, *args, **kwargs):
200 self.manager = manager
201 self.filter = (args, kwargs)
203 def setup_class(self, cls, name):
204 dict_name = '_%s_dencache_' % name
207 val = inst.__dict__.get(dict_name, None)
210 val = getattr(inst, self.manager).filter(*self.filter[0], **self.filter[1]).count()
211 inst.__dict__[dict_name] = val
216 def reset_cache(inst):
217 inst.__dict__.pop(dict_name, None)
220 cls.add_to_class(name, property(getter))
221 cls.add_to_class("reset_%s_cache" % name, reset_cache)
224 class BaseMetaClass(models.Model.__metaclass__):
227 def __new__(cls, *args, **kwargs):
228 new_cls = super(BaseMetaClass, cls).__new__(cls, *args, **kwargs)
230 BaseMetaClass.to_denormalize.extend(
231 [(new_cls, name, field) for name, field in new_cls.__dict__.items() if isinstance(field, DenormalizedField)]
237 def setup_denormalizes(cls):
238 for new_cls, name, field in BaseMetaClass.to_denormalize:
239 field.setup_class(new_cls, name)
242 class BaseModel(models.Model):
243 __metaclass__ = BaseMetaClass
245 objects = CachedManager()
251 def __init__(self, *args, **kwargs):
252 super(BaseModel, self).__init__(*args, **kwargs)
253 self.reset_original_state(kwargs.keys())
255 def reset_original_state(self, reset_fields=None):
256 self._original_state = self._as_dict()
259 self._original_state.update(dict([(f, None) for f in reset_fields]))
261 def get_dirty_fields(self):
262 return [f.name for f in self._meta.fields if self._original_state[f.attname] != self.__dict__[f.attname]]
265 return dict([(name, getattr(self, name)) for name in
266 ([f.attname for f in self._meta.fields] + [k for k in self.__dict__.keys() if k.endswith('_dencache_')])
269 def _get_update_kwargs(self):
271 (f.name, getattr(self, f.name)) for f in self._meta.fields if self._original_state[f.attname] != self.__dict__[f.attname]
274 def save(self, full_save=False, *args, **kwargs):
275 put_back = [k for k, v in self.__dict__.items() if isinstance(v, models.expressions.ExpressionNode)]
277 if hasattr(self, '_state'):
278 self._state.db = 'default'
280 if self.id and not full_save:
281 self.__class__.objects.filter(id=self.id).update(**self._get_update_kwargs())
283 super(BaseModel, self).save()
287 self.__dict__.update(
288 self.__class__.objects.filter(id=self.id).values(*put_back)[0]
291 logging.error("Unable to read %s from %s" % (", ".join(put_back), self.__class__.__name__))
294 self.reset_original_state()
295 self._set_query_cache_invalidation_timestamp()
299 def _get_cache_query_invalidation_key(cls):
300 return cls._generate_cache_key("INV_TS")
303 def _set_query_cache_invalidation_timestamp(cls):
304 cache.set(cls._get_cache_query_invalidation_key(), datetime.datetime.now(), 60 * 60 * 24)
306 for base in filter(lambda c: issubclass(c, BaseModel) and (not c is BaseModel), cls.__bases__):
307 base._set_query_cache_invalidation_timestamp()
310 def _generate_cache_key(cls, key, group=None):
314 return '%s:%s:%s' % (settings.APP_URL, group, key)
317 return self._generate_cache_key(self.id)
320 def value_to_list_on_cache_query(cls):
324 def infer_cache_key(cls, querydict):
326 pk = [v for (k,v) in querydict.items() if k in ('pk', 'pk__exact', 'id', 'id__exact'
327 ) or k.endswith('_ptr__pk') or k.endswith('_ptr__id')][0]
329 cache_key = cls._generate_cache_key(pk)
331 if len(cache_key) > django_settings.CACHE_MAX_KEY_LENGTH:
332 cache_key = cache_key[:django_settings.CACHE_MAX_KEY_LENGTH]
339 cache.set(self.cache_key(), self._as_dict(), 60 * 60)
342 cache.delete(self.cache_key())
346 self._set_query_cache_invalidation_timestamp()
347 super(BaseModel, self).delete()
350 from user import User
351 from node import Node, NodeRevision, NodeManager
352 from action import Action