]> git.openstreetmap.org Git - osqa.git/commitdiff
Reintegrate merge cacheimp -> trunk.
authorjordan <jordan@0cfe37f9-358a-4d5e-be75-b63607b5c754>
Sat, 2 Apr 2011 12:20:13 +0000 (12:20 +0000)
committerjordan <jordan@0cfe37f9-358a-4d5e-be75-b63607b5c754>
Sat, 2 Apr 2011 12:20:13 +0000 (12:20 +0000)
git-svn-id: http://svn.osqa.net/svnroot/osqa/trunk@924 0cfe37f9-358a-4d5e-be75-b63607b5c754

35 files changed:
forum/actions/user.py
forum/badges/base.py
forum/markdownext/mdx_auto_linker.py [new file with mode: 0644]
forum/markdownext/mdx_urlize.py [deleted file]
forum/models/action.py
forum/models/base.py
forum/models/meta.py
forum/models/node.py
forum/models/question.py
forum/models/tag.py
forum/models/user.py
forum/models/utils.py
forum/modules/ui.py
forum/modules/ui_objects.py
forum/registry.py
forum/settings/sidebar.py
forum/skins/default/media/js/osqa.ask.js
forum/skins/default/media/style/user.css
forum/skins/default/templates/paginator/page_numbers.html
forum/skins/default/templates/user.html
forum/skins/default/templates/users/questions.html
forum/startup.py
forum/templatetags/user_tags.py
forum/utils/pagination.py
forum/utils/userlinking.py
forum/views/commands.py
forum/views/meta.py
forum_modules/exporter/exporter.py
forum_modules/mysqlfulltext/__init__.py [new file with mode: 0644]
forum_modules/mysqlfulltext/fts_install.sql [new file with mode: 0644]
forum_modules/mysqlfulltext/models.py [new file with mode: 0644]
forum_modules/mysqlfulltext/settings.py [new file with mode: 0644]
forum_modules/mysqlfulltext/startup.py [new file with mode: 0644]
forum_modules/openidauth/consumer.py
forum_modules/sximporter/importer.py

index f9a9913918a3d9899019808098c4359ad08d83aa..15ad5ee9e3218e8d603d9ced57fd012e0e1a4f06 100644 (file)
@@ -1,4 +1,4 @@
-from django.utils.translation import ugettext as _
+from django.utils.translation import ungettext, ugettext as _
 from django.db.models import F
 from forum.models.action import ActionProxy
 from forum.models import Award, Badge, ValidationHash, User
@@ -98,15 +98,15 @@ class AwardPointsAction(ActionProxy):
 
     def repute_users(self):
         self.repute(self._affected, self._value)
+        self.repute(self.user, -self._value)
 
-        if self._value > 0:
-            self._affected.message_set.create(
-                    message=_("Congratulations, you have been awarded an extra %s reputation points.") % self._value +
-                    '<br />%s' % self.extra.get('message', _('Thank you')))
-        else:
-            self._affected.message_set.create(
-                    message=_("You gave %s reputation points.") % self._value +
-                    '<br />%s' % self.extra.get('message', ''))
+
+        self._affected.message_set.create(
+                message=_("Congratulations, you have been awarded an extra %(points)s reputation %(points_label)s on <a href=\"%(answer_url)s\">this</a> answer.") % {
+                        'points': self._value,
+                        'points_label': ungettext('point', 'points', self._value),
+                        'answer_url': self.node.get_absolute_url()
+                    })
 
     def describe(self, viewer=None):
         value = self.extra.get('value', _('unknown'))
index 79fcb523c73d5b1d1ab7c9a25becedc5cac40ca0..c78a925e80685242a3b903752c1a63e789bea651 100644 (file)
@@ -20,10 +20,12 @@ class BadgesMeta(type):
 
         if not dic.get('abstract', False):
             if not name in installed:
-                badge.ondb = Badge(cls=name, type=dic.get('type', Badge.BRONZE))
-                badge.ondb.save()
+                ondb = Badge(cls=name, type=dic.get('type', Badge.BRONZE))
+                ondb.save()
             else:
-                badge.ondb = installed[name]
+                ondb = installed[name]
+
+            badge.ondb = ondb.id
 
             inst = badge()
 
@@ -36,9 +38,8 @@ class BadgesMeta(type):
             for action in badge.listen_to:
                 action.hook(hook)
 
-            BadgesMeta.by_class[name] = badge
-            badge.ondb.__dict__['_class'] = inst
-            BadgesMeta.by_id[badge.ondb.id] = badge
+            BadgesMeta.by_class[name] = inst
+            BadgesMeta.by_id[ondb.id] = inst
 
         return badge
 
@@ -58,18 +59,19 @@ class AbstractBadge(object):
 
     @classmethod
     def award(cls, user, action, once=False):
+        db_object = Badge.objects.get(id=cls.ondb)
         try:
             if once:
                 node = None
-                awarded = AwardAction.get_for(user, cls.ondb)
+                awarded = AwardAction.get_for(user, db_object)
             else:
                 node = action.node
-                awarded = AwardAction.get_for(user, cls.ondb, node)
+                awarded = AwardAction.get_for(user, db_object, node)
 
             trigger = isinstance(action, Action) and action or None
 
             if not awarded:
-                AwardAction(user=user, node=node).save(data=dict(badge=cls.ondb, trigger=trigger))
+                AwardAction(user=user, node=node).save(data=dict(badge=db_object, trigger=trigger))
         except MultipleObjectsReturned:
             if node:
                 logging.error('Found multiple %s badges awarded for user %s (%s)' % (self.name, user.username, user.id))
