]> git.openstreetmap.org Git - osqa.git/commitdiff
more improvements in cache and denormalized data handling
authorhernani <hernani@0cfe37f9-358a-4d5e-be75-b63607b5c754>
Mon, 19 Apr 2010 23:07:14 +0000 (23:07 +0000)
committerhernani <hernani@0cfe37f9-358a-4d5e-be75-b63607b5c754>
Mon, 19 Apr 2010 23:07:14 +0000 (23:07 +0000)
git-svn-id: http://svn.osqa.net/svnroot/osqa/trunk@58 0cfe37f9-358a-4d5e-be75-b63607b5c754

forum/badges/base.py
forum/models/__init__.py
forum/models/base.py
forum/models/node.py
forum/models/user.py
forum/settings/__init__.py
forum_modules/default_badges/badges.py

index c2dbe30763c72d0a7f9fe9ccaf30a30caa688bec..6e5d14439a2db9c741f1493a4a416f698d2f21a8 100644 (file)
@@ -5,6 +5,7 @@ from django.contrib.contenttypes.models import ContentType
 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
@@ -59,12 +60,11 @@ class AbstractBadge(object):
 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):
index df42a5b802230d7dcf6868920509aa9f57db3d0e..5b6414afb5534bc9e549bb9a4d355174c0175d4e 100644 (file)
@@ -10,7 +10,7 @@ from utils import KeyValue
 \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
index 08ccb44dbbb1fe424de70de5b1f70851f8bc7bf8..41f448191d57fa14da285e6d7c2f231eb2d21e5c 100644 (file)
@@ -1,4 +1,5 @@
 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
@@ -21,6 +22,15 @@ from forum.const import *
 
 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:
@@ -35,7 +45,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
         
@@ -47,6 +59,17 @@ class CachedManager(models.Manager):
         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()
@@ -69,9 +92,32 @@ class BaseModel(models.Model):
                  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))
@@ -87,10 +133,7 @@ class UndeletedObjectManager(models.Manager):
     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')
index 9e734d26df9e641a14c0d76bea8591bacfbb79a1..00cbbd0d71cac3a719feed84da895788f06157e1 100644 (file)
@@ -90,12 +90,12 @@ class Node(BaseModel, NodeContent, DeletableContent):
 \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
@@ -205,7 +205,7 @@ class Node(BaseModel, NodeContent, DeletableContent):
         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
index 82cfe3aec3523d6bb177514d2a7a60e8f9e8cf10..eaa1516b8fd0509f5b901d39e2967a5348158efe 100644 (file)
@@ -80,11 +80,11 @@ class User(BaseModel, DjangoUser):
     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
index 2446496edf195c94707f70c01b9896bd9dc0bc43..5a8b308a4bc85c79b2f171272b6948567dd24fb5 100644 (file)
@@ -5,7 +5,7 @@ from forms import ImageFormWidget
 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 *
index 057d2ceef22682e1f5a98f7434dd00c5f46481bc..eed611c066f732e5dc8ad33e62f00c462f421eea 100644 (file)
@@ -7,6 +7,7 @@ from forum.badges.base import PostCountableAbstractBadge, ActivityAbstractBadge,
         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
@@ -237,22 +238,21 @@ class TeacherBadge(CountableAbstractBadge):
 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):