X-Git-Url: https://git.openstreetmap.org./osqa.git/blobdiff_plain/1a949f7c97dc2f34c135f5cdf088df2927d3d652..80e81e8ba3e132d6b51a0bb4c794d8f2c1f600d9:/forum/models/base.py?ds=inline diff --git a/forum/models/base.py b/forum/models/base.py index 63ab1eb..627888f 100644 --- a/forum/models/base.py +++ b/forum/models/base.py @@ -1,5 +1,5 @@ import datetime -import hashlib +import re from urllib import quote_plus, urlencode from django.db import models, IntegrityError, connection, transaction from django.utils.http import urlquote as django_urlquote @@ -14,18 +14,54 @@ from django.utils.translation import ugettext as _ from django.utils.safestring import mark_safe from django.contrib.sitemaps import ping_google import django.dispatch -from django.conf import settings -from forum import const +from forum import settings import logging -from forum.const import * + +class LazyQueryList(object): + def __init__(self, model, items): + self.model = model + self.items = items + + def __getitem__(self, k): + return self.model.objects.get(id=self.items[k]) + + def __iter__(self): + for id in self.items: + yield self.model.objects.get(id=id) + + def __len__(self): + return len(self.items) + +class CachedQuerySet(models.query.QuerySet): + def lazy(self): + if len(self.query.extra) == 0: + return LazyQueryList(self.model, list(self.values_list('id', flat=True))) + else: + return self + +from action import Action class CachedManager(models.Manager): use_for_related_fields = True + int_cache_re = re.compile('^_[\w_]+cache$') + + def get_query_set(self): + return CachedQuerySet(self.model) + + def cache_obj(self, obj): + int_cache_keys = [k for k in obj.__dict__.keys() if self.int_cache_re.match(k)] + d = obj.__dict__ + for k in int_cache_keys: + if not isinstance(obj.__dict__[k], Action): + del obj.__dict__[k] + + cache.set(self.model.cache_key(obj.id), obj, 60 * 60) def get(self, *args, **kwargs): try: - pk = [v for (k,v) in kwargs.items() if k in ('pk', 'pk__exact', 'id', 'id__exact') or k.endswith('_ptr__pk')][0] + pk = [v for (k,v) in kwargs.items() if k in ('pk', 'pk__exact', 'id', 'id__exact' + ) or k.endswith('_ptr__pk') or k.endswith('_ptr__id')][0] except: pk = None @@ -35,7 +71,9 @@ class CachedManager(models.Manager): if obj is None: obj = super(CachedManager, self).get(*args, **kwargs) - cache.set(key, obj, 60 * 60) + self.cache_obj(obj) + else: + d = obj.__dict__ return obj @@ -48,7 +86,53 @@ class CachedManager(models.Manager): return super(CachedManager, self).get_or_create(*args, **kwargs) +class DenormalizedField(object): + def __init__(self, manager, **kwargs): + self.manager = manager + self.filter = kwargs + + def setup_class(self, cls, name): + dict_name = '_%s_cache_' % name + + def getter(inst): + val = inst.__dict__.get(dict_name, None) + + if val is None: + val = getattr(inst, self.manager).filter(**self.filter).count() + inst.__dict__[dict_name] = val + inst.__class__.objects.cache_obj(inst) + + return val + + def reset_cache(inst): + inst.__dict__.pop(dict_name, None) + inst.__class__.objects.cache_obj(inst) + + cls.add_to_class(name, property(getter)) + cls.add_to_class("reset_%s_cache" % name, reset_cache) + + +class BaseMetaClass(models.Model.__metaclass__): + to_denormalize = [] + + def __new__(cls, *args, **kwargs): + new_cls = super(BaseMetaClass, cls).__new__(cls, *args, **kwargs) + + BaseMetaClass.to_denormalize.extend( + [(new_cls, name, field) for name, field in new_cls.__dict__.items() if isinstance(field, DenormalizedField)] + ) + + return new_cls + + @classmethod + def setup_denormalizes(cls): + for new_cls, name, field in BaseMetaClass.to_denormalize: + field.setup_class(new_cls, name) + + class BaseModel(models.Model): + __metaclass__ = BaseMetaClass + objects = CachedManager() class Meta: @@ -69,16 +153,34 @@ class BaseModel(models.Model): if self._original_state.get(k, missing) == missing or self._original_state[k] != v]) def save(self, *args, **kwargs): - super(BaseModel, self).save(*args, **kwargs) + put_back = [k for k, v in self.__dict__.items() if isinstance(v, models.expressions.ExpressionNode)] + super(BaseModel, self).save() + + if put_back: + try: + self.__dict__.update( + self.__class__.objects.filter(id=self.id).values(*put_back)[0] + ) + except: + logging.error("Unable to read %s from %s" % (", ".join(put_back), self.__class__.__name__)) + self.uncache() + self._original_state = dict(self.__dict__) - cache.set(self.cache_key(self.pk), self, 86400) + self.cache() - def delete(self): + def cache(self): + self.__class__.objects.cache_obj(self) + + def uncache(self): cache.delete(self.cache_key(self.pk)) + + def delete(self): + self.uncache() super(BaseModel, self).delete() class ActiveObjectManager(models.Manager): + use_for_related_fields = True def get_query_set(self): return super(ActiveObjectManager, self).get_query_set().filter(canceled=False) @@ -86,10 +188,7 @@ class UndeletedObjectManager(models.Manager): def get_query_set(self): return super(UndeletedObjectManager, self).get_query_set().filter(deleted=False) -class MetaContent(BaseModel): - """ - Base class for Vote, Comment and FlaggedItem - """ +class GenericContent(models.Model): content_type = models.ForeignKey(ContentType) object_id = models.PositiveIntegerField() content_object = generic.GenericForeignKey('content_type', 'object_id') @@ -98,6 +197,24 @@ class MetaContent(BaseModel): abstract = True app_label = 'forum' +class MetaContent(BaseModel): + node = models.ForeignKey('Node', null=True, related_name='%(class)ss') + + def __init__(self, *args, **kwargs): + if 'content_object' in kwargs: + kwargs['node'] = kwargs['content_object'] + del kwargs['content_object'] + + super (MetaContent, self).__init__(*args, **kwargs) + + @property + def content_object(self): + return self.node.leaf + + class Meta: + abstract = True + app_label = 'forum' + from user import User class UserContent(models.Model): @@ -108,8 +225,6 @@ class UserContent(models.Model): app_label = 'forum' -marked_deleted = django.dispatch.Signal(providing_args=["instance", "deleted_by"]) - class DeletableContent(models.Model): deleted = models.BooleanField(default=False) deleted_at = models.DateTimeField(null=True, blank=True) @@ -127,7 +242,6 @@ class DeletableContent(models.Model): self.deleted_at = datetime.datetime.now() self.deleted_by = user self.save() - marked_deleted.send(sender=self.__class__, instance=self, deleted_by=user) return True else: return False @@ -140,95 +254,28 @@ class DeletableContent(models.Model): else: return False +mark_canceled = django.dispatch.Signal(providing_args=['instance']) -class ContentRevision(models.Model): - """ - Base class for QuestionRevision and AnswerRevision - """ - revision = models.PositiveIntegerField() - author = models.ForeignKey(User, related_name='%(class)ss') - revised_at = models.DateTimeField() - summary = models.CharField(max_length=300, blank=True) - text = models.TextField() - - class Meta: - abstract = True - app_label = 'forum' - +class CancelableContent(models.Model): + canceled = models.BooleanField(default=False) -class AnonymousContent(models.Model): - """ - Base class for AnonymousQuestion and AnonymousAnswer - """ - session_key = models.CharField(max_length=40) #session id for anonymous questions - wiki = models.BooleanField(default=False) - added_at = models.DateTimeField(default=datetime.datetime.now) - ip_addr = models.IPAddressField(max_length=21) #allow high port numbers - author = models.ForeignKey(User,null=True) - text = models.TextField() - summary = models.CharField(max_length=180) + def cancel(self): + if not self.canceled: + self.canceled = True + self.save() + mark_canceled.send(sender=self.__class__, instance=self) + return True + + return False class Meta: abstract = True app_label = 'forum' -from meta import Comment, Vote, FlaggedItem -from user import activity_record - -class Content(BaseModel, DeletableContent): - """ - Base class for Question and Answer - """ - author = models.ForeignKey(User, related_name='%(class)ss') - added_at = models.DateTimeField(default=datetime.datetime.now) - - wiki = models.BooleanField(default=False) - wikified_at = models.DateTimeField(null=True, blank=True) - - #locked = models.BooleanField(default=False) - #locked_by = models.ForeignKey(User, null=True, blank=True, related_name='locked_%(class)ss') - #locked_at = models.DateTimeField(null=True, blank=True) +from node import Node, NodeRevision, NodeManager - score = models.IntegerField(default=0) - vote_up_count = models.IntegerField(default=0) - vote_down_count = models.IntegerField(default=0) - comment_count = models.PositiveIntegerField(default=0) - offensive_flag_count = models.SmallIntegerField(default=0) - last_edited_at = models.DateTimeField(null=True, blank=True) - last_edited_by = models.ForeignKey(User, null=True, blank=True, related_name='last_edited_%(class)ss') - html = models.TextField() - comments = generic.GenericRelation(Comment) - votes = generic.GenericRelation(Vote) - flagged_items = generic.GenericRelation(FlaggedItem) - class Meta: - abstract = True - app_label = 'forum' - - def save(self, *args, **kwargs): - self.__dict__['score'] = self.__dict__['vote_up_count'] - self.__dict__['vote_down_count'] - super(Content,self).save(*args, **kwargs) - - try: - ping_google() - except Exception: - logging.debug('problem pinging google did you register you sitemap with google?') - - - def post_get_last_update_info(self): - when = self.added_at - who = self.author - if self.last_edited_at and self.last_edited_at > when: - when = self.last_edited_at - who = self.last_edited_by - comments = self.comments.all() - if len(comments) > 0: - for c in comments: - if c.added_at > when: - when = c.added_at - who = c.user - return when, who \ No newline at end of file