diff --git a/forum/markdownext/mdx_auto_linker.py b/forum/markdownext/mdx_auto_linker.py
new file mode 100644 (file)
index 0000000..9d77c38
--- /dev/null
@@ -0,0 +1,105 @@
+import markdown
+import re, socket
+
+TLDS = ('gw', 'gu', 'gt', 'gs', 'gr', 'gq', 'gp', 'gy', 'gg', 'gf', 'ge', 'gd', 'ga', 'edu', 'va', 'gn', 'gl', 'gi',
+        'gh', 'iq', 'lb', 'lc', 'la', 'tv', 'tw', 'tt', 'arpa', 'lk', 'li', 'lv', 'to', 'lt', 'lr', 'ls', 'th', 'tf',
+        'su', 'td', 'aspx', 'tc', 'ly', 'do', 'coop', 'dj', 'dk', 'de', 'vc', 'me', 'dz', 'uy', 'yu', 'vg', 'ro',
+        'vu', 'qa', 'ml', 'us', 'zm', 'cfm', 'tel', 'ee', 'htm', 'za', 'ec', 'bg', 'uk', 'eu', 'et', 'zw',
+        'es', 'er', 'ru', 'rw', 'rs', 'asia', 're', 'it', 'net', 'gov', 'tz', 'bd', 'be', 'bf', 'asp', 'jobs', 'ba',
+        'bb', 'bm', 'bn', 'bo', 'bh', 'bi', 'bj', 'bt', 'jm', 'sb', 'bw', 'ws', 'br', 'bs', 'je', 'tg', 'by', 'bz',
+        'tn', 'om', 'ua', 'jo', 'pdf', 'mz', 'com', 'ck', 'ci', 'ch', 'co', 'cn', 'cm', 'cl', 'cc', 'tr', 'ca', 'cg',
+        'cf', 'cd', 'cz', 'cy', 'cx', 'org', 'cr', 'txt', 'cv', 'cu', 've', 'pr', 'ps', 'fk', 'pw', 'pt', 'museum',
+        'py', 'tl', 'int', 'pa', 'pf', 'pg', 'pe', 'pk', 'ph', 'pn', 'eg', 'pl', 'tk', 'hr', 'aero', 'ht', 'hu', 'hk',
+        'hn', 'vn', 'hm', 'jp', 'info', 'md', 'mg', 'ma', 'mc', 'uz', 'mm', 'local', 'mo', 'mn', 'mh', 'mk', 'cat',
+        'mu', 'mt', 'mw', 'mv', 'mq', 'ms', 'mr', 'im', 'ug', 'my', 'mx', 'il', 'pro', 'ac', 'sa', 'ae', 'ad', 'ag',
+        'af', 'ai', 'vi', 'is', 'ir', 'am', 'al', 'ao', 'an', 'aq', 'as', 'ar', 'au', 'at', 'aw', 'in', 'ax', 'az',
+        'ie', 'id', 'sr', 'nl', 'mil', 'no', 'na', 'travel', 'nc', 'ne', 'nf', 'ng', 'nz', 'dm', 'np',
+        'so', 'nr', 'nu', 'fr', 'io', 'ni', 'ye', 'sv', 'jsp', 'kz', 'fi', 'fj', 'php', 'fm', 'fo', 'tj', 'sz', 'sy',
+        'mobi', 'kg', 'ke', 'doc', 'ki', 'kh', 'kn', 'km', 'st', 'sk', 'kr', 'si', 'kp', 'kw', 'sn', 'sm', 'sl', 'sc',
+        'biz', 'ky', 'sg', 'se', 'sd')
+
+AUTO_LINK_RE = re.compile(r"""
+    (?P<ws>.?\s*)
+    (?P<url>
+        (?P<format1>
+            ((?P<protocol1>[a-z][a-z]+)://)?
+            (?P<domain1>\w(?:[\w-]*\w)?\.\w(?:[\w-]*\w)?(?:\.\w(?:[\w-]*\w)?)*)
+        ) | (?P<format2>
+            ((?P<protocol2>[a-z][a-z]+)://)
+            (?P<domain2>\w(?:[\w-]*\w)?(?:\.\w(?:[\w-]*\w)?)*)
+        )
+        (?P<port>:\d+)?
+        (?P<uri>/[^\s<]*)?
+    )
+
+""", re.X | re.I)
+
+def is_ip(addr):
+    try:
+        socket.inet_aton(addr)
+        return True
+    except:
+        return False
+
+def replacer(m):
+
+    ws = m.group('ws')
+
+    if ws and ws[0] in ("'", '"'):
+        return m.group(0)
+
+    elif not ws:
+        ws = ''
+
+    if m.group('format1'):
+        fn = 1
+    else:
+        fn = 2
+
+    protocol = m.group('protocol%s' % fn)
+    domain = m.group('domain%s' % fn)
+
+    if not protocol:
+        domain_chunks = domain.split('.')
+
+        if not ((len(domain_chunks) == 1 and domain_chunks[0].lower() == 'localhost') or (domain_chunks[-1].lower() in TLDS)):
+            return m.group(0)
+
+    if (not protocol) and is_ip(domain):
+        return m.group(0)
+
+
+    port = m.group('port')
+    uri = m.group('uri')
+
+    if not ws:
+        ws = ''
+
+    if not port:
+        port = ''
+
+    if not protocol:
+        protocol = 'http'
+
+    if not uri:
+        uri = ''
+
+    url = "%s://%s%s%s" % (protocol, domain, port, uri)
+
+    return "%s<a href=\"%s\">%s</a>" % (ws, url, m.group('url'))
+
+
+class AutoLinker(markdown.postprocessors.Postprocessor):
+
+    def run(self, text):
+        return AUTO_LINK_RE.sub(replacer, text)
+
+class AutoLinkerExtension(markdown.Extension):
+
+    def extendMarkdown(self, md, md_globals):
+        md.postprocessors['autolinker'] = AutoLinker()
+
+def makeExtension(configs=None):
+    return AutoLinkerExtension(configs=configs)
+
+
diff --git a/forum/markdownext/mdx_urlize.py b/forum/markdownext/mdx_urlize.py
deleted file mode 100644 (file)
index b323531..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-import markdown
-
-# Global Vars
-URLIZE_RE = '(%s)' % '|'.join([
-    r'<(?:f|ht)tps?://[^>]*>',
-    r'\b(?:f|ht)tps?://[^)<>\s]+[^.,)<>\s]',
-    r'\bwww\.[^)<>\s]+[^.,)<>\s]',
-    r'[^*(<\s]+\.(?:com|net|org)\b',
-])
-
-class UrlizePattern(markdown.inlinepatterns.Pattern):
-    """ Return a link Element given an autolink (`http://example/com`). """
-    def handleMatch(self, m):
-        url = m.group(2)
-        
-        if url.startswith('<'):
-            url = url[1:-1]
-            
-        text = url
-        
-        if not url.split('://')[0] in ('http','https','ftp'):
-            if '@' in url and not '/' in url:
-                url = 'mailto:' + url
-            else:
-                url = 'http://' + url
-    
-        el = markdown.etree.Element("a")
-        el.set('href', url)
-        el.text = markdown.AtomicString(text)
-        return el
-
-class UrlizeExtension(markdown.Extension):
-    """ Urlize Extension for Python-Markdown. """
-
-    def extendMarkdown(self, md, md_globals):
-        """ Replace autolink with UrlizePattern """
-        md.inlinePatterns['autolink'] = UrlizePattern(URLIZE_RE, md)
-
-def makeExtension(configs=None):
-    return UrlizeExtension(configs=configs)
-
-if __name__ == "__main__":
-    import doctest
-    doctest.testmod()
index 7fd2e6ff564c10c9ad1d695632147939b1b5caab..dc29a024f75d3a1fbf814811650ec5cbb7a6efdb 100644 (file)
@@ -16,7 +16,7 @@ class ActionQuerySet(CachedQuerySet):
             return super(ActionQuerySet, self).obj_from_datadict(datadict)
 
     def get(self, *args, **kwargs):            
-        action = super(ActionQuerySet, self).get(*args, **kwargs).leaf()
+        action = super(ActionQuerySet, self).get(*args, **kwargs).leaf
 
         if not isinstance(action, self.model):
             raise self.model.DoesNotExist()
@@ -101,6 +101,7 @@ class Action(BaseModel):
             cancel = ActionRepute(action=self, user=repute.user, value=(-repute.value), by_canceled=True)
             cancel.save()
 
+    @property
     def leaf(self):
         leaf_cls = ActionProxyMetaClass.types.get(self.action_type, None)
 
