from django.db.models.signals import post_save
from forum.models.user import activity_record
+from forum.models.base import denorm_update
from forum.models import Badge, Award, Activity, Node
import logging
class CountableAbstractBadge(AbstractBadge):
def __init__(self, model, field, expected_value, handler):
- def wrapper(instance, **kwargs):
- dirty_fields = instance.get_dirty_fields()
- if field in dirty_fields and instance.__dict__[field] == expected_value:
+ def wrapper(instance, sfield, old, new, **kwargs):
+ if sfield == field and (new == expected_value) or (old < expected_value and new > expected_value):
handler(instance=instance)
- post_save.connect(wrapper, sender=model, weak=False)
+ denorm_update.connect(wrapper, sender=model, weak=False)
class PostCountableAbstractBadge(CountableAbstractBadge):
def __init__(self, model, field, expected_value):
\r
try:\r
from south.modelsinspector import add_introspection_rules\r
- add_introspection_rules([], [r"^forum\.models\.utils\.\w+"])\r
+ add_introspection_rules([], [r"^forum\.models\.\w+\.\w+"])\r
except:\r
pass\r
\r
import datetime
+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
class CachedManager(models.Manager):
use_for_related_fields = True
+ int_cache_re = re.compile('^_[\w_]+cache$')
+
+ def cache_obj(self, obj):
+ int_cache_keys = [k for k in obj.__dict__.keys() if self.int_cache_re.match(k)]
+
+ for k in int_cache_keys:
+ del obj.__dict__[k]
+
+ cache.set(self.model.cache_key(obj.id), obj, 60 * 60)
def get(self, *args, **kwargs):
try:
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
except:
return super(CachedManager, self).get_or_create(*args, **kwargs)
+denorm_update = django.dispatch.Signal(providing_args=["instance", "field", "old", "new"])
+
+class DenormalizedField(models.PositiveIntegerField):
+ __metaclass__ = models.SubfieldBase
+
+ def contribute_to_class(self, cls, name):
+ super (DenormalizedField, self).contribute_to_class(cls, name)
+ if not hasattr(cls, '_denormalizad_fields'):
+ cls._denormalizad_fields = []
+
+ cls._denormalizad_fields.append(name)
class BaseModel(models.Model):
objects = CachedManager()
if self._original_state.get(k, missing) == missing or self._original_state[k] != v])
def save(self, *args, **kwargs):
+ put_back = None
+
+ if hasattr(self.__class__, '_denormalizad_fields'):
+ dirty = self.get_dirty_fields()
+ put_back = [f for f in self.__class__._denormalizad_fields if f in dirty]
+
+ if put_back:
+ for n in put_back:
+ self.__dict__[n] = models.F(n) + (self.__dict__[n] - dirty[n])
+
super(BaseModel, self).save(*args, **kwargs)
+
+ if put_back:
+ try:
+ self.__dict__.update(
+ self.__class__.objects.filter(id=self.id).values(*put_back)[0]
+ )
+ for f in put_back:
+ denorm_update.send(sender=self.__class__, instance=self, field=f,
+ old=self._original_state[f], new=self.__dict__[f])
+ except:
+ #todo: log this properly
+ pass
+
self._original_state = dict(self.__dict__)
- cache.set(self.cache_key(self.pk), self, 86400)
+ self.__class__.objects.cache_obj(self)
def delete(self):
cache.delete(self.cache_key(self.pk))
def get_query_set(self):
return super(UndeletedObjectManager, self).get_query_set().filter(deleted=False)
-class GenericContent(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')
\r
tags = models.ManyToManyField('Tag', related_name='%(class)ss')\r
\r
- score = models.IntegerField(default=0)\r
- vote_up_count = models.IntegerField(default=0)\r
+ score = DenormalizedField(default=0)\r
+ vote_up_count = DenormalizedField(default=0)\r
vote_down_count = models.IntegerField(default=0)\r
\r
- comment_count = models.PositiveIntegerField(default=0)\r
- offensive_flag_count = models.SmallIntegerField(default=0)\r
+ comment_count = DenormalizedField(default=0)\r
+ offensive_flag_count = DenormalizedField(default=0)\r
\r
last_edited_at = models.DateTimeField(null=True, blank=True)\r
last_edited_by = models.ForeignKey(User, null=True, blank=True, related_name='last_edited_%(class)ss')\r
app_label = 'forum'\r
\r
\r
-class NodeRevision(NodeContent):\r
+class NodeRevision(BaseModel, NodeContent):\r
node = models.ForeignKey(Node, related_name='revisions')\r
summary = models.CharField(max_length=300)\r
revision = models.PositiveIntegerField()\r
is_approved = models.BooleanField(default=False)\r
email_isvalid = models.BooleanField(default=False)\r
email_key = models.CharField(max_length=32, null=True)\r
- reputation = models.PositiveIntegerField(default=1)\r
+ reputation = DenormalizedField(default=1)\r
\r
- gold = models.SmallIntegerField(default=0)\r
- silver = models.SmallIntegerField(default=0)\r
- bronze = models.SmallIntegerField(default=0)\r
+ gold = DenormalizedField(default=0)\r
+ silver = DenormalizedField(default=0)\r
+ bronze = DenormalizedField(default=0)\r
\r
questions_per_page = models.SmallIntegerField(choices=QUESTIONS_PER_PAGE_CHOICES, default=10)\r
hide_ignored_questions = models.BooleanField(default=False)\r
from django.forms.widgets import Textarea
from django.utils.translation import ugettext_lazy as _
-INTERNAL_VERSION = Setting('INTERNAL_VERSION', "56")
+INTERNAL_VERSION = Setting('INTERNAL_VERSION', "58")
SETTINGS_PACK = Setting('SETTINGS_PACK', "default")
from basic import *
ActivityCountAbstractBadge, CountableAbstractBadge, AbstractBadge, NodeCountableAbstractBadge
from forum.models import Node, Question, Answer, Activity, Tag
from forum.models.user import activity_record
+from forum.models.base import denorm_update
from forum import const
import settings
class AcceptedAndVotedAnswerAbstractBadge(AbstractBadge):
def __init__(self, up_votes, handler):
def wrapper(sender, instance, **kwargs):
- if sender is Node:
- if not (instance.node_type == "answer" and "vote_up_count" in instance.get_dirty_fields()):
+ if sender is Answer:
+ if (not kwargs['field'] == "score") or (kwargs['new'] < kwargs['old']):
return
answer = instance.leaf
+ vote_count = kwargs['new']
else:
answer = instance.content_object
+ vote_count = answer.vote_up_count
- accepted = answer.accepted
- vote_count = answer.vote_up_count
-
- if accepted and vote_count == up_votes:
+ if answer.accepted and vote_count == up_votes:
handler(answer)
activity_record.connect(wrapper, sender=const.TYPE_ACTIVITY_MARK_ANSWER, weak=False)
- post_save.connect(wrapper, sender=Node, weak=False)
+ denorm_update.connect(wrapper, sender=Node, weak=False)
class EnlightenedBadge(AcceptedAndVotedAnswerAbstractBadge):