]> git.openstreetmap.org Git - osqa.git/blob - forum/models/base.py
Some changes necessary to make cache work on Django 1.2.
[osqa.git] / forum / models / base.py
1 import datetime
2 import re
3 from urllib import quote_plus, urlencode
4 from django.db import models, IntegrityError, connection, transaction
5 from django.utils.http import urlquote  as django_urlquote
6 from django.utils.html import strip_tags
7 from django.core.urlresolvers import reverse
8 from django.contrib.contenttypes import generic
9 from django.contrib.contenttypes.models import ContentType
10 from django.core.cache import cache
11 from django.template.defaultfilters import slugify
12 from django.db.models.signals import post_delete, post_save, pre_save, pre_delete
13 from django.utils.translation import ugettext as _
14 from django.utils.safestring import mark_safe
15 from django.contrib.sitemaps import ping_google
16 import django.dispatch
17 from forum import settings
18 import logging
19
20
21 class LazyQueryList(object):
22     def __init__(self, model, items):
23         self.model = model
24         self.items = items
25
26     def __getitem__(self, k):
27         return self.model.objects.get(id=self.items[k])
28
29     def __iter__(self):
30         for id in self.items:
31             yield self.model.objects.get(id=id)
32
33     def __len__(self):
34         return len(self.items)
35
36 class CachedQuerySet(models.query.QuerySet):
37     def lazy(self):
38         if len(self.query.extra) == 0:
39             return LazyQueryList(self.model, list(self.values_list('id', flat=True)))
40         else:
41             return self
42
43     def get(self, *args, **kwargs):
44         try:
45             pk = [v for (k,v) in kwargs.items() if k in ('pk', 'pk__exact', 'id', 'id__exact'
46                             ) or k.endswith('_ptr__pk') or k.endswith('_ptr__id')][0]
47         except:
48             pk = None
49
50         if pk is not None:
51             key = self.model.cache_key(pk)
52             obj = cache.get(key)
53
54             if obj is None:
55                 obj = super(CachedQuerySet, self).get(*args, **kwargs)
56                 obj.__class__.objects.cache_obj(obj)
57             else:
58                 d = obj.__dict__
59
60             return obj
61
62         return super(CachedQuerySet, self).get(*args, **kwargs) 
63
64 from action import Action
65
66 class CachedManager(models.Manager):
67     use_for_related_fields = True
68     int_cache_re = re.compile('^_[\w_]+cache$')
69
70     def get_query_set(self):
71         return CachedQuerySet(self.model)
72
73     def cache_obj(self, obj):
74         int_cache_keys = [k for k in obj.__dict__.keys() if self.int_cache_re.match(k)]
75         d = obj.__dict__
76         for k in int_cache_keys:
77             if not isinstance(obj.__dict__[k], Action):
78                 del obj.__dict__[k]
79
80         cache.set(self.model.cache_key(obj.id), obj, 60 * 60)
81
82     def get_or_create(self, *args, **kwargs):
83         try:
84             return self.get(*args, **kwargs)
85         except:
86             return super(CachedManager, self).get_or_create(*args, **kwargs)
87
88
89 class DenormalizedField(object):
90     def __init__(self, manager, **kwargs):
91         self.manager = manager
92         self.filter = kwargs
93
94     def setup_class(self, cls, name):
95         dict_name = '_%s_cache_' % name
96
97         def getter(inst):
98             val = inst.__dict__.get(dict_name, None)
99
100             if val is None:
101                 val = getattr(inst, self.manager).filter(**self.filter).count()
102                 inst.__dict__[dict_name] = val
103                 inst.__class__.objects.cache_obj(inst)
104
105             return val
106
107         def reset_cache(inst):
108             inst.__dict__.pop(dict_name, None)
109             inst.__class__.objects.cache_obj(inst)
110
111         cls.add_to_class(name, property(getter))
112         cls.add_to_class("reset_%s_cache" % name, reset_cache)
113
114
115 class BaseMetaClass(models.Model.__metaclass__):
116     to_denormalize = []
117
118     def __new__(cls, *args, **kwargs):
119         new_cls = super(BaseMetaClass, cls).__new__(cls, *args, **kwargs)
120
121         BaseMetaClass.to_denormalize.extend(
122             [(new_cls, name, field) for name, field in new_cls.__dict__.items() if isinstance(field, DenormalizedField)]
123         )
124
125         return new_cls
126
127     @classmethod
128     def setup_denormalizes(cls):
129         for new_cls, name, field in BaseMetaClass.to_denormalize:
130             field.setup_class(new_cls, name)
131
132
133 class BaseModel(models.Model):
134     __metaclass__ = BaseMetaClass
135
136     objects = CachedManager()
137
138     class Meta:
139         abstract = True
140         app_label = 'forum'
141
142     def __init__(self, *args, **kwargs):
143         super(BaseModel, self).__init__(*args, **kwargs)
144         self._original_state = dict([(k, v) for k,v in self.__dict__.items() if not k in kwargs])
145
146     @classmethod
147     def cache_key(cls, pk):
148         return '%s.%s:%s' % (settings.APP_URL, cls.__name__, pk)
149
150     def get_dirty_fields(self):
151         missing = object()
152         return dict([(k, self._original_state.get(k, None)) for k,v in self.__dict__.items()
153                  if self._original_state.get(k, missing) == missing or self._original_state[k] != v])
154
155     def save(self, *args, **kwargs):
156         put_back = [k for k, v in self.__dict__.items() if isinstance(v, models.expressions.ExpressionNode)]
157         super(BaseModel, self).save()
158
159         if put_back:
160             try:
161                 self.__dict__.update(
162                     self.__class__.objects.filter(id=self.id).values(*put_back)[0]
163                 )
164             except:
165                 logging.error("Unable to read %s from %s" % (", ".join(put_back), self.__class__.__name__))
166                 self.uncache()
167
168         self._original_state = dict(self.__dict__)
169         self.cache()
170
171     def cache(self):
172         self.__class__.objects.cache_obj(self)
173
174     def uncache(self):
175         cache.delete(self.cache_key(self.pk))
176
177     def delete(self):
178         self.uncache()
179         super(BaseModel, self).delete()
180
181
182 class ActiveObjectManager(models.Manager):
183     use_for_related_fields = True
184     def get_query_set(self):
185         return super(ActiveObjectManager, self).get_query_set().filter(canceled=False)
186
187 class UndeletedObjectManager(models.Manager):
188     def get_query_set(self):
189         return super(UndeletedObjectManager, self).get_query_set().filter(deleted=False)
190
191 class GenericContent(models.Model):
192     content_type   = models.ForeignKey(ContentType)
193     object_id      = models.PositiveIntegerField()
194     content_object = generic.GenericForeignKey('content_type', 'object_id')
195
196     class Meta:
197         abstract = True
198         app_label = 'forum'
199
200 class MetaContent(BaseModel):
201     node = models.ForeignKey('Node', null=True, related_name='%(class)ss')
202
203     def __init__(self, *args, **kwargs):
204         if 'content_object' in kwargs:
205             kwargs['node'] = kwargs['content_object']
206             del kwargs['content_object']
207
208         super (MetaContent, self).__init__(*args, **kwargs)
209     
210     @property
211     def content_object(self):
212         return self.node.leaf
213
214     class Meta:
215         abstract = True
216         app_label = 'forum'
217
218 from user import User
219
220 class UserContent(models.Model):
221     user = models.ForeignKey(User, related_name='%(class)ss')
222
223     class Meta:
224         abstract = True
225         app_label = 'forum'
226
227
228 class DeletableContent(models.Model):
229     deleted     = models.BooleanField(default=False)
230     deleted_at  = models.DateTimeField(null=True, blank=True)
231     deleted_by  = models.ForeignKey(User, null=True, blank=True, related_name='deleted_%(class)ss')
232
233     active = UndeletedObjectManager()
234
235     class Meta:
236         abstract = True
237         app_label = 'forum'
238
239     def mark_deleted(self, user):
240         if not self.deleted:
241             self.deleted = True
242             self.deleted_at = datetime.datetime.now()
243             self.deleted_by = user
244             self.save()
245             return True
246         else:
247             return False
248
249     def unmark_deleted(self):
250         if self.deleted:
251             self.deleted = False
252             self.save()
253             return True
254         else:
255             return False
256
257 mark_canceled = django.dispatch.Signal(providing_args=['instance'])
258
259 class CancelableContent(models.Model):
260     canceled = models.BooleanField(default=False)
261
262     def cancel(self):
263         if not self.canceled:
264             self.canceled = True
265             self.save()
266             mark_canceled.send(sender=self.__class__, instance=self)
267             return True
268             
269         return False
270
271     class Meta:
272         abstract = True
273         app_label = 'forum'
274
275
276 from node import Node, NodeRevision, NodeManager
277
278
279
280
281