index c5a80f993208a79866b9fe23b778e48fc3a74842..bf132c4e1bca3177cfe666ade6f95742c0bfe5a0 100644 (file)
@@ -1,5 +1,9 @@
 import datetime
 import re
+try:
+    from hashlib import md5
+except:
+    from md5 import new as md5
 from urllib import quote_plus, urlencode
 from django.db import models, IntegrityError, connection, transaction
 from django.utils.http import urlquote  as django_urlquote
@@ -18,6 +22,10 @@ from forum import settings
 import logging
 
 
+if not hasattr(cache, 'get_many'):
+    #put django 1.2 code here
+    pass
+
 class LazyQueryList(object):
     def __init__(self, model, items):
         self.items = items
@@ -33,6 +41,9 @@ class LazyQueryList(object):
     def __len__(self):
         return len(self.items)
 
+class ToFetch(str):
+    pass
+
 class CachedQuerySet(models.query.QuerySet):
 
     def lazy(self):
@@ -45,15 +56,20 @@ class CachedQuerySet(models.query.QuerySet):
 
             return LazyQueryList(self.model, list(self.values_list(*values_list)))
         else:
-            if len(self.query.extra):
-                print self.query.extra
             return self
 
     def obj_from_datadict(self, datadict):
         obj = self.model()
         obj.__dict__.update(datadict)
+
+        if hasattr(obj, '_state'):
+            obj._state.db = 'default'
+
         return obj
 
+    def _base_clone(self):
+        return self._clone(klass=models.query.QuerySet)
+
     def get(self, *args, **kwargs):
         key = self.model.infer_cache_key(kwargs)
 
@@ -61,7 +77,7 @@ class CachedQuerySet(models.query.QuerySet):
             obj = cache.get(key)
 
             if obj is None:
-                obj = super(CachedQuerySet, self).get(*args, **kwargs)
+                obj = self._base_clone().get(*args, **kwargs)
                 obj.cache()
             else:
                 obj = self.obj_from_datadict(obj)
@@ -69,7 +85,85 @@ class CachedQuerySet(models.query.QuerySet):
 
             return obj
 
-        return super(CachedQuerySet, self).get(*args, **kwargs)
+        return self._base_clone().get(*args, **kwargs)
+
+    def _fetch_from_query_cache(self, key):
+        invalidation_key = self.model._get_cache_query_invalidation_key()
+        cached_result = cache.get_many([invalidation_key, key])
+
+        if not invalidation_key in cached_result:
+            self.model._set_query_cache_invalidation_timestamp()
+            return None
+
+        if (key in cached_result) and(cached_result[invalidation_key] < cached_result[key][0]):
+            return cached_result[key][1]
+
+        return None
+
+    def count(self):
+        cache_key = self.model._generate_cache_key("CNT:%s" % self._get_query_hash())
+        result = self._fetch_from_query_cache(cache_key)
+
+        if result is not None:
+            return result
+
+        result = super(CachedQuerySet, self).count()
+        cache.set(cache_key, (datetime.datetime.now(), result), 60 * 60)
+        return result
+
+    def iterator(self):
+        cache_key = self.model._generate_cache_key("QUERY:%s" % self._get_query_hash())
+        on_cache_query_attr = self.model.value_to_list_on_cache_query()
+
+        to_return = None
+        to_cache = {}
+
+        key_list = self._fetch_from_query_cache(cache_key)
+
+        if key_list is None:
+            if not len(self.query.aggregates):
+                values_list = [on_cache_query_attr]
+
+                if len(self.query.extra):
+                    values_list += self.query.extra.keys()
+
+                key_list = [v[0] for v in self.values_list(*values_list)]
+                to_cache[cache_key] = (datetime.datetime.now(), key_list)
+            else:
+                to_return = list(super(CachedQuerySet, self).iterator())
+                to_cache[cache_key] = (datetime.datetime.now(), [row.__dict__[on_cache_query_attr] for row in to_return])
+
+        if (not to_return) and key_list:
+            row_keys = [self.model.infer_cache_key({on_cache_query_attr: attr}) for attr in key_list]
+            cached = cache.get_many(row_keys)
+
+            to_return = [
+                (ck in cached) and self.obj_from_datadict(cached[ck]) or ToFetch(key_list[i]) for i, ck in enumerate(row_keys)
+            ]
+
+            if len(cached) != len(row_keys):
+                to_fetch = [str(tr) for tr in to_return if isinstance(tr, ToFetch)]
+
+                fetched = dict([(str(r.__dict__[on_cache_query_attr]), r) for r in
+                              models.query.QuerySet(self.model).filter(**{"%s__in" % on_cache_query_attr: to_fetch})])
+
+                to_return = [(isinstance(tr, ToFetch) and fetched[str(tr)] or tr) for tr in to_return]
+                to_cache.update(dict([(self.model.infer_cache_key({on_cache_query_attr: attr}), r._as_dict()) for attr, r in fetched.items()]))
+
+        if len(to_cache):
+            cache.set_many(to_cache, 60 * 60)
+
+        if to_return:
+            for row in to_return:
+                if hasattr(row, 'leaf'):
+                    yield row.leaf
+                else:
+                    yield row
+
+    def _get_query_hash(self):
+        return md5(str(self.query)).hexdigest()
+
+
 
 class CachedManager(models.Manager):
     use_for_related_fields = True
@@ -178,8 +272,20 @@ class BaseModel(models.Model):
                 self.uncache()
 
         self.reset_original_state()
+        self._set_query_cache_invalidation_timestamp()
         self.cache()
 
+    @classmethod
+    def _get_cache_query_invalidation_key(cls):
+        return cls._generate_cache_key("INV_TS")
+
+    @classmethod
+    def _set_query_cache_invalidation_timestamp(cls):
+        cache.set(cls._get_cache_query_invalidation_key(), datetime.datetime.now(), 60 * 60 * 24)
+
+        for base in filter(lambda c: issubclass(c, BaseModel) and (not c is BaseModel), cls.__bases__):
+            base._set_query_cache_invalidation_timestamp()
+
     @classmethod
     def _generate_cache_key(cls, key, group=None):
         if group is None:
@@ -190,6 +296,10 @@ class BaseModel(models.Model):
     def cache_key(self):
         return self._generate_cache_key(self.id)
 
+    @classmethod
+    def value_to_list_on_cache_query(cls):
+        return 'id'
+
     @classmethod
     def infer_cache_key(cls, querydict):
         try:
@@ -208,6 +318,7 @@ class BaseModel(models.Model):
 
     def delete(self):
         self.uncache()
+        self._set_query_cache_invalidation_timestamp()
         super(BaseModel, self).delete()
 
 
index 41dc5d67d35fcc23ca7bda0c3943834976deb480..2b790d8e574b5b7f7bbdbf51944b4bdd12e79387 100644 (file)
@@ -24,27 +24,8 @@ class Flag(models.Model):
         app_label = 'forum'
         unique_together = ('user', 'node')
 
-class BadgesQuerySet(models.query.QuerySet):
-    def get(self, *args, **kwargs):
-        try:
-            pk = [v for (k,v) in kwargs.items() if k in ('pk', 'pk__exact', 'id', 'id__exact')][0]
-        except:
-            return super(BadgesQuerySet, self).get(*args, **kwargs)
 
-        from forum.badges.base import BadgesMeta
-        badge = BadgesMeta.by_id.get(int(pk), None)
-        if not badge:
-            return super(BadgesQuerySet, self).get(*args, **kwargs)
-        return badge.ondb
-
-
-class BadgeManager(models.Manager):
-    use_for_related_fields = True
-
-    def get_query_set(self):
-        return BadgesQuerySet(self.model)
-
-class Badge(models.Model):
+class Badge(BaseModel):
     GOLD = 1
     SILVER = 2
     BRONZE = 3
@@ -55,16 +36,18 @@ class Badge(models.Model):
     
     awarded_to    = models.ManyToManyField(User, through='Award', related_name='badges')
 
-    objects = BadgeManager()
+    def get_handler(self):
+        from forum.badges import BadgesMeta
+        return BadgesMeta.by_id.get(self.id, None)
 
     @property
     def name(self):
-        cls = self.__dict__.get('_class', None)
+        cls = self.get_handler()
         return cls and cls.name or _("Unknown")
 
     @property
     def description(self):
-        cls = self.__dict__.get('_class', None)
+        cls = self.get_handler()
         return cls and cls.description or _("No description available")
 
     @models.permalink
index 553b6c26188f9f36b173dc4b5cfb6a8db5e59407..0ebcd21ccfc2b203888135ee53e5692f6ecd75d2 100644 (file)
@@ -7,6 +7,7 @@ from django.utils.translation import ugettext as _
 from django.utils.safestring import mark_safe
 from django.utils.html import strip_tags
 from forum.utils.html import sanitize_html
+from forum.utils.userlinking import auto_user_link
 from forum.settings import SUMMARY_LENGTH
 from utils import PickledObjectField
 
@@ -24,6 +25,9 @@ class NodeContent(models.Model):
     def html(self):
         return self.body
 
+    def rendered(self, content):
+        return auto_user_link(self, self._as_markdown(content, *['auto_linker']))
+
     @classmethod
     def _as_markdown(cls, content, *extensions):
         try:
@@ -43,7 +47,6 @@ class NodeContent(models.Model):
 
     def tagname_list(self):
         if self.tagnames:
-            t = [name.strip() for name in self.tagnames.split(u' ') if name]
             return [name.strip() for name in self.tagnames.split(u' ') if name]
         else:
             return []
@@ -333,22 +336,32 @@ class Node(BaseModel, NodeContent):
     def create_revision(self, user, **kwargs):
         number = self.revisions.aggregate(last=models.Max('revision'))['last'] + 1
         revision = self._create_revision(user, number, **kwargs)
-        self.activate_revision(user, revision, extensions=['urlize'])
+        self.activate_revision(user, revision)
         return revision
 
-    def activate_revision(self, user, revision, extensions=['urlize']):
+    def activate_revision(self, user, revision):
         self.title = revision.title
         self.tagnames = revision.tagnames
-        
-        from forum.utils.userlinking import auto_user_link
-        
-        self.body = auto_user_link(self, self._as_markdown(revision.body, *extensions))
+
+        self.body = self.rendered(revision.body)
 
         self.active_revision = revision
         self.update_last_activity(user)
 
         self.save()
 
+    def get_active_users(self, active_users = None):
+        if not active_users:
+            active_users = set()
+
+        active_users.add(self.author)
+
+        for node in self.children.all():
+            if not node.nis.deleted:
+                node.get_active_users(active_users)
+
+        return active_users
+
     def _list_changes_in_tags(self):
         dirty = self.get_dirty_fields()
 
@@ -377,7 +390,7 @@ class Node(BaseModel, NodeContent):
             for name in tag_changes['added']:
                 try:
                     tag = Tag.objects.get(name=name)
-                except:
+                except Tag.DoesNotExist:
                     tag = Tag.objects.create(name=name, created_by=self._last_active_user())
 
                 if not self.nis.deleted:
@@ -437,7 +450,6 @@ class Node(BaseModel, NodeContent):
         tags_changed = self._process_changes_in_tags()
         
         super(Node, self).save(*args, **kwargs)
-        
         if tags_changed: self.tags = list(Tag.objects.filter(name__in=self.tagname_list()))
 
     class Meta:
index ef4a37d814390a25f4de23734991570947db755c..136d3de78590cc3d2fbba2604ef09e412489b63b 100644 (file)
@@ -60,21 +60,7 @@ class Question(Node):
 
         return [Question.objects.get(id=r['id']) for r in related_list]
     
-    def get_active_users(self):
-        active_users = set()
-        
-        active_users.add(self.author)
-        
-        for answer in self.answers:
-            active_users.add(answer.author)
-            
-            for comment in answer.comments:
-                active_users.add(comment.author)
-                        
-        for comment in self.comments:
-            active_users.add(comment.author)
-        
-        return active_users
+
 
 
 class QuestionSubscription(models.Model):
index dd628c830d2580b7e5e403241dfa065deb556f37..c84a922d192a3ae330a815b60a4c7b7cc13f530a 100644 (file)
@@ -5,7 +5,7 @@ from django.utils.translation import ugettext as _
 
 from forum import modules
 
-class ActiveTagManager(models.Manager):
+class ActiveTagManager(CachedManager):
     use_for_related_fields = True
 
     def get_query_set(self):
@@ -34,6 +34,20 @@ class Tag(BaseModel):
         else:
             self.used_count = models.F('used_count') + value
 
+    def cache_key(self):
+        return self._generate_cache_key(self.name)
+
+    @classmethod
+    def infer_cache_key(cls, querydict):
+        if 'name' in querydict:
+            return cls._generate_cache_key(querydict['name'])
+
+        return BaseModel.infer_cache_key(querydict)
+
+    @classmethod
+    def value_to_list_on_cache_query(cls):
+        return 'name'
+
     @models.permalink
     def get_absolute_url(self):
         return ('tag_questions', (), {'tag': self.name})
index f8c91d65f10a11fddbfc61f917d1c60ddb2b1650..d3b2c6f0042cf70206929b25cd2597cad20a787a 100644 (file)
@@ -4,10 +4,6 @@ from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.auth.models import User as DjangoUser, AnonymousUser as DjangoAnonymousUser
 from django.db.models import Q
-try:
-    from hashlib import md5
-except:
-    from md5 import new as md5
 
 import string
 from random import Random
index ecbe038c15072b4c2c4619ac4d375bd54f2196de..1fbda58e4f2d3cd3b20edc8bffc6d31e4f0d7937 100644 (file)
@@ -122,3 +122,7 @@ class KeyValue(BaseModel):
         except:
             return None
 
+    @classmethod
+    def value_to_list_on_cache_query(cls):
+        return 'key'
+
index 48c0246efc6d67dafd055d75fe5ab1027093a8d8..26941104d8a35d61198dc202b4dfc0de5ed58cf4 100644 (file)
@@ -9,6 +9,16 @@ class Registry(list):
 
         self.append(item)
 
+    def find_by_name(self, name):
+        for i in self:
+            if i.name and (i.name == name):
+                return i
+
+    def remove_by_name(self, name):
+        for i, r in enumerate(self):
+            if r.name and (r.name == name):
+                return self.pop(i)
+
 
 HEAD_CONTENT = 'HEAD_CONTENT'
 HEADER_LINKS = 'HEADER_LINKS'
index e42c94017ce6545cdc70ffbf8187607a71652a98..a51044ad527f3a9fe661880258c2060e389812dc 100644 (file)
@@ -7,7 +7,7 @@ from ui import Registry
 from copy import copy
 
 class Visibility(object):
-    def __init__(self, level='public'):
+    def __init__(self, level='public', negated=False):
         if level not in ['public', 'authenticated', 'staff', 'superuser', 'owner']:
             try:
                 int(level)
@@ -18,7 +18,7 @@ class Visibility(object):
             self.by_reputation = False
 
         self.level = level
-        self.negated = False
+        self.negated = negated
 
     def show_to(self, user):
         if self.by_reputation:
@@ -36,8 +36,7 @@ class Visibility(object):
             return res
 
     def __invert__(self):
-        inverted = copy(self)
-        inverted.negated = True
+        return Visibility(self.level, not self.negated)
         
 
 Visibility.PUBLIC = Visibility('public')
@@ -68,9 +67,10 @@ class ObjectBase(object):
             else:
                 return self.argument
 
-    def __init__(self, visibility=None, weight=500):
+    def __init__(self, visibility=None, weight=500, name=''):
         self.visibility = visibility
         self.weight = weight
+        self.name = name
 
     def _visible_to(self, user):
         return (not self.visibility) or (self.visibility and self.visibility.show_to(user))
@@ -94,8 +94,8 @@ class LoopBase(ObjectBase):
 
 
 class Link(ObjectBase):
-    def __init__(self, text, url, attrs=None, pre_code='', post_code='', visibility=None, weight=500):
-        super(Link, self).__init__(visibility, weight)
+    def __init__(self, text, url, attrs=None, pre_code='', post_code='', visibility=None, weight=500, name=''):
+        super(Link, self).__init__(visibility, weight, name)
         self.text = self.Argument(text)
         self.url = self.Argument(url)
         self.attrs = self.Argument(attrs or {})
@@ -108,8 +108,8 @@ class Link(ObjectBase):
             self.post_code(context))
 
 class Include(ObjectBase):
-    def __init__(self, tpl, visibility=None, weight=500):
-        super(Include, self).__init__(visibility, weight)
+    def __init__(self, tpl, visibility=None, weight=500, name=''):
+        super(Include, self).__init__(visibility, weight, name)
         self.template = template.loader.get_template(tpl)
 
     def render(self, context):
@@ -119,8 +119,8 @@ class Include(ObjectBase):
         
 
 class LoopContext(LoopBase):
-    def __init__(self, loop_context, visibility=None, weight=500):
-        super(LoopContext, self).__init__(visibility, weight)
+    def __init__(self, loop_context, visibility=None, weight=500, name=''):
+        super(LoopContext, self).__init__(visibility, weight, name)
         self.loop_context = self.Argument(loop_context)
 
     def update_context(self, context):
@@ -128,8 +128,8 @@ class LoopContext(LoopBase):
 
 
 class PageTab(LoopBase):
-    def __init__(self, tab_name, tab_title, url_getter, weight):
-        super(PageTab, self).__init__(weight=weight)
+    def __init__(self, tab_name, tab_title, url_getter, weight, name=''):
+        super(PageTab, self).__init__(weight=weight, name=name)
         self.tab_name = tab_name
         self.tab_title = tab_title
         self.url_getter = url_getter
@@ -144,7 +144,7 @@ class PageTab(LoopBase):
 
 class ProfileTab(LoopBase):
     def __init__(self, name, title, description, url_getter, private=False, render_to=None, weight=500):
-        super(ProfileTab, self).__init__(weight=weight)
+        super(ProfileTab, self).__init__(weight=weight, name=name)
         self.name = name
         self.title = title
         self.description = description
@@ -167,8 +167,8 @@ class ProfileTab(LoopBase):
 
 
 class AjaxMenuItem(ObjectBase):
-    def __init__(self, label, url, a_attrs=None, span_label='', span_attrs=None, visibility=None, weight=500):
-        super(AjaxMenuItem, self).__init__(visibility, weight)
+    def __init__(self, label, url, a_attrs=None, span_label='', span_attrs=None, visibility=None, weight=500, name=''):
+        super(AjaxMenuItem, self).__init__(visibility, weight, name)
         self.label = self.Argument(label)
         self.url = self.Argument(url)
         self.a_attrs = self.Argument(a_attrs or {})
@@ -182,8 +182,8 @@ class AjaxMenuItem(ObjectBase):
             **{'class': 'item'})
 
 class AjaxMenuGroup(ObjectBase, Registry):
-    def __init__(self, label, items, visibility=None, weight=500):
-        super(AjaxMenuGroup, self).__init__(visibility, weight)
+    def __init__(self, label, items, visibility=None, weight=500, name=''):
+        super(AjaxMenuGroup, self).__init__(visibility, weight, name)
         self.label = label
 
         for item in items:
index aedf5b0d1b0e1dbcc4d773ff8a4d4c3a021cfb87..43f11a7ac613e84e4c36a74b122579adfba966f2 100644 (file)
@@ -2,39 +2,43 @@ from forum.modules import ui, get_modules_script
 from django.utils.translation import ugettext as _
 from django.core.urlresolvers import reverse
 from django.template.defaultfilters import slugify
-from django.template import get_templatetags_modules
+
 from forum.templatetags.extra_tags import get_score_badge
 from forum.utils.html import cleanup_urls
 from forum import settings
 
 
-modules_template_tags = get_modules_script('templatetags')
-django_template_tags = get_templatetags_modules()
+try:
+    from django.template import get_templatetags_modules
+    modules_template_tags = get_modules_script('templatetags')
+    django_template_tags = get_templatetags_modules()
 
-for m in modules_template_tags:
-    django_template_tags.append(m.__name__)
+    for m in modules_template_tags:
+        django_template_tags.append(m.__name__)
+except:
+    pass
 
 ui.register(ui.HEADER_LINKS,
-            ui.Link(_('faq'), ui.Url('faq'), weight=400),
-            ui.Link(_('about'), ui.Url('about'), weight=300),
+            ui.Link(_('faq'), ui.Url('faq'), weight=400, name='FAQ'),
+            ui.Link(_('about'), ui.Url('about'), weight=300, name='ABOUT'),
 
             ui.Link(
                     text=lambda u, c: u.is_authenticated() and _('logout') or _('login'),
                     url=lambda u, c: u.is_authenticated() and reverse('logout') or reverse('auth_signin'),
-                    weight=200),
+                    weight=200, name='LOGIN/OUT'),
 
             ui.Link(
                     visibility=ui.Visibility.AUTHENTICATED,
                     text=lambda u, c: u.username,
                     url=lambda u, c: u.get_profile_url(),
                     post_code=lambda u, c: get_score_badge(u),
-                    weight=100),
+                    weight=100, name='ACCOUNT'),
 
             ui.Link(
                     visibility=ui.Visibility.SUPERUSER,
                     text=_('administration'),
                     url=lambda u, c: reverse('admin_index'),
-                    weight=0)
+                    weight=0, name='ADMINISTRATION')
 
 )
 
@@ -67,24 +71,28 @@ ui.register(ui.USER_MENU,
                 label=_("edit profile"),
                 url=lambda u, c: reverse('edit_user', kwargs={'id': c['user'].id}),
                 span_attrs={'class': 'user-edit'},
-                weight=0
+                weight=0,
+                name='EDIT_PROFILE'
             ),
             ui.UserMenuItem(
                 label=_("authentication settings"),
                 url=lambda u, c: reverse('user_authsettings', kwargs={'id': c['user'].id}),
                 span_attrs={'class': 'user-auth'},
-                weight=100
+                weight=100,
+                name='AUTH_SETTINGS'
             ),
             ui.UserMenuItem(
                 label=_("email notification settings"),
                 url=lambda u, c: reverse('user_subscriptions', kwargs={'id': c['user'].id, 'slug': slugify(c['user'].username)}),
                 span_attrs={'class': 'user-subscriptions'},
-                weight=200
+                weight=200,
+                name='EMAIL_SETTINGS'
             ),
             ui.UserMenuItem(
                 label=_("other preferences"),
                 url=lambda u, c: reverse('user_preferences', kwargs={'id': c['user'].id, 'slug': slugify(c['user'].username)}),
-                weight=200
+                weight=200,
+                name='OTHER_PREFS'
             ),
             ModerationMenuGroup(_("Moderation tools"), items=(
                 ui.UserMenuItem(
@@ -92,6 +100,7 @@ ui.register(ui.USER_MENU,
                     url=lambda u, c: reverse('user_suspend', kwargs={'id': c['user'].id}),
                     a_attrs=lambda u, c: {'class': c['user'].is_suspended() and 'ajax-command confirm' or 'ajax-command withprompt'},
                     render_to=lambda u: not u.is_superuser,
+                    name='SUSPENSION'
                 ),
                 ui.UserMenuItem(
                     label=lambda u, c: _("give/take karma"),
@@ -99,18 +108,21 @@ ui.register(ui.USER_MENU,
                     a_attrs=lambda u, c: {'id': 'award-rep-points', 'class': 'ajax-command withprompt'},
                     span_attrs={'class': 'user-award_rep'},
                     render_to=lambda u: not u.is_suspended(),
+                    name='KARMA'
                 ),
                 ui.UserMenuItem(
                     label=lambda u, c: c['user'].is_staff and _("remove moderator status") or _("grant moderator status"),
                     url=lambda u, c: reverse('user_powers', kwargs={'id': c['user'].id, 'action':c['user'].is_staff and 'remove' or 'grant', 'status': 'staff'}),
                     a_attrs=lambda u, c: {'class': 'ajax-command confirm'},
                     span_attrs={'class': 'user-moderator'},
+                    name='MODERATOR'
                 ),
                 SuperUserSwitchMenuItem(
                     label=lambda u, c: c['user'].is_superuser and _("remove super user status") or _("grant super user status"),
                     url=lambda u, c: reverse('user_powers', kwargs={'id': c['user'].id, 'action':c['user'].is_superuser and 'remove' or 'grant', 'status': 'super'}),
                     a_attrs=lambda u, c: {'class': 'ajax-command confirm'},
                     span_attrs={'class': 'user-superuser'},
+                    name='SUPERUSER'
                 ),
-            ), visibility=ui.Visibility.SUPERUSER, weight=500)
+            ), visibility=ui.Visibility.SUPERUSER, weight=500, name='MOD_TOOLS')
 )
index 22425387cb39d22060852444968c84414aace5ad..78145942459313c0fae951f4ae40c002ca7f9413 100644 (file)
@@ -26,7 +26,8 @@ u"""
 , SIDEBAR_SET, dict(
 label = "Question title tips",
 help_text = "Tips visible on the ask or edit questions page about the question title.",
-required=False))
+required=False,
+widget=Textarea(attrs={'rows': '10'})))
 
 QUESTION_TAG_TIPS = Setting('QUESTION_TAG_TIPS',
 u"""
@@ -37,7 +38,8 @@ u"""
 , SIDEBAR_SET, dict(
 label = "Tagging tips",
 help_text = "Tips visible on the ask or edit questions page about good tagging.",
-required=False))
+required=False,
+widget=Textarea(attrs={'rows': '10'})))
 
 
 SIDEBAR_UPPER_SHOW = Setting('SIDEBAR_UPPER_SHOW', True, SIDEBAR_SET, dict(
index 4bf05be01866dac100ff7726742f3711172c5636..fab1c0af92778afdf54da5159a39f1047e0411d1 100644 (file)
@@ -19,6 +19,7 @@ $(function() {
     var $input = $('#id_title');
     var $box = $('#ask-related-questions');
     var template = $('#question-summary-template').html();
+    var $editor = $('#editor');
 
     var results_cache = {};
 
@@ -80,7 +81,14 @@ $(function() {
 
     $input.keyup(reload_suggestions_box);
     $input.focus(reload_suggestions_box);
-    $input.blur(close_suggestions_box);
+
+    $editor.change(function() {
+        if ($editor.html().length > 10) {
+            close_suggestions_box();
+        }
+    });
+
+
 
     // for chrome
     $input.keydown(focus_on_question);
index f4fff84082e72ef2bbc1c518a82d90debe234696..7c8bcbb350be7e131ae7205fbd21fd34126bdc8e 100644 (file)
@@ -79,3 +79,4 @@ div.dialog.award-rep-points table input, div.dialog.award-rep-points table texta
 .user-moderator { background: url('/m/default/media/images/user-sprite.png') no-repeat 0 -51px; }
 .user-subscriptions { background: url('/m/default/media/images/user-sprite.png') no-repeat 0 -68px; }
 .user-superuser { background: url('/m/default/media/images/user-sprite.png') no-repeat 0 -85px; }
+
index abe021dd3a22928c3778e7152c8ef9321a7f9643..9d4fab7029a80c59122ad95f1ebf42a4926bf0dc 100644 (file)
@@ -19,7 +19,7 @@
         {% endif %}
     {% endfor %}
     {% if has_next %}
-        <span class="next"><a href="{{ next_url }}" title="{% trans "next page" %}">{% trans "next page" %} &raquo;</a></span>    
+        <span class="next"><a href="{{ next_url }}" title="{% trans "next page" %}">{% trans "next" %} &raquo;</a></span>
     {% endif %}
 </p>
 {% endspaceless %}
\ No newline at end of file
index ec852cc3896fea4a62387a63522d753bd89c69b4..61c63a0d3cb6dac88fb9c9b432153380ea32fe24 100644 (file)
                 $('#user-reputation').animate({ backgroundColor: "transparent" }, 1000);
             }
         </script>
-
         <link rel="stylesheet" href="http://jquery-ui.googlecode.com/svn/tags/latest/themes/base/jquery-ui.css" type="text/css" media="all" />
         <link rel="stylesheet" href="http://static.jquery.com/ui/css/demo-docs-theme/ui.theme.css" type="text/css" media="all" />
         <link rel="stylesheet" type="text/css" media="screen" href="{% media "/media/style/user.css" %}"/>
     {% endif %}
+    <style type="text/css">
+        #room {
+            border: 0;
+        }
+    </style>
     {% block userjs %}{% endblock %}
 {% endblock %}
 {% block content %}
index 21c193c5f2d4e30010aed490eb04abb56341c208..92de7cfac769b2d3a5325a6d4bbfbd1fd2aa651f 100644 (file)
@@ -1,11 +1,14 @@
 {% extends "user.html" %}
 
-{% load extra_tags %}
-{% load question_list_tags %}
+{% load extra_tags question_list_tags i18n %}
 {% block usercontent %}
 <div class="user-stats-table">
+{% if favorites %}
     {% for favorite in favorites %}
         {% question_list_item favorite.node favorite_count=yes signature_type=badges %}
     {% endfor %}
+{% else %}
+    {% trans "No favorite questions to display." %}
+{% endif %}
 </div>
 {% endblock %}
index 9582a252811e8cdd03ccadba18b98f2fde82a05a..7f06bfba760f4364da0bb8b7f7a390c1df583e14 100644 (file)
@@ -11,6 +11,7 @@ get_modules_script('startup')
 import forum.badges
 import forum.subscriptions
 import forum.registry
+get_modules_script('registry')
 
 
 
index b0f2b61943cefc35c6cbcf6ed49ac567cb0eb957..2bc4385f8f0cf35379e8304d156361d8811ec97d 100644 (file)
@@ -37,13 +37,16 @@ class ActivityNode(template.Node):
 \r
     def render(self, context):\r
         try:\r
-            action = self.activity.resolve(context).leaf()\r
+            action = self.activity.resolve(context).leaf\r
             viewer = self.viewer.resolve(context)\r
             describe = mark_safe(action.describe(viewer))\r
             return self.template.render(template.Context(dict(action=action, describe=describe)))\r
         except Exception, e:\r
-            #return action.action_type + ":" + str(e)\r
-            logging.error("Error in %s action describe: %s" % (action.action_type, str(e)))\r
+            import traceback\r
+            msg = "Error in action describe: \n %s" % (\r
+                traceback.format_exc()\r
+            )\r
+            logging.error(msg)\r
 \r
 @register.tag\r
 def activity_item(parser, token):\r
index fb99244c7d4190933309ac9134c8a07bdccaa314..382e59e1b63379995534ac5dc0278e4a162ac88e 100644 (file)
@@ -210,8 +210,8 @@ def _paginated(request, objects, context):
     def get_page():
         object_list = page_obj.object_list
 
-        if hasattr(object_list, 'lazy'):
-            return object_list.lazy()
+        #if hasattr(object_list, 'lazy'):
+        #    return object_list.lazy()
         return object_list
     paginator.page = get_page()
 
index 53a49dc05ecedab60e8153ed10639bc6026ed86d..6693883e1e6effb7866b9048ce9f80d68c7efa79 100644 (file)
@@ -1,6 +1,6 @@
 import re
 
-from forum.models import User,  Question,  Answer,  Comment
+from forum.models.user import User
 
 def find_best_match_in_name(content,  uname,  fullname,  start_index):      
     end_index = start_index + len(fullname)    
@@ -20,22 +20,8 @@ def find_best_match_in_name(content,  uname,  fullname,  start_index):
 APPEAL_PATTERN = re.compile(r'(?<!\w)@\w+')
 
 def auto_user_link(node, content):
-    
-    # We should find the root of the node tree (question) the current node belongs to.
-    if isinstance(node,  Question):
-        question = node
-    elif isinstance(node,  Answer):
-        question = node.question
-    elif isinstance(node,  Comment):
-        if node.question:
-            question = node.question
-        elif node.answer:
-            question = node.answer.question
-    else:
-        return content
-    
-    # Now we've got the root question. Let's get the list of active users.
-    active_users = question.get_active_users()
+
+    active_users = node.absolute_parent.get_active_users()
     
     appeals = APPEAL_PATTERN.finditer(content)
 
index f88dee8ed2948d17094ad73d5fe352421f14bd71..4aa96f8a1f9cadbd8363c36c25672aa147960417 100644 (file)
@@ -586,9 +586,6 @@ def award_points(request, user_id, answer_id):
         extra = dict(message=request.POST.get('message', ''), awarding_user=request.user.id, value=points)
 
         # We take points from the awarding user
-        AwardPointsAction(user=request.user, extra=extra).save(data=dict(value=-points, affected=user))
-
-        # And give them to the awarded one
-        AwardPointsAction(user=request.user, extra=extra).save(data=dict(value=points, affected=awarded_user))
+        AwardPointsAction(user=request.user, node=answer, extra=extra).save(data=dict(value=points, affected=awarded_user))
 
         return { 'message' : _("You have awarded %s with %d points") % (awarded_user, points) }
index a0c5cede68f84b5d49c2c6cae90e0ee2abf489f7..436676dfa7e462e86d30d1762270e80261a2d3e7 100644 (file)
@@ -88,7 +88,7 @@ def logout(request):
 
 @decorators.render('badges.html', 'badges', _('badges'), weight=300)
 def badges(request):
-    badges = [b.ondb for b in sorted(BadgesMeta.by_id.values(), lambda b1, b2: cmp(b1.name, b2.name))]
+    badges = sorted([Badge.objects.get(id=id) for id in BadgesMeta.by_id.keys()], lambda b1, b2: cmp(b1.name, b2.name))
 
     if request.user.is_authenticated():
         my_badges = Award.objects.filter(user=request.user).values('badge_id').distinct()
index 49f89da34941e75f7e80f90e1064801846b29446..2db3ca232a71e3cc2c641b015dbffaaf7c1fb439 100644 (file)
@@ -93,8 +93,13 @@ def ET_Element_add_tag(el, tag_name, content = None, **attrs):
     if content:
         try:
             tag.text = unicode(content)
-        except:
-            tag.text = u''
+        except Exception, e:
+            logging.error('error converting unicode characters')
+            import traceback
+            logging.error(traceback.print_exc())
+
+            import string
+            tag.text = unicode("".join([c for c in content if c in string.printable]))
 
     for k, v in attrs.items():
         tag.set(k, unicode(v))
diff --git a/forum_modules/mysqlfulltext/__init__.py b/forum_modules/mysqlfulltext/__init__.py
new file mode 100644 (file)
index 0000000..ff2463f
--- /dev/null
@@ -0,0 +1,10 @@
+NAME = 'Mysql Full Text Search'
+DESCRIPTION = "Enables Mysql full text search functionality."
+
+try:
+    import MySQLdb
+    from django.conf import settings
+    CAN_USE = settings.DATABASE_ENGINE == 'mysql'
+except:
+    CAN_USE = False
+  
\ No newline at end of file
diff --git a/forum_modules/mysqlfulltext/fts_install.sql b/forum_modules/mysqlfulltext/fts_install.sql
new file mode 100644 (file)
index 0000000..4cd6bd1
--- /dev/null
@@ -0,0 +1,31 @@
+CREATE TABLE forum_mysqlftsindex (
+       id int NOT NULL AUTO_INCREMENT,
+       node_id int NOT NULL UNIQUE,
+       body longtext NOT NULL,
+       PRIMARY KEY (id),
+       FOREIGN KEY (node_id) REFERENCES forum_node (id)   ON UPDATE CASCADE ON DELETE CASCADE,
+       FULLTEXT (body)
+) ENGINE=`MyISAM`;
+
+ALTER TABLE forum_mysqlftsindex CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
+
+delimiter |
+
+CREATE TRIGGER fts_on_insert AFTER INSERT ON forum_node
+  FOR EACH ROW
+  BEGIN
+    INSERT INTO forum_mysqlftsindex (node_id, body) VALUES (NEW.id, CONCAT_WS('\n', NEW.title, NEW.body, NEW.tagnames));
+  END;
+|
+
+delimiter |
+
+CREATE TRIGGER fts_on_update AFTER UPDATE ON forum_node
+  FOR EACH ROW
+  BEGIN
+    UPDATE forum_mysqlftsindex SET body = CONCAT_WS('\n', NEW.title, NEW.body, NEW.tagnames) WHERE node_id = NEW.id;
+  END;
+
+|
+
+INSERT INTO forum_mysqlftsindex (node_id, body) SELECT id, CONCAT_WS('\n', title, body, tagnames) FROM forum_node;
\ No newline at end of file
diff --git a/forum_modules/mysqlfulltext/models.py b/forum_modules/mysqlfulltext/models.py
new file mode 100644 (file)
index 0000000..8f22379
--- /dev/null
@@ -0,0 +1,9 @@
+from django.db import models
+
+class MysqlFtsIndex(models.Model):
+    node       = models.OneToOneField('Node', related_name='ftsindex')
+    body       = models.TextField()
+
+    class Meta:
+        managed = False
+        app_label = 'forum'
\ No newline at end of file
diff --git a/forum_modules/mysqlfulltext/settings.py b/forum_modules/mysqlfulltext/settings.py
new file mode 100644 (file)
index 0000000..5dcb186
--- /dev/null
@@ -0,0 +1,3 @@
+from forum.settings.base import Setting
+
+MYSQL_FTS_INSTALLED = Setting('MYSQL_FTS_INSTALLED', False)
\ No newline at end of file
diff --git a/forum_modules/mysqlfulltext/startup.py b/forum_modules/mysqlfulltext/startup.py
new file mode 100644 (file)
index 0000000..363b4e1
--- /dev/null
@@ -0,0 +1,34 @@
+from django.db import connection, transaction
+import os, settings
+
+import re
+from django.db import connection, transaction, models
+from django.db.models import Q
+from forum.models.question import Question, QuestionManager
+from forum.models.node import Node
+from forum.modules import decorate
+
+if not bool(settings.MYSQL_FTS_INSTALLED):
+    f = open(os.path.join(os.path.dirname(__file__), 'fts_install.sql'), 'r')
+
+    try:
+        cursor = connection.cursor()
+        cursor.execute(f.read())
+        transaction.commit_unless_managed()
+
+        settings.MYSQL_FTS_INSTALLED.set_value(True)
+
+    except Exception, e:
+        #import sys, traceback
+        #traceback.print_exc(file=sys.stdout)
+        pass
+    finally:
+        cursor.close()
+
+    f.close()
+
+word_re = re.compile(r'\w+', re.UNICODE)
+
+@decorate(QuestionManager.search, needs_origin=False)
+def question_search(self, keywords):
+    return False, self.filter(models.Q(ftsindex__body__search=keywords))
\ No newline at end of file
index 9b5d6e712ec06b055d3d0ecf20afb362027f9324..6ed5b2b111e3545a1eac1d21cfb789c0b7c6d040 100644 (file)
@@ -137,7 +137,8 @@ class OpenIdAbstractAuthConsumer(AuthenticationConsumer):
 
                     for t, s in available_types.items():
                         if not t in consumer_data:
-                            consumer_data[t] = axargs["value.%s.1" % s]
+                            if axargs.get("value.%s.1" % s, None):
+                                consumer_data[t] = axargs["value.%s.1" % s]
                     
             request.session['auth_consumer_data'] = consumer_data
 
index 98c9dbd64872a63fdc73ee9d9d8407b7539c4a36..6669b8c0fe7dc8bf44efd9b951b8ba8fe3758c22 100644 (file)
@@ -393,7 +393,7 @@ def remove_post_state(name, post):
     post.state_string = "".join("(%s)" % s for s in re.findall('\w+', post.state_string) if s != name)
 
 def postimport(dump, uidmap, tagmap):
-    all = []
+    all = {}
 
     def callback(sxpost):
         nodetype = (sxpost.get('posttypeid') == '1') and "nodetype" or "answer"
@@ -454,13 +454,15 @@ def postimport(dump, uidmap, tagmap):
             post.extra_count = sxpost.get('viewcount', 0)
 
             add_tags_to_post(post, tagmap)
+            all[int(post.id)] = int(post.id)
 
         else:
             post.parent_id = sxpost['parentid']
+            post.abs_parent_id = sxpost['parentid']
+            all[int(post.id)] = int(sxpost['parentid'])
 
         post.save()
 
-        all.append(int(post.id))
         create_and_activate_revision(post)
 
         del post
@@ -469,7 +471,9 @@ def postimport(dump, uidmap, tagmap):
 
     return all
 
-def comment_import(dump, uidmap, posts):
+def comment_import(dump, uidmap, absparent_map):
+    posts = absparent_map.keys()
+
     currid = IdIncrementer(max(posts))
     mapping = {}
 
@@ -482,6 +486,7 @@ def comment_import(dump, uidmap, posts):
                 author_id = uidmap[sxc.get('userid', 1)],
                 body = sxc['text'],
                 parent_id = sxc.get('postid'),
+                abs_parent_id = absparent_map.get(int(sxc.get('postid')), sxc.get('postid'))
                 )
 
         if sxc.get('deletiondate', None):
@@ -786,7 +791,7 @@ def save_setting(k, v):
     kv.save()
 
 
-def pages_import(dump, currid):
+def pages_import(dump, currid, owner):
     currid = IdIncrementer(currid)
     registry = {}
 
@@ -807,7 +812,7 @@ def pages_import(dump, currid):
                 'sidebar_render': "html",
                 'comments': False
                 }),
-                author_id = 1
+                author_id = owner
                 )
 
         create_and_activate_revision(page)
@@ -932,7 +937,7 @@ def sximport(dump, options):
 
     badges_import(dump, uidmap, posts)
 
-    pages_import(dump, max(posts))
+    pages_import(dump, max(posts), uidmap.default)
     static_import(dump)
     gc.collect()