]> git.openstreetmap.org Git - osqa.git/commitdiff
Merging the experimental branch back to trunk.
authorhernani <hernani@0cfe37f9-358a-4d5e-be75-b63607b5c754>
Fri, 7 May 2010 01:15:39 +0000 (01:15 +0000)
committerhernani <hernani@0cfe37f9-358a-4d5e-be75-b63607b5c754>
Fri, 7 May 2010 01:15:39 +0000 (01:15 +0000)
git-svn-id: http://svn.osqa.net/svnroot/osqa/trunk@156 0cfe37f9-358a-4d5e-be75-b63607b5c754

123 files changed:
forum/actions/__init__.py [new file with mode: 0644]
forum/actions/meta.py [new file with mode: 0644]
forum/actions/node.py [new file with mode: 0644]
forum/actions/user.py [new file with mode: 0644]
forum/activity.py [deleted file]
forum/admin.py
forum/akismet.py
forum/authentication/__init__.py
forum/badges/__init__.py
forum/badges/base.py
forum/feed.py
forum/forms.py
forum/management/__init__.py
forum/migrations/0014_auto__add_field_question_accepted_answer.py
forum/migrations/0022_auto__add_actionrepute__add_action__add_favoritenode__del_field_node_v.py [new file with mode: 0644]
forum/migrations/0023_flaten_node_inheritance_create_actions.py [new file with mode: 0644]
forum/migrations/0024_auto__del_repute__del_vote__del_answer__del_flaggeditem__del_anonymous.py [new file with mode: 0644]
forum/migrations/0025_auto__add_field_node_extra_action__add_field_node_deleted__add_field_n.py [new file with mode: 0644]
forum/migrations/0026_reset_deleted_and_lastedited_flags.py [new file with mode: 0644]
forum/migrations/0027_auto__del_activity.py [new file with mode: 0644]
forum/migrations/0028_auto__add_field_action_canceled_ip__add_field_actionrepute_date.py [new file with mode: 0644]
forum/migrations/0029_repute_dates.py [new file with mode: 0644]
forum/migrations/0030_auto__chg_field_action_extra__chg_field_keyvalue_value.py [new file with mode: 0644]
forum/migrations/0031_alter_pickle_storage.py [new file with mode: 0644]
forum/migrations/0032_auto__del_field_user_hide_ignored_questions__del_field_user_questions_.py [new file with mode: 0644]
forum/migrations/0033_auto__add_flag__add_vote__add_field_badge_cls__del_unique_badge_type_n.py [new file with mode: 0644]
forum/migrations/0034_new_badge_and_award.py [new file with mode: 0644]
forum/migrations/0035_auto__del_field_award_object_id__del_field_award_content_type__add_uni.py [new file with mode: 0644]
forum/models/__init__.py
forum/models/action.py [new file with mode: 0644]
forum/models/answer.py
forum/models/base.py
forum/models/comment.py
forum/models/meta.py
forum/models/node.py
forum/models/question.py
forum/models/repute.py [deleted file]
forum/models/tag.py
forum/models/user.py
forum/models/utils.py
forum/modules/__init__.py [moved from forum/modules.py with 97% similarity]
forum/modules/decorators.py [new file with mode: 0644]
forum/reputation.py [deleted file]
forum/settings/__init__.py
forum/settings/base.py
forum/settings/basic.py
forum/settings/email.py
forum/settings/extkeys.py
forum/settings/form.py
forum/settings/forms.py
forum/settings/moderation.py [new file with mode: 0644]
forum/skins/default/media/images/openid/aol.gif
forum/skins/default/media/images/openid/blogger.png [new file with mode: 0644]
forum/skins/default/media/images/openid/claimid.png [new file with mode: 0644]
forum/skins/default/media/images/openid/facebook.gif
forum/skins/default/media/images/openid/flickr.png [new file with mode: 0644]
forum/skins/default/media/images/openid/google.gif
forum/skins/default/media/images/openid/livejournal.png [new file with mode: 0644]
forum/skins/default/media/images/openid/myopenid.png [new file with mode: 0644]
forum/skins/default/media/images/openid/technorati.png [new file with mode: 0644]
forum/skins/default/media/images/openid/twitter.png
forum/skins/default/media/images/openid/verisign.png [new file with mode: 0644]
forum/skins/default/media/images/openid/wordpress.png [new file with mode: 0644]
forum/skins/default/media/images/openid/yahoo.gif
forum/skins/default/media/js/jquery.openid.js
forum/skins/default/media/js/osqa.admin.js [new file with mode: 0644]
forum/skins/default/media/js/osqa.main.js
forum/skins/default/media/style/admin.css
forum/skins/default/media/style/auth.css
forum/skins/default/media/style/style.css
forum/skins/default/templates/ask.html
forum/skins/default/templates/auth/signin.html
forum/skins/default/templates/badge.html
forum/skins/default/templates/faq.html
forum/skins/default/templates/node/comments.html
forum/skins/default/templates/node/post_controls.html
forum/skins/default/templates/node/report.html [new file with mode: 0644]
forum/skins/default/templates/osqaadmin/base.html
forum/skins/default/templates/osqaadmin/index.html
forum/skins/default/templates/post_contributor_info.html
forum/skins/default/templates/question.html
forum/skins/default/templates/question_list/tag_selector.html
forum/skins/default/templates/question_retag.html
forum/skins/default/templates/question_summary_list_roll.html
forum/skins/default/templates/reopen.html
forum/skins/default/templates/sidebar/recent_awards.html
forum/skins/default/templates/users/activity.html
forum/skins/default/templates/users/edit.html
forum/skins/default/templates/users/info.html
forum/skins/default/templates/users/moderation.html [new file with mode: 0644]
forum/skins/default/templates/users/questions.html
forum/skins/default/templates/users/recent.html
forum/skins/default/templates/users/reputation.html
forum/skins/default/templates/users/stats.html
forum/skins/default/templates/users/votes.html
forum/startup.py
forum/subscriptions.py
forum/templatetags/extra_tags.py
forum/templatetags/general_sidebar_tags.py
forum/templatetags/node_tags.py
forum/templatetags/user_tags.py
forum/urls.py
forum/views/admin.py
forum/views/auth.py
forum/views/commands.py
forum/views/decorators.py
forum/views/meta.py
forum/views/readers.py
forum/views/users.py
forum/views/writers.py
forum_modules/default_badges/badges.py
forum_modules/localauth/templates/loginform.html
forum_modules/localauth/views.py
forum_modules/openidauth/authentication.py
forum_modules/openidauth/templates/openidurl.html
forum_modules/pgfulltext/handlers.py
forum_modules/pgfulltext/pg_fts_install.sql
forum_modules/pgfulltext/settings.py [new file with mode: 0644]
forum_modules/pgfulltext/startup.py
forum_modules/project_badges/badges.py
mdx_limitedsyntax.py [new file with mode: 0644]
settings.py
settings_local.py.dist

diff --git a/forum/actions/__init__.py b/forum/actions/__init__.py
new file mode 100644 (file)
index 0000000..a681069
--- /dev/null
@@ -0,0 +1,3 @@
+from meta import *\r
+from node import *\r
+from user import *
\ No newline at end of file
diff --git a/forum/actions/meta.py b/forum/actions/meta.py
new file mode 100644 (file)
index 0000000..a45fe58
--- /dev/null
@@ -0,0 +1,207 @@
+from django.utils.translation import ugettext as _\r
+from django.db.models import F\r
+from forum.models.action import ActionProxy, DummyActionProxy\r
+from forum.models import Vote, Flag\r
+import settings\r
+\r
+class VoteAction(ActionProxy):\r
+    def update_node_score(self, inc):\r
+        self.node.score = F('score') + inc\r
+        self.node.save()\r
+\r
+    def process_vote_action(self, value):\r
+        self.update_node_score(value)\r
+        vote = Vote(node=self.node, user=self.user, action=self, value=value)\r
+        vote.save()\r
+\r
+    def cancel_action(self):\r
+        vote = self.vote.all()[0]\r
+        self.update_node_score(-vote.value)\r
+        vote.delete()\r
+\r
+    @classmethod\r
+    def get_for(cls, user, node):\r
+        try:\r
+            vote = Vote.objects.get(user=user, node=node)\r
+            return vote.value\r
+        except:\r
+            return None\r
+\r
+    def describe_vote(self, vote_desc, viewer=None):\r
+        return _("%(user)s %(vote_desc)s %(post_desc)s") % {\r
+            'user': self.hyperlink(self.user.get_profile_url(), self.friendly_username(viewer, self.user)),\r
+            'vote_desc': vote_desc, 'post_desc': self.describe_node(viewer, self.node)\r
+        }\r
+\r
+\r
+class VoteUpAction(VoteAction):\r
+    def repute_users(self):\r
+        self.repute(self.node.author, int(settings.REP_GAIN_BY_UPVOTED))\r
+\r
+    def process_action(self):\r
+        self.process_vote_action(1)\r
+        self.user.reset_vote_up_count_cache()\r
+\r
+    def cancel_action(self):\r
+        super(VoteUpAction, self).cancel_action()\r
+        self.user.reset_vote_up_count_cache()\r
+\r
+    def describe(self, viewer=None):\r
+        return self.describe_vote(_("voted up"), viewer)\r
+\r
+class VoteDownAction(VoteAction):\r
+    def repute_users(self):\r
+        self.repute(self.node.author, -int(settings.REP_LOST_BY_DOWNVOTED))\r
+        self.repute(self.user, -int(settings.REP_LOST_BY_DOWNVOTING))\r
+\r
+    def process_action(self):\r
+        self.process_vote_action(-1)\r
+        self.user.reset_vote_down_count_cache()\r
+\r
+    def cancel_action(self):\r
+        super(VoteDownAction, self).cancel_action()\r
+        self.user.reset_vote_down_count_cache()\r
+\r
+    def describe(self, viewer=None):\r
+        return self.describe_vote(_("voted down"), viewer)\r
+\r
+\r
+class VoteUpCommentAction(VoteUpAction):\r
+    def repute_users(self):\r
+        pass\r
+\r
+    def process_action(self):\r
+        self.process_vote_action(1)\r
+\r
+    def cancel_action(self):\r
+        super(VoteUpAction, self).cancel_action()\r
+\r
+    def describe(self, viewer=None):\r
+        return self.describe_vote(_("liked"), viewer)\r
+\r
+\r
+class FlagAction(ActionProxy):\r
+    def repute_users(self):\r
+        self.repute(self.node.author, -int(settings.REP_LOST_BY_FLAGGED))\r
+\r
+    def process_action(self):\r
+        flag = Flag(user=self.user, node=self.node, action=self, reason=self.extra)\r
+        flag.save()\r
+        self.node.reset_flag_count_cache()\r
+\r
+        if self.node.flag_count == int(settings.FLAG_COUNT_TO_HIDE_POST):\r
+            self.repute(self.node.author, -int(settings.REP_LOST_BY_FLAGGED_3_TIMES))\r
+\r
+        if self.node.flag_count == int(settings.FLAG_COUNT_TO_DELETE_POST):\r
+            self.repute(self.node.author, -int(settings.REP_LOST_BY_FLAGGED_5_TIMES))\r
+            if not self.node.deleted:\r
+                DeleteAction(node=self.node, user=self.user, extra="BYFLAGGED").save()\r
+\r
+    def cancel_action(self):\r
+        self.flag.all()[0].delete()\r
+        self.node.reset_flag_count_cache()\r
+\r
+    @classmethod\r
+    def get_for(cls, user, node):\r
+        try:\r
+            flag = Flag.objects.get(user=user, node=node)\r
+            return flag.reason or _("No reason given")\r
+        except:\r
+            return None\r
+\r
+    def describe(self, viewer=None):\r
+        return _("%(user)s flagged %(post_desc)s: %(reason)s") % {\r
+            'user': self.hyperlink(self.user.get_profile_url(), self.friendly_username(viewer, self.user)),\r
+            'post_desc': self.describe_node(viewer, self.node), 'reason': self.extra\r
+        }\r
+\r
+\r
+class AcceptAnswerAction(ActionProxy):\r
+    def repute_users(self):\r
+        if (self.user == self.node.parent.author) and (not self.user == self.node.author):\r
+            self.repute(self.user, int(settings.REP_GAIN_BY_ACCEPTING))\r
+\r
+        if self.user != self.node.author:\r
+            self.repute(self.node.author, int(settings.REP_GAIN_BY_ACCEPTED))\r
+\r
+    def process_action(self):\r
+        self.node.parent.extra_ref = self.node\r
+        self.node.parent.save()\r
+        self.node.marked = True\r
+        self.node.extra_action = self\r
+        self.node.save()\r
+\r
+    def cancel_action(self):\r
+        self.node.parent.extra_ref = None\r
+        self.node.parent.save()\r
+        self.node.marked = False\r
+        self.node.extra_action = None\r
+        self.node.save()\r
+\r
+    def describe(self, viewer=None):\r
+        answer = self.node\r
+        question = answer.parent\r
+\r
+        if self.user == question.author:\r
+            asker = (self.user == viewer) and _("your") or _("his")\r
+        else:\r
+            asker = self.hyperlink(question.author.get_profile_url(), question.author.username)\r
+\r
+        return _("%(user)s accepted %(answerer)s answer on %(asker)s question %(question)s") % {\r
+            'user': self.hyperlink(self.user.get_profile_url(), self.friendly_username(viewer, self.user)),\r
+            'answerer': self.hyperlink(answer.author.get_profile_url(), self.friendly_username(viewer, answer.author)),\r
+            'asker': asker,\r
+            'question': self.hyperlink(question.get_absolute_url(), question.title)\r
+        }\r
+\r
+\r
+class FavoriteAction(ActionProxy):\r
+    def process_action(self):\r
+        self.node.reset_favorite_count_cache()\r
+\r
+    def cancel_action(self):\r
+        self.process_action()\r
+\r
+    def describe(self, viewer=None):\r
+        return _("%(user)s marked %(post_desc)s as favorite") % {\r
+            'user': self.hyperlink(self.user.get_profile_url(), self.friendly_username(viewer, self.user)),\r
+            'post_desc': self.describe_node(viewer, self.node),\r
+        }\r
+\r
+\r
+class DeleteAction(ActionProxy):\r
+    def process_action(self):\r
+        self.node.deleted = self\r
+        self.node.save()\r
+        \r
+        if self.node.node_type == "answer":\r
+            self.node.question.reset_answer_count_cache()\r
+\r
+    def cancel_action(self):\r
+        self.node.deleted = None\r
+        self.node.save()\r
+\r
+        if self.node.node_type == "answer":\r
+            self.node.question.reset_answer_count_cache()\r
+\r
+    def describe(self, viewer=None):\r
+        return _("%(user)s deleted %(post_desc)s: %(reason)s") % {\r
+            'user': self.hyperlink(self.user.get_profile_url(), self.friendly_username(viewer, self.user)),\r
+            'post_desc': self.describe_node(viewer, self.node), 'reason': self.reason(),\r
+        }\r
+\r
+    def reason(self):\r
+        if self.extra != "BYFLAGGED":\r
+            return self.extra\r
+        else:\r
+            return _("flagged by multiple users: ") + "; ".join([f.extra for f in FlagAction.objects.filter(node=self.node)])\r
+\r
+\r
+class QuestionViewAction(DummyActionProxy):\r
+    def __init__(self, question, user):\r
+        self.viewuser = user\r
+        self.question = question\r
+\r
+    def process_action(self):\r
+        self.question.extra_count = F('extra_count') + 1\r
+        self.question.save()\r
diff --git a/forum/actions/node.py b/forum/actions/node.py
new file mode 100644 (file)
index 0000000..d7f9383
--- /dev/null
@@ -0,0 +1,115 @@
+from django.utils.html import strip_tags\r
+from django.utils.translation import ugettext as _\r
+from forum.models.action import ActionProxy\r
+from forum.models import Comment, Question, Answer, NodeRevision\r
+\r
+class NodeEditAction(ActionProxy):\r
+    def create_revision_data(self, initial=False, **data):\r
+        revision_data = dict(summary=data.get('summary', (initial and _('Initial revision' or ''))), body=data['text'])\r
+\r
+        if data.get('title', None):\r
+            revision_data['title'] = strip_tags(data['title'].strip())\r
+\r
+        if data.get('tags', None):\r
+            revision_data['tagnames'] = data['tags'].strip()\r
+\r
+        return revision_data\r
+\r
+class AskAction(NodeEditAction):\r
+    def process_data(self, **data):\r
+        question = Question(author=self.user, **self.create_revision_data(True, **data))\r
+        question.save()\r
+        self.node = question\r
+\r
+    def describe(self, viewer=None):\r
+        return _("%(user)s asked %(question)s") % {\r
+            'user': self.hyperlink(self.user.get_profile_url(), self.friendly_username(viewer, self.user)),\r
+            'question': self.hyperlink(self.node.get_absolute_url(), self.node.title)\r
+        }\r
+\r
+class AnswerAction(NodeEditAction):\r
+    def process_data(self, **data):\r
+        answer = Answer(author=self.user, parent=data['question'], **self.create_revision_data(True, **data))\r
+        answer.save()\r
+        self.node = answer\r
+\r
+    def process_action(self):\r
+        self.node.question.reset_answer_count_cache()\r
+\r
+    def describe(self, viewer=None):\r
+        question = self.node.parent\r
+        return _("%(user)s answered %(asker)s %(question)s") % {\r
+            'user': self.hyperlink(self.user.get_profile_url(), self.friendly_username(viewer, self.user)),\r
+            'asker': self.hyperlink(question.author.get_profile_url(), self.friendly_username(viewer, question.author)),\r
+            'question': self.hyperlink(self.node.get_absolute_url(), question.title)\r
+        }\r
+\r
+class CommentAction(ActionProxy):\r
+    def process_data(self, text='', parent=None):\r
+        comment = Comment(author=self.user, parent=parent, body=text)\r
+        comment.save()\r
+        self.node = comment\r
+\r
+    def describe(self, viewer=None):\r
+        return _("%(user)s commented on %(post_desc)s") % {\r
+            'user': self.hyperlink(self.node.author.get_profile_url(), self.friendly_username(viewer, self.node.author)),\r
+            'post_desc': self.describe_node(viewer, self.node.parent)\r
+        }\r
+\r
+class ReviseAction(NodeEditAction):\r
+    def process_data(self, **data):\r
+        revision_data = self.create_revision_data(**data)\r
+        revision = self.node.create_revision(self.user, action=self, **revision_data)\r
+        self.extra = revision.revision\r
+\r
+    def describe(self, viewer=None):\r
+        return _("%(user)s edited %(post_desc)s") % {\r
+            'user': self.hyperlink(self.user.get_profile_url(), self.friendly_username(viewer, self.user)),\r
+            'post_desc': self.describe_node(viewer, self.node)\r
+        }\r
+\r
+class RetagAction(ActionProxy):\r
+    def process_data(self, tagnames=''):\r
+        active = self.node.active_revision\r
+        revision_data = dict(summary=_('Retag'), title=active.title, tagnames=strip_tags(tagnames.strip()), body=active.body)\r
+        self.node.create_revision(self.user, action=self, **revision_data)\r
+\r
+    def describe(self, viewer=None):\r
+        return _("%(user)s retagged %(post_desc)s") % {\r
+            'user': self.hyperlink(self.user.get_profile_url(), self.friendly_username(viewer, self.user)),\r
+            'post_desc': self.describe_node(viewer, self.node)\r
+        }\r
+\r
+class RollbackAction(ActionProxy):\r
+    def process_data(self, activate=None):\r
+        previous = self.node.active_revision\r
+        self.node.activate_revision(self.user, activate, self)\r
+        self.extra = "%d:%d" % (previous.revision, activate.revision)\r
+\r
+    def describe(self, viewer=None):\r
+        revisions = [NodeRevision.objects.get(node=self.node, revision=int(n)) for n in self.extra.split(':')]\r
+\r
+        return _("%(user)s reverted %(post_desc)s from revision %(initial)d (%(initial_sum)s) to revision %(final)d (%(final_sum)s)") % {\r
+            'user': self.hyperlink(self.user.get_profile_url(), self.friendly_username(viewer, self.user)),\r
+            'post_desc': self.describe_node(viewer, self.node),\r
+            'initial': revisions[0].revision, 'initial_sum': revisions[0].summary,\r
+            'final': revisions[1].revision, 'final_sum': revisions[1].summary,\r
+        }\r
+\r
+class CloseAction(ActionProxy):\r
+    def process_action(self):\r
+        self.node.extra_action = self\r
+        self.node.marked = True\r
+        self.node.save()\r
+\r
+    def cancel_action(self):\r
+        self.node.extra_action = None\r
+        self.node.marked = False\r
+        self.node.save()\r
+\r
+    def describe(self, viewer=None):\r
+        return _("%(user)s closed %(post_desc)s: %(reason)s") % {\r
+            'user': self.hyperlink(self.user.get_profile_url(), self.friendly_username(viewer, self.user)),\r
+            'post_desc': self.describe_node(viewer, self.node),\r
+            'reason': self.extra\r
+        }
\ No newline at end of file
diff --git a/forum/actions/user.py b/forum/actions/user.py
new file mode 100644 (file)
index 0000000..2695f9f
--- /dev/null
@@ -0,0 +1,53 @@
+from django.utils.translation import ugettext as _\r
+from django.db.models import F\r
+from forum.models.action import ActionProxy\r
+from forum.models import Award\r
+from forum import settings\r
+\r
+class UserJoinsAction(ActionProxy):\r
+    def repute_users(self):\r
+        self.repute(self.user, int(settings.INITIAL_REP))\r
+\r
+class EditProfileAction(ActionProxy):\r
+    pass\r
+\r
+class AwardAction(ActionProxy):\r
+    def process_data(self, badge, trigger):\r
+        self.__dict__['_badge'] = badge\r
+        self.__dict__['_trigger'] = trigger\r
+\r
+    def process_action(self):\r
+        badge = self.__dict__['_badge']\r
+        trigger = self.__dict__['_trigger']\r
+\r
+        award = Award(user=self.user, badge=badge, trigger=trigger, action=self)\r
+        if self.node:\r
+            award.node = self.node\r
+\r
+        award.save()\r
+        award.badge.awarded_count = F('awarded_count') + 1\r
+        award.badge.save()\r
+        self.user.message_set.create(message=_("Congratulations, you have received a badge '%(badge_name)s'. " \\r
+                + u"Check out <a href=\"%(profile_url)s\">your profile</a>.") % dict(badge_name=award.badge.name,\r
+                                                                                    profile_url=self.user.get_profile_url()))\r
+\r
+    def cancel_action(self):\r
+        award = self.award\r
+        badge = award.badge\r
+        badge.awarded_count = F('awarded_count') - 1\r
+        badge.save()\r
+        award.delete()\r
+\r
+    @classmethod\r
+    def get_for(cls, user, node, badge):\r
+        try:\r
+            award = Award.objects.get(user=user, node=node, badge=badge)\r
+            return award.action\r
+        except:\r
+            return None\r
+\r
+    def describe(self, viewer=None):\r
+        return _("%(user)s was awarded the %(badge_name)s badge") % {\r
+            'user': self.hyperlink(self.user.get_profile_url(), self.friendly_username(viewer, self.user)),\r
+            'badge_name': self.award.all()[0].badge.name,\r
+        }
\ No newline at end of file
diff --git a/forum/activity.py b/forum/activity.py
deleted file mode 100644 (file)
index 1687798..0000000
+++ /dev/null
@@ -1,121 +0,0 @@
-import datetime\r
-from django.db.models.signals import post_save\r
-from forum.models import *\r
-from forum.models.base import marked_deleted, mark_canceled\r
-from forum.models.node import node_create\r
-from forum.models.answer import answer_accepted\r
-from forum.authentication import user_updated\r
-from forum.const import *\r
-\r
-def record_ask_event(instance, **kwargs):\r
-    activity = Activity(user=instance.author, active_at=instance.added_at, content_object=instance, activity_type=TYPE_ACTIVITY_ASK_QUESTION)\r
-    activity.save()\r
-\r
-node_create.connect(record_ask_event, sender=Question)\r
-\r
-\r
-def record_answer_event(instance, **kwargs):\r
-    activity = Activity(user=instance.author, active_at=instance.added_at, content_object=instance, activity_type=TYPE_ACTIVITY_ANSWER)\r
-    activity.save()\r
-\r
-node_create.connect(record_answer_event, sender=Answer)\r
-\r
-\r
-def record_comment_event(instance, **kwargs):\r
-    act_type = (instance.content_object.__class__ is Question) and TYPE_ACTIVITY_COMMENT_QUESTION or TYPE_ACTIVITY_COMMENT_ANSWER\r
-    activity = Activity(user=instance.user, active_at=instance.added_at, content_object=instance, activity_type=act_type)\r
-    activity.save()\r
-\r
-node_create.connect(record_comment_event, sender=Comment)\r
-\r
-\r
-def record_revision_event(instance, created, **kwargs):\r
-    if created and instance.revision <> 1 and instance.node.node_type in ('question', 'answer',):\r
-        activity_type = instance.node.node_type == 'question' and TYPE_ACTIVITY_UPDATE_QUESTION or TYPE_ACTIVITY_UPDATE_ANSWER\r
-        activity = Activity(user=instance.author, active_at=instance.revised_at, content_object=instance, activity_type=activity_type)\r
-        activity.save()\r
-\r
-post_save.connect(record_revision_event, sender=NodeRevision)\r
-\r
-\r
-def record_award_event(instance, created, **kwargs):\r
-    if created:\r
-        activity = Activity(user=instance.user, active_at=instance.awarded_at, content_object=instance,\r
-            activity_type=TYPE_ACTIVITY_PRIZE)\r
-        activity.save()\r
-\r
-post_save.connect(record_award_event, sender=Award)\r
-\r
-\r
-def record_answer_accepted(answer, user, **kwargs):\r
-    activity = Activity(user=user, active_at=datetime.datetime.now(), content_object=answer, activity_type=TYPE_ACTIVITY_MARK_ANSWER)\r
-    activity.save()\r
-\r
-answer_accepted.connect(record_answer_accepted)\r
-\r
-\r
-def update_last_seen(instance, **kwargs):\r
-    user = instance.user\r
-    user.last_seen = datetime.datetime.now()\r
-    user.save()\r
-\r
-post_save.connect(update_last_seen, sender=Activity)\r
-\r
-\r
-def record_vote(instance, created, **kwargs):\r
-    if created:\r
-        act_type = (instance.vote == 1) and TYPE_ACTIVITY_VOTE_UP or TYPE_ACTIVITY_VOTE_DOWN\r
-\r
-        activity = Activity(user=instance.user, active_at=instance.voted_at, content_object=instance, activity_type=act_type)\r
-        activity.save()\r
-\r
-post_save.connect(record_vote, sender=Vote)\r
-\r
-\r
-def record_cancel_vote(instance, **kwargs):\r
-    act_type = (instance.vote == 1) and TYPE_ACTIVITY_CANCEL_VOTE_UP or TYPE_ACTIVITY_CANCEL_VOTE_DOWN\r
-    activity = Activity(user=instance.user, active_at=datetime.datetime.now(), content_object=instance, activity_type=act_type)\r
-    activity.save()\r
-\r
-mark_canceled.connect(record_cancel_vote, sender=Vote)\r
-\r
-\r
-def record_delete_post(instance, **kwargs):\r
-    act_type = (instance.__class__ is Question) and TYPE_ACTIVITY_DELETE_QUESTION or TYPE_ACTIVITY_DELETE_ANSWER\r
-    activity = Activity(user=instance.deleted_by, active_at=datetime.datetime.now(), content_object=instance, activity_type=act_type)\r
-    activity.save()\r
-\r
-marked_deleted.connect(record_delete_post, sender=Question)\r
-marked_deleted.connect(record_delete_post, sender=Answer)\r
-\r
-\r
-def record_update_tags(instance, created, **kwargs):\r
-    if not created and 'tagnames' in instance.get_dirty_fields():\r
-        activity = Activity(user=instance.author, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_UPDATE_TAGS)\r
-        activity.save()\r
-\r
-post_save.connect(record_update_tags, sender=Question)\r
-\r
-\r
-def record_mark_offensive(instance, created, **kwargs):\r
-    if created:\r
-        activity = Activity(user=instance.user, active_at=datetime.datetime.now(), content_object=instance.content_object, activity_type=TYPE_ACTIVITY_MARK_OFFENSIVE)\r
-        activity.save()\r
-\r
-post_save.connect(record_mark_offensive, sender=FlaggedItem)\r
-\r
-\r
-def record_favorite_question(instance, created, **kwargs):\r
-    if created:\r
-        activity = Activity(user=instance.user, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_FAVORITE)\r
-        activity.save()\r
-\r
-post_save.connect(record_favorite_question, sender=FavoriteQuestion)\r
-\r
-\r
-def record_user_full_updated(instance, **kwargs):\r
-    activity = Activity(user=instance, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_USER_FULL_UPDATED)\r
-    activity.save()\r
-\r
-user_updated.connect(record_user_full_updated, sender=User)\r
-\r
index 3afa224181b9d0abaa42478139d2e21273f1f174..e77080600564ead399d8232d841ac1013aef570d 100644 (file)
@@ -6,7 +6,7 @@ from models import *
 class AnonymousQuestionAdmin(admin.ModelAdmin):
     """AnonymousQuestion admin class"""
 
 class AnonymousQuestionAdmin(admin.ModelAdmin):
     """AnonymousQuestion admin class"""
 
-class QuestionAdmin(admin.ModelAdmin):
+class NodeAdmin(admin.ModelAdmin):
     """Question admin class"""
 
 class TagAdmin(admin.ModelAdmin):
     """Question admin class"""
 
 class TagAdmin(admin.ModelAdmin):
@@ -42,7 +42,7 @@ class BadgeAdmin(admin.ModelAdmin):
 class ReputeAdmin(admin.ModelAdmin):
     """  admin class"""
 
 class ReputeAdmin(admin.ModelAdmin):
     """  admin class"""
 
-class ActivityAdmin(admin.ModelAdmin):
+class ActionAdmin(admin.ModelAdmin):
     """  admin class"""
     
 #class BookAdmin(admin.ModelAdmin):
     """  admin class"""
     
 #class BookAdmin(admin.ModelAdmin):
@@ -54,19 +54,13 @@ class ActivityAdmin(admin.ModelAdmin):
 #class BookAuthorRssAdmin(admin.ModelAdmin):
 #    """  admin class"""
     
 #class BookAuthorRssAdmin(admin.ModelAdmin):
 #    """  admin class"""
     
-admin.site.register(Question, QuestionAdmin)
+admin.site.register(Node, NodeAdmin)
 admin.site.register(Tag, TagAdmin)
 admin.site.register(Tag, TagAdmin)
-admin.site.register(Answer, Answerdmin)
-admin.site.register(Comment, CommentAdmin)
-admin.site.register(Vote, VoteAdmin)
-admin.site.register(FlaggedItem, FlaggedItemAdmin)
-admin.site.register(FavoriteQuestion, FavoriteQuestionAdmin)
 admin.site.register(QuestionRevision, QuestionRevisionAdmin)
 admin.site.register(AnswerRevision, AnswerRevisionAdmin)
 admin.site.register(Badge, BadgeAdmin)
 admin.site.register(Award, AwardAdmin)
 admin.site.register(QuestionRevision, QuestionRevisionAdmin)
 admin.site.register(AnswerRevision, AnswerRevisionAdmin)
 admin.site.register(Badge, BadgeAdmin)
 admin.site.register(Award, AwardAdmin)
-admin.site.register(Repute, ReputeAdmin)
-admin.site.register(Activity, ActivityAdmin)
+admin.site.register(Action, ActionAdmin)
 #admin.site.register(Book, BookAdmin)
 #admin.site.register(BookAuthorInfo, BookAuthorInfoAdmin)
 #admin.site.register(BookAuthorRss, BookAuthorRssAdmin)
 #admin.site.register(Book, BookAdmin)
 #admin.site.register(BookAuthorInfo, BookAuthorInfoAdmin)
 #admin.site.register(BookAuthorRss, BookAuthorRssAdmin)
index 940e5ddd1f0916227fc74b012055f2f4fc88c641..4138d5ad1453b4f413e860a8ec20fa7b00526cab 100644 (file)
@@ -119,8 +119,8 @@ class Akismet(object):
         if agent is None:
             agent = DEFAULTAGENT % __version__
         self.user_agent = user_agent % (agent, __version__)
         if agent is None:
             agent = DEFAULTAGENT % __version__
         self.user_agent = user_agent % (agent, __version__)
-        self.key = settings.WORDPRESS_API_KEY
-        self.blog_url = settings.WORDPRESS_BLOG_URL
+        self.key = str(settings.WORDPRESS_API_KEY)
+        self.blog_url = str(settings.WORDPRESS_BLOG_URL)
 
 
     def _getURL(self):
 
 
     def _getURL(self):
index f6e14b76a11564c94862efd8015784e3bae412b1..08ea03fd0575aa799464ade7274b8625223b6b6b 100644 (file)
@@ -34,7 +34,6 @@ AUTH_PROVIDERS = dict([
 
 #todo: probably this don't belong here, also this post_stored routine needs a lot of work
 user_logged_in = django.dispatch.Signal(providing_args=["user", "old_session"])
 
 #todo: probably this don't belong here, also this post_stored routine needs a lot of work
 user_logged_in = django.dispatch.Signal(providing_args=["user", "old_session"])
-user_updated = django.dispatch.Signal(providing_args=["instance", "updated_by"])
 
 #def post_stored_anonymous_content(user,old_session,**kwargs):
 #    from forum.models import AnonymousQuestion, AnonymousAnswer
 
 #def post_stored_anonymous_content(user,old_session,**kwargs):
 #    from forum.models import AnonymousQuestion, AnonymousAnswer
index 421348fed9966b4eb3611f246210fa1b646b2ee0..935effdbe2ff382d1009aac9bc182c9633bf228f 100644 (file)
@@ -1,10 +1,3 @@
-import re
+from forum.modules import get_modules_script
 
 
-from forum.badges.base import AbstractBadge
-from forum.modules import get_modules_script_classes
-
-ALL_BADGES = [
-            cls() for name, cls
-            in get_modules_script_classes('badges', AbstractBadge).items()
-            if not re.search('AbstractBadge$', name)
-        ]
+get_modules_script('badges')
index 63b6b0be1ec8c41c4de377a902fb186f6f1de93f..fce6b6e30301c542a585f76ba8e2bd799eabb9fc 100644 (file)
@@ -4,111 +4,66 @@ from string import lower
 from django.contrib.contenttypes.models import ContentType
 from django.db.models.signals import post_save
 
 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
+from forum.models import Badge, Node
+from forum.actions import AwardAction
 
 import logging
 
 
 import logging
 
-class AbstractBadge(object):
-
-    _instance = None
-
-    @property
-    def name(self):
-        return " ".join(re.findall(r'([A-Z][a-z1-9]+)', re.sub('Badge', '', self.__class__.__name__)))
-
-    @property
-    def description(self):
-        raise NotImplementedError
-
-    def __new__(cls, *args, **kwargs):
-        if cls._instance is None:
-            cls.badge = "-".join(map(lower, re.findall(r'([A-Z][a-z1-9]+)', re.sub('Badge', '', cls.__name__)))) 
-            cls._instance = super(AbstractBadge, cls).__new__(cls, *args, **kwargs)
-
-        return cls._instance
-
-    def install(self):
-        try:
-            installed = Badge.objects.get(slug=self.badge)
-        except:
-            badge = Badge(name=self.name, description=self.description, slug=self.badge, type=self.type)
-            badge.save()
-
-    def award_badge(self, user, obj=None, award_once=False):
-        try:
-            badge = Badge.objects.get(slug=self.badge)
-        except:
-            logging.log(1, 'Trying to award a badge not installed in the database.')
-            return
-            
-        content_type = ContentType.objects.get_for_model(obj.__class__)
-
-        awarded = user.awards.filter(badge=badge)
+installed = dict([(b.cls, b) for b in Badge.objects.all()])
 
 
-        if not award_once:
-            awarded = awarded.filter(content_type=content_type, object_id=obj.id)
+class BadgesMeta(type):
+    by_class = {}
+    by_id = {}
 
 
-        if len(awarded):
-            logging.log(1, 'Trying to award badged already awarded.')
-            return
-            
-        award = Award(user=user, badge=badge, content_type=content_type, object_id=obj.id)
-        award.save()
+    def __new__(mcs, name, bases, dic):
+        badge = type.__new__(mcs, name, bases, dic)
 
 
-class CountableAbstractBadge(AbstractBadge):
+        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()
+            else:
+                badge.ondb = installed[name]
 
 
-    def __init__(self, model, field, expected_value, handler):
-        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)
-        
-        denorm_update.connect(wrapper, sender=model, weak=False)
+            inst = badge()
 
 
-class PostCountableAbstractBadge(CountableAbstractBadge):
-    def __init__(self, model, field, expected_value):
+            def hook(action, new):
+                user = inst.award_to(action)
 
 
-        def handler(instance):            
-            self.award_badge(instance.author, instance)
+                if user:
+                    badge.award(user, action, badge.award_once)
 
 
-        super(PostCountableAbstractBadge, self).__init__(model, field, expected_value, handler)
+            for action in badge.listen_to:
+                action.hook(hook)
 
 
-class NodeCountableAbstractBadge(CountableAbstractBadge):
-    def __init__(self, node_type, field, expected_value):
+            BadgesMeta.by_class[name] = badge
+            badge.ondb.__dict__['_class'] = inst
+            BadgesMeta.by_id[badge.ondb.id] = badge
 
 
-        def handler(instance):
-            if instance.node_type == node_type:
-                self.award_badge(instance.author, instance)
+        return badge
 
 
-        super(NodeCountableAbstractBadge, self).__init__(Node, field, expected_value, handler)
-
-class ActivityAbstractBadge(AbstractBadge):
-
-    def __init__(self, activity_type, handler):
-
-        def wrapper(sender, **kwargs):
-            handler(instance=kwargs['instance'])
-
-        activity_record.connect(wrapper, sender=activity_type, weak=False)
-
-
-class ActivityCountAbstractBadge(AbstractBadge):
+class AbstractBadge(object):
+    __metaclass__ = BadgesMeta
 
 
-    def __init__(self, activity_type, count):
+    abstract = True
+    award_once = False
 
 
-        def handler(sender, **kwargs):
-            instance = kwargs['instance']
-            if Activity.objects.filter(user=instance.user, activity_type__in=activity_type).count() == count:
-                self.award_badge(instance.user, instance.content_object)
+    @property
+    def name(self):
+        raise NotImplementedError
 
 
-        if not isinstance(activity_type, (tuple, list)):
-            activity_type = (activity_type, )
+    @property
+    def description(self):
+        raise NotImplementedError
 
 
-        for type in activity_type:
-            activity_record.connect(handler, sender=type, weak=False)
+    @classmethod
+    def award(cls, user, action, once=False):
+        if once:
+            node = None
+        else:
+            node = action.node
 
 
-class FirstActivityAbstractBadge(ActivityCountAbstractBadge):
+        awarded = AwardAction.get_for(user, node, cls.ondb)
 
 
-    def __init__(self, activity_type):
-        super(FirstActivityAbstractBadge, self).__init__(activity_type, 1)
+        if not awarded:
+            AwardAction(user=user, node=node, ip=action.ip).save(data=dict(badge=cls.ondb, trigger=action))
\ No newline at end of file
index 0a0882548eb33f8d19cf61253fa26f4b4452f574..4017d61e35eec34146b2da441181ee23faf1a03b 100644 (file)
@@ -34,7 +34,7 @@ class RssLastestQuestionsFeed(Feed):
         return item.added_at
 
     def items(self, item):
         return item.added_at
 
     def items(self, item):
-       return Question.objects.filter(deleted=False).order_by('-last_activity_at')[:30]
+       return Question.objects.filter(deleted=None).order_by('-last_activity_at')[:30]
 
 def main():
     pass
 
 def main():
     pass
index 35c6083a8a0732668631d740c83cff46b212dcc0..70466b57c2478f2d6771bbb6172bc806855f028b 100644 (file)
@@ -101,18 +101,6 @@ class SummaryField(forms.CharField):
         self.label  = _('update summary:')
         self.help_text = _('enter a brief summary of your revision (e.g. fixed spelling, grammar, improved style, this field is optional)')
 
         self.label  = _('update summary:')
         self.help_text = _('enter a brief summary of your revision (e.g. fixed spelling, grammar, improved style, this field is optional)')
 
-class ModerateUserForm(forms.ModelForm):
-    is_approved = forms.BooleanField(label=_("Automatically accept user's contributions for the email updates"),
-                                     required=False)
-
-    def clean_is_approved(self):
-        if 'is_approved' not in self.cleaned_data:
-            self.cleaned_data['is_approved'] = False
-        return self.cleaned_data['is_approved']
-
-    class Meta:
-        model = User
-        fields = ('is_approved',)
 
 class FeedbackForm(forms.Form):
     name = forms.CharField(label=_('Your name:'), required=False)
 
 class FeedbackForm(forms.Form):
     name = forms.CharField(label=_('Your name:'), required=False)
index 49ad8d50340cc785c83c2d1a6290d4558a996630..b654caaa18ac13e43ce4d3b3652264cc6c3207a4 100644 (file)
@@ -1,13 +1,3 @@
 from forum.modules import get_modules_script
 from forum.modules import get_modules_script
-from django.db.models.signals import post_syncdb
-import forum.models
-
-def setup_badges(sender, **kwargs):
-    from forum.badges import ALL_BADGES
-
-    for badge in ALL_BADGES:
-        badge.install()
-
-post_syncdb.connect(setup_badges, sender=forum.models)
 
 get_modules_script('management')
\ No newline at end of file
 
 get_modules_script('management')
\ No newline at end of file
index 54ef07805390b58352c8a7294a4f3e620e1fa2c8..e73c960be836a279edcaf1f7f55a02e041055d6a 100644 (file)
@@ -12,10 +12,9 @@ class Migration(SchemaMigration):
         # Adding field 'Question.accepted_answer'\r
         db.add_column(u'question', 'accepted_answer', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['forum.Answer'], unique=True, null=True), keep_default=False)\r
 \r
         # Adding field 'Question.accepted_answer'\r
         db.add_column(u'question', 'accepted_answer', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['forum.Answer'], unique=True, null=True), keep_default=False)\r
 \r
-        if db.backend_name == "postgres" and not "pgfulltext" in settings.DISABLED_MODULES:\r
-            pass\r
-            db.execute("DROP TRIGGER tsvectorupdate ON question;")\r
-            db.execute("ALTER TABLE question DROP COLUMN tsv;")\r
+        #if db.backend_name == "postgres" and not "pgfulltext" in settings.DISABLED_MODULES:\r
+        #    db.execute("DROP TRIGGER tsvectorupdate ON question;")\r
+        #    db.execute("ALTER TABLE question DROP COLUMN tsv;")\r
     \r
     def backwards(self, orm):\r
         \r
     \r
     def backwards(self, orm):\r
         \r
diff --git a/forum/migrations/0022_auto__add_actionrepute__add_action__add_favoritenode__del_field_node_v.py b/forum/migrations/0022_auto__add_actionrepute__add_action__add_favoritenode__del_field_node_v.py
new file mode 100644 (file)
index 0000000..87b792e
--- /dev/null
@@ -0,0 +1,463 @@
+# encoding: utf-8\r
+import datetime\r
+from south.db import db\r
+from south.v2 import SchemaMigration\r
+from django.db import models\r
+\r
+class Migration(SchemaMigration):\r
+    \r
+    def forwards(self, orm):\r
+        \r
+        print "# Adding model 'ActionRepute'"\r
+        db.create_table('forum_actionrepute', (\r
+            ('action', self.gf('django.db.models.fields.related.ForeignKey')(related_name='reputes', to=orm['forum.Action'])),\r
+            ('by_canceled', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True)),\r
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),\r
+            ('value', self.gf('django.db.models.fields.IntegerField')(default=0)),\r
+            ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['forum.User'])),\r
+        ))\r
+        db.send_create_signal('forum', ['ActionRepute'])\r
+\r
+        print "# Adding model 'Action'"\r
+        db.create_table('forum_action', (\r
+            ('node', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['forum.Node'], null=True)),\r
+            ('extra', self.gf('django.db.models.fields.CharField')(max_length=255)),\r
+            ('canceled_by', self.gf('django.db.models.fields.related.ForeignKey')(related_name='canceled_actions', null=True, to=orm['forum.User'])),\r
+            ('canceled', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True)),\r
+            ('action_date', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)),\r
+            ('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='actions', to=orm['forum.User'])),\r
+            ('action_type', self.gf('django.db.models.fields.CharField')(max_length=16)),\r
+            ('canceled_at', self.gf('django.db.models.fields.DateTimeField')(null=True)),\r
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),\r
+            ('ip', self.gf('django.db.models.fields.CharField')(max_length=16, null=True)),\r
+        ))\r
+        db.send_create_signal('forum', ['Action'])\r
+\r
+        print "# Deleting field 'Node.vote_up_count'"\r
+        db.delete_column('forum_node', 'vote_up_count')\r
+\r
+        print "# Deleting field 'Node.comment_count'"\r
+        db.delete_column('forum_node', 'comment_count')\r
+\r
+        print "# Deleting field 'Node.offensive_flag_count'"\r
+        db.delete_column('forum_node', 'offensive_flag_count')\r
+\r
+        print "# Deleting field 'Node.vote_down_count'"\r
+        db.delete_column('forum_node', 'vote_down_count')\r
+\r
+        print "# Adding field 'Node.wiki'"\r
+        db.add_column('forum_node', 'wiki', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True), keep_default=False)\r
+\r
+        print "# Adding field 'Node.marked'"\r
+        db.add_column('forum_node', 'marked', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True), keep_default=False)\r
+\r
+        print "# Adding field 'Node.extra_count'"\r
+        db.add_column('forum_node', 'extra_count', self.gf('django.db.models.fields.IntegerField')(default=0), keep_default=False)\r
+\r
+        print "# Adding field 'Node.last_activity_by'"\r
+        db.add_column('forum_node', 'last_activity_by', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['forum.User'], null=True), keep_default=False)\r
+\r
+        print "# Adding field 'Node.extra_ref'"\r
+        db.add_column('forum_node', 'extra_ref', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['forum.Node'], null=True), keep_default=False)\r
+\r
+        print "# Adding field 'Node.last_activity_at'"\r
+        db.add_column('forum_node', 'last_activity_at', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True), keep_default=False)\r
+\r
+        # Changing field 'Answer.node_ptr'\r
+        #db.alter_column(u'answer', 'node_ptr_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['forum.Node'], null=True, primary_key=True))\r
+\r
+        print "# Changing field 'QuestionSubscription.question'"\r
+        db.alter_column('forum_questionsubscription', 'question_id', self.gf('django.db.models.fields.IntegerField')())\r
+\r
+        # Removing unique constraint on 'Award', fields ['badge', 'object_id', 'content_type', 'user']\r
+        #db.delete_unique(u'award', ['badge_id', 'object_id', 'content_type_id', 'user_id'])\r
+\r
+        print "# Changing field 'User.gold'"\r
+        db.alter_column('forum_user', 'gold', self.gf('django.db.models.fields.PositiveIntegerField')())\r
+\r
+        print "# Changing field 'User.silver'"\r
+        db.alter_column('forum_user', 'silver', self.gf('django.db.models.fields.PositiveIntegerField')())\r
+\r
+        print "# Changing field 'User.bronze'"\r
+        db.alter_column('forum_user', 'bronze', self.gf('django.db.models.fields.PositiveIntegerField')())\r
+\r
+        print "# Deleting field 'Question.answer_count'"\r
+        db.delete_column(u'question', 'answer_count')\r
+\r
+        print "# Deleting field 'Question.favourite_count'"\r
+        db.delete_column(u'question', 'favourite_count')\r
+\r
+        # Changing field 'Question.node_ptr'\r
+        #db.alter_column(u'question', 'node_ptr_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['forum.Node'], null=True, primary_key=True))\r
+    \r
+    \r
+    def backwards(self, orm):\r
+        \r
+        # Deleting model 'ActionRepute'\r
+        db.delete_table('forum_actionrepute')\r
+\r
+        # Deleting model 'Action'\r
+        db.delete_table('forum_action')\r
+\r
+        # Deleting model 'FavoriteNode'\r
+        db.delete_table('forum_favoritenode')\r
+\r
+        # Adding field 'Node.vote_up_count'\r
+        db.add_column('forum_node', 'vote_up_count', self.gf('django.db.models.fields.IntegerField')(default=0), keep_default=False)\r
+\r
+        # Adding field 'Node.comment_count'\r
+        db.add_column('forum_node', 'comment_count', self.gf('django.db.models.fields.PositiveIntegerField')(default=0), keep_default=False)\r
+\r
+        # Adding field 'Node.offensive_flag_count'\r
+        db.add_column('forum_node', 'offensive_flag_count', self.gf('django.db.models.fields.SmallIntegerField')(default=0), keep_default=False)\r
+\r
+        # Adding field 'Node.vote_down_count'\r
+        db.add_column('forum_node', 'vote_down_count', self.gf('django.db.models.fields.IntegerField')(default=0), keep_default=False)\r
+\r
+        # Deleting field 'Node.wiki'\r
+        db.delete_column('forum_node', 'wiki')\r
+\r
+        # Deleting field 'Node.marked'\r
+        db.delete_column('forum_node', 'marked')\r
+\r
+        # Deleting field 'Node.extra_count'\r
+        db.delete_column('forum_node', 'extra_count')\r
+\r
+        # Deleting field 'Node.last_activity_by'\r
+        db.delete_column('forum_node', 'last_activity_by_id')\r
+\r
+        # Deleting field 'Node.extra_ref'\r
+        db.delete_column('forum_node', 'extra_ref_id')\r
+\r
+        # Deleting field 'Node.last_activity_at'\r
+        db.delete_column('forum_node', 'last_activity_at')\r
+\r
+        # Changing field 'Answer.node_ptr'\r
+        db.alter_column(u'answer', 'node_ptr_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['forum.Node'], unique=True, primary_key=True))\r
+\r
+        # Adding unique constraint on 'Award', fields ['badge', 'object_id', 'content_type', 'user']\r
+        db.create_unique(u'award', ['badge_id', 'object_id', 'content_type_id', 'user_id'])\r
+\r
+        # Changing field 'User.gold'\r
+        db.alter_column('forum_user', 'gold', self.gf('django.db.models.fields.SmallIntegerField')())\r
+\r
+        # Changing field 'User.silver'\r
+        db.alter_column('forum_user', 'silver', self.gf('django.db.models.fields.SmallIntegerField')())\r
+\r
+        # Changing field 'User.bronze'\r
+        db.alter_column('forum_user', 'bronze', self.gf('django.db.models.fields.SmallIntegerField')())\r
+\r
+        # Adding field 'Question.answer_count'\r
+        db.add_column(u'question', 'answer_count', self.gf('django.db.models.fields.PositiveIntegerField')(default=0), keep_default=False)\r
+\r
+        # Adding field 'Question.favourite_count'\r
+        db.add_column(u'question', 'favourite_count', self.gf('django.db.models.fields.IntegerField')(default=0), keep_default=False)\r
+\r
+        # Changing field 'Question.node_ptr'\r
+        db.alter_column(u'question', 'node_ptr_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['forum.Node'], unique=True))\r
+    \r
+    \r
+    models = {\r
+        'auth.group': {\r
+            'Meta': {'object_name': 'Group'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),\r
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'})\r
+        },\r
+        'auth.permission': {\r
+            'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},\r
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})\r
+        },\r
+        'auth.user': {\r
+            'Meta': {'object_name': 'User'},\r
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),\r
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),\r
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),\r
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),\r
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'}),\r
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})\r
+        },\r
+        'contenttypes.contenttype': {\r
+            'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},\r
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})\r
+        },\r
+        'forum.action': {\r
+            'Meta': {'object_name': 'Action'},\r
+            'action_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'action_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'canceled_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),\r
+            'canceled_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'canceled_actions'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'extra': ('django.db.models.fields.CharField', [], {'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'ip': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']", 'null': 'True'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.actionrepute': {\r
+            'Meta': {'object_name': 'ActionRepute'},\r
+            'action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reputes'", 'to': "orm['forum.Action']"}),\r
+            'by_canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"}),\r
+            'value': ('django.db.models.fields.IntegerField', [], {'default': '0'})\r
+        },\r
+        'forum.activity': {\r
+            'Meta': {'object_name': 'Activity', 'db_table': "u'activity'"},\r
+            'active_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'activity_type': ('django.db.models.fields.SmallIntegerField', [], {}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'is_auditted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        },\r
+        'forum.anonymousnode': {\r
+            'Meta': {'object_name': 'AnonymousNode', '_ormbases': ['forum.Node']},\r
+            'convertible_to': ('django.db.models.fields.CharField', [], {'default': "'node'", 'max_length': '16'}),\r
+            'node_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['forum.Node']", 'unique': 'True', 'primary_key': 'True'}),\r
+            'validation_hash': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'anonymous_content'", 'to': "orm['forum.Node']"})\r
+        },\r
+        'forum.answer': {\r
+            'Meta': {'object_name': 'Answer', 'db_table': "u'answer'"},\r
+            'accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'accepted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'accepted_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']", 'null': 'True'}),\r
+            'node_ptr': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']", 'null': 'True', 'primary_key': 'True'}),\r
+            'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'wikified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})\r
+        },\r
+        'forum.authkeyuserassociation': {\r
+            'Meta': {'object_name': 'AuthKeyUserAssociation'},\r
+            'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'provider': ('django.db.models.fields.CharField', [], {'max_length': '64'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_keys'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.award': {\r
+            'Meta': {'object_name': 'Award', 'db_table': "u'award'"},\r
+            'awarded_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'badge': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award_badge'", 'to': "orm['forum.Badge']"}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'notified': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'awards'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.badge': {\r
+            'Meta': {'unique_together': "(('name', 'type'),)", 'object_name': 'Badge', 'db_table': "u'badge'"},\r
+            'awarded_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'awarded_to': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'badges'", 'through': "'Award'", 'to': "orm['forum.User']"}),\r
+            'description': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'multiple': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),\r
+            'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '50', 'blank': 'True'}),\r
+            'type': ('django.db.models.fields.SmallIntegerField', [], {})\r
+        },\r
+        'forum.favoritequestion': {\r
+            'Meta': {'unique_together': "(('question', 'user'),)", 'object_name': 'FavoriteQuestion', 'db_table': "u'favorite_question'"},\r
+            'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'question': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'favourites'", 'to': "orm['forum.Question']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_favorite_questions'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.flaggeditem': {\r
+            'Meta': {'object_name': 'FlaggedItem', 'db_table': "u'flagged_item'"},\r
+            'canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'flagged_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'flaggeditems'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'reason': ('django.db.models.fields.CharField', [], {'max_length': '300', 'null': 'True'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'flaggeditems'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.keyvalue': {\r
+            'Meta': {'object_name': 'KeyValue'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'value': ('forum.models.utils.PickledObjectField', [], {})\r
+        },\r
+        'forum.markedtag': {\r
+            'Meta': {'object_name': 'MarkedTag'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'reason': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_selections'", 'to': "orm['forum.Tag']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tag_selections'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.node': {\r
+            'Meta': {'object_name': 'Node'},\r
+            'abs_parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'all_children'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'active_revision': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'active'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.NodeRevision']"}),\r
+            'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'nodes'", 'to': "orm['forum.User']"}),\r
+            'body': ('django.db.models.fields.TextField', [], {}),\r
+            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_nodes'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'extra_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'extra_ref': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']", 'null': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'last_activity_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'last_activity_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']", 'null': 'True'}),\r
+            'last_edited_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'last_edited_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'last_edited_nodes'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'marked': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'node_type': ('django.db.models.fields.CharField', [], {'default': "'node'", 'max_length': '16'}),\r
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'children'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),\r
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'nodes'", 'to': "orm['forum.Tag']"}),\r
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'})\r
+        },\r
+        'forum.noderevision': {\r
+            'Meta': {'unique_together': "(('node', 'revision'),)", 'object_name': 'NodeRevision'},\r
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'noderevisions'", 'to': "orm['forum.User']"}),\r
+            'body': ('django.db.models.fields.TextField', [], {}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revisions'", 'to': "orm['forum.Node']"}),\r
+            'revised_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'revision': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'summary': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),\r
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '300'})\r
+        },\r
+        'forum.openidassociation': {\r
+            'Meta': {'object_name': 'OpenIdAssociation'},\r
+            'assoc_type': ('django.db.models.fields.TextField', [], {'max_length': '64'}),\r
+            'handle': ('django.db.models.fields.CharField', [], {'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'issued': ('django.db.models.fields.IntegerField', [], {}),\r
+            'lifetime': ('django.db.models.fields.IntegerField', [], {}),\r
+            'secret': ('django.db.models.fields.TextField', [], {'max_length': '255'}),\r
+            'server_url': ('django.db.models.fields.TextField', [], {'max_length': '2047'})\r
+        },\r
+        'forum.openidnonce': {\r
+            'Meta': {'object_name': 'OpenIdNonce'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'salt': ('django.db.models.fields.CharField', [], {'max_length': '50'}),\r
+            'server_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),\r
+            'timestamp': ('django.db.models.fields.IntegerField', [], {})\r
+        },\r
+        'forum.question': {\r
+            'Meta': {'object_name': 'Question', 'db_table': "u'question'"},\r
+            'accepted_answer': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'question_accepting'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.Answer']"}),\r
+            'close_reason': ('django.db.models.fields.SmallIntegerField', [], {'null': 'True', 'blank': 'True'}),\r
+            'closed': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'closed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'closed_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'closed_questions'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'favorited_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'favorite_questions'", 'through': "'FavoriteQuestion'", 'to': "orm['forum.User']"}),\r
+            'last_activity_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'last_activity_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'last_active_in_questions'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'node_ptr': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']", 'null': 'True', 'primary_key': 'True'}),\r
+            'view_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'wikified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})\r
+        },\r
+        'forum.questionsubscription': {\r
+            'Meta': {'object_name': 'QuestionSubscription'},\r
+            'auto_subscription': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'last_view': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 4, 27, 11, 37, 29, 356000)'}),\r
+            'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        },\r
+        'forum.repute': {\r
+            'Meta': {'object_name': 'Repute', 'db_table': "u'repute'"},\r
+            'canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reputes'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Question']"}),\r
+            'reputation_type': ('django.db.models.fields.SmallIntegerField', [], {}),\r
+            'reputed_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reputes'", 'to': "orm['forum.User']"}),\r
+            'user_previous_rep': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'value': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'})\r
+        },\r
+        'forum.subscriptionsettings': {\r
+            'Meta': {'object_name': 'SubscriptionSettings'},\r
+            'all_questions': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'all_questions_watched_tags': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'enable_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'member_joins': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),\r
+            'new_question': ('django.db.models.fields.CharField', [], {'default': "'d'", 'max_length': '1'}),\r
+            'new_question_watched_tags': ('django.db.models.fields.CharField', [], {'default': "'i'", 'max_length': '1'}),\r
+            'notify_accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'notify_answers': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'notify_comments': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'notify_comments_own_post': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'notify_reply_to_comments': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_answered': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_asked': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_commented': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'questions_viewed': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'subscribed_questions': ('django.db.models.fields.CharField', [], {'default': "'i'", 'max_length': '1'}),\r
+            'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'subscription_settings'", 'unique': 'True', 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.tag': {\r
+            'Meta': {'object_name': 'Tag', 'db_table': "u'tag'"},\r
+            'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_tags'", 'to': "orm['forum.User']"}),\r
+            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_tags'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'marked_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'marked_tags'", 'through': "'MarkedTag'", 'to': "orm['forum.User']"}),\r
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'used_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})\r
+        },\r
+        'forum.user': {\r
+            'Meta': {'object_name': 'User', '_ormbases': ['auth.User']},\r
+            'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),\r
+            'bronze': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),\r
+            'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),\r
+            'gold': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'hide_ignored_questions': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),\r
+            'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),\r
+            'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),\r
+            'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),\r
+            'silver': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'subscriptions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'subscribers'", 'through': "'QuestionSubscription'", 'to': "orm['forum.Node']"}),\r
+            'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}),\r
+            'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})\r
+        },\r
+        'forum.validationhash': {\r
+            'Meta': {'unique_together': "(('user', 'type'),)", 'object_name': 'ValidationHash'},\r
+            'expiration': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 4, 28, 11, 37, 29, 624000)'}),\r
+            'hash_code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'seed': ('django.db.models.fields.CharField', [], {'max_length': '12'}),\r
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '12'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        },\r
+        'forum.vote': {\r
+            'Meta': {'object_name': 'Vote', 'db_table': "u'vote'"},\r
+            'canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'votes'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'votes'", 'to': "orm['forum.User']"}),\r
+            'vote': ('django.db.models.fields.SmallIntegerField', [], {}),\r
+            'voted_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'})\r
+        }\r
+    }\r
+    \r
+    complete_apps = ['forum']\r
diff --git a/forum/migrations/0023_flaten_node_inheritance_create_actions.py b/forum/migrations/0023_flaten_node_inheritance_create_actions.py
new file mode 100644 (file)
index 0000000..d4b081b
--- /dev/null
@@ -0,0 +1,685 @@
+# encoding: utf-8\r
+import datetime\r
+from south.db import db\r
+from south.v2 import DataMigration\r
+from django.db import models\r
+from forum.migrations import ProgressBar\r
+\r
+GAIN_BY_UPVOTED = 1\r
+GAIN_BY_ANSWER_ACCEPTED = 2\r
+GAIN_BY_ACCEPTING_ANSWER = 3\r
+GAIN_BY_DOWNVOTE_CANCELED = 4\r
+GAIN_BY_CANCELING_DOWNVOTE = 5\r
+LOST_BY_CANCELLING_ACCEPTED_ANSWER = -1\r
+LOST_BY_ACCEPTED_ANSWER_CANCELED = -2\r
+LOST_BY_DOWNVOTED = -3\r
+LOST_BY_FLAGGED = -4\r
+LOST_BY_DOWNVOTING = -5\r
+LOST_BY_FLAGGED_3_TIMES = -6\r
+LOST_BY_FLAGGED_5_TIMES = -7\r
+LOST_BY_UPVOTE_CANCELED = -8\r
+\r
+class Migration(DataMigration):\r
+    \r
+    def forwards(self, orm):\r
+        rephist = dict([(t, []) for t in range(-8, 6) if t != 0])\r
+\r
+        r_count = orm.Repute.objects.count()\r
+        print "\nCalculating rep gain/losses history through %d records:" % r_count\r
+        progress = ProgressBar(r_count)\r
+\r
+        for r in orm.Repute.objects.all():\r
+            l = rephist.get(r.reputation_type, None)\r
+            if l is None: continue\r
+\r
+            if (len(l) == 0) or (l[-1][1] != r.value):\r
+                l.append((r.reputed_at, r.value))\r
+\r
+            progress.update()\r
+\r
+        print "\n...done\n"\r
+\r
+\r
+        def repval_at(reptype, repdate, default):\r
+            l = rephist.get(reptype, None)\r
+\r
+            if l is None: return 0\r
+            if len(l) == 0: return default\r
+\r
+            for r in l:\r
+                if r[0] <= repdate:\r
+                    return r[1] or default\r
+\r
+\r
+        q_count = orm.Question.objects.count()\r
+        print "\nConverting %d questions:" % q_count\r
+        progress = ProgressBar(q_count)\r
+\r
+        for q in orm.Question.objects.all():\r
+            n = q.node_ptr\r
+            n.last_activity_at = q.last_activity_at\r
+            n.last_activity_by = q.last_activity_by\r
+\r
+            if q.accepted_answer:\r
+                n.extra_ref = q.accepted_answer.node_ptr\r
+                \r
+            n.extra_count = q.view_count\r
+\r
+            n.marked = q.closed\r
+            n.wiki = q.wiki\r
+\r
+            n.save()\r
+\r
+            ask = orm.Action(\r
+                user = n.author,\r
+                action_date = n.added_at,\r
+                node = n,\r
+                action_type = "ask",\r
+                extra = ''\r
+            )\r
+\r
+            ask.save()\r
+\r
+            if n.deleted:\r
+                action = orm.Action(\r
+                    user = n.deleted_by,\r
+                    node = n,\r
+                    action_type = "delete",\r
+                    action_date = n.deleted_at or datetime.datetime.now(),\r
+                    extra = ''\r
+                )\r
+\r
+                action.save()\r
+\r
+\r
+            if n.marked:\r
+                action = orm.Action(\r
+                    user = q.closed_by,\r
+                    node = n,\r
+                    action_type = "close",\r
+                    extra = q.close_reason,\r
+                    action_date = q.closed_at or datetime.datetime.now(),\r
+                )\r
+\r
+                action.save()\r
+\r
+            if n.wiki:\r
+                action = orm.Action(\r
+                    user = n.author,\r
+                    node = n,\r
+                    action_type = "wikify",\r
+                    action_date = q.wikified_at or datetime.datetime.now(),\r
+                    extra = ''\r
+                )\r
+\r
+                action.save()\r
+\r
+            progress.update()\r
+\r
+        print "\n...done\n"\r
+\r
+        a_count = orm.Answer.objects.count()\r
+        print "\nConverting %d answers:" % a_count\r
+        progress = ProgressBar(a_count)\r
+\r
+        for a in orm.Answer.objects.all():\r
+            n = a.node_ptr\r
+\r
+            n.marked = a.accepted\r
+            n.wiki = a.wiki\r
+\r
+            n.save()\r
+\r
+            ans = orm.Action(\r
+                user = n.author,\r
+                action_date = n.added_at,\r
+                node = n,\r
+                action_type = "answer",\r
+                extra = ''\r
+            )\r
+\r
+            ans.save()\r
+\r
+            if n.deleted:\r
+                action = orm.Action(\r
+                    user = n.deleted_by,\r
+                    node = n,\r
+                    action_type = "delete",\r
+                    action_date = n.deleted_at or datetime.datetime.now(),\r
+                    extra = ''\r
+                )\r
+\r
+                action.save()\r
+\r
+            if a.accepted:\r
+                action = orm.Action(\r
+                    user = a.accepted_by,\r
+                    node = n,\r
+                    action_type = "acceptanswer",\r
+                    action_date = a.accepted_at or datetime.datetime.now(),\r
+                    extra = ''\r
+                )\r
+\r
+                action.save()\r
+\r
+                if not a.wiki or a.wikified_at > action.action_date:\r
+                    if action.user == n.author:\r
+                        rep = orm.ActionRepute(\r
+                            action = action,\r
+                            user = action.user,\r
+                            value = repval_at(GAIN_BY_ACCEPTING_ANSWER, action.action_date, 2)\r
+                        )\r
+                        rep.save()\r
+\r
+                    if n.author != n.parent.author:\r
+                        rep = orm.ActionRepute(\r
+                            action = action,\r
+                            user = n.author,\r
+                            value = repval_at(GAIN_BY_ANSWER_ACCEPTED, action.action_date, 15)\r
+                        )\r
+                        rep.save()\r
+\r
+            if n.wiki:\r
+                action = orm.Action(\r
+                    user = n.author,\r
+                    node = n,\r
+                    action_type = "wikify",\r
+                    action_date = a.wikified_at or datetime.datetime.now(),\r
+                    extra = ''\r
+                )\r
+\r
+                action.save()\r
+\r
+            progress.update()\r
+\r
+        print "\n...done\n"\r
+\r
+        v_count = orm.Vote.objects.count()\r
+        print "\nConverting %d votes:" % v_count\r
+        progress = ProgressBar(v_count)\r
+\r
+        for v in orm.Vote.objects.exclude(canceled=True):\r
+            a = orm.Action(\r
+                action_type = (v.vote == 1) and ((v.node.node_type == "comment") and "voteupcomment" or "voteup") or "votedown",\r
+                user = v.user,\r
+                node = v.node,\r
+                action_date = v.voted_at,\r
+                canceled = v.canceled,\r
+                extra = ''\r
+            )\r
+\r
+            a.save()\r
+\r
+            def impl(node):\r
+                if node.node_type == "question":\r
+                    return orm.Question.objects.get(node_ptr=node)\r
+                else:\r
+                    return orm.Answer.objects.get(node_ptr=node)\r
+\r
+            if a.node.node_type in ("question", "answer") and (not a.node.wiki or impl(a.node).wikified_at > a.action_date):\r
+                reptype, default = (v.vote == 1) and (GAIN_BY_UPVOTED, 10) or (LOST_BY_DOWNVOTED, 2)\r
+                rep = orm.ActionRepute(\r
+                    action = a,\r
+                    user = a.node.author,\r
+                    value = repval_at(reptype, a.action_date, default) or default\r
+                )\r
+                rep.save()\r
+\r
+                if v.vote == -1:\r
+                    rep = orm.ActionRepute(\r
+                        action = a,\r
+                        user = a.node.author,\r
+                        value = repval_at(LOST_BY_DOWNVOTING, a.action_date, 1) or default\r
+                    )\r
+                    rep.save()\r
+\r
+            progress.update()\r
+\r
+        print "\n...done\n"\r
+\r
+        f_count = orm.FlaggedItem.objects.count()\r
+        print "\nConverting %d flags:" % f_count\r
+        progress = ProgressBar(f_count)\r
+\r
+        for f in orm.FlaggedItem.objects.all():\r
+            a = orm.Action(\r
+                action_type = "flag",\r
+                user = f.user,\r
+                node = f.node,\r
+                action_date = f.flagged_at,\r
+                extra = f.reason or ''\r
+            )\r
+\r
+            a.save()\r
+\r
+            rep = orm.ActionRepute(\r
+                action = a,\r
+                user = a.node.author,\r
+                value = repval_at(LOST_BY_FLAGGED, a.action_date, 2) or 2\r
+            )\r
+            rep.save()\r
+\r
+            progress.update()\r
+\r
+        print "\n...done\n"\r
+\r
+        n_count = orm.Node.objects.all().count()\r
+        print "\nChecking flag count of %d nodes:" % n_count\r
+        progress = ProgressBar(n_count)\r
+\r
+        for n in orm.Node.objects.all():\r
+            flags = list(orm.Action.objects.filter(action_type="flag", node=n, canceled=False).order_by('-action_date'))\r
+\r
+            if len(flags) >= 3:\r
+                a = flags[2]\r
+                rep = orm.ActionRepute(\r
+                    action = a,\r
+                    user = n.author,\r
+                    value = repval_at(LOST_BY_FLAGGED_3_TIMES, a.action_date, 30)\r
+                )\r
+                rep.save()\r
+\r
+\r
+            if len(flags) >= 5:\r
+                a = flags[4]\r
+                rep = orm.ActionRepute(\r
+                    action = a,\r
+                    user = n.author,\r
+                    value = repval_at(LOST_BY_FLAGGED_5_TIMES, a.action_date, 100)\r
+                )\r
+                rep.save()\r
+\r
+            progress.update()\r
+\r
+        print "\n...done\n"\r
+\r
+        c_count = orm.Node.objects.filter(node_type="comment").count()\r
+        print "\nCreating %d comment actions:" % c_count\r
+        progress = ProgressBar(c_count)\r
+\r
+        for c in orm.Node.objects.filter(node_type="comment").all():\r
+            a = orm.Action(\r
+                action_type = "comment",\r
+                user = c.author,\r
+                node = c,\r
+                action_date = c.added_at,\r
+                extra = ''\r
+            )\r
+\r
+            a.save()\r
+\r
+            if c.deleted:\r
+                action = orm.Action(\r
+                    user = c.deleted_by,\r
+                    node = c,\r
+                    action_type = "delete",\r
+                    action_date = c.deleted_at or datetime.datetime.now(),\r
+                    extra = ''\r
+                )\r
+\r
+                action.save()\r
+\r
+            progress.update()\r
+\r
+        print "\n...done\n"\r
+\r
+\r
+        r_count = orm.NodeRevision.objects.exclude(revision=1).count()\r
+        print "\nCreating %d edit actions:" % r_count\r
+        progress = ProgressBar(r_count)\r
+\r
+        for r in orm.NodeRevision.objects.exclude(revision=1):\r
+            a = orm.Action(\r
+                action_type = "revise",\r
+                user = r.author,\r
+                node = r.node,\r
+                action_date = r.revised_at,\r
+                extra = r.revision\r
+            )\r
+\r
+            a.save()\r
+            progress.update()\r
+\r
+        print "\n...done\n"\r
+\r
+        u_count = orm.User.objects.all().count()\r
+        print "\nCreating %d user join actions and reputation recalculation:" % u_count\r
+        progress = ProgressBar(u_count)\r
+\r
+        for u in orm.User.objects.all():\r
+            a = orm.Action(\r
+                user = u,\r
+                action_date = u.date_joined,\r
+                action_type = "userjoins",\r
+            )\r
+\r
+            a.save()\r
+\r
+            rep = orm.ActionRepute(\r
+                action = a,\r
+                user = u,\r
+                value = 1\r
+            )\r
+            rep.save()\r
+\r
+            new_rep = orm.ActionRepute.objects.filter(user=u).aggregate(reputation=models.Sum('value'))['reputation']\r
+\r
+            if new_rep < 0:\r
+                new_rep = 1\r
+\r
+            u.reputation = new_rep\r
+            u.save()\r
+\r
+            progress.update()\r
+\r
+        print "\n...done\n"\r
+\r
+    \r
+    \r
+    def backwards(self, orm):\r
+        "Write your backwards methods here."\r
+    \r
+    models = {\r
+        'auth.group': {\r
+            'Meta': {'object_name': 'Group'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),\r
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'})\r
+        },\r
+        'auth.permission': {\r
+            'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},\r
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})\r
+        },\r
+        'auth.user': {\r
+            'Meta': {'object_name': 'User'},\r
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),\r
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),\r
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),\r
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),\r
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'}),\r
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})\r
+        },\r
+        'contenttypes.contenttype': {\r
+            'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},\r
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})\r
+        },\r
+        'forum.action': {\r
+            'Meta': {'object_name': 'Action'},\r
+            'action_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'action_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'canceled_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),\r
+            'canceled_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'canceled_actions'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'extra': ('django.db.models.fields.CharField', [], {'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'ip': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']", 'null': 'True'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.actionrepute': {\r
+            'Meta': {'object_name': 'ActionRepute'},\r
+            'action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reputes'", 'to': "orm['forum.Action']"}),\r
+            'by_canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"}),\r
+            'value': ('django.db.models.fields.IntegerField', [], {'default': '0'})\r
+        },\r
+        'forum.activity': {\r
+            'Meta': {'object_name': 'Activity', 'db_table': "u'activity'"},\r
+            'active_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'activity_type': ('django.db.models.fields.SmallIntegerField', [], {}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'is_auditted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        },\r
+        'forum.anonymousnode': {\r
+            'Meta': {'object_name': 'AnonymousNode', '_ormbases': ['forum.Node']},\r
+            'convertible_to': ('django.db.models.fields.CharField', [], {'default': "'node'", 'max_length': '16'}),\r
+            'node_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['forum.Node']", 'unique': 'True', 'primary_key': 'True'}),\r
+            'validation_hash': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'anonymous_content'", 'to': "orm['forum.Node']"})\r
+        },\r
+        'forum.answer': {\r
+            'Meta': {'object_name': 'Answer', 'db_table': "u'answer'"},\r
+            'accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'accepted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'accepted_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']", 'null': 'True'}),\r
+            'node_ptr': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']", 'null': 'True', 'primary_key': 'True'}),\r
+            'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'wikified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})\r
+        },\r
+        'forum.authkeyuserassociation': {\r
+            'Meta': {'object_name': 'AuthKeyUserAssociation'},\r
+            'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'provider': ('django.db.models.fields.CharField', [], {'max_length': '64'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_keys'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.award': {\r
+            'Meta': {'object_name': 'Award', 'db_table': "u'award'"},\r
+            'awarded_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'badge': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award_badge'", 'to': "orm['forum.Badge']"}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'notified': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'awards'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.badge': {\r
+            'Meta': {'unique_together': "(('name', 'type'),)", 'object_name': 'Badge', 'db_table': "u'badge'"},\r
+            'awarded_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'awarded_to': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'badges'", 'through': "'Award'", 'to': "orm['forum.User']"}),\r
+            'description': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'multiple': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),\r
+            'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '50', 'blank': 'True'}),\r
+            'type': ('django.db.models.fields.SmallIntegerField', [], {})\r
+        },\r
+        'forum.favoritequestion': {\r
+            'Meta': {'unique_together': "(('question', 'user'),)", 'object_name': 'FavoriteQuestion', 'db_table': "u'favorite_question'"},\r
+            'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'question': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'favourites'", 'to': "orm['forum.Question']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_favorite_questions'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.flaggeditem': {\r
+            'Meta': {'object_name': 'FlaggedItem', 'db_table': "u'flagged_item'"},\r
+            'canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'flagged_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'flaggeditems'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'reason': ('django.db.models.fields.CharField', [], {'max_length': '300', 'null': 'True'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'flaggeditems'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.keyvalue': {\r
+            'Meta': {'object_name': 'KeyValue'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'value': ('forum.models.utils.PickledObjectField', [], {})\r
+        },\r
+        'forum.markedtag': {\r
+            'Meta': {'object_name': 'MarkedTag'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'reason': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_selections'", 'to': "orm['forum.Tag']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tag_selections'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.node': {\r
+            'Meta': {'object_name': 'Node'},\r
+            'abs_parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'all_children'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'active_revision': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'active'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.NodeRevision']"}),\r
+            'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'nodes'", 'to': "orm['forum.User']"}),\r
+            'body': ('django.db.models.fields.TextField', [], {}),\r
+            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_nodes'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'extra_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'extra_ref': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']", 'null': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'last_activity_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'last_activity_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']", 'null': 'True'}),\r
+            'last_edited_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'last_edited_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'last_edited_nodes'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'marked': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'node_type': ('django.db.models.fields.CharField', [], {'default': "'node'", 'max_length': '16'}),\r
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'children'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),\r
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'nodes'", 'to': "orm['forum.Tag']"}),\r
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'})\r
+        },\r
+        'forum.noderevision': {\r
+            'Meta': {'unique_together': "(('node', 'revision'),)", 'object_name': 'NodeRevision'},\r
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'noderevisions'", 'to': "orm['forum.User']"}),\r
+            'body': ('django.db.models.fields.TextField', [], {}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revisions'", 'to': "orm['forum.Node']"}),\r
+            'revised_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'revision': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'summary': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),\r
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '300'})\r
+        },\r
+        'forum.openidassociation': {\r
+            'Meta': {'object_name': 'OpenIdAssociation'},\r
+            'assoc_type': ('django.db.models.fields.TextField', [], {'max_length': '64'}),\r
+            'handle': ('django.db.models.fields.CharField', [], {'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'issued': ('django.db.models.fields.IntegerField', [], {}),\r
+            'lifetime': ('django.db.models.fields.IntegerField', [], {}),\r
+            'secret': ('django.db.models.fields.TextField', [], {'max_length': '255'}),\r
+            'server_url': ('django.db.models.fields.TextField', [], {'max_length': '2047'})\r
+        },\r
+        'forum.openidnonce': {\r
+            'Meta': {'object_name': 'OpenIdNonce'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'salt': ('django.db.models.fields.CharField', [], {'max_length': '50'}),\r
+            'server_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),\r
+            'timestamp': ('django.db.models.fields.IntegerField', [], {})\r
+        },\r
+        'forum.question': {\r
+            'Meta': {'object_name': 'Question', 'db_table': "u'question'"},\r
+            'accepted_answer': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'question_accepting'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.Answer']"}),\r
+            'close_reason': ('django.db.models.fields.SmallIntegerField', [], {'null': 'True', 'blank': 'True'}),\r
+            'closed': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'closed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'closed_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'closed_questions'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'favorited_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'favorite_questions'", 'through': "'FavoriteQuestion'", 'to': "orm['forum.User']"}),\r
+            'last_activity_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'last_activity_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'last_active_in_questions'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'node_ptr': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']", 'null': 'True', 'primary_key': 'True'}),\r
+            'view_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'wikified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})\r
+        },\r
+        'forum.questionsubscription': {\r
+            'Meta': {'object_name': 'QuestionSubscription'},\r
+            'auto_subscription': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'last_view': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 4, 27, 11, 40, 32, 68000)'}),\r
+            'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        },\r
+        'forum.repute': {\r
+            'Meta': {'object_name': 'Repute', 'db_table': "u'repute'"},\r
+            'canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reputes'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Question']"}),\r
+            'reputation_type': ('django.db.models.fields.SmallIntegerField', [], {}),\r
+            'reputed_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reputes'", 'to': "orm['forum.User']"}),\r
+            'user_previous_rep': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'value': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'})\r
+        },\r
+        'forum.subscriptionsettings': {\r
+            'Meta': {'object_name': 'SubscriptionSettings'},\r
+            'all_questions': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'all_questions_watched_tags': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'enable_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'member_joins': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),\r
+            'new_question': ('django.db.models.fields.CharField', [], {'default': "'d'", 'max_length': '1'}),\r
+            'new_question_watched_tags': ('django.db.models.fields.CharField', [], {'default': "'i'", 'max_length': '1'}),\r
+            'notify_accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'notify_answers': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'notify_comments': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'notify_comments_own_post': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'notify_reply_to_comments': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_answered': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_asked': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_commented': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'questions_viewed': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'subscribed_questions': ('django.db.models.fields.CharField', [], {'default': "'i'", 'max_length': '1'}),\r
+            'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'subscription_settings'", 'unique': 'True', 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.tag': {\r
+            'Meta': {'object_name': 'Tag', 'db_table': "u'tag'"},\r
+            'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_tags'", 'to': "orm['forum.User']"}),\r
+            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_tags'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'marked_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'marked_tags'", 'through': "'MarkedTag'", 'to': "orm['forum.User']"}),\r
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'used_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})\r
+        },\r
+        'forum.user': {\r
+            'Meta': {'object_name': 'User', '_ormbases': ['auth.User']},\r
+            'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),\r
+            'bronze': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),\r
+            'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),\r
+            'gold': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'hide_ignored_questions': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),\r
+            'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),\r
+            'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),\r
+            'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),\r
+            'silver': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'subscriptions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'subscribers'", 'through': "'QuestionSubscription'", 'to': "orm['forum.Node']"}),\r
+            'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}),\r
+            'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})\r
+        },\r
+        'forum.validationhash': {\r
+            'Meta': {'unique_together': "(('user', 'type'),)", 'object_name': 'ValidationHash'},\r
+            'expiration': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 4, 28, 11, 40, 32, 153000)'}),\r
+            'hash_code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'seed': ('django.db.models.fields.CharField', [], {'max_length': '12'}),\r
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '12'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        },\r
+        'forum.vote': {\r
+            'Meta': {'object_name': 'Vote', 'db_table': "u'vote'"},\r
+            'canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'votes'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'votes'", 'to': "orm['forum.User']"}),\r
+            'vote': ('django.db.models.fields.SmallIntegerField', [], {}),\r
+            'voted_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'})\r
+        }\r
+    }\r
+    \r
+    complete_apps = ['forum']\r
diff --git a/forum/migrations/0024_auto__del_repute__del_vote__del_answer__del_flaggeditem__del_anonymous.py b/forum/migrations/0024_auto__del_repute__del_vote__del_answer__del_flaggeditem__del_anonymous.py
new file mode 100644 (file)
index 0000000..38a7604
--- /dev/null
@@ -0,0 +1,377 @@
+# encoding: utf-8\r
+import datetime\r
+from south.db import db\r
+from south.v2 import SchemaMigration\r
+from django.db import models\r
+\r
+class Migration(SchemaMigration):\r
+    \r
+    def forwards(self, orm):\r
+        \r
+        # Deleting model 'Repute'\r
+        db.delete_table(u'repute')\r
+\r
+        # Deleting model 'Vote'\r
+        db.delete_table(u'vote')\r
+\r
+        # Deleting model 'Answer'\r
+        db.delete_table(u'answer')\r
+\r
+        # Deleting model 'FlaggedItem'\r
+        db.delete_table(u'flagged_item')\r
+\r
+        # Deleting model 'AnonymousNode'\r
+        db.delete_table('forum_anonymousnode')\r
+\r
+        # Deleting model 'FavoriteQuestion'\r
+        db.delete_table(u'favorite_question')\r
+\r
+        # Deleting model 'Question'\r
+        db.delete_table(u'question')\r
+\r
+        # Deleting field 'Node.deleted_at'\r
+        db.delete_column('forum_node', 'deleted_at')\r
+\r
+        # Deleting field 'Node.last_edited_by'\r
+        db.delete_column('forum_node', 'last_edited_by_id')\r
+\r
+        # Deleting field 'Node.deleted'\r
+        db.delete_column('forum_node', 'deleted')\r
+\r
+        # Deleting field 'Node.deleted_by'\r
+        db.delete_column('forum_node', 'deleted_by_id')\r
+\r
+        # Deleting field 'Node.last_edited_at'\r
+        db.delete_column('forum_node', 'last_edited_at')\r
+    \r
+    \r
+    def backwards(self, orm):\r
+        \r
+        # Adding model 'Repute'\r
+        db.create_table(u'repute', (\r
+            ('node', self.gf('django.db.models.fields.related.ForeignKey')(related_name='reputes', null=True, to=orm['forum.Node'])),\r
+            ('reputed_at', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)),\r
+            ('question', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['forum.Question'])),\r
+            ('value', self.gf('django.db.models.fields.SmallIntegerField')(default=0)),\r
+            ('canceled', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True)),\r
+            ('reputation_type', self.gf('django.db.models.fields.SmallIntegerField')()),\r
+            ('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='reputes', to=orm['forum.User'])),\r
+            ('user_previous_rep', self.gf('django.db.models.fields.IntegerField')(default=0)),\r
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),\r
+        ))\r
+        db.send_create_signal('forum', ['Repute'])\r
+\r
+        # Adding model 'Vote'\r
+        db.create_table(u'vote', (\r
+            ('node', self.gf('django.db.models.fields.related.ForeignKey')(related_name='votes', null=True, to=orm['forum.Node'])),\r
+            ('vote', self.gf('django.db.models.fields.SmallIntegerField')()),\r
+            ('canceled', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True)),\r
+            ('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='votes', to=orm['forum.User'])),\r
+            ('voted_at', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)),\r
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),\r
+        ))\r
+        db.send_create_signal('forum', ['Vote'])\r
+\r
+        # Adding model 'Answer'\r
+        db.create_table(u'answer', (\r
+            ('wiki', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True)),\r
+            ('accepted_by', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['forum.User'], null=True)),\r
+            ('accepted_at', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),\r
+            ('wikified_at', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),\r
+            ('node_ptr', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['forum.Node'], null=True, primary_key=True)),\r
+            ('accepted', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True)),\r
+        ))\r
+        db.send_create_signal('forum', ['Answer'])\r
+\r
+        # Adding model 'FlaggedItem'\r
+        db.create_table(u'flagged_item', (\r
+            ('node', self.gf('django.db.models.fields.related.ForeignKey')(related_name='flaggeditems', null=True, to=orm['forum.Node'])),\r
+            ('flagged_at', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)),\r
+            ('canceled', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True)),\r
+            ('reason', self.gf('django.db.models.fields.CharField')(max_length=300, null=True)),\r
+            ('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='flaggeditems', to=orm['forum.User'])),\r
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),\r
+        ))\r
+        db.send_create_signal('forum', ['FlaggedItem'])\r
+\r
+        # Adding model 'AnonymousNode'\r
+        db.create_table('forum_anonymousnode', (\r
+            ('convertible_to', self.gf('django.db.models.fields.CharField')(default='node', max_length=16)),\r
+            ('validation_hash', self.gf('django.db.models.fields.related.ForeignKey')(related_name='anonymous_content', to=orm['forum.Node'])),\r
+            ('node_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['forum.Node'], unique=True, primary_key=True)),\r
+        ))\r
+        db.send_create_signal('forum', ['AnonymousNode'])\r
+\r
+        # Adding model 'FavoriteQuestion'\r
+        db.create_table(u'favorite_question', (\r
+            ('question', self.gf('django.db.models.fields.related.ForeignKey')(related_name='favourites', to=orm['forum.Question'])),\r
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),\r
+            ('added_at', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)),\r
+            ('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='user_favorite_questions', to=orm['forum.User'])),\r
+        ))\r
+        db.send_create_signal('forum', ['FavoriteQuestion'])\r
+\r
+        # Adding model 'Question'\r
+        db.create_table(u'question', (\r
+            ('wiki', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True)),\r
+            ('last_activity_by', self.gf('django.db.models.fields.related.ForeignKey')(related_name='last_active_in_questions', null=True, to=orm['forum.User'])),\r
+            ('close_reason', self.gf('django.db.models.fields.SmallIntegerField')(null=True, blank=True)),\r
+            ('last_activity_at', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)),\r
+            ('view_count', self.gf('django.db.models.fields.IntegerField')(default=0)),\r
+            ('node_ptr', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['forum.Node'], null=True, primary_key=True)),\r
+            ('accepted_answer', self.gf('django.db.models.fields.related.OneToOneField')(related_name='question_accepting', unique=True, null=True, to=orm['forum.Answer'])),\r
+            ('closed', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True)),\r
+            ('wikified_at', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),\r
+            ('closed_at', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),\r
+            ('closed_by', self.gf('django.db.models.fields.related.ForeignKey')(related_name='closed_questions', null=True, to=orm['forum.User'], blank=True)),\r
+        ))\r
+        db.send_create_signal('forum', ['Question'])\r
+\r
+        # Adding field 'Node.deleted_at'\r
+        db.add_column('forum_node', 'deleted_at', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True), keep_default=False)\r
+\r
+        # Adding field 'Node.last_edited_by'\r
+        db.add_column('forum_node', 'last_edited_by', self.gf('django.db.models.fields.related.ForeignKey')(related_name='last_edited_nodes', null=True, to=orm['forum.User'], blank=True), keep_default=False)\r
+\r
+        # Adding field 'Node.deleted'\r
+        db.add_column('forum_node', 'deleted', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True), keep_default=False)\r
+\r
+        # Adding field 'Node.deleted_by'\r
+        db.add_column('forum_node', 'deleted_by', self.gf('django.db.models.fields.related.ForeignKey')(related_name='deleted_nodes', null=True, to=orm['forum.User'], blank=True), keep_default=False)\r
+\r
+        # Adding field 'Node.last_edited_at'\r
+        db.add_column('forum_node', 'last_edited_at', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True), keep_default=False)\r
+    \r
+    \r
+    models = {\r
+        'auth.group': {\r
+            'Meta': {'object_name': 'Group'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),\r
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'})\r
+        },\r
+        'auth.permission': {\r
+            'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},\r
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})\r
+        },\r
+        'auth.user': {\r
+            'Meta': {'object_name': 'User'},\r
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),\r
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),\r
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),\r
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),\r
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'}),\r
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})\r
+        },\r
+        'contenttypes.contenttype': {\r
+            'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},\r
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})\r
+        },\r
+        'forum.action': {\r
+            'Meta': {'object_name': 'Action'},\r
+            'action_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'action_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'canceled_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),\r
+            'canceled_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'canceled_actions'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'extra': ('django.db.models.fields.CharField', [], {'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'ip': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.actionrepute': {\r
+            'Meta': {'object_name': 'ActionRepute'},\r
+            'action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reputes'", 'to': "orm['forum.Action']"}),\r
+            'by_canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"}),\r
+            'value': ('django.db.models.fields.IntegerField', [], {'default': '0'})\r
+        },\r
+        'forum.activity': {\r
+            'Meta': {'object_name': 'Activity', 'db_table': "u'activity'"},\r
+            'active_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'activity_type': ('django.db.models.fields.SmallIntegerField', [], {}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'is_auditted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        },\r
+        'forum.authkeyuserassociation': {\r
+            'Meta': {'object_name': 'AuthKeyUserAssociation'},\r
+            'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'provider': ('django.db.models.fields.CharField', [], {'max_length': '64'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_keys'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.award': {\r
+            'Meta': {'object_name': 'Award', 'db_table': "u'award'"},\r
+            'awarded_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'badge': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award_badge'", 'to': "orm['forum.Badge']"}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'notified': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'awards'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.badge': {\r
+            'Meta': {'unique_together': "(('name', 'type'),)", 'object_name': 'Badge', 'db_table': "u'badge'"},\r
+            'awarded_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'awarded_to': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'badges'", 'through': "'Award'", 'to': "orm['forum.User']"}),\r
+            'description': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'multiple': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),\r
+            'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '50', 'blank': 'True'}),\r
+            'type': ('django.db.models.fields.SmallIntegerField', [], {})\r
+        },\r
+        'forum.keyvalue': {\r
+            'Meta': {'object_name': 'KeyValue'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'value': ('forum.models.utils.PickledObjectField', [], {})\r
+        },\r
+        'forum.markedtag': {\r
+            'Meta': {'object_name': 'MarkedTag'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'reason': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_selections'", 'to': "orm['forum.Tag']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tag_selections'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.node': {\r
+            'Meta': {'object_name': 'Node'},\r
+            'abs_parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'all_children'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'active_revision': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'active'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.NodeRevision']"}),\r
+            'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'nodes'", 'to': "orm['forum.User']"}),\r
+            'body': ('django.db.models.fields.TextField', [], {}),\r
+            'extra_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'extra_ref': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']", 'null': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'last_activity_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'last_activity_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']", 'null': 'True'}),\r
+            'marked': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'node_type': ('django.db.models.fields.CharField', [], {'default': "'node'", 'max_length': '16'}),\r
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'children'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),\r
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'nodes'", 'to': "orm['forum.Tag']"}),\r
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'})\r
+        },\r
+        'forum.noderevision': {\r
+            'Meta': {'unique_together': "(('node', 'revision'),)", 'object_name': 'NodeRevision'},\r
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'noderevisions'", 'to': "orm['forum.User']"}),\r
+            'body': ('django.db.models.fields.TextField', [], {}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revisions'", 'to': "orm['forum.Node']"}),\r
+            'revised_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'revision': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'summary': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),\r
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '300'})\r
+        },\r
+        'forum.openidassociation': {\r
+            'Meta': {'object_name': 'OpenIdAssociation'},\r
+            'assoc_type': ('django.db.models.fields.TextField', [], {'max_length': '64'}),\r
+            'handle': ('django.db.models.fields.CharField', [], {'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'issued': ('django.db.models.fields.IntegerField', [], {}),\r
+            'lifetime': ('django.db.models.fields.IntegerField', [], {}),\r
+            'secret': ('django.db.models.fields.TextField', [], {'max_length': '255'}),\r
+            'server_url': ('django.db.models.fields.TextField', [], {'max_length': '2047'})\r
+        },\r
+        'forum.openidnonce': {\r
+            'Meta': {'object_name': 'OpenIdNonce'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'salt': ('django.db.models.fields.CharField', [], {'max_length': '50'}),\r
+            'server_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),\r
+            'timestamp': ('django.db.models.fields.IntegerField', [], {})\r
+        },\r
+        'forum.questionsubscription': {\r
+            'Meta': {'object_name': 'QuestionSubscription'},\r
+            'auto_subscription': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'last_view': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 4, 28, 23, 43, 52, 301000)'}),\r
+            'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        },\r
+        'forum.subscriptionsettings': {\r
+            'Meta': {'object_name': 'SubscriptionSettings'},\r
+            'all_questions': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'all_questions_watched_tags': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'enable_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'member_joins': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),\r
+            'new_question': ('django.db.models.fields.CharField', [], {'default': "'d'", 'max_length': '1'}),\r
+            'new_question_watched_tags': ('django.db.models.fields.CharField', [], {'default': "'i'", 'max_length': '1'}),\r
+            'notify_accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'notify_answers': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'notify_comments': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'notify_comments_own_post': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'notify_reply_to_comments': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_answered': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_asked': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_commented': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'questions_viewed': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'subscribed_questions': ('django.db.models.fields.CharField', [], {'default': "'i'", 'max_length': '1'}),\r
+            'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'subscription_settings'", 'unique': 'True', 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.tag': {\r
+            'Meta': {'object_name': 'Tag', 'db_table': "u'tag'"},\r
+            'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_tags'", 'to': "orm['forum.User']"}),\r
+            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_tags'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'marked_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'marked_tags'", 'through': "'MarkedTag'", 'to': "orm['forum.User']"}),\r
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'used_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})\r
+        },\r
+        'forum.user': {\r
+            'Meta': {'object_name': 'User', '_ormbases': ['auth.User']},\r
+            'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),\r
+            'bronze': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),\r
+            'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),\r
+            'gold': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'hide_ignored_questions': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),\r
+            'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),\r
+            'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),\r
+            'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),\r
+            'silver': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'subscriptions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'subscribers'", 'through': "'QuestionSubscription'", 'to': "orm['forum.Node']"}),\r
+            'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}),\r
+            'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})\r
+        },\r
+        'forum.validationhash': {\r
+            'Meta': {'unique_together': "(('user', 'type'),)", 'object_name': 'ValidationHash'},\r
+            'expiration': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 4, 29, 23, 43, 52, 512000)'}),\r
+            'hash_code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'seed': ('django.db.models.fields.CharField', [], {'max_length': '12'}),\r
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '12'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        }\r
+    }\r
+    \r
+    complete_apps = ['forum']\r
diff --git a/forum/migrations/0025_auto__add_field_node_extra_action__add_field_node_deleted__add_field_n.py b/forum/migrations/0025_auto__add_field_node_extra_action__add_field_node_deleted__add_field_n.py
new file mode 100644 (file)
index 0000000..2cf638c
--- /dev/null
@@ -0,0 +1,267 @@
+# encoding: utf-8\r
+import datetime\r
+from south.db import db\r
+from south.v2 import SchemaMigration\r
+from django.db import models\r
+\r
+class Migration(SchemaMigration):\r
+    \r
+    def forwards(self, orm):\r
+        \r
+        # Adding field 'Node.extra_action'\r
+        db.add_column('forum_node', 'extra_action', self.gf('django.db.models.fields.related.ForeignKey')(related_name='extra_node', null=True, to=orm['forum.Action']), keep_default=False)\r
+\r
+        # Adding field 'Node.deleted'\r
+        db.add_column('forum_node', 'deleted', self.gf('django.db.models.fields.related.ForeignKey')(related_name='deleted_node', unique=True, null=True, to=orm['forum.Action']), keep_default=False)\r
+\r
+        # Adding field 'Node.last_edited'\r
+        db.add_column('forum_node', 'last_edited', self.gf('django.db.models.fields.related.ForeignKey')(related_name='edited_node', unique=True, null=True, to=orm['forum.Action']), keep_default=False)\r
+    \r
+    \r
+    def backwards(self, orm):\r
+        \r
+        # Deleting field 'Node.extra_action'\r
+        db.delete_column('forum_node', 'extra_action_id')\r
+\r
+        # Deleting field 'Node.deleted'\r
+        db.delete_column('forum_node', 'deleted_id')\r
+\r
+        # Deleting field 'Node.last_edited'\r
+        db.delete_column('forum_node', 'last_edited_id')\r
+    \r
+    \r
+    models = {\r
+        'auth.group': {\r
+            'Meta': {'object_name': 'Group'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),\r
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'})\r
+        },\r
+        'auth.permission': {\r
+            'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},\r
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})\r
+        },\r
+        'auth.user': {\r
+            'Meta': {'object_name': 'User'},\r
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),\r
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),\r
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),\r
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),\r
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'}),\r
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})\r
+        },\r
+        'contenttypes.contenttype': {\r
+            'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},\r
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})\r
+        },\r
+        'forum.action': {\r
+            'Meta': {'object_name': 'Action'},\r
+            'action_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'action_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'canceled_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),\r
+            'canceled_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'canceled_actions'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'extra': ('django.db.models.fields.CharField', [], {'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'ip': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.actionrepute': {\r
+            'Meta': {'object_name': 'ActionRepute'},\r
+            'action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reputes'", 'to': "orm['forum.Action']"}),\r
+            'by_canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"}),\r
+            'value': ('django.db.models.fields.IntegerField', [], {'default': '0'})\r
+        },\r
+        'forum.activity': {\r
+            'Meta': {'object_name': 'Activity', 'db_table': "u'activity'"},\r
+            'active_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'activity_type': ('django.db.models.fields.SmallIntegerField', [], {}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'is_auditted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        },\r
+        'forum.authkeyuserassociation': {\r
+            'Meta': {'object_name': 'AuthKeyUserAssociation'},\r
+            'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'provider': ('django.db.models.fields.CharField', [], {'max_length': '64'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_keys'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.award': {\r
+            'Meta': {'object_name': 'Award', 'db_table': "u'award'"},\r
+            'awarded_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'badge': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award_badge'", 'to': "orm['forum.Badge']"}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'notified': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'awards'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.badge': {\r
+            'Meta': {'unique_together': "(('name', 'type'),)", 'object_name': 'Badge', 'db_table': "u'badge'"},\r
+            'awarded_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'awarded_to': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'badges'", 'through': "'Award'", 'to': "orm['forum.User']"}),\r
+            'description': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'multiple': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),\r
+            'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '50', 'blank': 'True'}),\r
+            'type': ('django.db.models.fields.SmallIntegerField', [], {})\r
+        },\r
+        'forum.keyvalue': {\r
+            'Meta': {'object_name': 'KeyValue'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'value': ('forum.models.utils.PickledObjectField', [], {})\r
+        },\r
+        'forum.markedtag': {\r
+            'Meta': {'object_name': 'MarkedTag'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'reason': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_selections'", 'to': "orm['forum.Tag']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tag_selections'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.node': {\r
+            'Meta': {'object_name': 'Node'},\r
+            'abs_parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'all_children'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'active_revision': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'active'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.NodeRevision']"}),\r
+            'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'nodes'", 'to': "orm['forum.User']"}),\r
+            'body': ('django.db.models.fields.TextField', [], {}),\r
+            'deleted': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'deleted_node'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'extra_action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'extra_node'", 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'extra_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'extra_ref': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']", 'null': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'last_activity_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'last_activity_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']", 'null': 'True'}),\r
+            'last_edited': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'edited_node'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'marked': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'node_type': ('django.db.models.fields.CharField', [], {'default': "'node'", 'max_length': '16'}),\r
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'children'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),\r
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'nodes'", 'to': "orm['forum.Tag']"}),\r
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'})\r
+        },\r
+        'forum.noderevision': {\r
+            'Meta': {'unique_together': "(('node', 'revision'),)", 'object_name': 'NodeRevision'},\r
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'noderevisions'", 'to': "orm['forum.User']"}),\r
+            'body': ('django.db.models.fields.TextField', [], {}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revisions'", 'to': "orm['forum.Node']"}),\r
+            'revised_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'revision': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'summary': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),\r
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '300'})\r
+        },\r
+        'forum.openidassociation': {\r
+            'Meta': {'object_name': 'OpenIdAssociation'},\r
+            'assoc_type': ('django.db.models.fields.TextField', [], {'max_length': '64'}),\r
+            'handle': ('django.db.models.fields.CharField', [], {'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'issued': ('django.db.models.fields.IntegerField', [], {}),\r
+            'lifetime': ('django.db.models.fields.IntegerField', [], {}),\r
+            'secret': ('django.db.models.fields.TextField', [], {'max_length': '255'}),\r
+            'server_url': ('django.db.models.fields.TextField', [], {'max_length': '2047'})\r
+        },\r
+        'forum.openidnonce': {\r
+            'Meta': {'object_name': 'OpenIdNonce'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'salt': ('django.db.models.fields.CharField', [], {'max_length': '50'}),\r
+            'server_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),\r
+            'timestamp': ('django.db.models.fields.IntegerField', [], {})\r
+        },\r
+        'forum.questionsubscription': {\r
+            'Meta': {'object_name': 'QuestionSubscription'},\r
+            'auto_subscription': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'last_view': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 4, 28, 23, 49, 37, 322000)'}),\r
+            'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        },\r
+        'forum.subscriptionsettings': {\r
+            'Meta': {'object_name': 'SubscriptionSettings'},\r
+            'all_questions': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'all_questions_watched_tags': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'enable_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'member_joins': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),\r
+            'new_question': ('django.db.models.fields.CharField', [], {'default': "'d'", 'max_length': '1'}),\r
+            'new_question_watched_tags': ('django.db.models.fields.CharField', [], {'default': "'i'", 'max_length': '1'}),\r
+            'notify_accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'notify_answers': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'notify_comments': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'notify_comments_own_post': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'notify_reply_to_comments': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_answered': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_asked': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_commented': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'questions_viewed': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'subscribed_questions': ('django.db.models.fields.CharField', [], {'default': "'i'", 'max_length': '1'}),\r
+            'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'subscription_settings'", 'unique': 'True', 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.tag': {\r
+            'Meta': {'object_name': 'Tag', 'db_table': "u'tag'"},\r
+            'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_tags'", 'to': "orm['forum.User']"}),\r
+            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_tags'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'marked_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'marked_tags'", 'through': "'MarkedTag'", 'to': "orm['forum.User']"}),\r
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'used_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})\r
+        },\r
+        'forum.user': {\r
+            'Meta': {'object_name': 'User', '_ormbases': ['auth.User']},\r
+            'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),\r
+            'bronze': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),\r
+            'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),\r
+            'gold': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'hide_ignored_questions': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),\r
+            'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),\r
+            'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),\r
+            'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),\r
+            'silver': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'subscriptions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'subscribers'", 'through': "'QuestionSubscription'", 'to': "orm['forum.Node']"}),\r
+            'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}),\r
+            'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})\r
+        },\r
+        'forum.validationhash': {\r
+            'Meta': {'unique_together': "(('user', 'type'),)", 'object_name': 'ValidationHash'},\r
+            'expiration': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 4, 29, 23, 49, 37, 506000)'}),\r
+            'hash_code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'seed': ('django.db.models.fields.CharField', [], {'max_length': '12'}),\r
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '12'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        }\r
+    }\r
+    \r
+    complete_apps = ['forum']\r
diff --git a/forum/migrations/0026_reset_deleted_and_lastedited_flags.py b/forum/migrations/0026_reset_deleted_and_lastedited_flags.py
new file mode 100644 (file)
index 0000000..8521ffd
--- /dev/null
@@ -0,0 +1,278 @@
+# encoding: utf-8\r
+import datetime\r
+from south.db import db\r
+from south.v2 import DataMigration\r
+from django.db import models\r
+from forum.migrations import ProgressBar\r
+\r
+class Migration(DataMigration):\r
+    \r
+    def forwards(self, orm):\r
+        n_count = orm.Node.objects.all().count()\r
+        print "\nReseting %d nodes:" % n_count\r
+        progress = ProgressBar(n_count)\r
+\r
+        for n in orm.Node.objects.all():\r
+            try:\r
+                d = orm.Action.objects.get(node=n, action_type="delete", canceled=False)\r
+                n.deleted_id = d.id\r
+            except Exception, e:\r
+                n.deleted = None\r
+\r
+            if orm.Action.objects.filter(node=n, action_type="revise").count() > 0:\r
+                n.last_edited_id = orm.Action.objects.filter(node=n, action_type="revise").order_by('-action_date')[0].id\r
+            else:\r
+                n.last_edited = None\r
+\r
+\r
+            if n.node_type == "answer" and n.marked:\r
+                n.extra_action_id = orm.Action.objects.get(node=n, action_type="acceptanswer", canceled=False).id\r
+\r
+            if n.node_type == "question" and n.marked:\r
+                n.extra_action_id = orm.Action.objects.get(node=n, action_type="close", canceled=False).id\r
+\r
+            n.save()\r
+\r
+            progress.update()\r
+\r
+        print "\n...done\n"\r
+    \r
+    \r
+    def backwards(self, orm):\r
+        "Write your backwards methods here."\r
+    \r
+    models = {\r
+        'auth.group': {\r
+            'Meta': {'object_name': 'Group'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),\r
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'})\r
+        },\r
+        'auth.permission': {\r
+            'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},\r
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})\r
+        },\r
+        'auth.user': {\r
+            'Meta': {'object_name': 'User'},\r
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),\r
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),\r
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),\r
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),\r
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'}),\r
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})\r
+        },\r
+        'contenttypes.contenttype': {\r
+            'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},\r
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})\r
+        },\r
+        'forum.action': {\r
+            'Meta': {'object_name': 'Action'},\r
+            'action_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'action_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'canceled_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),\r
+            'canceled_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'canceled_actions'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'extra': ('django.db.models.fields.CharField', [], {'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'ip': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.actionrepute': {\r
+            'Meta': {'object_name': 'ActionRepute'},\r
+            'action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reputes'", 'to': "orm['forum.Action']"}),\r
+            'by_canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reputes'", 'to': "orm['forum.User']"}),\r
+            'value': ('django.db.models.fields.IntegerField', [], {'default': '0'})\r
+        },\r
+        'forum.activity': {\r
+            'Meta': {'object_name': 'Activity', 'db_table': "u'activity'"},\r
+            'active_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'activity_type': ('django.db.models.fields.SmallIntegerField', [], {}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'is_auditted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        },\r
+        'forum.authkeyuserassociation': {\r
+            'Meta': {'object_name': 'AuthKeyUserAssociation'},\r
+            'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'provider': ('django.db.models.fields.CharField', [], {'max_length': '64'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_keys'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.award': {\r
+            'Meta': {'object_name': 'Award', 'db_table': "u'award'"},\r
+            'awarded_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'badge': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award_badge'", 'to': "orm['forum.Badge']"}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'notified': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'awards'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.badge': {\r
+            'Meta': {'unique_together': "(('name', 'type'),)", 'object_name': 'Badge', 'db_table': "u'badge'"},\r
+            'awarded_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'awarded_to': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'badges'", 'through': "'Award'", 'to': "orm['forum.User']"}),\r
+            'description': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'multiple': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),\r
+            'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '50', 'blank': 'True'}),\r
+            'type': ('django.db.models.fields.SmallIntegerField', [], {})\r
+        },\r
+        'forum.keyvalue': {\r
+            'Meta': {'object_name': 'KeyValue'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'value': ('forum.models.utils.PickledObjectField', [], {})\r
+        },\r
+        'forum.markedtag': {\r
+            'Meta': {'object_name': 'MarkedTag'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'reason': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_selections'", 'to': "orm['forum.Tag']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tag_selections'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.node': {\r
+            'Meta': {'object_name': 'Node'},\r
+            'abs_parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'all_children'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'active_revision': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'active'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.NodeRevision']"}),\r
+            'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'nodes'", 'to': "orm['forum.User']"}),\r
+            'body': ('django.db.models.fields.TextField', [], {}),\r
+            'deleted': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'deleted_node'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'extra_action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'extra_node'", 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'extra_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'extra_ref': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']", 'null': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'last_activity_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'last_activity_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']", 'null': 'True'}),\r
+            'last_edited': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'edited_node'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'marked': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'node_type': ('django.db.models.fields.CharField', [], {'default': "'node'", 'max_length': '16'}),\r
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'children'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),\r
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'nodes'", 'to': "orm['forum.Tag']"}),\r
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'})\r
+        },\r
+        'forum.noderevision': {\r
+            'Meta': {'unique_together': "(('node', 'revision'),)", 'object_name': 'NodeRevision'},\r
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'noderevisions'", 'to': "orm['forum.User']"}),\r
+            'body': ('django.db.models.fields.TextField', [], {}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revisions'", 'to': "orm['forum.Node']"}),\r
+            'revised_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'revision': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'summary': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),\r
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '300'})\r
+        },\r
+        'forum.openidassociation': {\r
+            'Meta': {'object_name': 'OpenIdAssociation'},\r
+            'assoc_type': ('django.db.models.fields.TextField', [], {'max_length': '64'}),\r
+            'handle': ('django.db.models.fields.CharField', [], {'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'issued': ('django.db.models.fields.IntegerField', [], {}),\r
+            'lifetime': ('django.db.models.fields.IntegerField', [], {}),\r
+            'secret': ('django.db.models.fields.TextField', [], {'max_length': '255'}),\r
+            'server_url': ('django.db.models.fields.TextField', [], {'max_length': '2047'})\r
+        },\r
+        'forum.openidnonce': {\r
+            'Meta': {'object_name': 'OpenIdNonce'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'salt': ('django.db.models.fields.CharField', [], {'max_length': '50'}),\r
+            'server_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),\r
+            'timestamp': ('django.db.models.fields.IntegerField', [], {})\r
+        },\r
+        'forum.questionsubscription': {\r
+            'Meta': {'object_name': 'QuestionSubscription'},\r
+            'auto_subscription': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'last_view': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 4, 28, 23, 55, 36, 647000)'}),\r
+            'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        },\r
+        'forum.subscriptionsettings': {\r
+            'Meta': {'object_name': 'SubscriptionSettings'},\r
+            'all_questions': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'all_questions_watched_tags': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'enable_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'member_joins': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),\r
+            'new_question': ('django.db.models.fields.CharField', [], {'default': "'d'", 'max_length': '1'}),\r
+            'new_question_watched_tags': ('django.db.models.fields.CharField', [], {'default': "'i'", 'max_length': '1'}),\r
+            'notify_accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'notify_answers': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'notify_comments': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'notify_comments_own_post': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'notify_reply_to_comments': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_answered': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_asked': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_commented': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'questions_viewed': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'subscribed_questions': ('django.db.models.fields.CharField', [], {'default': "'i'", 'max_length': '1'}),\r
+            'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'subscription_settings'", 'unique': 'True', 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.tag': {\r
+            'Meta': {'object_name': 'Tag', 'db_table': "u'tag'"},\r
+            'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_tags'", 'to': "orm['forum.User']"}),\r
+            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_tags'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'marked_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'marked_tags'", 'through': "'MarkedTag'", 'to': "orm['forum.User']"}),\r
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'used_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})\r
+        },\r
+        'forum.user': {\r
+            'Meta': {'object_name': 'User', '_ormbases': ['auth.User']},\r
+            'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),\r
+            'bronze': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),\r
+            'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),\r
+            'gold': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'hide_ignored_questions': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),\r
+            'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),\r
+            'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),\r
+            'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),\r
+            'silver': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'subscriptions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'subscribers'", 'through': "'QuestionSubscription'", 'to': "orm['forum.Node']"}),\r
+            'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}),\r
+            'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})\r
+        },\r
+        'forum.validationhash': {\r
+            'Meta': {'unique_together': "(('user', 'type'),)", 'object_name': 'ValidationHash'},\r
+            'expiration': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 4, 29, 23, 55, 36, 708000)'}),\r
+            'hash_code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'seed': ('django.db.models.fields.CharField', [], {'max_length': '12'}),\r
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '12'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        }\r
+    }\r
+    \r
+    complete_apps = ['forum']\r
diff --git a/forum/migrations/0027_auto__del_activity.py b/forum/migrations/0027_auto__del_activity.py
new file mode 100644 (file)
index 0000000..589b5bb
--- /dev/null
@@ -0,0 +1,254 @@
+# encoding: utf-8\r
+import datetime\r
+from south.db import db\r
+from south.v2 import SchemaMigration\r
+from django.db import models\r
+\r
+class Migration(SchemaMigration):\r
+    \r
+    def forwards(self, orm):\r
+        \r
+        # Deleting model 'Activity'\r
+        db.delete_table(u'activity')\r
+    \r
+    \r
+    def backwards(self, orm):\r
+        \r
+        # Adding model 'Activity'\r
+        db.create_table(u'activity', (\r
+            ('is_auditted', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True)),\r
+            ('object_id', self.gf('django.db.models.fields.PositiveIntegerField')()),\r
+            ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['forum.User'])),\r
+            ('content_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['contenttypes.ContentType'])),\r
+            ('active_at', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)),\r
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),\r
+            ('activity_type', self.gf('django.db.models.fields.SmallIntegerField')()),\r
+        ))\r
+        db.send_create_signal('forum', ['Activity'])\r
+    \r
+    \r
+    models = {\r
+        'auth.group': {\r
+            'Meta': {'object_name': 'Group'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),\r
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'})\r
+        },\r
+        'auth.permission': {\r
+            'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},\r
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})\r
+        },\r
+        'auth.user': {\r
+            'Meta': {'object_name': 'User'},\r
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),\r
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),\r
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),\r
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),\r
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'}),\r
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})\r
+        },\r
+        'contenttypes.contenttype': {\r
+            'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},\r
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})\r
+        },\r
+        'forum.action': {\r
+            'Meta': {'object_name': 'Action'},\r
+            'action_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'action_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'canceled_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),\r
+            'canceled_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'canceled_actions'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'extra': ('django.db.models.fields.CharField', [], {'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'ip': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.actionrepute': {\r
+            'Meta': {'object_name': 'ActionRepute'},\r
+            'action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reputes'", 'to': "orm['forum.Action']"}),\r
+            'by_canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reputes'", 'to': "orm['forum.User']"}),\r
+            'value': ('django.db.models.fields.IntegerField', [], {'default': '0'})\r
+        },\r
+        'forum.authkeyuserassociation': {\r
+            'Meta': {'object_name': 'AuthKeyUserAssociation'},\r
+            'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'provider': ('django.db.models.fields.CharField', [], {'max_length': '64'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_keys'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.award': {\r
+            'Meta': {'object_name': 'Award', 'db_table': "u'award'"},\r
+            'awarded_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'badge': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award_badge'", 'to': "orm['forum.Badge']"}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'notified': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'awards'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.badge': {\r
+            'Meta': {'unique_together': "(('name', 'type'),)", 'object_name': 'Badge', 'db_table': "u'badge'"},\r
+            'awarded_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'awarded_to': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'badges'", 'through': "'Award'", 'to': "orm['forum.User']"}),\r
+            'description': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'multiple': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),\r
+            'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '50', 'blank': 'True'}),\r
+            'type': ('django.db.models.fields.SmallIntegerField', [], {})\r
+        },\r
+        'forum.keyvalue': {\r
+            'Meta': {'object_name': 'KeyValue'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'value': ('forum.models.utils.PickledObjectField', [], {})\r
+        },\r
+        'forum.markedtag': {\r
+            'Meta': {'object_name': 'MarkedTag'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'reason': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_selections'", 'to': "orm['forum.Tag']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tag_selections'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.node': {\r
+            'Meta': {'object_name': 'Node'},\r
+            'abs_parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'all_children'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'active_revision': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'active'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.NodeRevision']"}),\r
+            'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'nodes'", 'to': "orm['forum.User']"}),\r
+            'body': ('django.db.models.fields.TextField', [], {}),\r
+            'deleted': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'deleted_node'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'extra_action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'extra_node'", 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'extra_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'extra_ref': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']", 'null': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'last_activity_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'last_activity_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']", 'null': 'True'}),\r
+            'last_edited': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'edited_node'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'marked': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'node_type': ('django.db.models.fields.CharField', [], {'default': "'node'", 'max_length': '16'}),\r
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'children'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),\r
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'nodes'", 'to': "orm['forum.Tag']"}),\r
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'})\r
+        },\r
+        'forum.noderevision': {\r
+            'Meta': {'unique_together': "(('node', 'revision'),)", 'object_name': 'NodeRevision'},\r
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'noderevisions'", 'to': "orm['forum.User']"}),\r
+            'body': ('django.db.models.fields.TextField', [], {}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revisions'", 'to': "orm['forum.Node']"}),\r
+            'revised_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'revision': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'summary': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),\r
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '300'})\r
+        },\r
+        'forum.openidassociation': {\r
+            'Meta': {'object_name': 'OpenIdAssociation'},\r
+            'assoc_type': ('django.db.models.fields.TextField', [], {'max_length': '64'}),\r
+            'handle': ('django.db.models.fields.CharField', [], {'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'issued': ('django.db.models.fields.IntegerField', [], {}),\r
+            'lifetime': ('django.db.models.fields.IntegerField', [], {}),\r
+            'secret': ('django.db.models.fields.TextField', [], {'max_length': '255'}),\r
+            'server_url': ('django.db.models.fields.TextField', [], {'max_length': '2047'})\r
+        },\r
+        'forum.openidnonce': {\r
+            'Meta': {'object_name': 'OpenIdNonce'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'salt': ('django.db.models.fields.CharField', [], {'max_length': '50'}),\r
+            'server_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),\r
+            'timestamp': ('django.db.models.fields.IntegerField', [], {})\r
+        },\r
+        'forum.questionsubscription': {\r
+            'Meta': {'object_name': 'QuestionSubscription'},\r
+            'auto_subscription': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'last_view': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 4, 29, 1, 30, 30, 35000)'}),\r
+            'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        },\r
+        'forum.subscriptionsettings': {\r
+            'Meta': {'object_name': 'SubscriptionSettings'},\r
+            'all_questions': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'all_questions_watched_tags': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'enable_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'member_joins': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),\r
+            'new_question': ('django.db.models.fields.CharField', [], {'default': "'d'", 'max_length': '1'}),\r
+            'new_question_watched_tags': ('django.db.models.fields.CharField', [], {'default': "'i'", 'max_length': '1'}),\r
+            'notify_accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'notify_answers': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'notify_comments': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'notify_comments_own_post': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'notify_reply_to_comments': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_answered': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_asked': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_commented': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'questions_viewed': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'subscribed_questions': ('django.db.models.fields.CharField', [], {'default': "'i'", 'max_length': '1'}),\r
+            'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'subscription_settings'", 'unique': 'True', 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.tag': {\r
+            'Meta': {'object_name': 'Tag', 'db_table': "u'tag'"},\r
+            'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_tags'", 'to': "orm['forum.User']"}),\r
+            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_tags'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'marked_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'marked_tags'", 'through': "'MarkedTag'", 'to': "orm['forum.User']"}),\r
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'used_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})\r
+        },\r
+        'forum.user': {\r
+            'Meta': {'object_name': 'User', '_ormbases': ['auth.User']},\r
+            'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),\r
+            'bronze': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),\r
+            'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),\r
+            'gold': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'hide_ignored_questions': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),\r
+            'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),\r
+            'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),\r
+            'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),\r
+            'silver': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'subscriptions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'subscribers'", 'through': "'QuestionSubscription'", 'to': "orm['forum.Node']"}),\r
+            'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}),\r
+            'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})\r
+        },\r
+        'forum.validationhash': {\r
+            'Meta': {'unique_together': "(('user', 'type'),)", 'object_name': 'ValidationHash'},\r
+            'expiration': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 4, 30, 1, 30, 30, 211000)'}),\r
+            'hash_code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'seed': ('django.db.models.fields.CharField', [], {'max_length': '12'}),\r
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '12'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        }\r
+    }\r
+    \r
+    complete_apps = ['forum']\r
diff --git a/forum/migrations/0028_auto__add_field_action_canceled_ip__add_field_actionrepute_date.py b/forum/migrations/0028_auto__add_field_action_canceled_ip__add_field_actionrepute_date.py
new file mode 100644 (file)
index 0000000..1170bf7
--- /dev/null
@@ -0,0 +1,253 @@
+# encoding: utf-8\r
+import datetime\r
+from south.db import db\r
+from south.v2 import SchemaMigration\r
+from django.db import models\r
+\r
+class Migration(SchemaMigration):\r
+    \r
+    def forwards(self, orm):\r
+        \r
+        # Adding field 'Action.canceled_ip'\r
+        db.add_column('forum_action', 'canceled_ip', self.gf('django.db.models.fields.CharField')(default='', max_length=16), keep_default=False)\r
+\r
+        # Adding field 'ActionRepute.date'\r
+        db.add_column('forum_actionrepute', 'date', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now), keep_default=False)\r
+    \r
+    \r
+    def backwards(self, orm):\r
+        \r
+        # Deleting field 'Action.canceled_ip'\r
+        db.delete_column('forum_action', 'canceled_ip')\r
+\r
+        # Deleting field 'ActionRepute.date'\r
+        db.delete_column('forum_actionrepute', 'date')\r
+    \r
+    \r
+    models = {\r
+        'auth.group': {\r
+            'Meta': {'object_name': 'Group'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),\r
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'})\r
+        },\r
+        'auth.permission': {\r
+            'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},\r
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})\r
+        },\r
+        'auth.user': {\r
+            'Meta': {'object_name': 'User'},\r
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),\r
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),\r
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),\r
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),\r
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'}),\r
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})\r
+        },\r
+        'contenttypes.contenttype': {\r
+            'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},\r
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})\r
+        },\r
+        'forum.action': {\r
+            'Meta': {'object_name': 'Action'},\r
+            'action_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'action_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'canceled_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),\r
+            'canceled_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'canceled_actions'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'canceled_ip': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'extra': ('django.db.models.fields.CharField', [], {'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'ip': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.actionrepute': {\r
+            'Meta': {'object_name': 'ActionRepute'},\r
+            'action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reputes'", 'to': "orm['forum.Action']"}),\r
+            'by_canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reputes'", 'to': "orm['forum.User']"}),\r
+            'value': ('django.db.models.fields.IntegerField', [], {'default': '0'})\r
+        },\r
+        'forum.authkeyuserassociation': {\r
+            'Meta': {'object_name': 'AuthKeyUserAssociation'},\r
+            'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'provider': ('django.db.models.fields.CharField', [], {'max_length': '64'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_keys'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.award': {\r
+            'Meta': {'object_name': 'Award', 'db_table': "u'award'"},\r
+            'awarded_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'badge': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award_badge'", 'to': "orm['forum.Badge']"}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'notified': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'awards'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.badge': {\r
+            'Meta': {'unique_together': "(('name', 'type'),)", 'object_name': 'Badge', 'db_table': "u'badge'"},\r
+            'awarded_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'awarded_to': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'badges'", 'through': "'Award'", 'to': "orm['forum.User']"}),\r
+            'description': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'multiple': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),\r
+            'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '50', 'blank': 'True'}),\r
+            'type': ('django.db.models.fields.SmallIntegerField', [], {})\r
+        },\r
+        'forum.keyvalue': {\r
+            'Meta': {'object_name': 'KeyValue'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'value': ('forum.models.utils.PickledObjectField', [], {})\r
+        },\r
+        'forum.markedtag': {\r
+            'Meta': {'object_name': 'MarkedTag'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'reason': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_selections'", 'to': "orm['forum.Tag']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tag_selections'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.node': {\r
+            'Meta': {'object_name': 'Node'},\r
+            'abs_parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'all_children'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'active_revision': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'active'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.NodeRevision']"}),\r
+            'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'nodes'", 'to': "orm['forum.User']"}),\r
+            'body': ('django.db.models.fields.TextField', [], {}),\r
+            'deleted': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'deleted_node'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'extra_action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'extra_node'", 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'extra_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'extra_ref': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']", 'null': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'last_activity_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'last_activity_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']", 'null': 'True'}),\r
+            'last_edited': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'edited_node'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'marked': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'node_type': ('django.db.models.fields.CharField', [], {'default': "'node'", 'max_length': '16'}),\r
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'children'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),\r
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'nodes'", 'to': "orm['forum.Tag']"}),\r
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'})\r
+        },\r
+        'forum.noderevision': {\r
+            'Meta': {'unique_together': "(('node', 'revision'),)", 'object_name': 'NodeRevision'},\r
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'noderevisions'", 'to': "orm['forum.User']"}),\r
+            'body': ('django.db.models.fields.TextField', [], {}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revisions'", 'to': "orm['forum.Node']"}),\r
+            'revised_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'revision': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'summary': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),\r
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '300'})\r
+        },\r
+        'forum.openidassociation': {\r
+            'Meta': {'object_name': 'OpenIdAssociation'},\r
+            'assoc_type': ('django.db.models.fields.TextField', [], {'max_length': '64'}),\r
+            'handle': ('django.db.models.fields.CharField', [], {'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'issued': ('django.db.models.fields.IntegerField', [], {}),\r
+            'lifetime': ('django.db.models.fields.IntegerField', [], {}),\r
+            'secret': ('django.db.models.fields.TextField', [], {'max_length': '255'}),\r
+            'server_url': ('django.db.models.fields.TextField', [], {'max_length': '2047'})\r
+        },\r
+        'forum.openidnonce': {\r
+            'Meta': {'object_name': 'OpenIdNonce'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'salt': ('django.db.models.fields.CharField', [], {'max_length': '50'}),\r
+            'server_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),\r
+            'timestamp': ('django.db.models.fields.IntegerField', [], {})\r
+        },\r
+        'forum.questionsubscription': {\r
+            'Meta': {'object_name': 'QuestionSubscription'},\r
+            'auto_subscription': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'last_view': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 4, 29, 21, 20, 24, 880000)'}),\r
+            'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        },\r
+        'forum.subscriptionsettings': {\r
+            'Meta': {'object_name': 'SubscriptionSettings'},\r
+            'all_questions': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'all_questions_watched_tags': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'enable_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'member_joins': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),\r
+            'new_question': ('django.db.models.fields.CharField', [], {'default': "'d'", 'max_length': '1'}),\r
+            'new_question_watched_tags': ('django.db.models.fields.CharField', [], {'default': "'i'", 'max_length': '1'}),\r
+            'notify_accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'notify_answers': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'notify_comments': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'notify_comments_own_post': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'notify_reply_to_comments': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_answered': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_asked': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_commented': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'questions_viewed': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'subscribed_questions': ('django.db.models.fields.CharField', [], {'default': "'i'", 'max_length': '1'}),\r
+            'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'subscription_settings'", 'unique': 'True', 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.tag': {\r
+            'Meta': {'object_name': 'Tag', 'db_table': "u'tag'"},\r
+            'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_tags'", 'to': "orm['forum.User']"}),\r
+            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_tags'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'marked_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'marked_tags'", 'through': "'MarkedTag'", 'to': "orm['forum.User']"}),\r
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'used_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})\r
+        },\r
+        'forum.user': {\r
+            'Meta': {'object_name': 'User', '_ormbases': ['auth.User']},\r
+            'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),\r
+            'bronze': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),\r
+            'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),\r
+            'gold': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'hide_ignored_questions': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),\r
+            'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),\r
+            'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),\r
+            'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),\r
+            'silver': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'subscriptions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'subscribers'", 'through': "'QuestionSubscription'", 'to': "orm['forum.Node']"}),\r
+            'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}),\r
+            'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})\r
+        },\r
+        'forum.validationhash': {\r
+            'Meta': {'unique_together': "(('user', 'type'),)", 'object_name': 'ValidationHash'},\r
+            'expiration': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 4, 30, 21, 20, 35, 361000)'}),\r
+            'hash_code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'seed': ('django.db.models.fields.CharField', [], {'max_length': '12'}),\r
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '12'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        }\r
+    }\r
+    \r
+    complete_apps = ['forum']\r
diff --git a/forum/migrations/0029_repute_dates.py b/forum/migrations/0029_repute_dates.py
new file mode 100644 (file)
index 0000000..14d6e62
--- /dev/null
@@ -0,0 +1,259 @@
+# encoding: utf-8\r
+import datetime\r
+from south.db import db\r
+from south.v2 import DataMigration\r
+from django.db import models\r
+from forum.migrations import ProgressBar\r
+\r
+class Migration(DataMigration):\r
+    \r
+    def forwards(self, orm):\r
+        r_count = orm.ActionRepute.objects.count()\r
+        print "\nAdding dates to %d repute actions:" % r_count\r
+        progress = ProgressBar(r_count)\r
+\r
+        for r in orm.ActionRepute.objects.all():\r
+            a = r.action\r
+\r
+            if r.by_canceled:\r
+                r.date = a.canceled_at\r
+            else:\r
+                r.date = a.action_date\r
+\r
+            r.save()\r
+\r
+            progress.update()\r
+\r
+        print "\n...done\n"\r
+    \r
+    \r
+    def backwards(self, orm):\r
+        "Write your backwards methods here."\r
+    \r
+    models = {\r
+        'auth.group': {\r
+            'Meta': {'object_name': 'Group'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),\r
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'})\r
+        },\r
+        'auth.permission': {\r
+            'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},\r
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})\r
+        },\r
+        'auth.user': {\r
+            'Meta': {'object_name': 'User'},\r
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),\r
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),\r
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),\r
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),\r
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'}),\r
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})\r
+        },\r
+        'contenttypes.contenttype': {\r
+            'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},\r
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})\r
+        },\r
+        'forum.action': {\r
+            'Meta': {'object_name': 'Action'},\r
+            'action_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'action_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'canceled_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),\r
+            'canceled_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'canceled_actions'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'canceled_ip': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'extra': ('django.db.models.fields.CharField', [], {'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'ip': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.actionrepute': {\r
+            'Meta': {'object_name': 'ActionRepute'},\r
+            'action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reputes'", 'to': "orm['forum.Action']"}),\r
+            'by_canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reputes'", 'to': "orm['forum.User']"}),\r
+            'value': ('django.db.models.fields.IntegerField', [], {'default': '0'})\r
+        },\r
+        'forum.authkeyuserassociation': {\r
+            'Meta': {'object_name': 'AuthKeyUserAssociation'},\r
+            'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'provider': ('django.db.models.fields.CharField', [], {'max_length': '64'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_keys'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.award': {\r
+            'Meta': {'object_name': 'Award', 'db_table': "u'award'"},\r
+            'awarded_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'badge': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award_badge'", 'to': "orm['forum.Badge']"}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'notified': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'awards'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.badge': {\r
+            'Meta': {'unique_together': "(('name', 'type'),)", 'object_name': 'Badge', 'db_table': "u'badge'"},\r
+            'awarded_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'awarded_to': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'badges'", 'through': "'Award'", 'to': "orm['forum.User']"}),\r
+            'description': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'multiple': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),\r
+            'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '50', 'blank': 'True'}),\r
+            'type': ('django.db.models.fields.SmallIntegerField', [], {})\r
+        },\r
+        'forum.keyvalue': {\r
+            'Meta': {'object_name': 'KeyValue'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'value': ('forum.models.utils.PickledObjectField', [], {})\r
+        },\r
+        'forum.markedtag': {\r
+            'Meta': {'object_name': 'MarkedTag'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'reason': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_selections'", 'to': "orm['forum.Tag']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tag_selections'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.node': {\r
+            'Meta': {'object_name': 'Node'},\r
+            'abs_parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'all_children'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'active_revision': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'active'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.NodeRevision']"}),\r
+            'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'nodes'", 'to': "orm['forum.User']"}),\r
+            'body': ('django.db.models.fields.TextField', [], {}),\r
+            'deleted': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'deleted_node'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'extra_action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'extra_node'", 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'extra_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'extra_ref': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']", 'null': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'last_activity_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'last_activity_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']", 'null': 'True'}),\r
+            'last_edited': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'edited_node'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'marked': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'node_type': ('django.db.models.fields.CharField', [], {'default': "'node'", 'max_length': '16'}),\r
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'children'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),\r
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'nodes'", 'to': "orm['forum.Tag']"}),\r
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'})\r
+        },\r
+        'forum.noderevision': {\r
+            'Meta': {'unique_together': "(('node', 'revision'),)", 'object_name': 'NodeRevision'},\r
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'noderevisions'", 'to': "orm['forum.User']"}),\r
+            'body': ('django.db.models.fields.TextField', [], {}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revisions'", 'to': "orm['forum.Node']"}),\r
+            'revised_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'revision': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'summary': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),\r
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '300'})\r
+        },\r
+        'forum.openidassociation': {\r
+            'Meta': {'object_name': 'OpenIdAssociation'},\r
+            'assoc_type': ('django.db.models.fields.TextField', [], {'max_length': '64'}),\r
+            'handle': ('django.db.models.fields.CharField', [], {'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'issued': ('django.db.models.fields.IntegerField', [], {}),\r
+            'lifetime': ('django.db.models.fields.IntegerField', [], {}),\r
+            'secret': ('django.db.models.fields.TextField', [], {'max_length': '255'}),\r
+            'server_url': ('django.db.models.fields.TextField', [], {'max_length': '2047'})\r
+        },\r
+        'forum.openidnonce': {\r
+            'Meta': {'object_name': 'OpenIdNonce'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'salt': ('django.db.models.fields.CharField', [], {'max_length': '50'}),\r
+            'server_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),\r
+            'timestamp': ('django.db.models.fields.IntegerField', [], {})\r
+        },\r
+        'forum.questionsubscription': {\r
+            'Meta': {'object_name': 'QuestionSubscription'},\r
+            'auto_subscription': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'last_view': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 4, 29, 21, 21, 16, 237000)'}),\r
+            'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        },\r
+        'forum.subscriptionsettings': {\r
+            'Meta': {'object_name': 'SubscriptionSettings'},\r
+            'all_questions': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'all_questions_watched_tags': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'enable_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'member_joins': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),\r
+            'new_question': ('django.db.models.fields.CharField', [], {'default': "'d'", 'max_length': '1'}),\r
+            'new_question_watched_tags': ('django.db.models.fields.CharField', [], {'default': "'i'", 'max_length': '1'}),\r
+            'notify_accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'notify_answers': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'notify_comments': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'notify_comments_own_post': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'notify_reply_to_comments': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_answered': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_asked': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_commented': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'questions_viewed': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'subscribed_questions': ('django.db.models.fields.CharField', [], {'default': "'i'", 'max_length': '1'}),\r
+            'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'subscription_settings'", 'unique': 'True', 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.tag': {\r
+            'Meta': {'object_name': 'Tag', 'db_table': "u'tag'"},\r
+            'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_tags'", 'to': "orm['forum.User']"}),\r
+            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_tags'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'marked_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'marked_tags'", 'through': "'MarkedTag'", 'to': "orm['forum.User']"}),\r
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'used_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})\r
+        },\r
+        'forum.user': {\r
+            'Meta': {'object_name': 'User', '_ormbases': ['auth.User']},\r
+            'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),\r
+            'bronze': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),\r
+            'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),\r
+            'gold': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'hide_ignored_questions': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),\r
+            'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),\r
+            'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),\r
+            'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),\r
+            'silver': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'subscriptions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'subscribers'", 'through': "'QuestionSubscription'", 'to': "orm['forum.Node']"}),\r
+            'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}),\r
+            'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})\r
+        },\r
+        'forum.validationhash': {\r
+            'Meta': {'unique_together': "(('user', 'type'),)", 'object_name': 'ValidationHash'},\r
+            'expiration': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 4, 30, 21, 21, 16, 298000)'}),\r
+            'hash_code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'seed': ('django.db.models.fields.CharField', [], {'max_length': '12'}),\r
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '12'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        }\r
+    }\r
+    \r
+    complete_apps = ['forum']\r
diff --git a/forum/migrations/0030_auto__chg_field_action_extra__chg_field_keyvalue_value.py b/forum/migrations/0030_auto__chg_field_action_extra__chg_field_keyvalue_value.py
new file mode 100644 (file)
index 0000000..cd4e1f1
--- /dev/null
@@ -0,0 +1,253 @@
+# encoding: utf-8\r
+import datetime\r
+from south.db import db\r
+from south.v2 import SchemaMigration\r
+from django.db import models\r
+\r
+class Migration(SchemaMigration):\r
+    \r
+    def forwards(self, orm):\r
+        \r
+        # Changing field 'Action.extra'\r
+        db.alter_column('forum_action', 'extra', self.gf('forum.models.utils.PickledObjectField')(null=True))\r
+\r
+        # Changing field 'KeyValue.value'\r
+        db.alter_column('forum_keyvalue', 'value', self.gf('forum.models.utils.PickledObjectField')(null=True))\r
+    \r
+    \r
+    def backwards(self, orm):\r
+        \r
+        # Changing field 'Action.extra'\r
+        db.alter_column('forum_action', 'extra', self.gf('django.db.models.fields.CharField')(max_length=255))\r
+\r
+        # Changing field 'KeyValue.value'\r
+        db.alter_column('forum_keyvalue', 'value', self.gf('forum.models.utils.PickledObjectField')())\r
+    \r
+    \r
+    models = {\r
+        'auth.group': {\r
+            'Meta': {'object_name': 'Group'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),\r
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'})\r
+        },\r
+        'auth.permission': {\r
+            'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},\r
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})\r
+        },\r
+        'auth.user': {\r
+            'Meta': {'object_name': 'User'},\r
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),\r
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),\r
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),\r
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),\r
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'}),\r
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})\r
+        },\r
+        'contenttypes.contenttype': {\r
+            'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},\r
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})\r
+        },\r
+        'forum.action': {\r
+            'Meta': {'object_name': 'Action'},\r
+            'action_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'action_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'canceled_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),\r
+            'canceled_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'canceled_actions'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'canceled_ip': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'extra': ('forum.models.utils.PickledObjectField', [], {'null': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'ip': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.actionrepute': {\r
+            'Meta': {'object_name': 'ActionRepute'},\r
+            'action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reputes'", 'to': "orm['forum.Action']"}),\r
+            'by_canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reputes'", 'to': "orm['forum.User']"}),\r
+            'value': ('django.db.models.fields.IntegerField', [], {'default': '0'})\r
+        },\r
+        'forum.authkeyuserassociation': {\r
+            'Meta': {'object_name': 'AuthKeyUserAssociation'},\r
+            'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'provider': ('django.db.models.fields.CharField', [], {'max_length': '64'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_keys'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.award': {\r
+            'Meta': {'object_name': 'Award', 'db_table': "u'award'"},\r
+            'awarded_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'badge': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award_badge'", 'to': "orm['forum.Badge']"}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'notified': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'awards'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.badge': {\r
+            'Meta': {'unique_together': "(('name', 'type'),)", 'object_name': 'Badge', 'db_table': "u'badge'"},\r
+            'awarded_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'awarded_to': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'badges'", 'through': "'Award'", 'to': "orm['forum.User']"}),\r
+            'description': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'multiple': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),\r
+            'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '50', 'blank': 'True'}),\r
+            'type': ('django.db.models.fields.SmallIntegerField', [], {})\r
+        },\r
+        'forum.keyvalue': {\r
+            'Meta': {'object_name': 'KeyValue'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'value': ('forum.models.utils.PickledObjectField', [], {'null': 'True'})\r
+        },\r
+        'forum.markedtag': {\r
+            'Meta': {'object_name': 'MarkedTag'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'reason': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_selections'", 'to': "orm['forum.Tag']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tag_selections'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.node': {\r
+            'Meta': {'object_name': 'Node'},\r
+            'abs_parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'all_children'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'active_revision': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'active'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.NodeRevision']"}),\r
+            'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'nodes'", 'to': "orm['forum.User']"}),\r
+            'body': ('django.db.models.fields.TextField', [], {}),\r
+            'deleted': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'deleted_node'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'extra_action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'extra_node'", 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'extra_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'extra_ref': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']", 'null': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'last_activity_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'last_activity_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']", 'null': 'True'}),\r
+            'last_edited': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'edited_node'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'marked': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'node_type': ('django.db.models.fields.CharField', [], {'default': "'node'", 'max_length': '16'}),\r
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'children'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),\r
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'nodes'", 'to': "orm['forum.Tag']"}),\r
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'})\r
+        },\r
+        'forum.noderevision': {\r
+            'Meta': {'unique_together': "(('node', 'revision'),)", 'object_name': 'NodeRevision'},\r
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'noderevisions'", 'to': "orm['forum.User']"}),\r
+            'body': ('django.db.models.fields.TextField', [], {}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revisions'", 'to': "orm['forum.Node']"}),\r
+            'revised_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'revision': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'summary': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),\r
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '300'})\r
+        },\r
+        'forum.openidassociation': {\r
+            'Meta': {'object_name': 'OpenIdAssociation'},\r
+            'assoc_type': ('django.db.models.fields.TextField', [], {'max_length': '64'}),\r
+            'handle': ('django.db.models.fields.CharField', [], {'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'issued': ('django.db.models.fields.IntegerField', [], {}),\r
+            'lifetime': ('django.db.models.fields.IntegerField', [], {}),\r
+            'secret': ('django.db.models.fields.TextField', [], {'max_length': '255'}),\r
+            'server_url': ('django.db.models.fields.TextField', [], {'max_length': '2047'})\r
+        },\r
+        'forum.openidnonce': {\r
+            'Meta': {'object_name': 'OpenIdNonce'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'salt': ('django.db.models.fields.CharField', [], {'max_length': '50'}),\r
+            'server_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),\r
+            'timestamp': ('django.db.models.fields.IntegerField', [], {})\r
+        },\r
+        'forum.questionsubscription': {\r
+            'Meta': {'object_name': 'QuestionSubscription'},\r
+            'auto_subscription': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'last_view': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 4, 30, 23, 58, 8, 677000)'}),\r
+            'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        },\r
+        'forum.subscriptionsettings': {\r
+            'Meta': {'object_name': 'SubscriptionSettings'},\r
+            'all_questions': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'all_questions_watched_tags': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'enable_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'member_joins': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),\r
+            'new_question': ('django.db.models.fields.CharField', [], {'default': "'d'", 'max_length': '1'}),\r
+            'new_question_watched_tags': ('django.db.models.fields.CharField', [], {'default': "'i'", 'max_length': '1'}),\r
+            'notify_accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'notify_answers': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'notify_comments': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'notify_comments_own_post': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'notify_reply_to_comments': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_answered': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_asked': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_commented': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'questions_viewed': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'subscribed_questions': ('django.db.models.fields.CharField', [], {'default': "'i'", 'max_length': '1'}),\r
+            'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'subscription_settings'", 'unique': 'True', 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.tag': {\r
+            'Meta': {'object_name': 'Tag', 'db_table': "u'tag'"},\r
+            'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_tags'", 'to': "orm['forum.User']"}),\r
+            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_tags'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'marked_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'marked_tags'", 'through': "'MarkedTag'", 'to': "orm['forum.User']"}),\r
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'used_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})\r
+        },\r
+        'forum.user': {\r
+            'Meta': {'object_name': 'User', '_ormbases': ['auth.User']},\r
+            'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),\r
+            'bronze': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),\r
+            'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),\r
+            'gold': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'hide_ignored_questions': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),\r
+            'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),\r
+            'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),\r
+            'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),\r
+            'silver': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'subscriptions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'subscribers'", 'through': "'QuestionSubscription'", 'to': "orm['forum.Node']"}),\r
+            'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}),\r
+            'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})\r
+        },\r
+        'forum.validationhash': {\r
+            'Meta': {'unique_together': "(('user', 'type'),)", 'object_name': 'ValidationHash'},\r
+            'expiration': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 5, 1, 23, 58, 8, 841000)'}),\r
+            'hash_code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'seed': ('django.db.models.fields.CharField', [], {'max_length': '12'}),\r
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '12'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        }\r
+    }\r
+    \r
+    complete_apps = ['forum']\r
diff --git a/forum/migrations/0031_alter_pickle_storage.py b/forum/migrations/0031_alter_pickle_storage.py
new file mode 100644 (file)
index 0000000..5c8bb7b
--- /dev/null
@@ -0,0 +1,274 @@
+# encoding: utf-8\r
+import datetime\r
+from south.db import db\r
+from south.v2 import DataMigration\r
+from django.db import models\r
+from forum.migrations import ProgressBar\r
+from forum.models.utils import dbsafe_encode\r
+\r
+try:\r
+    from cPickle import loads, dumps\r
+except ImportError:\r
+    from pickle import loads, dumps\r
+\r
+class Migration(DataMigration):\r
+    \r
+    def forwards(self, orm):\r
+        k_count = orm.KeyValue.objects.count()\r
+        print "\nConverting %d keyvalue objects:" % k_count\r
+        progress = ProgressBar(k_count)\r
+\r
+        for kv in orm.KeyValue.objects.all():\r
+            try:\r
+                o = loads(kv.value.encode('utf-8'))\r
+            except:\r
+                o = kv.value\r
+\r
+            kv.value = dbsafe_encode(o, compress_object=True)\r
+            kv.save()\r
+            progress.update()\r
+\r
+        print "\n...done\n"\r
+\r
+        a_count = orm.Action.objects.count()\r
+        print "\nConverting %d actions extra fields:" % a_count\r
+        progress = ProgressBar(a_count)\r
+\r
+        for a in orm.Action.objects.all():\r
+            a.extra = dbsafe_encode(a.extra, compress_object=True)\r
+            a.save()\r
+            progress.update()\r
+\r
+        print "\n...done\n"\r
+    \r
+    \r
+    def backwards(self, orm):\r
+        "Write your backwards methods here."\r
+    \r
+    models = {\r
+        'auth.group': {\r
+            'Meta': {'object_name': 'Group'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),\r
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'})\r
+        },\r
+        'auth.permission': {\r
+            'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},\r
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})\r
+        },\r
+        'auth.user': {\r
+            'Meta': {'object_name': 'User'},\r
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),\r
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),\r
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),\r
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),\r
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'}),\r
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})\r
+        },\r
+        'contenttypes.contenttype': {\r
+            'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},\r
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})\r
+        },\r
+        'forum.action': {\r
+            'Meta': {'object_name': 'Action'},\r
+            'action_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'action_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'canceled_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),\r
+            'canceled_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'canceled_actions'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'canceled_ip': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'extra': ('forum.models.utils.PickledObjectField', [], {'null': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'ip': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.actionrepute': {\r
+            'Meta': {'object_name': 'ActionRepute'},\r
+            'action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reputes'", 'to': "orm['forum.Action']"}),\r
+            'by_canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reputes'", 'to': "orm['forum.User']"}),\r
+            'value': ('django.db.models.fields.IntegerField', [], {'default': '0'})\r
+        },\r
+        'forum.authkeyuserassociation': {\r
+            'Meta': {'object_name': 'AuthKeyUserAssociation'},\r
+            'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'provider': ('django.db.models.fields.CharField', [], {'max_length': '64'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_keys'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.award': {\r
+            'Meta': {'object_name': 'Award', 'db_table': "u'award'"},\r
+            'awarded_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'badge': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award_badge'", 'to': "orm['forum.Badge']"}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'notified': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'awards'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.badge': {\r
+            'Meta': {'unique_together': "(('name', 'type'),)", 'object_name': 'Badge', 'db_table': "u'badge'"},\r
+            'awarded_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'awarded_to': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'badges'", 'through': "'Award'", 'to': "orm['forum.User']"}),\r
+            'description': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'multiple': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),\r
+            'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '50', 'blank': 'True'}),\r
+            'type': ('django.db.models.fields.SmallIntegerField', [], {})\r
+        },\r
+        'forum.keyvalue': {\r
+            'Meta': {'object_name': 'KeyValue'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'value': ('forum.models.utils.PickledObjectField', [], {'null': 'True'})\r
+        },\r
+        'forum.markedtag': {\r
+            'Meta': {'object_name': 'MarkedTag'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'reason': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_selections'", 'to': "orm['forum.Tag']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tag_selections'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.node': {\r
+            'Meta': {'object_name': 'Node'},\r
+            'abs_parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'all_children'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'active_revision': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'active'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.NodeRevision']"}),\r
+            'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'nodes'", 'to': "orm['forum.User']"}),\r
+            'body': ('django.db.models.fields.TextField', [], {}),\r
+            'deleted': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'deleted_node'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'extra_action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'extra_node'", 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'extra_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'extra_ref': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']", 'null': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'last_activity_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'last_activity_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']", 'null': 'True'}),\r
+            'last_edited': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'edited_node'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'marked': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'node_type': ('django.db.models.fields.CharField', [], {'default': "'node'", 'max_length': '16'}),\r
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'children'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),\r
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'nodes'", 'to': "orm['forum.Tag']"}),\r
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'})\r
+        },\r
+        'forum.noderevision': {\r
+            'Meta': {'unique_together': "(('node', 'revision'),)", 'object_name': 'NodeRevision'},\r
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'noderevisions'", 'to': "orm['forum.User']"}),\r
+            'body': ('django.db.models.fields.TextField', [], {}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revisions'", 'to': "orm['forum.Node']"}),\r
+            'revised_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'revision': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'summary': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),\r
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '300'})\r
+        },\r
+        'forum.openidassociation': {\r
+            'Meta': {'object_name': 'OpenIdAssociation'},\r
+            'assoc_type': ('django.db.models.fields.TextField', [], {'max_length': '64'}),\r
+            'handle': ('django.db.models.fields.CharField', [], {'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'issued': ('django.db.models.fields.IntegerField', [], {}),\r
+            'lifetime': ('django.db.models.fields.IntegerField', [], {}),\r
+            'secret': ('django.db.models.fields.TextField', [], {'max_length': '255'}),\r
+            'server_url': ('django.db.models.fields.TextField', [], {'max_length': '2047'})\r
+        },\r
+        'forum.openidnonce': {\r
+            'Meta': {'object_name': 'OpenIdNonce'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'salt': ('django.db.models.fields.CharField', [], {'max_length': '50'}),\r
+            'server_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),\r
+            'timestamp': ('django.db.models.fields.IntegerField', [], {})\r
+        },\r
+        'forum.questionsubscription': {\r
+            'Meta': {'object_name': 'QuestionSubscription'},\r
+            'auto_subscription': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'last_view': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 5, 1, 0, 0, 32, 37000)'}),\r
+            'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        },\r
+        'forum.subscriptionsettings': {\r
+            'Meta': {'object_name': 'SubscriptionSettings'},\r
+            'all_questions': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'all_questions_watched_tags': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'enable_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'member_joins': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),\r
+            'new_question': ('django.db.models.fields.CharField', [], {'default': "'d'", 'max_length': '1'}),\r
+            'new_question_watched_tags': ('django.db.models.fields.CharField', [], {'default': "'i'", 'max_length': '1'}),\r
+            'notify_accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'notify_answers': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'notify_comments': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'notify_comments_own_post': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'notify_reply_to_comments': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_answered': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_asked': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_commented': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'questions_viewed': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'subscribed_questions': ('django.db.models.fields.CharField', [], {'default': "'i'", 'max_length': '1'}),\r
+            'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'subscription_settings'", 'unique': 'True', 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.tag': {\r
+            'Meta': {'object_name': 'Tag', 'db_table': "u'tag'"},\r
+            'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_tags'", 'to': "orm['forum.User']"}),\r
+            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_tags'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'marked_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'marked_tags'", 'through': "'MarkedTag'", 'to': "orm['forum.User']"}),\r
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'used_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})\r
+        },\r
+        'forum.user': {\r
+            'Meta': {'object_name': 'User', '_ormbases': ['auth.User']},\r
+            'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),\r
+            'bronze': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),\r
+            'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),\r
+            'gold': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'hide_ignored_questions': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),\r
+            'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),\r
+            'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),\r
+            'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),\r
+            'silver': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'subscriptions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'subscribers'", 'through': "'QuestionSubscription'", 'to': "orm['forum.Node']"}),\r
+            'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}),\r
+            'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})\r
+        },\r
+        'forum.validationhash': {\r
+            'Meta': {'unique_together': "(('user', 'type'),)", 'object_name': 'ValidationHash'},\r
+            'expiration': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 5, 2, 0, 0, 32, 86000)'}),\r
+            'hash_code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'seed': ('django.db.models.fields.CharField', [], {'max_length': '12'}),\r
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '12'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        }\r
+    }\r
+    \r
+    complete_apps = ['forum']\r
diff --git a/forum/migrations/0032_auto__del_field_user_hide_ignored_questions__del_field_user_questions_.py b/forum/migrations/0032_auto__del_field_user_hide_ignored_questions__del_field_user_questions_.py
new file mode 100644 (file)
index 0000000..7a15772
--- /dev/null
@@ -0,0 +1,263 @@
+# encoding: utf-8\r
+import datetime\r
+from south.db import db\r
+from south.v2 import SchemaMigration\r
+from django.db import models\r
+\r
+class Migration(SchemaMigration):\r
+    \r
+    def forwards(self, orm):\r
+        \r
+        # Deleting field 'User.hide_ignored_questions'\r
+        db.delete_column('forum_user', 'hide_ignored_questions')\r
+\r
+        # Deleting field 'User.questions_per_page'\r
+        db.delete_column('forum_user', 'questions_per_page')\r
+\r
+        # Deleting field 'User.email_key'\r
+        db.delete_column('forum_user', 'email_key')\r
+\r
+        # Adding field 'Node.in_moderation'\r
+        db.add_column('forum_node', 'in_moderation', self.gf('django.db.models.fields.related.ForeignKey')(related_name='moderated_node', unique=True, null=True, to=orm['forum.Action']), keep_default=False)\r
+    \r
+    \r
+    def backwards(self, orm):\r
+        \r
+        # Adding field 'User.hide_ignored_questions'\r
+        db.add_column('forum_user', 'hide_ignored_questions', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True), keep_default=False)\r
+\r
+        # Adding field 'User.questions_per_page'\r
+        db.add_column('forum_user', 'questions_per_page', self.gf('django.db.models.fields.SmallIntegerField')(default=10), keep_default=False)\r
+\r
+        # Adding field 'User.email_key'\r
+        db.add_column('forum_user', 'email_key', self.gf('django.db.models.fields.CharField')(max_length=32, null=True), keep_default=False)\r
+\r
+        # Deleting field 'Node.in_moderation'\r
+        db.delete_column('forum_node', 'in_moderation_id')\r
+    \r
+    \r
+    models = {\r
+        'auth.group': {\r
+            'Meta': {'object_name': 'Group'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),\r
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'})\r
+        },\r
+        'auth.permission': {\r
+            'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},\r
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})\r
+        },\r
+        'auth.user': {\r
+            'Meta': {'object_name': 'User'},\r
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),\r
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),\r
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),\r
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),\r
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'}),\r
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})\r
+        },\r
+        'contenttypes.contenttype': {\r
+            'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},\r
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})\r
+        },\r
+        'forum.action': {\r
+            'Meta': {'object_name': 'Action'},\r
+            'action_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'action_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'canceled_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),\r
+            'canceled_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'canceled_actions'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'canceled_ip': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'extra': ('forum.models.utils.PickledObjectField', [], {'null': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'ip': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.actionrepute': {\r
+            'Meta': {'object_name': 'ActionRepute'},\r
+            'action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reputes'", 'to': "orm['forum.Action']"}),\r
+            'by_canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reputes'", 'to': "orm['forum.User']"}),\r
+            'value': ('django.db.models.fields.IntegerField', [], {'default': '0'})\r
+        },\r
+        'forum.authkeyuserassociation': {\r
+            'Meta': {'object_name': 'AuthKeyUserAssociation'},\r
+            'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'provider': ('django.db.models.fields.CharField', [], {'max_length': '64'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_keys'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.award': {\r
+            'Meta': {'object_name': 'Award', 'db_table': "u'award'"},\r
+            'awarded_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'badge': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award_badge'", 'to': "orm['forum.Badge']"}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'notified': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'awards'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.badge': {\r
+            'Meta': {'unique_together': "(('name', 'type'),)", 'object_name': 'Badge', 'db_table': "u'badge'"},\r
+            'awarded_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'awarded_to': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'badges'", 'through': "'Award'", 'to': "orm['forum.User']"}),\r
+            'description': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'multiple': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),\r
+            'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '50', 'blank': 'True'}),\r
+            'type': ('django.db.models.fields.SmallIntegerField', [], {})\r
+        },\r
+        'forum.keyvalue': {\r
+            'Meta': {'object_name': 'KeyValue'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'value': ('forum.models.utils.PickledObjectField', [], {'null': 'True'})\r
+        },\r
+        'forum.markedtag': {\r
+            'Meta': {'object_name': 'MarkedTag'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'reason': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_selections'", 'to': "orm['forum.Tag']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tag_selections'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.node': {\r
+            'Meta': {'object_name': 'Node'},\r
+            'abs_parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'all_children'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'active_revision': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'active'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.NodeRevision']"}),\r
+            'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'nodes'", 'to': "orm['forum.User']"}),\r
+            'body': ('django.db.models.fields.TextField', [], {}),\r
+            'deleted': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'deleted_node'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'extra_action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'extra_node'", 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'extra_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'extra_ref': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']", 'null': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'in_moderation': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'moderated_node'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'last_activity_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'last_activity_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']", 'null': 'True'}),\r
+            'last_edited': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'edited_node'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'marked': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'node_type': ('django.db.models.fields.CharField', [], {'default': "'node'", 'max_length': '16'}),\r
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'children'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),\r
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'nodes'", 'to': "orm['forum.Tag']"}),\r
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'})\r
+        },\r
+        'forum.noderevision': {\r
+            'Meta': {'unique_together': "(('node', 'revision'),)", 'object_name': 'NodeRevision'},\r
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'noderevisions'", 'to': "orm['forum.User']"}),\r
+            'body': ('django.db.models.fields.TextField', [], {}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revisions'", 'to': "orm['forum.Node']"}),\r
+            'revised_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'revision': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'summary': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),\r
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '300'})\r
+        },\r
+        'forum.openidassociation': {\r
+            'Meta': {'object_name': 'OpenIdAssociation'},\r
+            'assoc_type': ('django.db.models.fields.TextField', [], {'max_length': '64'}),\r
+            'handle': ('django.db.models.fields.CharField', [], {'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'issued': ('django.db.models.fields.IntegerField', [], {}),\r
+            'lifetime': ('django.db.models.fields.IntegerField', [], {}),\r
+            'secret': ('django.db.models.fields.TextField', [], {'max_length': '255'}),\r
+            'server_url': ('django.db.models.fields.TextField', [], {'max_length': '2047'})\r
+        },\r
+        'forum.openidnonce': {\r
+            'Meta': {'object_name': 'OpenIdNonce'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'salt': ('django.db.models.fields.CharField', [], {'max_length': '50'}),\r
+            'server_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),\r
+            'timestamp': ('django.db.models.fields.IntegerField', [], {})\r
+        },\r
+        'forum.questionsubscription': {\r
+            'Meta': {'object_name': 'QuestionSubscription'},\r
+            'auto_subscription': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'last_view': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 5, 2, 4, 54, 13, 72000)'}),\r
+            'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        },\r
+        'forum.subscriptionsettings': {\r
+            'Meta': {'object_name': 'SubscriptionSettings'},\r
+            'all_questions': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'all_questions_watched_tags': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'enable_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'member_joins': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),\r
+            'new_question': ('django.db.models.fields.CharField', [], {'default': "'d'", 'max_length': '1'}),\r
+            'new_question_watched_tags': ('django.db.models.fields.CharField', [], {'default': "'i'", 'max_length': '1'}),\r
+            'notify_accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'notify_answers': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'notify_comments': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'notify_comments_own_post': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'notify_reply_to_comments': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_answered': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_asked': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_commented': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'questions_viewed': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'subscribed_questions': ('django.db.models.fields.CharField', [], {'default': "'i'", 'max_length': '1'}),\r
+            'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'subscription_settings'", 'unique': 'True', 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.tag': {\r
+            'Meta': {'object_name': 'Tag', 'db_table': "u'tag'"},\r
+            'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_tags'", 'to': "orm['forum.User']"}),\r
+            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_tags'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'marked_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'marked_tags'", 'through': "'MarkedTag'", 'to': "orm['forum.User']"}),\r
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'used_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})\r
+        },\r
+        'forum.user': {\r
+            'Meta': {'object_name': 'User', '_ormbases': ['auth.User']},\r
+            'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),\r
+            'bronze': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),\r
+            'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'gold': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),\r
+            'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),\r
+            'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),\r
+            'silver': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'subscriptions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'subscribers'", 'through': "'QuestionSubscription'", 'to': "orm['forum.Node']"}),\r
+            'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}),\r
+            'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})\r
+        },\r
+        'forum.validationhash': {\r
+            'Meta': {'unique_together': "(('user', 'type'),)", 'object_name': 'ValidationHash'},\r
+            'expiration': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 5, 3, 4, 54, 13, 256000)'}),\r
+            'hash_code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'seed': ('django.db.models.fields.CharField', [], {'max_length': '12'}),\r
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '12'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        }\r
+    }\r
+    \r
+    complete_apps = ['forum']\r
diff --git a/forum/migrations/0033_auto__add_flag__add_vote__add_field_badge_cls__del_unique_badge_type_n.py b/forum/migrations/0033_auto__add_flag__add_vote__add_field_badge_cls__del_unique_badge_type_n.py
new file mode 100644 (file)
index 0000000..f2b38e8
--- /dev/null
@@ -0,0 +1,333 @@
+# encoding: utf-8\r
+import datetime\r
+from south.db import db\r
+from south.v2 import SchemaMigration\r
+from django.db import models\r
+\r
+class Migration(SchemaMigration):\r
+    \r
+    def forwards(self, orm):\r
+        db.rename_table('award', 'forum_award')\r
+        db.rename_table('badge', 'forum_badge')\r
+        db.rename_table('tag', 'forum_tag')\r
+        \r
+        # Adding model 'Flag'\r
+        db.create_table('forum_flag', (\r
+            ('node', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['forum.Node'])),\r
+            ('action', self.gf('django.db.models.fields.related.ForeignKey')(related_name='flag', unique=True, to=orm['forum.Action'])),\r
+            ('reason', self.gf('django.db.models.fields.CharField')(max_length=300)),\r
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),\r
+            ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['forum.User'])),\r
+            ('flagged_at', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)),\r
+        ))\r
+        #db.send_create_signal('forum', ['Flag'])\r
+\r
+        # Adding model 'Vote'\r
+        db.create_table('forum_vote', (\r
+            ('node', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['forum.Node'])),\r
+            ('action', self.gf('django.db.models.fields.related.ForeignKey')(related_name='vote', unique=True, to=orm['forum.Action'])),\r
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),\r
+            ('value', self.gf('django.db.models.fields.SmallIntegerField')()),\r
+            ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['forum.User'])),\r
+            ('voted_at', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)),\r
+        ))\r
+        #db.send_create_signal('forum', ['Vote'])\r
+\r
+        # Adding field 'Badge.cls'\r
+        db.add_column('forum_badge', 'cls', self.gf('django.db.models.fields.CharField')(max_length=50, null=True), keep_default=False)\r
+\r
+        # Removing unique constraint on 'Badge', fields ['type', 'name']\r
+        db.delete_unique(u'forum_badge', ['type', 'name'])\r
+\r
+        # Deleting field 'Award.notified'\r
+        db.delete_column(u'forum_award', 'notified')\r
+\r
+        # Adding field 'Award.node'\r
+        db.add_column('forum_award', 'node', self.gf('django.db.models.fields.related.ForeignKey')(default=1, to=orm['forum.Node'], null=True), keep_default=False)\r
+\r
+        # Adding field 'Award.trigger'\r
+        db.add_column('forum_award', 'trigger', self.gf('django.db.models.fields.related.ForeignKey')(related_name='awards', null=True, to=orm['forum.Action']), keep_default=False)\r
+\r
+        # Adding field 'Award.action'\r
+        db.add_column('forum_award', 'action', self.gf('django.db.models.fields.related.ForeignKey')(default=1, related_name='award', to=orm['forum.Action']), keep_default=False)\r
+\r
+        # Adding unique constraint on 'Award', fields ['node', 'badge', 'user']\r
+        #db.create_unique('forum_award', ['node_id', 'badge_id', 'user_id'])\r
+    \r
+    \r
+    def backwards(self, orm):\r
+        \r
+        # Deleting model 'Flag'\r
+        db.delete_table('forum_flag')\r
+\r
+        # Deleting model 'Vote'\r
+        db.delete_table('forum_vote')\r
+\r
+        # Deleting field 'Badge.cls'\r
+        db.delete_column('forum_badge', 'cls')\r
+\r
+        # Adding unique constraint on 'Badge', fields ['type', 'name']\r
+        db.create_unique(u'badge', ['type', 'name'])\r
+\r
+        # Adding field 'Award.notified'\r
+        db.add_column(u'award', 'notified', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True), keep_default=False)\r
+\r
+        # Deleting field 'Award.node'\r
+        db.delete_column('forum_award', 'node_id')\r
+\r
+        # Deleting field 'Award.trigger'\r
+        db.delete_column('forum_award', 'trigger_id')\r
+\r
+        # Deleting field 'Award.action'\r
+        db.delete_column('forum_award', 'action_id')\r
+\r
+        # Removing unique constraint on 'Award', fields ['node', 'badge', 'user']\r
+        db.delete_unique('forum_award', ['node_id', 'badge_id', 'user_id'])\r
+    \r
+    \r
+    models = {\r
+        'auth.group': {\r
+            'Meta': {'object_name': 'Group'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),\r
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'})\r
+        },\r
+        'auth.permission': {\r
+            'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},\r
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})\r
+        },\r
+        'auth.user': {\r
+            'Meta': {'object_name': 'User'},\r
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),\r
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),\r
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),\r
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),\r
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'}),\r
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})\r
+        },\r
+        'contenttypes.contenttype': {\r
+            'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},\r
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})\r
+        },\r
+        'forum.action': {\r
+            'Meta': {'object_name': 'Action'},\r
+            'action_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'action_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'canceled_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),\r
+            'canceled_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'canceled_actions'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'canceled_ip': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'extra': ('forum.models.utils.PickledObjectField', [], {'null': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'ip': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.actionrepute': {\r
+            'Meta': {'object_name': 'ActionRepute'},\r
+            'action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reputes'", 'to': "orm['forum.Action']"}),\r
+            'by_canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reputes'", 'to': "orm['forum.User']"}),\r
+            'value': ('django.db.models.fields.IntegerField', [], {'default': '0'})\r
+        },\r
+        'forum.authkeyuserassociation': {\r
+            'Meta': {'object_name': 'AuthKeyUserAssociation'},\r
+            'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'provider': ('django.db.models.fields.CharField', [], {'max_length': '64'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_keys'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.award': {\r
+            'Meta': {'unique_together': "(('user', 'badge', 'node'),)", 'object_name': 'Award'},\r
+            'action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award'", 'to': "orm['forum.Action']"}),\r
+            'awarded_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'badge': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Badge']"}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']", 'null': 'True'}),\r
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'trigger': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'awards'", 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        },\r
+        'forum.badge': {\r
+            'Meta': {'object_name': 'Badge'},\r
+            'awarded_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'awarded_to': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'badges'", 'through': "'Award'", 'to': "orm['forum.User']"}),\r
+            'cls': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True'}),\r
+            'description': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'multiple': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),\r
+            'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '50', 'blank': 'True'}),\r
+            'type': ('django.db.models.fields.SmallIntegerField', [], {})\r
+        },\r
+        'forum.flag': {\r
+            'Meta': {'unique_together': "(('user', 'node'),)", 'object_name': 'Flag'},\r
+            'action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'flag'", 'unique': 'True', 'to': "orm['forum.Action']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']"}),\r
+            'reason': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"}),\r
+            'flagged_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+        },\r
+        'forum.keyvalue': {\r
+            'Meta': {'object_name': 'KeyValue'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'value': ('forum.models.utils.PickledObjectField', [], {'null': 'True'})\r
+        },\r
+        'forum.markedtag': {\r
+            'Meta': {'object_name': 'MarkedTag'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'reason': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_selections'", 'to': "orm['forum.Tag']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tag_selections'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.node': {\r
+            'Meta': {'object_name': 'Node'},\r
+            'abs_parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'all_children'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'active_revision': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'active'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.NodeRevision']"}),\r
+            'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'nodes'", 'to': "orm['forum.User']"}),\r
+            'body': ('django.db.models.fields.TextField', [], {}),\r
+            'deleted': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'deleted_node'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'extra_action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'extra_node'", 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'extra_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'extra_ref': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']", 'null': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'in_moderation': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'moderated_node'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'last_activity_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'last_activity_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']", 'null': 'True'}),\r
+            'last_edited': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'edited_node'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'marked': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'node_type': ('django.db.models.fields.CharField', [], {'default': "'node'", 'max_length': '16'}),\r
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'children'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),\r
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'nodes'", 'to': "orm['forum.Tag']"}),\r
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'})\r
+        },\r
+        'forum.noderevision': {\r
+            'Meta': {'unique_together': "(('node', 'revision'),)", 'object_name': 'NodeRevision'},\r
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'noderevisions'", 'to': "orm['forum.User']"}),\r
+            'body': ('django.db.models.fields.TextField', [], {}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revisions'", 'to': "orm['forum.Node']"}),\r
+            'revised_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'revision': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'summary': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),\r
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '300'})\r
+        },\r
+        'forum.openidassociation': {\r
+            'Meta': {'object_name': 'OpenIdAssociation'},\r
+            'assoc_type': ('django.db.models.fields.TextField', [], {'max_length': '64'}),\r
+            'handle': ('django.db.models.fields.CharField', [], {'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'issued': ('django.db.models.fields.IntegerField', [], {}),\r
+            'lifetime': ('django.db.models.fields.IntegerField', [], {}),\r
+            'secret': ('django.db.models.fields.TextField', [], {'max_length': '255'}),\r
+            'server_url': ('django.db.models.fields.TextField', [], {'max_length': '2047'})\r
+        },\r
+        'forum.openidnonce': {\r
+            'Meta': {'object_name': 'OpenIdNonce'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'salt': ('django.db.models.fields.CharField', [], {'max_length': '50'}),\r
+            'server_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),\r
+            'timestamp': ('django.db.models.fields.IntegerField', [], {})\r
+        },\r
+        'forum.questionsubscription': {\r
+            'Meta': {'object_name': 'QuestionSubscription'},\r
+            'auto_subscription': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'last_view': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 5, 3, 11, 41, 55, 831000)'}),\r
+            'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        },\r
+        'forum.subscriptionsettings': {\r
+            'Meta': {'object_name': 'SubscriptionSettings'},\r
+            'all_questions': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'all_questions_watched_tags': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'enable_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'member_joins': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),\r
+            'new_question': ('django.db.models.fields.CharField', [], {'default': "'d'", 'max_length': '1'}),\r
+            'new_question_watched_tags': ('django.db.models.fields.CharField', [], {'default': "'i'", 'max_length': '1'}),\r
+            'notify_accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'notify_answers': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'notify_comments': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'notify_comments_own_post': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'notify_reply_to_comments': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_answered': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_asked': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_commented': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'questions_viewed': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'subscribed_questions': ('django.db.models.fields.CharField', [], {'default': "'i'", 'max_length': '1'}),\r
+            'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'subscription_settings'", 'unique': 'True', 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.tag': {\r
+            'Meta': {'object_name': 'Tag'},\r
+            'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_tags'", 'to': "orm['forum.User']"}),\r
+            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_tags'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'marked_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'marked_tags'", 'through': "'MarkedTag'", 'to': "orm['forum.User']"}),\r
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'used_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})\r
+        },\r
+        'forum.user': {\r
+            'Meta': {'object_name': 'User', '_ormbases': ['auth.User']},\r
+            'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),\r
+            'bronze': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),\r
+            'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'gold': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),\r
+            'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),\r
+            'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),\r
+            'silver': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'subscriptions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'subscribers'", 'through': "'QuestionSubscription'", 'to': "orm['forum.Node']"}),\r
+            'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}),\r
+            'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})\r
+        },\r
+        'forum.validationhash': {\r
+            'Meta': {'unique_together': "(('user', 'type'),)", 'object_name': 'ValidationHash'},\r
+            'expiration': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 5, 4, 11, 41, 59, 140000)'}),\r
+            'hash_code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'seed': ('django.db.models.fields.CharField', [], {'max_length': '12'}),\r
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '12'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        },\r
+        'forum.vote': {\r
+            'Meta': {'unique_together': "(('user', 'node'),)", 'object_name': 'Vote'},\r
+            'action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'vote'", 'unique': 'True', 'to': "orm['forum.Action']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"}),\r
+            'value': ('django.db.models.fields.SmallIntegerField', [], {}),\r
+            'voted_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+        }\r
+    }\r
+    \r
+    complete_apps = ['forum']\r
diff --git a/forum/migrations/0034_new_badge_and_award.py b/forum/migrations/0034_new_badge_and_award.py
new file mode 100644 (file)
index 0000000..89399f9
--- /dev/null
@@ -0,0 +1,344 @@
+# encoding: utf-8\r
+import datetime\r
+from south.db import db\r
+from south.v2 import DataMigration\r
+from django.db import models\r
+from forum.migrations import ProgressBar\r
+from forum.models.utils import dbsafe_decode\r
+\r
+class Migration(DataMigration):\r
+\r
+    def forwards(self, orm):\r
+        b_count = orm.Badge.objects.count()\r
+        print "\nConverting %d badges:" % b_count\r
+        progress = ProgressBar(b_count)\r
+\r
+        for b in orm.Badge.objects.all():\r
+            b.cls = "".join([s[0].upper() + s[1:] for s in b.slug.split('-')])\r
+            b.save()\r
+\r
+            progress.update()\r
+\r
+        print "\n...done\n"\r
+\r
+        ctypes = dict([(ct.name, ct.id) for ct in orm['contenttypes.ContentType'].objects.all()])\r
+\r
+        a_count = orm.Award.objects.count()\r
+        print "\nConverting %d awards:" % a_count\r
+        progress = ProgressBar(a_count)\r
+\r
+        for a in orm.Award.objects.all():\r
+            if a.content_type.id == ctypes['user']:\r
+                a.node = None\r
+            else:\r
+                try:\r
+                    a.node = orm.Node.objects.get(id=a.object_id)\r
+                except:\r
+                    a.delete()\r
+                    continue\r
+\r
+            action = orm.Action(\r
+                user = a.user,\r
+                node = a.node,\r
+                action_type = "award",\r
+                action_date = a.awarded_at,\r
+            )\r
+\r
+            action.save()\r
+\r
+            a.action = action\r
+            a.save()\r
+\r
+            progress.update()\r
+\r
+        print "\n...done\n"\r
+\r
+\r
+        a_count = orm.Action.objects.filter(action_type__in=("voteup", "votedown", "voteupcomment")).count()\r
+        print "\nConverting %d votes:" % a_count\r
+        progress = ProgressBar(a_count)\r
+\r
+        for a in orm.Action.objects.filter(action_type__in=("voteup", "votedown", "voteupcomment"), canceled=False):\r
+            v = orm.Vote(\r
+                user = a.user,\r
+                node = a.node,\r
+                value = (a.action_type in ("voteup", "voteupcomment")) and 1 or -1,\r
+                action = a,\r
+                voted_at = a.action_date\r
+            )\r
+\r
+            v.save()\r
+\r
+            progress.update()\r
+\r
+        print "\n...done\n"\r
+\r
+        a_count = orm.Action.objects.filter(action_type__in=("voteup", "votedown", "voteupcomment")).count()\r
+        print "\nConverting %d votes:" % a_count\r
+        progress = ProgressBar(a_count)\r
+\r
+        for a in orm.Action.objects.filter(action_type="flag", canceled=False):\r
+            f = orm.Flag(\r
+                user = a.user,\r
+                node = a.node,\r
+                reason = a.extra,\r
+                action = a,\r
+                flagged_at = a.action_date\r
+            )\r
+\r
+            f.save()\r
+\r
+            progress.update()\r
+\r
+        print "\n...done\n"\r
+    \r
+    \r
+    def backwards(self, orm):\r
+        "Write your backwards methods here."\r
+    \r
+    models = {\r
+        'auth.group': {\r
+            'Meta': {'object_name': 'Group'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),\r
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'})\r
+        },\r
+        'auth.permission': {\r
+            'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},\r
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})\r
+        },\r
+        'auth.user': {\r
+            'Meta': {'object_name': 'User'},\r
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),\r
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),\r
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),\r
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),\r
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'}),\r
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})\r
+        },\r
+        'contenttypes.contenttype': {\r
+            'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},\r
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})\r
+        },\r
+        'forum.action': {\r
+            'Meta': {'object_name': 'Action'},\r
+            'action_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'action_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'canceled_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),\r
+            'canceled_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'canceled_actions'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'canceled_ip': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'extra': ('forum.models.utils.PickledObjectField', [], {'null': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'ip': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.actionrepute': {\r
+            'Meta': {'object_name': 'ActionRepute'},\r
+            'action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reputes'", 'to': "orm['forum.Action']"}),\r
+            'by_canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reputes'", 'to': "orm['forum.User']"}),\r
+            'value': ('django.db.models.fields.IntegerField', [], {'default': '0'})\r
+        },\r
+        'forum.authkeyuserassociation': {\r
+            'Meta': {'object_name': 'AuthKeyUserAssociation'},\r
+            'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'provider': ('django.db.models.fields.CharField', [], {'max_length': '64'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_keys'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.award': {\r
+            'Meta': {'unique_together': "(('user', 'badge', 'node'),)", 'object_name': 'Award'},\r
+            'action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award'", 'to': "orm['forum.Action']"}),\r
+            'awarded_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'badge': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Badge']"}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']", 'null': 'True'}),\r
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'trigger': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'awards'", 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        },\r
+        'forum.badge': {\r
+            'Meta': {'object_name': 'Badge'},\r
+            'awarded_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'awarded_to': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'badges'", 'through': "'Award'", 'to': "orm['forum.User']"}),\r
+            'cls': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True'}),\r
+            'description': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'multiple': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),\r
+            'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '50', 'blank': 'True'}),\r
+            'type': ('django.db.models.fields.SmallIntegerField', [], {})\r
+        },\r
+        'forum.flag': {\r
+            'Meta': {'unique_together': "(('user', 'node'),)", 'object_name': 'Flag'},\r
+            'action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'flag'", 'unique': 'True', 'to': "orm['forum.Action']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']"}),\r
+            'reason': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"}),\r
+            'flagged_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+        },\r
+        'forum.keyvalue': {\r
+            'Meta': {'object_name': 'KeyValue'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'value': ('forum.models.utils.PickledObjectField', [], {'null': 'True'})\r
+        },\r
+        'forum.markedtag': {\r
+            'Meta': {'object_name': 'MarkedTag'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'reason': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_selections'", 'to': "orm['forum.Tag']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tag_selections'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.node': {\r
+            'Meta': {'object_name': 'Node'},\r
+            'abs_parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'all_children'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'active_revision': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'active'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.NodeRevision']"}),\r
+            'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'nodes'", 'to': "orm['forum.User']"}),\r
+            'body': ('django.db.models.fields.TextField', [], {}),\r
+            'deleted': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'deleted_node'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'extra_action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'extra_node'", 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'extra_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'extra_ref': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']", 'null': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'in_moderation': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'moderated_node'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'last_activity_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'last_activity_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']", 'null': 'True'}),\r
+            'last_edited': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'edited_node'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'marked': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'node_type': ('django.db.models.fields.CharField', [], {'default': "'node'", 'max_length': '16'}),\r
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'children'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),\r
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'nodes'", 'to': "orm['forum.Tag']"}),\r
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'})\r
+        },\r
+        'forum.noderevision': {\r
+            'Meta': {'unique_together': "(('node', 'revision'),)", 'object_name': 'NodeRevision'},\r
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'noderevisions'", 'to': "orm['forum.User']"}),\r
+            'body': ('django.db.models.fields.TextField', [], {}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revisions'", 'to': "orm['forum.Node']"}),\r
+            'revised_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'revision': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'summary': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),\r
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '300'})\r
+        },\r
+        'forum.openidassociation': {\r
+            'Meta': {'object_name': 'OpenIdAssociation'},\r
+            'assoc_type': ('django.db.models.fields.TextField', [], {'max_length': '64'}),\r
+            'handle': ('django.db.models.fields.CharField', [], {'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'issued': ('django.db.models.fields.IntegerField', [], {}),\r
+            'lifetime': ('django.db.models.fields.IntegerField', [], {}),\r
+            'secret': ('django.db.models.fields.TextField', [], {'max_length': '255'}),\r
+            'server_url': ('django.db.models.fields.TextField', [], {'max_length': '2047'})\r
+        },\r
+        'forum.openidnonce': {\r
+            'Meta': {'object_name': 'OpenIdNonce'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'salt': ('django.db.models.fields.CharField', [], {'max_length': '50'}),\r
+            'server_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),\r
+            'timestamp': ('django.db.models.fields.IntegerField', [], {})\r
+        },\r
+        'forum.questionsubscription': {\r
+            'Meta': {'object_name': 'QuestionSubscription'},\r
+            'auto_subscription': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'last_view': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 5, 3, 11, 43, 54, 540000)'}),\r
+            'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        },\r
+        'forum.subscriptionsettings': {\r
+            'Meta': {'object_name': 'SubscriptionSettings'},\r
+            'all_questions': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'all_questions_watched_tags': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'enable_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'member_joins': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),\r
+            'new_question': ('django.db.models.fields.CharField', [], {'default': "'d'", 'max_length': '1'}),\r
+            'new_question_watched_tags': ('django.db.models.fields.CharField', [], {'default': "'i'", 'max_length': '1'}),\r
+            'notify_accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'notify_answers': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'notify_comments': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'notify_comments_own_post': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'notify_reply_to_comments': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_answered': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_asked': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_commented': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'questions_viewed': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'subscribed_questions': ('django.db.models.fields.CharField', [], {'default': "'i'", 'max_length': '1'}),\r
+            'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'subscription_settings'", 'unique': 'True', 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.tag': {\r
+            'Meta': {'object_name': 'Tag'},\r
+            'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_tags'", 'to': "orm['forum.User']"}),\r
+            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_tags'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'marked_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'marked_tags'", 'through': "'MarkedTag'", 'to': "orm['forum.User']"}),\r
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'used_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})\r
+        },\r
+        'forum.user': {\r
+            'Meta': {'object_name': 'User', '_ormbases': ['auth.User']},\r
+            'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),\r
+            'bronze': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),\r
+            'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'gold': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),\r
+            'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),\r
+            'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),\r
+            'silver': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'subscriptions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'subscribers'", 'through': "'QuestionSubscription'", 'to': "orm['forum.Node']"}),\r
+            'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}),\r
+            'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})\r
+        },\r
+        'forum.validationhash': {\r
+            'Meta': {'unique_together': "(('user', 'type'),)", 'object_name': 'ValidationHash'},\r
+            'expiration': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 5, 4, 11, 43, 54, 592000)'}),\r
+            'hash_code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'seed': ('django.db.models.fields.CharField', [], {'max_length': '12'}),\r
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '12'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        },\r
+        'forum.vote': {\r
+            'Meta': {'unique_together': "(('user', 'node'),)", 'object_name': 'Vote'},\r
+            'action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'vote'", 'unique': 'True', 'to': "orm['forum.Action']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"}),\r
+            'value': ('django.db.models.fields.SmallIntegerField', [], {}),\r
+            'voted_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+        }\r
+    }\r
+    \r
+    complete_apps = ['forum']\r
diff --git a/forum/migrations/0035_auto__del_field_award_object_id__del_field_award_content_type__add_uni.py b/forum/migrations/0035_auto__del_field_award_object_id__del_field_award_content_type__add_uni.py
new file mode 100644 (file)
index 0000000..b566ef0
--- /dev/null
@@ -0,0 +1,296 @@
+# encoding: utf-8\r
+import datetime\r
+from south.db import db\r
+from south.v2 import SchemaMigration\r
+from django.db import models\r
+\r
+class Migration(SchemaMigration):\r
+    \r
+    def forwards(self, orm):\r
+        \r
+        # Deleting field 'Award.object_id'\r
+        db.delete_column('forum_award', 'object_id')\r
+\r
+        # Deleting field 'Award.content_type'\r
+        db.delete_column('forum_award', 'content_type_id')\r
+\r
+        # Adding unique constraint on 'Award', fields ['action']\r
+        db.create_unique('forum_award', ['action_id'])\r
+\r
+        # Deleting field 'Badge.multiple'\r
+        db.delete_column('forum_badge', 'multiple')\r
+\r
+        # Deleting field 'Badge.name'\r
+        db.delete_column('forum_badge', 'name')\r
+\r
+        # Deleting field 'Badge.slug'\r
+        db.delete_column('forum_badge', 'slug')\r
+\r
+        # Deleting field 'Badge.description'\r
+        db.delete_column('forum_badge', 'description')\r
+    \r
+    \r
+    def backwards(self, orm):\r
+        \r
+        # Adding field 'Award.object_id'\r
+        db.add_column('forum_award', 'object_id', self.gf('django.db.models.fields.PositiveIntegerField')(default=1), keep_default=False)\r
+\r
+        # Adding field 'Award.content_type'\r
+        db.add_column('forum_award', 'content_type', self.gf('django.db.models.fields.related.ForeignKey')(default=1, to=orm['contenttypes.ContentType']), keep_default=False)\r
+\r
+        # Removing unique constraint on 'Award', fields ['action']\r
+        db.delete_unique('forum_award', ['action_id'])\r
+\r
+        # Adding field 'Badge.multiple'\r
+        db.add_column('forum_badge', 'multiple', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True), keep_default=False)\r
+\r
+        # Adding field 'Badge.name'\r
+        db.add_column('forum_badge', 'name', self.gf('django.db.models.fields.CharField')(default=1, max_length=50), keep_default=False)\r
+\r
+        # Adding field 'Badge.slug'\r
+        db.add_column('forum_badge', 'slug', self.gf('django.db.models.fields.SlugField')(blank=True, default=1, max_length=50, db_index=True), keep_default=False)\r
+\r
+        # Adding field 'Badge.description'\r
+        db.add_column('forum_badge', 'description', self.gf('django.db.models.fields.CharField')(default=1, max_length=300), keep_default=False)\r
+    \r
+    \r
+    models = {\r
+        'auth.group': {\r
+            'Meta': {'object_name': 'Group'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),\r
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'})\r
+        },\r
+        'auth.permission': {\r
+            'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},\r
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})\r
+        },\r
+        'auth.user': {\r
+            'Meta': {'object_name': 'User'},\r
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),\r
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),\r
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),\r
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),\r
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'}),\r
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})\r
+        },\r
+        'contenttypes.contenttype': {\r
+            'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},\r
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),\r
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})\r
+        },\r
+        'forum.action': {\r
+            'Meta': {'object_name': 'Action'},\r
+            'action_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'action_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'canceled_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),\r
+            'canceled_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'canceled_actions'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'canceled_ip': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'extra': ('forum.models.utils.PickledObjectField', [], {'null': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'ip': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.actionrepute': {\r
+            'Meta': {'object_name': 'ActionRepute'},\r
+            'action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reputes'", 'to': "orm['forum.Action']"}),\r
+            'by_canceled': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reputes'", 'to': "orm['forum.User']"}),\r
+            'value': ('django.db.models.fields.IntegerField', [], {'default': '0'})\r
+        },\r
+        'forum.authkeyuserassociation': {\r
+            'Meta': {'object_name': 'AuthKeyUserAssociation'},\r
+            'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'provider': ('django.db.models.fields.CharField', [], {'max_length': '64'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_keys'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.award': {\r
+            'Meta': {'unique_together': "(('user', 'badge', 'node'),)", 'object_name': 'Award'},\r
+            'action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award'", 'unique': 'True', 'to': "orm['forum.Action']"}),\r
+            'awarded_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'badge': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Badge']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']", 'null': 'True'}),\r
+            'trigger': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'awards'", 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        },\r
+        'forum.badge': {\r
+            'Meta': {'object_name': 'Badge'},\r
+            'awarded_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'awarded_to': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'badges'", 'through': "'Award'", 'to': "orm['forum.User']"}),\r
+            'cls': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'type': ('django.db.models.fields.SmallIntegerField', [], {})\r
+        },\r
+        'forum.flag': {\r
+            'Meta': {'unique_together': "(('user', 'node'),)", 'object_name': 'Flag'},\r
+            'action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'flag'", 'unique': 'True', 'to': "orm['forum.Action']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']"}),\r
+            'reason': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"}),\r
+            'flagged_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+        },\r
+        'forum.keyvalue': {\r
+            'Meta': {'object_name': 'KeyValue'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'value': ('forum.models.utils.PickledObjectField', [], {'null': 'True'})\r
+        },\r
+        'forum.markedtag': {\r
+            'Meta': {'object_name': 'MarkedTag'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'reason': ('django.db.models.fields.CharField', [], {'max_length': '16'}),\r
+            'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_selections'", 'to': "orm['forum.Tag']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tag_selections'", 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.node': {\r
+            'Meta': {'object_name': 'Node'},\r
+            'abs_parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'all_children'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'active_revision': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'active'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.NodeRevision']"}),\r
+            'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'nodes'", 'to': "orm['forum.User']"}),\r
+            'body': ('django.db.models.fields.TextField', [], {}),\r
+            'deleted': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'deleted_node'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'extra_action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'extra_node'", 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'extra_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'extra_ref': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']", 'null': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'in_moderation': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'moderated_node'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'last_activity_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'last_activity_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']", 'null': 'True'}),\r
+            'last_edited': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'edited_node'", 'unique': 'True', 'null': 'True', 'to': "orm['forum.Action']"}),\r
+            'marked': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'node_type': ('django.db.models.fields.CharField', [], {'default': "'node'", 'max_length': '16'}),\r
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'children'", 'null': 'True', 'to': "orm['forum.Node']"}),\r
+            'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}),\r
+            'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),\r
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'nodes'", 'to': "orm['forum.Tag']"}),\r
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'})\r
+        },\r
+        'forum.noderevision': {\r
+            'Meta': {'unique_together': "(('node', 'revision'),)", 'object_name': 'NodeRevision'},\r
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'noderevisions'", 'to': "orm['forum.User']"}),\r
+            'body': ('django.db.models.fields.TextField', [], {}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revisions'", 'to': "orm['forum.Node']"}),\r
+            'revised_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'revision': ('django.db.models.fields.PositiveIntegerField', [], {}),\r
+            'summary': ('django.db.models.fields.CharField', [], {'max_length': '300'}),\r
+            'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),\r
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '300'})\r
+        },\r
+        'forum.openidassociation': {\r
+            'Meta': {'object_name': 'OpenIdAssociation'},\r
+            'assoc_type': ('django.db.models.fields.TextField', [], {'max_length': '64'}),\r
+            'handle': ('django.db.models.fields.CharField', [], {'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'issued': ('django.db.models.fields.IntegerField', [], {}),\r
+            'lifetime': ('django.db.models.fields.IntegerField', [], {}),\r
+            'secret': ('django.db.models.fields.TextField', [], {'max_length': '255'}),\r
+            'server_url': ('django.db.models.fields.TextField', [], {'max_length': '2047'})\r
+        },\r
+        'forum.openidnonce': {\r
+            'Meta': {'object_name': 'OpenIdNonce'},\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'salt': ('django.db.models.fields.CharField', [], {'max_length': '50'}),\r
+            'server_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),\r
+            'timestamp': ('django.db.models.fields.IntegerField', [], {})\r
+        },\r
+        'forum.questionsubscription': {\r
+            'Meta': {'object_name': 'QuestionSubscription'},\r
+            'auto_subscription': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'last_view': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 5, 3, 11, 46, 22, 80000)'}),\r
+            'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        },\r
+        'forum.subscriptionsettings': {\r
+            'Meta': {'object_name': 'SubscriptionSettings'},\r
+            'all_questions': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'all_questions_watched_tags': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'enable_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'member_joins': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),\r
+            'new_question': ('django.db.models.fields.CharField', [], {'default': "'d'", 'max_length': '1'}),\r
+            'new_question_watched_tags': ('django.db.models.fields.CharField', [], {'default': "'i'", 'max_length': '1'}),\r
+            'notify_accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'notify_answers': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'notify_comments': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'notify_comments_own_post': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'notify_reply_to_comments': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_answered': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_asked': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),\r
+            'questions_commented': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'questions_viewed': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'subscribed_questions': ('django.db.models.fields.CharField', [], {'default': "'i'", 'max_length': '1'}),\r
+            'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'subscription_settings'", 'unique': 'True', 'to': "orm['forum.User']"})\r
+        },\r
+        'forum.tag': {\r
+            'Meta': {'object_name': 'Tag'},\r
+            'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_tags'", 'to': "orm['forum.User']"}),\r
+            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),\r
+            'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_tags'", 'null': 'True', 'to': "orm['forum.User']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'marked_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'marked_tags'", 'through': "'MarkedTag'", 'to': "orm['forum.User']"}),\r
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'used_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})\r
+        },\r
+        'forum.user': {\r
+            'Meta': {'object_name': 'User', '_ormbases': ['auth.User']},\r
+            'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),\r
+            'bronze': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),\r
+            'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'gold': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),\r
+            'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+            'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),\r
+            'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),\r
+            'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),\r
+            'silver': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),\r
+            'subscriptions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'subscribers'", 'through': "'QuestionSubscription'", 'to': "orm['forum.Node']"}),\r
+            'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}),\r
+            'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})\r
+        },\r
+        'forum.validationhash': {\r
+            'Meta': {'unique_together': "(('user', 'type'),)", 'object_name': 'ValidationHash'},\r
+            'expiration': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 5, 4, 11, 46, 28, 428000)'}),\r
+            'hash_code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'seed': ('django.db.models.fields.CharField', [], {'max_length': '12'}),\r
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '12'}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"})\r
+        },\r
+        'forum.vote': {\r
+            'Meta': {'unique_together': "(('user', 'node'),)", 'object_name': 'Vote'},\r
+            'action': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'vote'", 'unique': 'True', 'to': "orm['forum.Action']"}),\r
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),\r
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.Node']"}),\r
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['forum.User']"}),\r
+            'value': ('django.db.models.fields.SmallIntegerField', [], {}),\r
+            'voted_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),\r
+        }\r
+    }\r
+    \r
+    complete_apps = ['forum']\r
index 5b6414afb5534bc9e549bb9a4d355174c0175d4e..2c9c9970aad196a48b42019935d197ec8e91ca4e 100644 (file)
@@ -1,11 +1,11 @@
-from question import Question ,QuestionRevision, FavoriteQuestion, QuestionSubscription\r
+from question import Question ,QuestionRevision, QuestionSubscription\r
 from answer import Answer, AnswerRevision\r
 from tag import Tag, MarkedTag\r
 from answer import Answer, AnswerRevision\r
 from tag import Tag, MarkedTag\r
-from meta import Vote, FlaggedItem\r
-from user import User, Activity, ValidationHash, AuthKeyUserAssociation, SubscriptionSettings\r
-from repute import Badge, Award, Repute\r
-from node import Node, NodeRevision, NodeMetaClass, AnonymousNode\r
+from user import User, ValidationHash, AuthKeyUserAssociation, SubscriptionSettings\r
+from node import Node, NodeRevision, NodeMetaClass\r
 from comment import Comment\r
 from comment import Comment\r
+from action import Action, ActionRepute\r
+from meta import Vote, Flag, Badge, Award\r
 from utils import KeyValue\r
 \r
 try:\r
 from utils import KeyValue\r
 \r
 try:\r
@@ -16,20 +16,13 @@ except:
 \r
 from base import *\r
 \r
 \r
 from base import *\r
 \r
-def is_new(sender, instance, **kwargs):\r
-    try:\r
-        instance._is_new = not bool(instance.id)\r
-    except:\r
-        pass\r
-\r
-pre_save.connect(is_new)\r
-\r
 __all__ = [\r
 __all__ = [\r
-        'Node', 'NodeRevision', 'AnonymousNode', \r
-        'Question', 'FavoriteQuestion', 'QuestionSubscription', 'QuestionRevision',\r
+        'Node', 'NodeRevision',  \r
+        'Question', 'QuestionSubscription', 'QuestionRevision',\r
         'Answer', 'AnswerRevision',\r
         'Answer', 'AnswerRevision',\r
-        'Tag', 'Comment', 'Vote', 'FlaggedItem', 'MarkedTag', 'Badge', 'Award', 'Repute',\r
-        'Activity', 'ValidationHash', 'AuthKeyUserAssociation', 'SubscriptionSettings', 'KeyValue', 'User',\r
+        'Tag', 'Comment', 'MarkedTag', 'Badge', 'Award',\r
+        'ValidationHash', 'AuthKeyUserAssociation', 'SubscriptionSettings', 'KeyValue', 'User',\r
+        'Action', 'ActionRepute', 'Vote', 'Flag'\r
         ]\r
 \r
 \r
         ]\r
 \r
 \r
@@ -40,4 +33,5 @@ for k, v in get_modules_script_classes('models', models.Model).items():
         __all__.append(k)\r
         exec "%s = v" % k\r
 \r
         __all__.append(k)\r
         exec "%s = v" % k\r
 \r
-NodeMetaClass.setup_relations()
\ No newline at end of file
+NodeMetaClass.setup_relations()\r
+BaseMetaClass.setup_denormalizes()
\ No newline at end of file
diff --git a/forum/models/action.py b/forum/models/action.py
new file mode 100644 (file)
index 0000000..10eb165
--- /dev/null
@@ -0,0 +1,268 @@
+from django.utils.translation import ugettext as _\r
+from utils import PickledObjectField\r
+from threading import Thread\r
+from base import *\r
+import re\r
+\r
+class ActionManager(models.Manager):\r
+    use_for_related_fields = True\r
+\r
+    def get_query_set(self):\r
+        qs = super(ActionManager, self).get_query_set().filter(canceled=False)\r
+\r
+        if self.model is not Action:\r
+            return qs.filter(action_type=self.model.get_type())\r
+        else:\r
+            return qs\r
+\r
+    def get(self, *args, **kwargs):\r
+        action = super(ActionManager, self).get(*args, **kwargs)\r
+        if self.model == Action:\r
+            return action.leaf()\r
+        return action\r
+\r
+    def get_for_types(self, types, *args, **kwargs):\r
+        kwargs['action_type__in'] = [t.get_type() for t in types]\r
+        return self.get(*args, **kwargs)\r
+\r
+        \r
+\r
+class Action(models.Model):\r
+    user = models.ForeignKey('User', related_name="actions")\r
+    ip   = models.CharField(max_length=16)\r
+    node = models.ForeignKey('Node', null=True, related_name="actions")\r
+    action_type = models.CharField(max_length=16)\r
+    action_date = models.DateTimeField(default=datetime.datetime.now)\r
+\r
+    extra = PickledObjectField()\r
+\r
+    canceled = models.BooleanField(default=False)\r
+    canceled_by = models.ForeignKey('User', null=True, related_name="canceled_actions")\r
+    canceled_at = models.DateTimeField(null=True)\r
+    canceled_ip = models.CharField(max_length=16)\r
+\r
+    hooks = {}\r
+\r
+    objects = ActionManager()\r
+\r
+    @property\r
+    def at(self):\r
+        return self.action_date\r
+\r
+    @property\r
+    def by(self):\r
+        return self.user\r
+\r
+    def repute_users(self):\r
+        pass\r
+\r
+    def process_data(self, **data):\r
+        pass\r
+\r
+    def process_action(self):\r
+        pass\r
+\r
+    def cancel_action(self):\r
+        pass\r
+\r
+    def describe(self, viewer=None):\r
+        return ""\r
+\r
+    def repute(self, user, value):\r
+        repute = ActionRepute(action=self, user=user, value=value)\r
+        repute.save()\r
+        return repute\r
+\r
+    def cancel_reputes(self):\r
+        for repute in self.reputes.all():\r
+            cancel = ActionRepute(action=self, user=repute.user, value=(-repute.value), by_canceled=True)\r
+            cancel.save()\r
+\r
+    def leaf(self):\r
+        leaf_cls = ActionProxyMetaClass.types.get(self.action_type, None)\r
+\r
+        if leaf_cls is None:\r
+            return self\r
+\r
+        leaf = leaf_cls()\r
+        leaf.__dict__ = self.__dict__\r
+        return leaf\r
+\r
+    @classmethod\r
+    def get_type(cls):\r
+        return re.sub(r'action$', '', cls.__name__.lower())\r
+\r
+    def save(self, data=None, *args, **kwargs):\r
+        isnew = False\r
+\r
+        if not self.id:\r
+            self.action_type = self.__class__.get_type()\r
+            isnew = True\r
+\r
+        if data:\r
+            self.process_data(**data)\r
+\r
+        super(Action, self).save(*args, **kwargs)\r
+\r
+        if isnew:\r
+            if (self.node is None) or (not self.node.wiki):\r
+                self.repute_users()\r
+            self.process_action()\r
+            self.trigger_hooks(True)\r
+\r
+        return self\r
+\r
+    def delete(self):\r
+        self.cancel_action()\r
+        super(Action, self).delete()\r
+\r
+    def cancel(self, user=None, ip=None):\r
+        if not self.canceled:\r
+            self.canceled = True\r
+            self.canceled_at = datetime.datetime.now()\r
+            self.canceled_by = (user is None) and self.user or user\r
+            if ip:\r
+                self.canceled_ip = ip\r
+            self.save()\r
+            self.cancel_reputes()\r
+            self.cancel_action()\r
+            #self.trigger_hooks(False)\r
+\r
+    @classmethod\r
+    def get_current(cls, **kwargs):\r
+        kwargs['canceled'] = False\r
+\r
+        try:\r
+            return cls.objects.get(**kwargs)\r
+        except cls.MultipleObjectsReturned:\r
+            logging.error("Got multiple values for action %s with args %s", cls.__name__,\r
+                          ", ".join(["%s='%s'" % i for i in kwargs.items()]))\r
+            raise\r
+        except cls.DoesNotExist:\r
+            return None\r
+\r
+    @classmethod\r
+    def hook(cls, fn):\r
+        if not Action.hooks.get(cls, None):\r
+            Action.hooks[cls] = []\r
+\r
+        Action.hooks[cls].append(fn)\r
+\r
+    def trigger_hooks(self, new=True):\r
+        thread = Thread(target=trigger_hooks_threaded,  args=[self, Action.hooks, new])\r
+        thread.setDaemon(True)\r
+        thread.start()\r
+\r
+    class Meta:\r
+        app_label = 'forum'\r
+\r
+def trigger_hooks_threaded(action, hooks, new):\r
+    for cls, hooklist in hooks.items():\r
+        if isinstance(action, cls):\r
+            for hook in hooklist:\r
+                try:\r
+                    hook(action=action, new=new)\r
+                except Exception, e:\r
+                    logging.error("Error in %s hook: %s" % (cls.__name__, str(e)))\r
+\r
+class ActionProxyMetaClass(models.Model.__metaclass__):\r
+    types = {}\r
+\r
+    def __new__(cls, *args, **kwargs):\r
+        new_cls = super(ActionProxyMetaClass, cls).__new__(cls, *args, **kwargs)\r
+        cls.types[new_cls.get_type()] = new_cls\r
+\r
+        class Meta:\r
+            proxy = True\r
+\r
+        new_cls.Meta = Meta\r
+        return new_cls\r
+\r
+class ActionProxy(Action):\r
+    __metaclass__ = ActionProxyMetaClass\r
+\r
+    def friendly_username(self, viewer, user):\r
+        return (viewer == user) and _('You') or user.username\r
+\r
+    def friendly_ownername(self, owner, user):\r
+        return (owner == user) and _('your') or user.username\r
+\r
+    def hyperlink(self, url, title, **attrs):\r
+        return '<a href="%s" %s>%s</a>' % (url, " ".join('%s="%s"' % i for i in attrs.items()), title)\r
+\r
+    def describe_node(self, viewer, node):\r
+        node_link = self.hyperlink(node.get_absolute_url(), node.headline)\r
+\r
+        if node.parent:\r
+            node_desc = _("on %(link)s") % {'link': node_link}\r
+        else:\r
+            node_desc = node_link\r
+\r
+        return _("%(user)s %(node_name)s %(node_desc)s") % {\r
+            'user': self.hyperlink(node.author.get_profile_url(), self.friendly_ownername(viewer, node.author)),\r
+            'node_name': node.friendly_name, 'node_desc': node_desc,\r
+        }\r
+    \r
+    class Meta:\r
+        proxy = True\r
+\r
+class DummyActionProxy(Action):\r
+    __metaclass__ = ActionProxyMetaClass\r
+\r
+    hooks = []\r
+\r
+    def process_data(self, **data):\r
+        pass\r
+\r
+    def process_action(self):\r
+        pass\r
+\r
+    def save(self, data=None):\r
+        self.process_action()\r
+\r
+        if data:\r
+            self.process_data(**data)\r
+\r
+        for hook in self.__class__.hooks:\r
+            hook(self, True)\r
+\r
+    @classmethod\r
+    def get_type(cls):\r
+        return re.sub(r'action$', '', cls.__name__.lower())\r
+\r
+    @classmethod\r
+    def hook(cls, fn):\r
+        cls.hooks.append(fn)\r
+\r
+\r
+\r
+class ActionRepute(models.Model):\r
+    action = models.ForeignKey(Action, related_name='reputes')\r
+    date = models.DateTimeField(default=datetime.datetime.now)\r
+    user = models.ForeignKey('User', related_name='reputes')\r
+    value = models.IntegerField(default=0)\r
+    by_canceled = models.BooleanField(default=False)\r
+\r
+    @property\r
+    def positive(self):\r
+        if self.value > 0: return self.value\r
+        return 0\r
+\r
+    @property\r
+    def negative(self):\r
+        if self.value < 0: return self.value\r
+        return 0\r
+\r
+    def save(self, *args, **kwargs):\r
+        super(ActionRepute, self).save(*args, **kwargs)\r
+        self.user.reputation = models.F('reputation') + self.value\r
+        self.user.save()\r
+\r
+    def delete(self):\r
+        self.user.reputation = models.F('reputation') - self.value\r
+        self.user.save()\r
+        super(ActionRepute, self).delete()\r
+\r
+    class Meta:\r
+        app_label = 'forum'\r
+\r
index 01e0aae734e28f15c88df5e4c0c2d070593f9dc3..090e19d004a5d8db4ebeabb82f1f7deaafbdf8e0 100644 (file)
@@ -1,79 +1,23 @@
 from base import *
 from base import *
+from django.utils.translation import ugettext as _
 
 
-from question import Question
+class Answer(Node):
+    friendly_name = _("answer")
 
 
-class Answer(QandA):
-    accepted    = models.BooleanField(default=False)
-    accepted_at = models.DateTimeField(null=True, blank=True)
-    accepted_by = models.ForeignKey(User, null=True)
+    class Meta(Node.Meta):
+        proxy = True
 
 
-    class Meta(QandA.Meta):
-        db_table = u'answer'
+    @property    
+    def accepted(self):
+        return self.extra_action
 
     @property
     def headline(self):
         return self.question.headline
 
 
     @property
     def headline(self):
         return self.question.headline
 
-    def mark_accepted(self, user):
-        if not self.accepted and not self.question.answer_accepted:
-            self.accepted = True
-            self.accepted_at = datetime.datetime.now()
-            self.accepted_by = user
-            self.save()
-            self.question.accepted_answer = self
-            self.question.save()
-            answer_accepted.send(sender=Answer, answer=self, user=user)
-            return True
-
-        return False
-
-    def unmark_accepted(self, user):
-        if self.accepted:
-            self.accepted = False
-            self.save()
-            self.question.accepted_answer = None
-            self.question.save()
-            answer_accepted_canceled.send(sender=Answer, answer=self, user=user)
-            return True
-
-        return False
-
-    def _update_question_answer_count(self, diff):
-        self.question.answer_count = self.question.answer_count + diff
-        self.question.save()
-
-    def activate_revision(self, user, revision):
-        super(Answer, self).activate_revision(user, revision)
-        self.question.update_last_activity(user)
-
-    def mark_deleted(self, user):
-        if super(Answer, self).mark_deleted(user):
-            self._update_question_answer_count(-1)
-
-    def unmark_deleted(self):
-        if super(Answer, self).unmark_deleted():
-            self._update_question_answer_count(1)
-
-    def get_latest_revision(self):
-        return self.revisions.all()[0]
-
-    def get_question_title(self):
-        return self.question.title
-
     def get_absolute_url(self):
         return '%s#%s' % (self.question.get_absolute_url(), self.id)
 
     def get_absolute_url(self):
         return '%s#%s' % (self.question.get_absolute_url(), self.id)
 
-    def save(self, *args, **kwargs):
-        super(Answer, self).save(*args, **kwargs)
-
-        if self._is_new:
-            self._update_question_answer_count(1)
-
-    def __unicode__(self):
-        return self.html
-        
-answer_accepted = django.dispatch.Signal(providing_args=['answer', 'user'])
-answer_accepted_canceled = django.dispatch.Signal(providing_args=['answer', 'user'])
 
 class AnswerRevision(NodeRevision):
     class Meta:
 
 class AnswerRevision(NodeRevision):
     class Meta:
index 43668d9bcb0c0065818307572a93f48cab400344..56124a82e9a66be5d37f5fee8f883e30c4a0dcc3 100644 (file)
@@ -20,15 +20,43 @@ import logging
 
 from forum.const import *
 
 
 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$')
 
 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)]
     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:
         for k in int_cache_keys:
-            del obj.__dict__[k]
+            if not isinstance(obj.__dict__[k], Action):
+                del obj.__dict__[k]
 
         cache.set(self.model.cache_key(obj.id), obj, 60 * 60)
 
 
         cache.set(self.model.cache_key(obj.id), obj, 60 * 60)
 
@@ -59,19 +87,54 @@ class CachedManager(models.Manager):
         except:
             return super(CachedManager, self).get_or_create(*args, **kwargs)
 
         except:
             return super(CachedManager, self).get_or_create(*args, **kwargs)
 
-denorm_update = django.dispatch.Signal(providing_args=["instance", "field", "old", "new"])
 
 
-class DenormalizedField(models.IntegerField):
-    __metaclass__ = models.SubfieldBase
+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)
 
 
-    def contribute_to_class(self, cls, name):
-        super (DenormalizedField, self).contribute_to_class(cls, name)
-        if not hasattr(cls, '_denormalizad_fields'):
-            cls._denormalizad_fields = []
+        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)
 
 
-        cls._denormalizad_fields.append(name)
 
 class BaseModel(models.Model):
 
 class BaseModel(models.Model):
+    __metaclass__ = BaseMetaClass
+
     objects = CachedManager()
 
     class Meta:
     objects = CachedManager()
 
     class Meta:
@@ -92,35 +155,29 @@ class BaseModel(models.Model):
                  if self._original_state.get(k, missing) == missing or self._original_state[k] != v])
 
     def save(self, *args, **kwargs):
                  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)
+        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]
                 )
 
         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:
             except:
-                #todo: log this properly
-                pass
+                logging.error("Unable to read %s from %s" % (", ".join(put_back), self.__class__.__name__))
+                self.uncache()
 
         self._original_state = dict(self.__dict__)
 
         self._original_state = dict(self.__dict__)
+        self.cache()
+
+    def cache(self):
         self.__class__.objects.cache_obj(self)
 
         self.__class__.objects.cache_obj(self)
 
-    def delete(self):
+    def uncache(self):
         cache.delete(self.cache_key(self.pk))
         cache.delete(self.cache_key(self.pk))
+
+    def delete(self):
+        self.uncache()
         super(BaseModel, self).delete()
 
 
         super(BaseModel, self).delete()
 
 
@@ -170,8 +227,6 @@ class UserContent(models.Model):
         app_label = 'forum'
 
 
         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)
 class DeletableContent(models.Model):
     deleted     = models.BooleanField(default=False)
     deleted_at  = models.DateTimeField(null=True, blank=True)
@@ -189,7 +244,6 @@ class DeletableContent(models.Model):
             self.deleted_at = datetime.datetime.now()
             self.deleted_by = user
             self.save()
             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
             return True
         else:
             return False
@@ -223,19 +277,6 @@ class CancelableContent(models.Model):
 
 from node import Node, NodeRevision
 
 
 from node import Node, NodeRevision
 
-class QandA(Node):
-    wiki                 = models.BooleanField(default=False)
-    wikified_at          = models.DateTimeField(null=True, blank=True)
-
-    class Meta:
-        abstract = True
-        app_label = 'forum'
-
-    def wikify(self):
-        if not self.wiki:
-            self.wiki = True
-            self.wikified_at = datetime.datetime.now()
-            self.save()
 
 
 
 
 
 
index a2e74258e036fc8dfdbb1f321680c982e5eba923..a70d4749f762a366a20b0a428c7498ad8f8f6db9 100644 (file)
@@ -1,7 +1,10 @@
 from base import *\r
 from base import *\r
+from django.utils.translation import ugettext as _\r
 import re\r
 \r
 class Comment(Node):\r
 import re\r
 \r
 class Comment(Node):\r
+    friendly_name = _("comment")\r
+\r
     class Meta(Node.Meta):\r
         ordering = ('-added_at',)\r
         proxy = True\r
     class Meta(Node.Meta):\r
         ordering = ('-added_at',)\r
         proxy = True\r
@@ -13,7 +16,10 @@ class Comment(Node):
 \r
     @property\r
     def comment(self):\r
 \r
     @property\r
     def comment(self):\r
-        return self.body\r
+        if settings.FORM_ALLOW_MARKDOWN_IN_COMMENTS:\r
+            return self.as_markdown('limitedsyntax')\r
+        else:\r
+            return self.body\r
 \r
     @property\r
     def headline(self):\r
 \r
     @property\r
     def headline(self):\r
@@ -26,21 +32,16 @@ class Comment(Node):
     def save(self, *args, **kwargs):\r
         super(Comment,self).save(*args, **kwargs)\r
 \r
     def save(self, *args, **kwargs):\r
         super(Comment,self).save(*args, **kwargs)\r
 \r
-        if self._is_new:\r
-            self._update_parent_comment_count(1)\r
-\r
-        try:\r
-            ping_google()\r
-        except Exception:\r
-            logging.debug('problem pinging google did you register your sitemap with google?')\r
+        if not self.id:\r
+            self.parent.reset_comment_count_cache()\r
 \r
     def mark_deleted(self, user):\r
         if super(Comment, self).mark_deleted(user):\r
 \r
     def mark_deleted(self, user):\r
         if super(Comment, self).mark_deleted(user):\r
-            self._update_parent_comment_count(-1)\r
+            self.parent.reset_comment_count_cache()\r
 \r
     def unmark_deleted(self):\r
         if super(Comment, self).unmark_deleted():\r
 \r
     def unmark_deleted(self):\r
         if super(Comment, self).unmark_deleted():\r
-            self._update_parent_comment_count(1)\r
+            self.parent.reset_comment_count_cache()\r
 \r
     def is_reply_to(self, user):\r
         inreply = re.search('@\w+', self.body)\r
 \r
     def is_reply_to(self, user):\r
         inreply = re.search('@\w+', self.body)\r
index 86a479cde1999b820b029a1184fa03a85066809c..1498935b0bc4811f7b3f7df407eeac2940e52540 100644 (file)
@@ -1,75 +1,86 @@
-from base import *
-
-class Vote(MetaContent, CancelableContent, UserContent):
-    VOTE_UP = +1
-    VOTE_DOWN = -1
-    VOTE_CHOICES = (
-        (VOTE_UP,   u'Up'),
-        (VOTE_DOWN, u'Down'),
-    )
-
-    vote           = models.SmallIntegerField(choices=VOTE_CHOICES)
-    voted_at       = models.DateTimeField(default=datetime.datetime.now)
-
-    active = ActiveObjectManager()
-
-    class Meta(MetaContent.Meta):
-        db_table = u'vote'
-
-    def __unicode__(self):
-        return '[%s] voted at %s: %s' %(self.user, self.voted_at, self.vote)
-
-    def _update_post_vote_count(self, diff):
-        post = self.node.leaf
-        field = self.vote == 1 and 'vote_up_count' or 'vote_down_count'
-        post.__dict__[field] = post.__dict__[field] + diff
-        post.save()
-
-    def cancel(self):
-        if super(Vote, self).cancel():
-            self._update_post_vote_count(-1)
-
-    def save(self, *args, **kwargs):
-        super(Vote, self).save(*args, **kwargs)
-        if self._is_new:
-            self._update_post_vote_count(1)
-
-    def is_upvote(self):
-        return self.vote == self.VOTE_UP
-
-    def is_downvote(self):
-        return self.vote == self.VOTE_DOWN
-
-
-class FlaggedItem(MetaContent, UserContent):
-    flagged_at     = models.DateTimeField(default=datetime.datetime.now)
-    reason         = models.CharField(max_length=300, null=True)
-    canceled       = models.BooleanField(default=False)
-
-    active = ActiveObjectManager()
-
-    class Meta(MetaContent.Meta):
-        db_table = u'flagged_item'
-
-    def __unicode__(self):
-        return '[%s] flagged at %s' %(self.user, self.flagged_at)
-
-    def _update_post_flag_count(self, diff):
-        post = self.node
-        post.offensive_flag_count = post.offensive_flag_count + diff
-        post.save()
-
-    def save(self, *args, **kwargs):
-        super(FlaggedItem, self).save(*args, **kwargs)
-        if self._is_new:
-            self._update_post_flag_count(1)
-
-    def cancel(self):
-        if not self.canceled:
-            self.canceled = True
-            self.save()
-            self._update_post_flag_count(-1)
-            
-
-
-
+from django.utils.translation import ugettext as _\r
+from base import *\r
+\r
+class Vote(models.Model):\r
+    user = models.ForeignKey(User, related_name="votes")\r
+    node = models.ForeignKey(Node, related_name="votes")\r
+    value = models.SmallIntegerField()\r
+    action = models.ForeignKey(Action, unique=True, related_name="vote")\r
+    voted_at = models.DateTimeField(default=datetime.datetime.now)\r
+\r
+    class Meta:\r
+        app_label = 'forum'\r
+        unique_together = ('user', 'node')\r
+\r
+\r
+class Flag(models.Model):\r
+    user = models.ForeignKey(User, related_name="flags")\r
+    node = models.ForeignKey(Node, related_name="flags")\r
+    reason = models.CharField(max_length=300)\r
+    action = models.ForeignKey(Action, unique=True, related_name="flag")\r
+    flagged_at = models.DateTimeField(default=datetime.datetime.now)\r
+\r
+    class Meta:\r
+        app_label = 'forum'\r
+        unique_together = ('user', 'node')\r
+\r
+class BadgeManager(models.Manager):\r
+    use_for_related_fields = True\r
+    \r
+    def get(self, *args, **kwargs):\r
+        try:\r
+            pk = [v for (k,v) in kwargs.items() if k in ('pk', 'pk__exact', 'id', 'id__exact')][0]\r
+        except:\r
+            return super(BadgeManager, self).get(*args, **kwargs)\r
+\r
+        from forum.badges.base import BadgesMeta\r
+        badge = BadgesMeta.by_id.get(pk, None)\r
+        if not badge:\r
+            return super(BadgeManager, self).get(*args, **kwargs)\r
+        return badge.ondb\r
+\r
+class Badge(models.Model):\r
+    GOLD = 1\r
+    SILVER = 2\r
+    BRONZE = 3\r
+\r
+    type        = models.SmallIntegerField()\r
+    cls         = models.CharField(max_length=50, null=True)\r
+    awarded_count = models.PositiveIntegerField(default=0)\r
+    \r
+    awarded_to    = models.ManyToManyField(User, through='Award', related_name='badges')\r
+\r
+    objects = BadgeManager()\r
+\r
+    @property\r
+    def name(self):\r
+        cls = self.__dict__.get('_class', None)\r
+        return cls and cls.name or _("Unknown")\r
+\r
+    @property\r
+    def description(self):\r
+        cls = self.__dict__.get('_class', None)\r
+        return cls and cls.description or _("No description available")\r
+\r
+    @models.permalink\r
+    def get_absolute_url(self):\r
+        return ('badge', [], {'id': self.id, 'slug': slugify(self.name)})        \r
+\r
+    class Meta:\r
+        app_label = 'forum'\r
+\r
+\r
+class Award(models.Model):\r
+    user = models.ForeignKey(User)\r
+    badge = models.ForeignKey('Badge', related_name="awards")\r
+    node = models.ForeignKey(Node, null=True)\r
+\r
+    awarded_at = models.DateTimeField(default=datetime.datetime.now)\r
+\r
+    trigger = models.ForeignKey(Action, related_name="awards", null=True)\r
+    action = models.ForeignKey(Action, related_name="award", unique=True)\r
+\r
+\r
+    class Meta:\r
+        unique_together = ('user', 'badge', 'node')\r
+        app_label = 'forum'
\ No newline at end of file
index a92dee990b22d29123ba490440663191ce5ae554..a7e122ec509930e1039b135ebf2074742da6fa8a 100644 (file)
@@ -1,8 +1,9 @@
-from akismet import *\r
+from forum.akismet import *\r
 from base import *\r
 from tag import Tag\r
 \r
 import markdown\r
 from base import *\r
 from tag import Tag\r
 \r
 import markdown\r
+from django.utils.translation import ugettext as _\r
 from django.utils.safestring import mark_safe\r
 from django.utils.html import strip_tags\r
 from forum.utils.html import sanitize_html\r
 from django.utils.safestring import mark_safe\r
 from django.utils.html import strip_tags\r
 from forum.utils.html import sanitize_html\r
@@ -19,7 +20,10 @@ class NodeContent(models.Model):
 \r
     @property\r
     def html(self):\r
 \r
     @property\r
     def html(self):\r
-        return mark_safe(sanitize_html(markdown.markdown(self.body)))\r
+        return self.as_markdown()\r
+\r
+    def as_markdown(self, *extensions):\r
+        return mark_safe(sanitize_html(markdown.markdown(self.body, extensions=extensions)))\r
 \r
     @property\r
     def headline(self):\r
 \r
     @property\r
     def headline(self):\r
@@ -38,14 +42,14 @@ class NodeContent(models.Model):
         abstract = True\r
         app_label = 'forum'\r
 \r
         abstract = True\r
         app_label = 'forum'\r
 \r
-class NodeMetaClass(models.Model.__metaclass__):\r
+class NodeMetaClass(BaseMetaClass):\r
     types = {}\r
 \r
     def __new__(cls, *args, **kwargs):\r
         new_cls = super(NodeMetaClass, cls).__new__(cls, *args, **kwargs)\r
 \r
         if not new_cls._meta.abstract and new_cls.__name__ is not 'Node':\r
     types = {}\r
 \r
     def __new__(cls, *args, **kwargs):\r
         new_cls = super(NodeMetaClass, cls).__new__(cls, *args, **kwargs)\r
 \r
         if not new_cls._meta.abstract and new_cls.__name__ is not 'Node':\r
-            NodeMetaClass.types[new_cls.__name__.lower()] = new_cls\r
+            NodeMetaClass.types[new_cls.get_type()] = new_cls\r
 \r
         return new_cls\r
 \r
 \r
         return new_cls\r
 \r
@@ -59,10 +63,7 @@ class NodeMetaClass(models.Model.__metaclass__):
         name = node_cls.__name__.lower()\r
 \r
         def children(self):\r
         name = node_cls.__name__.lower()\r
 \r
         def children(self):\r
-            if node_cls._meta.proxy:\r
-                return node_cls.objects.filter(node_type=name, parent=self)\r
-            else:\r
-                return node_cls.objects.filter(parent=self)\r
+            return node_cls.objects.filter(parent=self)\r
 \r
         def parent(self):\r
             p = self.__dict__.get('_%s_cache' % name, None)\r
 \r
         def parent(self):\r
             p = self.__dict__.get('_%s_cache' % name, None)\r
@@ -77,35 +78,82 @@ class NodeMetaClass(models.Model.__metaclass__):
         Node.add_to_class(name, property(parent))\r
 \r
 \r
         Node.add_to_class(name, property(parent))\r
 \r
 \r
-node_create = django.dispatch.Signal(providing_args=['instance'])\r
-node_edit = django.dispatch.Signal(providing_args=['instance'])\r
+class NodeManager(CachedManager):\r
+    use_for_related_fields = True\r
 \r
 \r
-class Node(BaseModel, NodeContent, DeletableContent):\r
-    __metaclass__ = NodeMetaClass\r
+    def get_query_set(self):\r
+        qs = super(NodeManager, self).get_query_set()\r
+\r
+        if self.model is not Node:\r
+            return qs.filter(node_type=self.model.get_type())\r
+        else:\r
+            return qs\r
+\r
+    def get(self, *args, **kwargs):\r
+        node = super(NodeManager, self).get(*args, **kwargs)\r
+        cls = NodeMetaClass.types.get(node.node_type, None)\r
+\r
+        if cls and node.__class__ is not cls:\r
+            return node.leaf\r
+        return node\r
+\r
+    def get_for_types(self, types, *args, **kwargs):\r
+        kwargs['node_type__in'] = [t.get_type() for t in types]\r
+        return self.get(*args, **kwargs)\r
 \r
 \r
-    node_type             = models.CharField(max_length=16, default='node')\r
-    parent                = models.ForeignKey('Node', related_name='children', null=True)\r
-    abs_parent            = models.ForeignKey('Node', related_name='all_children', null=True)\r
 \r
 \r
-    added_at              = models.DateTimeField(default=datetime.datetime.now)\r
+class Node(BaseModel, NodeContent):\r
+    __metaclass__ = NodeMetaClass\r
 \r
 \r
-    tags                  = models.ManyToManyField('Tag', related_name='%(class)ss')\r
+    node_type            = models.CharField(max_length=16, default='node')\r
+    parent               = models.ForeignKey('Node', related_name='children', null=True)\r
+    abs_parent           = models.ForeignKey('Node', related_name='all_children', null=True)\r
 \r
 \r
-    score                 = DenormalizedField(default=0)\r
-    vote_up_count         = DenormalizedField(default=0)\r
-    vote_down_count       = models.IntegerField(default=0)\r
+    added_at             = models.DateTimeField(default=datetime.datetime.now)\r
+    score                 = models.IntegerField(default=0)\r
 \r
 \r
-    comment_count         = DenormalizedField(default=0)\r
-    offensive_flag_count  = DenormalizedField(default=0)\r
+    deleted               = models.ForeignKey('Action', null=True, unique=True, related_name="deleted_node")\r
+    in_moderation         = models.ForeignKey('Action', null=True, unique=True, related_name="moderated_node")\r
+    last_edited           = models.ForeignKey('Action', null=True, unique=True, related_name="edited_node")\r
 \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
+    last_activity_by       = models.ForeignKey(User, null=True)\r
+    last_activity_at       = models.DateTimeField(null=True, blank=True)\r
 \r
 \r
+    tags                 = models.ManyToManyField('Tag', related_name='%(class)ss')\r
     active_revision       = models.OneToOneField('NodeRevision', related_name='active', null=True)\r
 \r
     active_revision       = models.OneToOneField('NodeRevision', related_name='active', null=True)\r
 \r
+    extra_ref = models.ForeignKey('Node', null=True)\r
+    extra_count = models.IntegerField(default=0)\r
+    extra_action = models.ForeignKey('Action', null=True, related_name="extra_node")\r
+    \r
+    marked = models.BooleanField(default=False)\r
+    wiki = models.BooleanField(default=False)\r
+\r
+    comment_count = DenormalizedField("children", node_type="comment", canceled=False)\r
+    flag_count = DenormalizedField("flags")\r
+\r
+    friendly_name = _("post")\r
+\r
+    objects = NodeManager()\r
+\r
+    @classmethod\r
+    def cache_key(cls, pk):\r
+        return '%s.node:%s' % (settings.APP_URL, pk)\r
+\r
+    @classmethod\r
+    def get_type(cls):\r
+        return cls.__name__.lower()\r
+\r
     @property\r
     def leaf(self):\r
     @property\r
     def leaf(self):\r
-        return NodeMetaClass.types[self.node_type].objects.get(id=self.id)\r
+        leaf_cls = NodeMetaClass.types.get(self.node_type, None)\r
+\r
+        if leaf_cls is None:\r
+            return self\r
+\r
+        leaf = leaf_cls()\r
+        leaf.__dict__ = self.__dict__\r
+        return leaf\r
 \r
     @property    \r
     def absolute_parent(self):\r
 \r
     @property    \r
     def absolute_parent(self):\r
@@ -118,40 +166,40 @@ class Node(BaseModel, NodeContent, DeletableContent):
     def summary(self):\r
         return strip_tags(self.html)[:300]\r
 \r
     def summary(self):\r
         return strip_tags(self.html)[:300]\r
 \r
-    def create_revision(self, user, **kwargs):\r
-        revision = NodeRevision(author=user, **kwargs)\r
-        \r
-        if not self.id:\r
-            self.author = user\r
-            self.save()\r
-            revision.revision = 1\r
-        else:\r
-            revision.revision = self.revisions.aggregate(last=models.Max('revision'))['last'] + 1\r
+    def update_last_activity(self, user):\r
+        self.last_activity_by = user\r
+        self.last_activity_at = datetime.datetime.now()\r
 \r
 \r
-        revision.node_id = self.id\r
+        if self.parent:\r
+            self.parent.update_last_activity(user)\r
+\r
+    def _create_revision(self, user, number, **kwargs):\r
+        revision = NodeRevision(author=user, revision=number, node=self, **kwargs)\r
         revision.save()\r
         revision.save()\r
-        self.activate_revision(user, revision)\r
+        return revision\r
+\r
+    def create_revision(self, user, action=None, **kwargs):\r
+        number = self.revisions.aggregate(last=models.Max('revision'))['last'] + 1\r
+        revision = self._create_revision(user, number, **kwargs)\r
+        self.activate_revision(user, revision, action)\r
+        return revision\r
 \r
 \r
-    def activate_revision(self, user, revision):\r
+    def activate_revision(self, user, revision, action=None):\r
         self.title = revision.title\r
         self.tagnames = revision.tagnames\r
         self.body = revision.body\r
 \r
         self.title = revision.title\r
         self.tagnames = revision.tagnames\r
         self.body = revision.body\r
 \r
-        old_revision = self.active_revision\r
         self.active_revision = revision\r
         self.active_revision = revision\r
+        self.update_last_activity(user)\r
 \r
 \r
-        if not old_revision:\r
-            signal = node_create\r
-        else:\r
-            self.last_edited_at = datetime.datetime.now()\r
-            self.last_edited_by = user\r
-            signal = node_edit\r
+        if action:\r
+            self.last_edited = action\r
 \r
         self.save()\r
 \r
         self.save()\r
-        signal.send(sender=self.__class__, instance=self)\r
 \r
     def get_tag_list_if_changed(self):\r
         dirty = self.get_dirty_fields()\r
 \r
     def get_tag_list_if_changed(self):\r
         dirty = self.get_dirty_fields()\r
+        active_user = self.last_edited and self.last_edited.by or self.author\r
 \r
         if 'tagnames' in dirty:\r
             new_tags = self.tagname_list()\r
 \r
         if 'tagnames' in dirty:\r
             new_tags = self.tagname_list()\r
@@ -168,7 +216,7 @@ class Node(BaseModel, NodeContent, DeletableContent):
                 try:\r
                     tag = Tag.objects.get(name=name)\r
                 except:\r
                 try:\r
                     tag = Tag.objects.get(name=name)\r
                 except:\r
-                    tag = Tag.objects.create(name=name, created_by=self.last_edited_by or self.author)\r
+                    tag = Tag.objects.create(name=name, created_by=active_user or self.author)\r
 \r
                 tag_list.append(tag)\r
 \r
 \r
                 tag_list.append(tag)\r
 \r
@@ -182,7 +230,7 @@ class Node(BaseModel, NodeContent, DeletableContent):
                 tag = Tag.objects.get(name=name)\r
                 tag.used_count = tag.used_count - 1\r
                 if tag.used_count == 0:\r
                 tag = Tag.objects.get(name=name)\r
                 tag.used_count = tag.used_count - 1\r
                 if tag.used_count == 0:\r
-                    tag.mark_deleted(self.last_edited_by or self.author)\r
+                    tag.mark_deleted(active_user)\r
                 tag.save()\r
 \r
             return tag_list\r
                 tag.save()\r
 \r
             return tag_list\r
@@ -191,13 +239,14 @@ class Node(BaseModel, NodeContent, DeletableContent):
 \r
     def save(self, *args, **kwargs):\r
         if not self.id:\r
 \r
     def save(self, *args, **kwargs):\r
         if not self.id:\r
-            self.node_type = self.__class__.__name__.lower()\r
+            self.node_type = self.get_type()\r
+            super(BaseModel, self).save(*args, **kwargs)\r
+            self.active_revision = self._create_revision(self.author, 1, title=self.title, tagnames=self.tagnames, body=self.body)\r
+            self.update_last_activity(self.author)\r
 \r
         if self.parent_id and not self.abs_parent_id:\r
             self.abs_parent = self.parent.absolute_parent\r
 \r
         if self.parent_id and not self.abs_parent_id:\r
             self.abs_parent = self.parent.absolute_parent\r
-\r
-        self.__dict__['score'] = self.__dict__['vote_up_count'] - self.__dict__['vote_down_count']\r
-            \r
+        \r
         tags = self.get_tag_list_if_changed()\r
         super(Node, self).save(*args, **kwargs)\r
         if tags is not None: self.tags = tags\r
         tags = self.get_tag_list_if_changed()\r
         super(Node, self).save(*args, **kwargs)\r
         if tags is not None: self.tags = tags\r
@@ -205,8 +254,9 @@ class Node(BaseModel, NodeContent, DeletableContent):
     @staticmethod\r
     def isSpam(comment, data):\r
         api = Akismet()\r
     @staticmethod\r
     def isSpam(comment, data):\r
         api = Akismet()\r
-        if api.key is None:\r
-            print "problem" # raise APIKeyError\r
+\r
+        if not api.key:\r
+            return False\r
         else:\r
             if api.comment_check(comment, data):\r
                 return True\r
         else:\r
             if api.comment_check(comment, data):\r
                 return True\r
@@ -229,11 +279,3 @@ class NodeRevision(BaseModel, NodeContent):
         app_label = 'forum'\r
 \r
 \r
         app_label = 'forum'\r
 \r
 \r
-from user import ValidationHash\r
-\r
-class AnonymousNode(Node):\r
-    validation_hash = models.ForeignKey(Node, related_name='anonymous_content')\r
-    convertible_to = models.CharField(max_length=16, default='node')\r
-\r
-    class Meta:\r
-        app_label = 'forum'
\ No newline at end of file
index da8083f6d1407f41d46649fce8593ea59ee3d01d..54840166d1d9c51534de77884296f670622bb0cf 100644 (file)
@@ -4,29 +4,26 @@ from django.utils.translation import ugettext as _
 
 question_view = django.dispatch.Signal(providing_args=['instance', 'user'])
 
 
 question_view = django.dispatch.Signal(providing_args=['instance', 'user'])
 
-class Question(QandA):
-    accepted_answer = models.OneToOneField('Answer', null=True, related_name="question_accepting")
-    closed          = models.BooleanField(default=False)
-    closed_by       = models.ForeignKey(User, null=True, blank=True, related_name='closed_questions')
-    closed_at       = models.DateTimeField(null=True, blank=True)
-    close_reason    = models.SmallIntegerField(choices=CLOSE_REASONS, null=True, blank=True)
-    subscribers     = models.ManyToManyField(User, related_name='subscriptions', through='QuestionSubscription')
-
-    # Denormalised data
-    answer_count         = models.PositiveIntegerField(default=0)
-    view_count           = models.IntegerField(default=0)
-    favourite_count      = models.IntegerField(default=0)
-    last_activity_at     = models.DateTimeField(default=datetime.datetime.now)
-    last_activity_by     = models.ForeignKey(User, related_name='last_active_in_questions', null=True)
-
-    favorited_by         = models.ManyToManyField(User, through='FavoriteQuestion', related_name='favorite_questions')
-
-    class Meta(QandA.Meta):
-        db_table = u'question'
+class Question(Node):
+    class Meta(Node.Meta):
+        proxy = True
+
+    answer_count = DenormalizedField("children", node_type="answer", deleted=None)
+    favorite_count = DenormalizedField("actions", action_type="favorite", canceled=False)
+
+    friendly_name = _("question")
+
+    @property   
+    def closed(self):
+        return self.extra_action
+
+    @property    
+    def view_count(self):
+        return self.extra_count
 
     @property
     def headline(self):
 
     @property
     def headline(self):
-        if self.closed:
+        if self.marked:
             return _('[closed] ') + self.title
 
         if self.deleted:
             return _('[closed] ') + self.title
 
         if self.deleted:
@@ -36,43 +33,16 @@ class Question(QandA):
 
     @property
     def answer_accepted(self):
 
     @property
     def answer_accepted(self):
-        return self.accepted_answer is not None
+        return self.extra_ref is not None
 
 
-    def save(self, *args, **kwargs):
-        if not self.last_activity_by:
-            self.last_activity_by = self.author
-        super(Question, self).save(*args, **kwargs)
-
-    def update_last_activity(self, user):
-        self.last_activity_by = user
-        self.last_activity_at = datetime.datetime.now()
-        self.save()
-
-    def activate_revision(self, user, revision):
-        super(Question, self).activate_revision(user, revision)
-        self.update_last_activity(user)
+    @property
+    def accepted_answer(self):
+        return self.extra_ref
 
     @models.permalink    
     def get_absolute_url(self):
         return ('question', (), {'id': self.id, 'slug': django_urlquote(slugify(self.title))})
 
 
     @models.permalink    
     def get_absolute_url(self):
         return ('question', (), {'id': self.id, 'slug': django_urlquote(slugify(self.title))})
 
-    def get_answer_count_by_user(self, user_id):
-        from answer import Answer
-        query_set = Answer.objects.filter(author__id=user_id)
-        return query_set.filter(question=self).count()
-
-    def get_question_title(self):
-        if self.closed:
-            attr = CONST['closed']
-        elif self.deleted:
-            attr = CONST['deleted']
-        else:
-            attr = None
-        if attr is not None:
-            return u'%s %s' % (self.title, attr)
-        else:
-            return self.title
-
     def get_revision_url(self):
         return reverse('question_revisions', args=[self.id])
 
     def get_revision_url(self):
         return reverse('question_revisions', args=[self.id])
 
@@ -82,49 +52,22 @@ class Question(QandA):
 
         if related_list is None:
             related_list = Question.objects.values('id').filter(tags__id__in=[t.id for t in self.tags.all()]
 
         if related_list is None:
             related_list = Question.objects.values('id').filter(tags__id__in=[t.id for t in self.tags.all()]
-            ).exclude(id=self.id).exclude(deleted=True).annotate(frequency=models.Count('id')).order_by('-frequency')[:count]
+            ).exclude(id=self.id).filter(deleted=None).annotate(frequency=models.Count('id')).order_by('-frequency')[:count]
             cache.set(cache_key, related_list, 60 * 60)
 
         return [Question.objects.get(id=r['id']) for r in related_list]
 
             cache.set(cache_key, related_list, 60 * 60)
 
         return [Question.objects.get(id=r['id']) for r in related_list]
 
-    def __unicode__(self):
-        return self.title
 
 def question_viewed(instance, **kwargs):
 
 def question_viewed(instance, **kwargs):
-    instance.view_count += 1
+    instance.extra_count += 1
     instance.save()
 
 question_view.connect(question_viewed)
 
     instance.save()
 
 question_view.connect(question_viewed)
 
-class FavoriteQuestion(models.Model):
-    question      = models.ForeignKey('Question')
-    user          = models.ForeignKey(User, related_name='user_favorite_questions')
-    added_at      = models.DateTimeField(default=datetime.datetime.now)
-
-    class Meta:
-        unique_together = ('question', 'user')
-        app_label = 'forum'
-        db_table = u'favorite_question'
-
-    def __unicode__(self):
-        return '[%s] favorited at %s' %(self.user, self.added_at)
-
-    def _update_question_fav_count(self, diff):
-        self.question.favourite_count = self.question.favourite_count + diff
-        self.question.save()
-
-    def save(self, *args, **kwargs):
-        super(FavoriteQuestion, self).save(*args, **kwargs)
-        if self._is_new:
-            self._update_question_fav_count(1)
-
-    def delete(self):
-        self._update_question_fav_count(-1)
-        super(FavoriteQuestion, self).delete()
 
 class QuestionSubscription(models.Model):
     user = models.ForeignKey(User)
 
 class QuestionSubscription(models.Model):
     user = models.ForeignKey(User)
-    question = models.ForeignKey(Question)
+    question = models.ForeignKey(Node)
     auto_subscription = models.BooleanField(default=True)
     last_view = models.DateTimeField(default=datetime.datetime.now())
 
     auto_subscription = models.BooleanField(default=True)
     last_view = models.DateTimeField(default=datetime.datetime.now())
 
diff --git a/forum/models/repute.py b/forum/models/repute.py
deleted file mode 100644 (file)
index 0c54c1b..0000000
+++ /dev/null
@@ -1,127 +0,0 @@
-from base import *
-from django.contrib.contenttypes.models import ContentType
-from forum.models import User
-
-from django.utils.translation import ugettext as _
-
-class Badge(BaseModel):
-    """Awarded for notable actions performed on the site by Users."""
-    GOLD = 1
-    SILVER = 2
-    BRONZE = 3
-    TYPE_CHOICES = (
-        (GOLD,   _('gold')),
-        (SILVER, _('silver')),
-        (BRONZE, _('bronze')),
-    )
-
-    name        = models.CharField(max_length=50)
-    type        = models.SmallIntegerField(choices=TYPE_CHOICES)
-    slug        = models.SlugField(max_length=50, blank=True)
-    description = models.CharField(max_length=300)
-    multiple    = models.BooleanField(default=False)
-    # Denormalised data
-    awarded_count = models.PositiveIntegerField(default=0)
-    awarded_to    = models.ManyToManyField(User, through='Award', related_name='badges')
-
-    class Meta:
-        app_label = 'forum'
-        db_table = u'badge'
-        ordering = ('name',)
-        unique_together = ('name', 'type')
-
-    def __unicode__(self):
-        return u'%s: %s' % (self.get_type_display(), self.name)
-
-    def save(self, *args, **kwargs):
-        if not self.slug:
-            self.slug = self.name#slugify(self.name)
-        super(Badge, self).save(*args, **kwargs)
-
-    def get_absolute_url(self):
-        return '%s%s/' % (reverse('badge', args=[self.id]), self.slug)
-
-
-class AwardManager(models.Manager):
-    def get_recent_awards(self):
-        awards = super(AwardManager, self).extra(
-            select={'badge_id': 'badge.id', 'badge_name':'badge.name',
-                          'badge_description': 'badge.description', 'badge_type': 'badge.type',
-                          'user_id': 'auth_user.id', 'user_name': 'auth_user.username'
-                          },
-            tables=['award', 'badge', 'auth_user'],
-            order_by=['-awarded_at'],
-            where=['auth_user.id=award.user_id AND badge_id=badge.id'],
-        ).values('badge_id', 'badge_name', 'badge_description', 'badge_type', 'user_id', 'user_name')
-        return awards
-
-class Award(GenericContent, UserContent):
-    """The awarding of a Badge to a User."""
-    badge      = models.ForeignKey('Badge', related_name='award_badge')
-    awarded_at = models.DateTimeField(default=datetime.datetime.now)
-    notified   = models.BooleanField(default=False)
-
-    objects = AwardManager()
-
-    def __unicode__(self):
-        return u'[%s] is awarded a badge [%s] at %s' % (self.user.username, self.badge.name, self.awarded_at)
-
-    def save(self, *args, **kwargs):
-        super(Award, self).save(*args, **kwargs)
-
-        if self._is_new:
-            self.badge.awarded_count += 1
-            self.badge.save()
-
-            if self.badge.type == Badge.GOLD:
-                self.user.gold += 1
-            if self.badge.type == Badge.SILVER:
-                self.user.silver += 1
-            if self.badge.type == Badge.BRONZE:
-                self.user.bronze += 1
-            self.user.save()
-
-    class Meta:
-        #unique_together = ('content_type', 'object_id', 'user', 'badge')
-        app_label = 'forum'
-        db_table = u'award'
-
-
-class Repute(MetaContent, CancelableContent, UserContent):
-    value    = models.SmallIntegerField(default=0)
-    question = models.ForeignKey('Question')
-    reputed_at = models.DateTimeField(default=datetime.datetime.now)
-    reputation_type = models.SmallIntegerField(choices=TYPE_REPUTATION)
-    user_previous_rep = models.IntegerField(default=0)
-
-    def __unicode__(self):
-        return u'[%s]\' reputation changed at %s' % (self.user.username, self.reputed_at)
-
-    @property
-    def positive(self):
-        if self.value > 0: return self.value
-        return 0
-
-    @property
-    def negative(self):
-        if self.value < 0: return self.value
-        return 0
-
-    @property
-    def reputation(self):
-        return self.user_previous_rep + self.value
-
-    def cancel(self):
-        if super(Repute, self).cancel():
-            self.user.reputation = self.user.reputation - self.value
-            self.user.save()
-
-    def save(self, *args, **kwargs):
-        self.user_previous_rep = self.user.reputation
-        self.user.reputation = self.user.reputation + self.value
-        self.user.save()
-        super(Repute, self).save(*args, **kwargs)
-
-    class Meta:
-        app_label = 'forum'
-        db_table = u'repute'
index 50eb5aa90a8231aa2d8f0f43f388e852287ba913..16f1c6cdc80a1699215d669f7984fd7dada35d9a 100644 (file)
@@ -18,7 +18,6 @@ class Tag(BaseModel, DeletableContent):
     active = ActiveTagManager()
 
     class Meta(DeletableContent.Meta):
     active = ActiveTagManager()
 
     class Meta(DeletableContent.Meta):
-        db_table = u'tag'
         ordering = ('-used_count', 'name')
 
     def __unicode__(self):
         ordering = ('-used_count', 'name')
 
     def __unicode__(self):
index abb398931a495dafe96a23972dec68e3f0a2f161..53b5ee223a10b3ae1bc1ac7f8574a6bbb8facb05 100644 (file)
@@ -6,7 +6,8 @@ from django.db.models import Q
 try:\r
     from hashlib import md5\r
 except:\r
 try:\r
     from hashlib import md5\r
 except:\r
-    import md5\r
+    from md5 import new as md5\r
+\r
 import string\r
 from random import Random\r
 \r
 import string\r
 from random import Random\r
 \r
@@ -26,7 +27,7 @@ class UserManager(CachedManager):
 \r
 class AnonymousUser(DjangoAnonymousUser):\r
     def get_visible_answers(self, question):\r
 \r
 class AnonymousUser(DjangoAnonymousUser):\r
     def get_visible_answers(self, question):\r
-        return question.answers.filter(deleted=False)\r
+        return question.answers.filter(deleted=None)\r
 \r
     def can_view_deleted_post(self, post):\r
         return False\r
 \r
     def can_view_deleted_post(self, post):\r
         return False\r
@@ -76,18 +77,19 @@ class AnonymousUser(DjangoAnonymousUser):
     def can_upload_files(self):\r
         return False\r
 \r
     def can_upload_files(self):\r
         return False\r
 \r
+def true_if_is_super_or_staff(fn):\r
+    def decorated(self, *args, **kwargs):\r
+        return self.is_superuser or self.is_staff or fn(self, *args, **kwargs)\r
+    return decorated\r
+\r
 class User(BaseModel, DjangoUser):\r
     is_approved = models.BooleanField(default=False)\r
     email_isvalid = models.BooleanField(default=False)\r
 class User(BaseModel, DjangoUser):\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 = DenormalizedField(default=1)\r
 \r
 \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
+    reputation = models.PositiveIntegerField(default=0)\r
+    gold = models.PositiveIntegerField(default=0)\r
+    silver = models.PositiveIntegerField(default=0)\r
+    bronze = models.PositiveIntegerField(default=0)\r
     \r
     last_seen = models.DateTimeField(default=datetime.datetime.now)\r
     real_name = models.CharField(max_length=100, blank=True)\r
     \r
     last_seen = models.DateTimeField(default=datetime.datetime.now)\r
     real_name = models.CharField(max_length=100, blank=True)\r
@@ -95,7 +97,12 @@ class User(BaseModel, DjangoUser):
     location = models.CharField(max_length=100, blank=True)\r
     date_of_birth = models.DateField(null=True, blank=True)\r
     about = models.TextField(blank=True)\r
     location = models.CharField(max_length=100, blank=True)\r
     date_of_birth = models.DateField(null=True, blank=True)\r
     about = models.TextField(blank=True)\r
-    \r
+\r
+    subscriptions = models.ManyToManyField('Node', related_name='subscribers', through='QuestionSubscription')\r
+\r
+    vote_up_count = DenormalizedField("actions", canceled=False, action_type="voteup")\r
+    vote_down_count = DenormalizedField("actions", canceled=False, action_type="votedown")\r
+   \r
     objects = UserManager()\r
 \r
     @property\r
     objects = UserManager()\r
 \r
     @property\r
@@ -104,10 +111,16 @@ class User(BaseModel, DjangoUser):
 \r
     def save(self, *args, **kwargs):\r
         if self.reputation < 0:\r
 \r
     def save(self, *args, **kwargs):\r
         if self.reputation < 0:\r
-            self.reputation = 1\r
+            self.reputation = 0\r
+\r
+        new = not bool(self.id)\r
 \r
         super(User, self).save(*args, **kwargs)\r
 \r
 \r
         super(User, self).save(*args, **kwargs)\r
 \r
+        if new:\r
+            sub_settings = SubscriptionSettings(user=self)\r
+            sub_settings.save()\r
+\r
     def get_absolute_url(self):\r
         return self.get_profile_url()\r
 \r
     def get_absolute_url(self):\r
         return self.get_profile_url()\r
 \r
@@ -127,15 +140,13 @@ class User(BaseModel, DjangoUser):
         profile_link = u'<a href="%s">%s</a>' % (self.get_profile_url(),self.username)\r
         return mark_safe(profile_link)\r
 \r
         profile_link = u'<a href="%s">%s</a>' % (self.get_profile_url(),self.username)\r
         return mark_safe(profile_link)\r
 \r
+    def get_visible_answers(self, question):\r
+        return question.answers.filter(deleted=None, in_moderation=None)\r
+\r
     def get_vote_count_today(self):\r
         today = datetime.date.today()\r
     def get_vote_count_today(self):\r
         today = datetime.date.today()\r
-        return self.votes.filter(voted_at__range=(today - datetime.timedelta(days=1), today)).count()\r
-\r
-    def get_up_vote_count(self):\r
-        return self.votes.filter(vote=1).count()\r
-\r
-    def get_down_vote_count(self):\r
-        return self.votes.filter(vote=-1).count()\r
+        return self.actions.filter(canceled=False, action_type__in=("voteup", "votedown"),\r
+                action_date__range=(today - datetime.timedelta(days=1), today)).count()\r
 \r
     def get_reputation_by_upvoted_today(self):\r
         today = datetime.datetime.now()\r
 \r
     def get_reputation_by_upvoted_today(self):\r
         today = datetime.datetime.now()\r
@@ -149,130 +160,87 @@ class User(BaseModel, DjangoUser):
 \r
     def get_flagged_items_count_today(self):\r
         today = datetime.date.today()\r
 \r
     def get_flagged_items_count_today(self):\r
         today = datetime.date.today()\r
-        return self.flaggeditems.filter(flagged_at__range=(today - datetime.timedelta(days=1), today)).count()\r
-\r
-    def get_visible_answers(self, question):\r
-        if self.is_superuser:\r
-            return question.answers\r
-        else:\r
-            return question.answers.filter(models.Q(deleted=False) | models.Q(deleted_by=self))\r
+        return self.actions.filter(canceled=False, action_type="flag",\r
+                action_date__range=(today - datetime.timedelta(days=1), today)).count()\r
 \r
 \r
+    @true_if_is_super_or_staff\r
     def can_view_deleted_post(self, post):\r
     def can_view_deleted_post(self, post):\r
-        return self.is_superuser or post.author == self\r
+        return post.author == self\r
 \r
 \r
+    @true_if_is_super_or_staff\r
     def can_vote_up(self):\r
     def can_vote_up(self):\r
-        return self.reputation >= int(settings.REP_TO_VOTE_UP) or self.is_superuser\r
+        return self.reputation >= int(settings.REP_TO_VOTE_UP)\r
 \r
 \r
+    @true_if_is_super_or_staff\r
     def can_vote_down(self):\r
     def can_vote_down(self):\r
-        return self.reputation >= int(settings.REP_TO_VOTE_DOWN) or self.is_superuser\r
+        return self.reputation >= int(settings.REP_TO_VOTE_DOWN)\r
 \r
     def can_flag_offensive(self, post=None):\r
         if post is not None and post.author == self:\r
             return False\r
 \r
     def can_flag_offensive(self, post=None):\r
         if post is not None and post.author == self:\r
             return False\r
-        return self.is_superuser or self.reputation >= int(settings.REP_TO_FLAG)\r
+        return self.is_superuser or self.is_staff or self.reputation >= int(settings.REP_TO_FLAG)\r
 \r
 \r
+    @true_if_is_super_or_staff\r
     def can_view_offensive_flags(self, post=None):\r
         if post is not None and post.author == self:\r
             return True\r
     def can_view_offensive_flags(self, post=None):\r
         if post is not None and post.author == self:\r
             return True\r
-        return self.is_superuser or self.reputation >= int(settings.REP_TO_VIEW_FLAGS)\r
+        return self.reputation >= int(settings.REP_TO_VIEW_FLAGS)\r
 \r
 \r
+    @true_if_is_super_or_staff\r
     def can_comment(self, post):\r
         return self == post.author or self.reputation >= int(settings.REP_TO_COMMENT\r
     def can_comment(self, post):\r
         return self == post.author or self.reputation >= int(settings.REP_TO_COMMENT\r
-        ) or self.is_superuser or (post.__class__.__name__ == "Answer" and self == post.question.author)\r
+        ) or (post.__class__.__name__ == "Answer" and self == post.question.author)\r
 \r
 \r
+    @true_if_is_super_or_staff\r
     def can_like_comment(self, comment):\r
     def can_like_comment(self, comment):\r
-        return self != comment.user and (self.reputation >= int(settings.REP_TO_LIKE_COMMENT) or self.is_superuser)\r
+        return self != comment.author and (self.reputation >= int(settings.REP_TO_LIKE_COMMENT))\r
 \r
 \r
+    @true_if_is_super_or_staff\r
     def can_edit_comment(self, comment):\r
     def can_edit_comment(self, comment):\r
-        return (comment.user == self and comment.added_at >= datetime.datetime.now() - datetime.timedelta(minutes=60)\r
+        return (comment.author == self and comment.added_at >= datetime.datetime.now() - datetime.timedelta(minutes=60)\r
         ) or self.is_superuser\r
 \r
         ) or self.is_superuser\r
 \r
+    @true_if_is_super_or_staff\r
     def can_delete_comment(self, comment):\r
     def can_delete_comment(self, comment):\r
-        return self == comment.user or self.reputation >= int(settings.REP_TO_DELETE_COMMENTS) or self.is_superuser\r
+        return self == comment.author or self.reputation >= int(settings.REP_TO_DELETE_COMMENTS)\r
 \r
 \r
+    @true_if_is_super_or_staff\r
     def can_accept_answer(self, answer):\r
     def can_accept_answer(self, answer):\r
-        return self.is_superuser or self == answer.question.author\r
+        return self == answer.question.author\r
 \r
 \r
+    @true_if_is_super_or_staff\r
     def can_edit_post(self, post):\r
     def can_edit_post(self, post):\r
-        return self.is_superuser or self == post.author or self.reputation >= int(settings.REP_TO_EDIT_OTHERS\r
+        return self == post.author or self.reputation >= int(settings.REP_TO_EDIT_OTHERS\r
         ) or (post.wiki and self.reputation >= int(settings.REP_TO_EDIT_WIKI))\r
 \r
         ) or (post.wiki and self.reputation >= int(settings.REP_TO_EDIT_WIKI))\r
 \r
+    @true_if_is_super_or_staff\r
     def can_retag_questions(self):\r
         return self.reputation >= int(settings.REP_TO_RETAG)\r
 \r
     def can_retag_questions(self):\r
         return self.reputation >= int(settings.REP_TO_RETAG)\r
 \r
+    @true_if_is_super_or_staff\r
     def can_close_question(self, question):\r
     def can_close_question(self, question):\r
-        return self.is_superuser or (self == question.author and self.reputation >= int(settings.REP_TO_CLOSE_OWN)\r
+        return (self == question.author and self.reputation >= int(settings.REP_TO_CLOSE_OWN)\r
         ) or self.reputation >= int(settings.REP_TO_CLOSE_OTHERS)\r
 \r
         ) or self.reputation >= int(settings.REP_TO_CLOSE_OTHERS)\r
 \r
+    @true_if_is_super_or_staff\r
     def can_reopen_question(self, question):\r
     def can_reopen_question(self, question):\r
-        return self.is_superuser or (self == question.author and self.reputation >= settings.REP_TO_REOPEN_OWN)\r
+        return self == question.author and self.reputation >= settings.REP_TO_REOPEN_OWN\r
 \r
 \r
+    @true_if_is_super_or_staff\r
     def can_delete_post(self, post):\r
     def can_delete_post(self, post):\r
-        return self.is_superuser or (self == post.author and (post.__class__.__name__ == "Answer" or\r
-        not post.answers.filter(~Q(author=self)).count()))\r
+        if post.node_type == "comment":\r
+            return self.can_delete_comment(post)\r
+            \r
+        return (self == post.author and (post.__class__.__name__ == "Answer" or\r
+            not post.answers.exclude(author=self).count()))\r
 \r
 \r
+    @true_if_is_super_or_staff\r
     def can_upload_files(self):\r
     def can_upload_files(self):\r
-        return self.is_superuser or self.reputation >= int(settings.REP_TO_UPLOAD)\r
+        return self.reputation >= int(settings.REP_TO_UPLOAD)\r
 \r
     class Meta:\r
         app_label = 'forum'\r
 \r
 \r
     class Meta:\r
         app_label = 'forum'\r
 \r
-class Activity(GenericContent):\r
-    """\r
-    We keep some history data for user activities\r
-    """\r
-    user = models.ForeignKey(User)\r
-    activity_type = models.SmallIntegerField(choices=TYPE_ACTIVITY)\r
-    active_at = models.DateTimeField(default=datetime.datetime.now)\r
-    is_auditted    = models.BooleanField(default=False)\r
-\r
-    class Meta:\r
-        app_label = 'forum'\r
-        db_table = u'activity'\r
-\r
-    def __unicode__(self):\r
-        return u'[%s] was active at %s' % (self.user.username, self.active_at)\r
-\r
-    def save(self, *args, **kwargs):\r
-        super(Activity, self).save(*args, **kwargs)\r
-        if self._is_new:\r
-            activity_record.send(sender=self.activity_type, instance=self)\r
-\r
-    @property\r
-    def node(self):\r
-        if self.activity_type in (const.TYPE_ACTIVITY_ANSWER, const.TYPE_ACTIVITY_ASK_QUESTION,\r
-                const.TYPE_ACTIVITY_MARK_ANSWER, const.TYPE_ACTIVITY_COMMENT_QUESTION, const.TYPE_ACTIVITY_COMMENT_ANSWER):\r
-            return self.content_object.leaf\r
-\r
-        if self.activity_type in (const.TYPE_ACTIVITY_UPDATE_ANSWER, const.TYPE_ACTIVITY_UPDATE_QUESTION):\r
-            return self.content_object.node.leaf            \r
-            \r
-        raise NotImplementedError()\r
-\r
-    @property\r
-    def type_as_string(self):\r
-        if self.activity_type == const.TYPE_ACTIVITY_ASK_QUESTION:\r
-            return _("asked")\r
-        elif self.activity_type  == const.TYPE_ACTIVITY_ANSWER:\r
-            return _("answered")\r
-        elif self.activity_type  == const.TYPE_ACTIVITY_MARK_ANSWER:\r
-            return _("marked an answer")\r
-        elif self.activity_type  == const.TYPE_ACTIVITY_UPDATE_QUESTION:\r
-            return _("edited a question")\r
-        elif self.activity_type == const.TYPE_ACTIVITY_COMMENT_QUESTION:\r
-            return _("commented a question")\r
-        elif self.activity_type == const.TYPE_ACTIVITY_COMMENT_ANSWER:\r
-            return _("commented an answer")\r
-        elif self.activity_type == const.TYPE_ACTIVITY_UPDATE_ANSWER:\r
-            return _("edited an answer")\r
-        elif self.activity_type == const.TYPE_ACTIVITY_PRIZE:\r
-            return _("received badge")\r
-        else:\r
-            raise NotImplementedError()\r
-\r
-\r
-activity_record = django.dispatch.Signal(providing_args=['instance'])\r
-\r
 class SubscriptionSettings(models.Model):\r
     user = models.OneToOneField(User, related_name='subscription_settings')\r
 \r
 class SubscriptionSettings(models.Model):\r
     user = models.OneToOneField(User, related_name='subscription_settings')\r
 \r
index 984a55ccb20bb1c1372b27e26965bdc6125320fd..5ac89001c07ceaaab616ca92b2e9a2308cd23771 100644 (file)
@@ -1,44 +1,84 @@
 from django.db import models
 from django.core.cache import cache
 from django.conf import settings
 from django.db import models
 from django.core.cache import cache
 from django.conf import settings
-#try:
-#      import cPickle as pickle
-#except ImportError:
-import pickle
-import django.dispatch
+from django.utils.encoding import force_unicode
+
+try:
+    from cPickle import loads, dumps
+except ImportError:
+    from pickle import loads, dumps
+
+from copy import deepcopy
+from base64 import b64encode, b64decode
+from zlib import compress, decompress
+
 
 class PickledObject(str):
 
 class PickledObject(str):
-       pass
+    pass
+
+def dbsafe_encode(value, compress_object=True):
+    if not compress_object:
+        value = b64encode(dumps(deepcopy(value)))
+    else:
+        value = b64encode(compress(dumps(deepcopy(value))))
+    return PickledObject(value)
+
+def dbsafe_decode(value, compress_object=True):
+    if not compress_object:
+        value = loads(b64decode(value))
+    else:
+        value = loads(decompress(b64decode(value)))
+    return value
 
 class PickledObjectField(models.Field):
     __metaclass__ = models.SubfieldBase
 
 
 class PickledObjectField(models.Field):
     __metaclass__ = models.SubfieldBase
 
+    def __init__(self, *args, **kwargs):
+        self.compress = kwargs.pop('compress', True)
+        self.protocol = kwargs.pop('protocol', 2)
+        kwargs.setdefault('null', True)
+        kwargs.setdefault('editable', False)
+        super(PickledObjectField, self).__init__(*args, **kwargs)
+
+    def get_default(self):
+        if self.has_default():
+            if callable(self.default):
+                return self.default()
+            return self.default
+
+        return super(PickledObjectField, self).get_default()
+
     def to_python(self, value):
     def to_python(self, value):
-        if isinstance (value, PickledObject):
-            return value
-
-        try:
-            return pickle.loads(value.encode('utf-8'))
-        except:
-            return value
-    
-    def get_db_prep_save(self, value):
         if value is not None:
         if value is not None:
-            if isinstance(value, PickledObject):
-                return str(value)
-            else:
-                           value = pickle.dumps(value)
+            try:
+                value = dbsafe_decode(value, self.compress)
+            except:
+                if isinstance(value, PickledObject):
+                    raise
+        return value
 
 
+    def get_db_prep_value(self, value):
+        if value is not None and not isinstance(value, PickledObject):
+            value = force_unicode(dbsafe_encode(value, self.compress))
         return value
 
         return value
 
+    def value_to_string(self, obj):
+        value = self._get_val_from_obj(obj)
+        return self.get_db_prep_value(value)
+
     def get_internal_type(self):
         return 'TextField'
 
     def get_internal_type(self):
         return 'TextField'
 
+    def get_db_prep_lookup(self, lookup_type, value):
+        if lookup_type not in ['exact', 'in', 'isnull']:
+            raise TypeError('Lookup type %s is not supported.' % lookup_type)
+        return super(PickledObjectField, self).get_db_prep_lookup(lookup_type, value)
+
 
 class KeyValueManager(models.Manager):
 
     def create_cache_key(self, key):
 
 class KeyValueManager(models.Manager):
 
     def create_cache_key(self, key):
-        return "%s:key_value:%s" % (settings.APP_URL, key)
+        return "%s:keyvalue:%s" % (settings.APP_URL, key)
 
     def save_to_cache(self, instance):
         cache.set(self.create_cache_key(instance.key), instance, 2592000)
 
     def save_to_cache(self, instance):
         cache.set(self.create_cache_key(instance.key), instance, 2592000)
similarity index 97%
rename from forum/modules.py
rename to forum/modules/__init__.py
index 10bbf656b119cbc867c5c5744a49457b7288912d..023b46406220894d72e9849e0e195fc2a5428610 100644 (file)
@@ -7,7 +7,7 @@ from django.conf import settings
 
 MODULES_PACKAGE = 'forum_modules'
 
 
 MODULES_PACKAGE = 'forum_modules'
 
-MODULES_FOLDER = os.path.join(os.path.dirname(__file__), '../' + MODULES_PACKAGE)
+MODULES_FOLDER = os.path.join(os.path.dirname(__file__), '../../' + MODULES_PACKAGE)
 
 DISABLED_MODULES = getattr(settings, 'DISABLED_MODULES', [])
 
 
 DISABLED_MODULES = getattr(settings, 'DISABLED_MODULES', [])
 
diff --git a/forum/modules/decorators.py b/forum/modules/decorators.py
new file mode 100644 (file)
index 0000000..17d248b
--- /dev/null
@@ -0,0 +1,53 @@
+import inspect\r
+\r
+class DecoratableObject(object):\r
+    def __init__(self, fn):\r
+        self._callable = fn\r
+\r
+    def decorate(self, fn, needs_origin):\r
+        origin = self._callable\r
+\r
+        if needs_origin:\r
+            self._callable = lambda *args, **kwargs: fn(origin, *args, **kwargs)\r
+        else:\r
+            self._callable = lambda *args, **kwargs: fn(*args, **kwargs)\r
+\r
+    def __call__(self, *args, **kwargs):\r
+        return self._callable(*args, **kwargs)\r
+\r
+\r
+def decoratable(fn):\r
+    return DecoratableObject(fn)\r
+\r
+def decoratable_method(fn):\r
+    obj = DecoratableObject(fn)\r
+    def decorated(self, *args, **kwargs):\r
+        return obj(self, *args, **kwargs)\r
+\r
+    decorated.__obj = obj\r
+    return decorated\r
+\r
+decoratable.method = decoratable_method\r
+\r
+def decorate(origin, needs_origin=True):\r
+    if not isinstance(origin, DecoratableObject):\r
+        if hasattr(origin, '__obj'):\r
+            def decorator(fn):\r
+                origin.__obj.decorate(fn, needs_origin)\r
+                return origin\r
+            return decorator\r
+\r
+        raise Exception('Not an decoratable function: %s' % origin.name)\r
+\r
+    def decorator(fn):\r
+        origin.decorate(fn, needs_origin)\r
+        return origin\r
+\r
+    return decorator\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
diff --git a/forum/reputation.py b/forum/reputation.py
deleted file mode 100644 (file)
index 71f9d65..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-from django.db.models.signals import post_save\r
-from forum.models.base import mark_canceled\r
-from forum.models.answer import answer_accepted, answer_accepted_canceled\r
-\r
-from forum.models import *\r
-from forum.const import *\r
-import settings\r
-\r
-def on_flagged_item(instance, created, **kwargs):\r
-    if not created:\r
-        return\r
-\r
-    post = instance.content_object.leaf\r
-    question = (post.__class__ == Question) and post or post.question\r
-\r
-    post.author.reputes.create(value=-int(settings.REP_LOST_BY_FLAGGED), question=question,\r
-               reputation_type=TYPE_REPUTATION_LOST_BY_FLAGGED)\r
-\r
-\r
-    if post.offensive_flag_count == settings.FLAG_COUNT_TO_HIDE_POST:\r
-        post.author.reputes.create(value=-int(settings.REP_LOST_BY_FLAGGED_3_TIMES),\r
-                   question=question, reputation_type=TYPE_REPUTATION_LOST_BY_FLAGGED_3_TIMES)\r
-\r
-    if post.offensive_flag_count == settings.FLAG_COUNT_TO_DELETE_POST:\r
-        post.author.reputes.create(value=-int(settings.REP_LOST_BY_FLAGGED_5_TIMES),\r
-                   question=question, reputation_type=TYPE_REPUTATION_LOST_BY_FLAGGED_5_TIMES)\r
-\r
-        post.mark_deleted(User.objects.get_site_owner())\r
-\r
-post_save.connect(on_flagged_item, sender=FlaggedItem)\r
-\r
-def on_answer_accepted(answer, user, **kwargs):\r
-    if user == answer.question.author and not user == answer.author:\r
-        user.reputes.create(\r
-            value=int(settings.REP_GAIN_BY_ACCEPTING), question=answer.question,\r
-            reputation_type=TYPE_REPUTATION_GAIN_BY_ACCEPTING_ANSWER)\r
-\r
-    if not user == answer.author:\r
-        answer.author.reputes.create(\r
-            value=int(settings.REP_GAIN_BY_ACCEPTED), question=answer.question,\r
-            reputation_type=TYPE_REPUTATION_GAIN_BY_ANSWER_ACCEPTED)\r
-\r
-answer_accepted.connect(on_answer_accepted)\r
-\r
-\r
-def on_answer_accepted_canceled(answer, user, **kwargs):\r
-    if user == answer.accepted_by:\r
-        user.reputes.create(\r
-            value=-int(settings.REP_LOST_BY_CANCELING_ACCEPTED), question=answer.question,\r
-            reputation_type=TYPE_REPUTATION_LOST_BY_CANCELLING_ACCEPTED_ANSWER)\r
-\r
-    if not user == answer.author:\r
-        answer.author.reputes.create(\r
-            value=-int(settings.REP_LOST_BY_ACCEPTED_CANCELED), question=answer.question,\r
-            reputation_type=TYPE_REPUTATION_LOST_BY_ACCEPTED_ANSWER_CANCELED)\r
-\r
-answer_accepted_canceled.connect(on_answer_accepted)\r
-\r
-\r
-def on_vote(instance, created, **kwargs):\r
-    if created and (instance.content_object.node_type in ("question", "answer") and not instance.content_object.wiki):\r
-        post = instance.content_object.leaf\r
-        question = (post.__class__ == Question) and post or post.question\r
-\r
-        if instance.vote == -1:\r
-            instance.user.reputes.create(value=-int(settings.REP_LOST_BY_DOWNVOTING),\r
-            question=question, reputation_type=TYPE_REPUTATION_LOST_BY_DOWNVOTING)\r
-\r
-        if instance.vote == 1 and post.author.get_reputation_by_upvoted_today() >= int(settings.MAX_REP_BY_UPVOTE_DAY):\r
-            return\r
-\r
-        repute_type, repute_value = (instance.vote == 1) and (\r
-            TYPE_REPUTATION_GAIN_BY_UPVOTED, int(settings.REP_GAIN_BY_UPVOTED)) or (\r
-            TYPE_REPUTATION_LOST_BY_DOWNVOTED, -int(settings.REP_LOST_BY_DOWNVOTED))\r
-\r
-        post.author.reputes.create(value=repute_value, question=question, reputation_type=repute_type)\r
-\r
-post_save.connect(on_vote, sender=Vote)\r
-\r
-\r
-def on_vote_canceled(instance, **kwargs):\r
-    if instance.content_object.node_type in ("question", "answer") and not instance.content_object.wiki:\r
-        post = instance.content_object.leaf\r
-        question = (post.__class__ == Question) and post or post.question\r
-\r
-        if instance.vote == -1:\r
-            instance.user.reputes.create(value=int(settings.REP_GAIN_BY_CANCELING_DOWNVOTE),\r
-            question=question, reputation_type=TYPE_REPUTATION_GAIN_BY_CANCELING_DOWNVOTE)\r
-\r
-        repute_type, repute_value = (instance.vote == 1) and (\r
-            TYPE_REPUTATION_LOST_BY_UPVOTE_CANCELED, -int(settings.REP_LOST_BY_UPVOTE_CANCELED)) or (\r
-            TYPE_REPUTATION_GAIN_BY_DOWNVOTE_CANCELED, int(settings.REP_GAIN_BY_DOWNVOTE_CANCELED))\r
-\r
-        post.author.reputes.create(value=repute_value, question=question, reputation_type=repute_type)\r
-\r
-mark_canceled.connect(on_vote_canceled, sender=Vote)\r
-\r
-\r
-    \r
-\r
-\r
index 58ed460ce1f948356f3dcdce41ea3c7d3947a13d..a8530ee633aa5d99b6deb245173519f7bc6fa259 100644 (file)
@@ -18,6 +18,7 @@ from upload import *
 from about import *
 from faq import *
 from form import *
 from about import *
 from faq import *
 from form import *
+from moderation import *
 
 BADGES_SET = SettingSet('badges', _('Badges config'), _("Configure badges on your OSQA site."), 500)
 
 
 BADGES_SET = SettingSet('badges', _('Badges config'), _("Configure badges on your OSQA site."), 500)
 
index dc0157718e7a0c7d9b58ec19847f3839b2fac88d..5bcf5fc51a6d929c6c82209c15ca2a3b1d22e9a9 100644 (file)
@@ -8,12 +8,32 @@ class SettingSet(list):
         self.description = description
         self.weight = weight
         self.markdown = markdown
         self.description = description
         self.weight = weight
         self.markdown = markdown
+        
 
 class BaseSetting(object):
 
 class BaseSetting(object):
-    def __init__(self, name, default, field_context):
+    @classmethod
+    def add_to_class(cls, name):
+        def wrapper(self, *args, **kwargs):
+            return self.value.__getattribute__(name)(*args, **kwargs)
+
+        setattr(cls, name, wrapper)
+
+    def __init__(self, name, default, set=None, field_context=None):
         self.name = name
         self.default = default
         self.name = name
         self.default = default
-        self.field_context = field_context
+        self.field_context = field_context or {}
+
+        if set is not None:
+            if not set.name in Setting.sets:
+                Setting.sets[set.name] = set
+
+            Setting.sets[set.name].append(self)
+
+    def __str__(self):
+        return str(self.value)
+
+    def __unicode__(self):
+        return unicode(self.value)
 
     @property
     def value(self):
 
     @property
     def value(self):
@@ -29,112 +49,44 @@ class BaseSetting(object):
 
     def set_value(self, new_value):
         new_value = self._parse(new_value)
 
     def set_value(self, new_value):
         new_value = self._parse(new_value)
+        self.save(new_value)
+
+    def save(self, value):
         from forum.models import KeyValue
 
         try:
             kv = KeyValue.objects.get(key=self.name)
         from forum.models import KeyValue
 
         try:
             kv = KeyValue.objects.get(key=self.name)
-            old_value = kv.value
         except:
             kv = KeyValue(key=self.name)
         except:
             kv = KeyValue(key=self.name)
-            old_value = self.default
 
 
-        kv.value = new_value
+        kv.value = value
         kv.save()
 
         kv.save()
 
-        setting_update.send(sender=self, old_value=old_value, new_value=new_value)
-
     def to_default(self):
         self.set_value(self.default)
 
     def _parse(self, value):
         return value
 
     def to_default(self):
         self.set_value(self.default)
 
     def _parse(self, value):
         return value
 
-    def __str__(self):
-        return str(self.value)
-
-    def __unicode__(self):
-        return unicode(self.value)
-
-    def __nonzero__(self):
-        return bool(self.value)
-
-
-class StringSetting(BaseSetting):
-    def _parse(self, value):
-        if isinstance(value, unicode):
-            return value.encode('utf8')
-        else:
-            return str(value)
-
-    def __unicode__(self):
-        return unicode(self.value.decode('utf8'))
-
-    def __add__(self, other):
-        return "%s%s" % (unicode(self), other)
-
-    def __cmp__(self, other):
-        return cmp(str(self), str(other))
-
-class IntegerSetting(BaseSetting):
-    def _parse(self, value):
-        return int(value)
-
-    def __int__(self):
-        return int(self.value)
-
-    def __add__(self, other):
-        return int(self) + int(other)
-
-    def __sub__(self, other):
-        return int(self) - int(other)
-
-    def __cmp__(self, other):
-        return int(self) - int(other)
-
-class FloatSetting(BaseSetting):
-    def _parse(self, value):
-        return float(value)
-
-    def __int__(self):
-        return int(self.value)
-
-    def __float__(self):
-        return float(self.value)
-
-    def __add__(self, other):
-        return float(self) + float(other)
-
-    def __sub__(self, other):
-        return float(self) - float(other)
-
-    def __cmp__(self, other):
-        return float(self) - float(other)
-
-class BoolSetting(BaseSetting):
-    def _parse(self, value):
-        return bool(value)
 
 class Setting(object):
 
 class Setting(object):
+    emulators = {}
     sets = {}
 
     sets = {}
 
-    def __new__(cls, name, default, set=None, field_context={}):
-        if isinstance(default, bool):
-            instance = BoolSetting(name, default, field_context)
-        elif isinstance(default, str):
-            instance = StringSetting(name, default, field_context)
-        elif isinstance(default, float):
-            instance = FloatSetting(name, default, field_context)
-        elif isinstance(default, int):
-            instance = IntegerSetting(name, default, field_context)
+    def __new__(cls, name, default, set=None, field_context=None):
+        deftype = type(default)
+
+        if deftype in Setting.emulators:
+            emul = Setting.emulators[deftype]
         else:
         else:
-            instance = BaseSetting(name, default, field_context)
+            emul = type(deftype.__name__ + cls.__name__, (BaseSetting,), {})
+            fns = [n for n, f in [(p, getattr(deftype, p)) for p in dir(deftype) if not p in dir(cls)] if callable(f)]
 
 
-        if set is not None:
-            if not set.name in cls.sets:
-                cls.sets[set.name] = set
+            for n in fns:
+               emul.add_to_class(n)
+
+            Setting.emulators[deftype] = emul
 
 
-            cls.sets[set.name].append(instance)
+        return emul(name, default, set, field_context)
 
 
-        return instance
 
 
-setting_update = django.dispatch.Signal(providing_args=['old_value', 'new_value'])
index 80c8a626f9f30792e52599ac2d882217a7678131..23427ea0fed8253fdc1703b5e8f627efbc579ab2 100644 (file)
@@ -6,7 +6,7 @@ from forms import ImageFormWidget
 from django.utils.translation import ugettext_lazy as _\r
 from django.forms.widgets import Textarea\r
 \r
 from django.utils.translation import ugettext_lazy as _\r
 from django.forms.widgets import Textarea\r
 \r
-BASIC_SET = SettingSet('basic', _('Basic Settings'), _("The basic settings for your application"), 1)\r
+BASIC_SET = SettingSet('basic', _('Basic settings'), _("The basic settings for your application"), 1)\r
 \r
 APP_LOGO = Setting('APP_LOGO', '/m/default/media/images/logo.png', BASIC_SET, dict(\r
 label = _("Application logo"),\r
 \r
 APP_LOGO = Setting('APP_LOGO', '/m/default/media/images/logo.png', BASIC_SET, dict(\r
 label = _("Application logo"),\r
index d95a164c625fe15dd18326289635cf5626140799..fb49f99741b0297c1f1157d1f94d6d73b63253c3 100644 (file)
@@ -2,7 +2,7 @@ from base import Setting, SettingSet
 from django.utils.translation import ugettext_lazy as _\r
 from django.forms.widgets import PasswordInput\r
 \r
 from django.utils.translation import ugettext_lazy as _\r
 from django.forms.widgets import PasswordInput\r
 \r
-EMAIL_SET = SettingSet('email', _('Email Settings'), _("Email server and other email related settings."), 50)\r
+EMAIL_SET = SettingSet('email', _('Email settings'), _("Email server and other email related settings."), 50)\r
 \r
 EMAIL_HOST = Setting('EMAIL_HOST', '', EMAIL_SET, dict(\r
 label = _("Email Server"),\r
 \r
 EMAIL_HOST = Setting('EMAIL_HOST', '', EMAIL_SET, dict(\r
 label = _("Email Server"),\r
index a2e8222132e136193b82eddf2d183f937afa678a..abe860bb9b369d1044c54272a17364555e3f3232 100644 (file)
@@ -8,11 +8,10 @@ label = _("Google sitemap code"),
 help_text = _("This is the code you get when you register your site at <a href='https://www.google.com/webmasters/tools/'>Google webmaster central</a>."),\r
 required=False))\r
 \r
 help_text = _("This is the code you get when you register your site at <a href='https://www.google.com/webmasters/tools/'>Google webmaster central</a>."),\r
 required=False))\r
 \r
-GOOGLE_ANALYTICS_KEY = Setting('GOOGLE_ANALYTICS_KEY', '', EXT_KEYS_SET, dict(                     \r
+GOOGLE_ANALYTICS_KEY = Setting('GOOGLE_ANALYTICS_KEY', '', EXT_KEYS_SET, dict(\r
 label = _("Google analytics key"),\r
 help_text = _("Your Google analytics key. You can get one at the <a href='http://www.google.com/analytics/'>Google analytics official website</a>"),\r
 required=False))\r
 label = _("Google analytics key"),\r
 help_text = _("Your Google analytics key. You can get one at the <a href='http://www.google.com/analytics/'>Google analytics official website</a>"),\r
 required=False))\r
-\r
 WORDPRESS_API_KEY = Setting('WORDPRESS_API_KEY', '', EXT_KEYS_SET, dict(\r
 label = _("Wordpress API key"),\r
 help_text = _("Your Wordpress API key. You can get one at <a href='http://wordpress.com/'>http://wordpress.com/</a>"),\r
 WORDPRESS_API_KEY = Setting('WORDPRESS_API_KEY', '', EXT_KEYS_SET, dict(\r
 label = _("Wordpress API key"),\r
 help_text = _("Your Wordpress API key. You can get one at <a href='http://wordpress.com/'>http://wordpress.com/</a>"),\r
index 5cb1aee8520e6155408826c011ae93fc0b33a28f..7329d5f9a0f8a69cfa316930d0a27fb778b82ae6 100644 (file)
@@ -37,4 +37,14 @@ help_text = _("The minimum number of characters a user must enter into the body
 
 FORM_MAX_COMMENT_BODY = Setting('FORM_MAX_COMMENT_BODY', 600, FORUM_SET, dict(
 label = _("Maximum length of comment"),
 
 FORM_MAX_COMMENT_BODY = Setting('FORM_MAX_COMMENT_BODY', 600, FORUM_SET, dict(
 label = _("Maximum length of comment"),
-help_text = _("The maximum number of characters a user can enter into the body of a comment.")))
\ No newline at end of file
+help_text = _("The maximum number of characters a user can enter into the body of a comment.")))
+
+FORM_ALLOW_MARKDOWN_IN_COMMENTS = Setting('FORM_ALLOW_MARKDOWN_IN_COMMENTS', True, FORUM_SET, dict(
+label = _("Allow markdown in comments"),
+help_text = _("Allow users to use markdown in comments."),
+required=False))
+
+FORM_GRAVATAR_IN_COMMENTS = Setting('FORM_GRAVATAR_IN_COMMENTS', False, FORUM_SET, dict(
+label = _("Show author gravatar in comments"),
+help_text = _("Show the gravatar image of a comment author."),
+required=False))
\ No newline at end of file
index 337dea134961083319b5e38f95b243b0fdf61df9..aa5a35285f6c03294bba0999ee52bd4d49d1cd61 100644 (file)
@@ -1,9 +1,17 @@
 import os
 from django import forms
 import os
 from django import forms
-from base import Setting, StringSetting, IntegerSetting, BoolSetting, FloatSetting
+from base import Setting
 from django.utils.translation import ugettext as _
 from django.core.files.storage import FileSystemStorage
 
 from django.utils.translation import ugettext as _
 from django.core.files.storage import FileSystemStorage
 
+class DummySetting:
+    pass
+
+class UnfilteredField(forms.CharField):
+    def clean(self, value):
+            return value
+
+
 class SettingsSetForm(forms.Form):
     def __init__(self, set, data=None, *args, **kwargs):
         if data is None:
 class SettingsSetForm(forms.Form):
     def __init__(self, set, data=None, *args, **kwargs):
         if data is None:
@@ -12,16 +20,16 @@ class SettingsSetForm(forms.Form):
         super(SettingsSetForm, self).__init__(data, *args, **kwargs)
 
         for setting in set:
         super(SettingsSetForm, self).__init__(data, *args, **kwargs)
 
         for setting in set:
-            if isinstance(setting, StringSetting):
+            if isinstance(setting, Setting.emulators.get(str, DummySetting)):
                 field = forms.CharField(**setting.field_context)
                 field = forms.CharField(**setting.field_context)
-            elif isinstance(setting, FloatSetting):
+            elif isinstance(setting, Setting.emulators.get(float, DummySetting)):
                 field = forms.FloatField(**setting.field_context)
                 field = forms.FloatField(**setting.field_context)
-            elif isinstance(setting, IntegerSetting):
+            elif isinstance(setting, Setting.emulators.get(int, DummySetting)):
                 field = forms.IntegerField(**setting.field_context)
                 field = forms.IntegerField(**setting.field_context)
-            elif isinstance(setting, BoolSetting):
+            elif isinstance(setting, Setting.emulators.get(bool, DummySetting)):
                 field = forms.BooleanField(**setting.field_context)
             else:
                 field = forms.BooleanField(**setting.field_context)
             else:
-                field = forms.CharField(**setting.field_context)
+                field = UnfilteredField(**setting.field_context)
 
             self.fields[setting.name] = field
 
 
             self.fields[setting.name] = field
 
@@ -60,4 +68,28 @@ class ImageFormWidget(forms.Widget):
             elif name in data:
                 return data[name]
 
             elif name in data:
                 return data[name]
 
+class StringListWidget(forms.Widget):
+    def render(self, name, value, attrs=None):
+        ret = ""
+        for s in value:
+            ret += """
+            <div>
+                <input type="text" name="%(name)s" value="%(value)s" />
+                <button class="string_list_widget_button">-</button>
+            </div>
+            """  % {'name': name, 'value': s}
+
+        return """
+            <div class="string_list_widgets">
+                %(ret)s
+                <div><button name="%(name)s" class="string_list_widget_button add">+</button></div>
+            </div>
+            """ % dict(name=name, ret=ret)
+
+    def value_from_datadict(self, data, files, name):
+        if 'submit' in data:
+            return data.getlist(name)
+        else:
+            return data[name]
+
 
 
diff --git a/forum/settings/moderation.py b/forum/settings/moderation.py
new file mode 100644 (file)
index 0000000..6af5401
--- /dev/null
@@ -0,0 +1,24 @@
+from base import Setting, SettingSet
+from forms import StringListWidget
+
+from django.utils.translation import ugettext_lazy as _
+from django.forms.widgets import Textarea
+
+MODERATION_SET = SettingSet('moderation', _('Moderation settings'), _("Define the moderation workflow of your site"), 100)
+
+FLAG_TYPES = Setting('FLAG_TYPES',
+["Spam", "Advertising", "Offensive, Abusive, or Inappropriate", "Content violates terms of use", "Copyright Violation",
+ "Misleading", "Someone is not being nice", "Not relevant/off-topic", "Other"],
+MODERATION_SET, dict(
+label = _("Flag Reasons"),
+help_text = _("Create some flag reasons to use in the flag post popup."),
+widget=StringListWidget))
+
+
+CLOSE_TYPES = Setting('CLOSE_TYPES',
+["Duplicate Question", "Question is off-topic or not relevant", "Too subjective and argumentative",
+ "The question is answered, right answer was accepted", "Problem is not reproducible or outdated", "Other"],
+MODERATION_SET, dict(
+label = _("Close Reasons"),
+help_text = _("Create some close reasons to use in the close question popup."),
+widget=StringListWidget))
index decc4f12362124c74e1e0b78ad4e5d132621ab23..dd629cb68a515201dc6a1930f156fb8a644a0fb2 100644 (file)
Binary files a/forum/skins/default/media/images/openid/aol.gif and b/forum/skins/default/media/images/openid/aol.gif differ
diff --git a/forum/skins/default/media/images/openid/blogger.png b/forum/skins/default/media/images/openid/blogger.png
new file mode 100644 (file)
index 0000000..3c46818
Binary files /dev/null and b/forum/skins/default/media/images/openid/blogger.png differ
diff --git a/forum/skins/default/media/images/openid/claimid.png b/forum/skins/default/media/images/openid/claimid.png
new file mode 100644 (file)
index 0000000..441c0ed
Binary files /dev/null and b/forum/skins/default/media/images/openid/claimid.png differ
index b997b358f78c96357c36f288395789ddc4c57376..9a32529ba77668ac4a96d8175d05c27fd8caa7a4 100644 (file)
Binary files a/forum/skins/default/media/images/openid/facebook.gif and b/forum/skins/default/media/images/openid/facebook.gif differ
diff --git a/forum/skins/default/media/images/openid/flickr.png b/forum/skins/default/media/images/openid/flickr.png
new file mode 100644 (file)
index 0000000..6c443e1
Binary files /dev/null and b/forum/skins/default/media/images/openid/flickr.png differ
index 1b6cd07bd8b9f27175e0a03be3b5849e0f5479c7..be451e56d4b1236c6e231d994ec64fa6625193e4 100644 (file)
Binary files a/forum/skins/default/media/images/openid/google.gif and b/forum/skins/default/media/images/openid/google.gif differ
diff --git a/forum/skins/default/media/images/openid/livejournal.png b/forum/skins/default/media/images/openid/livejournal.png
new file mode 100644 (file)
index 0000000..9f2f6dd
Binary files /dev/null and b/forum/skins/default/media/images/openid/livejournal.png differ
diff --git a/forum/skins/default/media/images/openid/myopenid.png b/forum/skins/default/media/images/openid/myopenid.png
new file mode 100644 (file)
index 0000000..e5df78d
Binary files /dev/null and b/forum/skins/default/media/images/openid/myopenid.png differ
diff --git a/forum/skins/default/media/images/openid/technorati.png b/forum/skins/default/media/images/openid/technorati.png
new file mode 100644 (file)
index 0000000..7216641
Binary files /dev/null and b/forum/skins/default/media/images/openid/technorati.png differ
index 9a6552d18444ac98fdc776e6c58b794dd4452772..6178f9ccffe9eccc49f4627323c77fe20ff33fed 100644 (file)
Binary files a/forum/skins/default/media/images/openid/twitter.png and b/forum/skins/default/media/images/openid/twitter.png differ
diff --git a/forum/skins/default/media/images/openid/verisign.png b/forum/skins/default/media/images/openid/verisign.png
new file mode 100644 (file)
index 0000000..bc5c5f3
Binary files /dev/null and b/forum/skins/default/media/images/openid/verisign.png differ
diff --git a/forum/skins/default/media/images/openid/wordpress.png b/forum/skins/default/media/images/openid/wordpress.png
new file mode 100644 (file)
index 0000000..f261705
Binary files /dev/null and b/forum/skins/default/media/images/openid/wordpress.png differ
index 0f0eb8efe7eb64bfb49b71bcfed741c1193fae55..1ebaa7f46b541d1c57953b8042fa8759a0567d48 100644 (file)
Binary files a/forum/skins/default/media/images/openid/yahoo.gif and b/forum/skins/default/media/images/openid/yahoo.gif differ
index af7d8cb9767b3574d975ebb5cfd526a7dc39690d..a1316c191d10d346b546536cb09e595e58324cba 100644 (file)
@@ -57,11 +57,6 @@ var providers_small = {
         label: 'Your Verisign username',
         url: 'http://{username}.pip.verisignlabs.com/'
     },
         label: 'Your Verisign username',
         url: 'http://{username}.pip.verisignlabs.com/'
     },
-    vidoop: {
-        name: 'Vidoop',
-        label: 'Your Vidoop username',
-        url: 'http://{username}.myvidoop.com/'
-    },
     verisign: {
         name: 'Verisign',
         label: 'Your Verisign username',
     verisign: {
         name: 'Verisign',
         label: 'Your Verisign username',
@@ -101,7 +96,7 @@ var openid = {
         if (providers_small) {
                openid_btns.append('<br/>');
                for (id in providers_small) {       
         if (providers_small) {
                openid_btns.append('<br/>');
                for (id in providers_small) {       
-                       openid_btns.append(this.getBoxHTML(providers_small[id], 'small', '.ico'));
+                       openid_btns.append(this.getBoxHTML(providers_small[id], 'small', '.png'));
                }
         }
 
                }
         }
 
diff --git a/forum/skins/default/media/js/osqa.admin.js b/forum/skins/default/media/js/osqa.admin.js
new file mode 100644 (file)
index 0000000..ce0fc79
--- /dev/null
@@ -0,0 +1,21 @@
+$(function() {\r
+    $('.string_list_widget_button').live('click', function() {\r
+        $but = $(this);\r
+\r
+        if ($but.is('.add')) {\r
+            $new = $("<div style=\"display: none\">" +\r
+                    "<input type=\"text\" name=\"" + $but.attr('name') + "\" value=\"\" />" +\r
+                    "<button class=\"string_list_widget_button\">-</button>" +\r
+                    "</div>");\r
+\r
+            $but.before($new);\r
+            $new.slideDown('fast');\r
+        } else {\r
+            $but.parent().slideUp('fast', function() {\r
+                $but.parent().remove();\r
+            });\r
+        }\r
+\r
+        return false;\r
+    })\r
+});
\ No newline at end of file
index 6264fb42ee6e4a4aca8b63de7bd2a0fa3056ea54..06b2cc91ad24d086e678790fa8450b4a9d4d2eee 100644 (file)
@@ -1,4 +1,8 @@
 var response_commands = {\r
 var response_commands = {\r
+    refresh_page: function() {\r
+        window.location.reload(true)\r
+    },\r
+    \r
     update_post_score: function(id, inc) {\r
         var $score_board = $('#post-' + id + '-score');\r
         var current = parseInt($score_board.html())\r
     update_post_score: function(id, inc) {\r
         var $score_board = $('#post-' + id + '-score');\r
         var current = parseInt($score_board.html())\r
@@ -68,7 +72,7 @@ var response_commands = {
         });\r
     },\r
 \r
         });\r
     },\r
 \r
-    insert_comment: function(post_id, comment_id, comment, username, profile_url, delete_url) {\r
+    insert_comment: function(post_id, comment_id, comment, username, profile_url, delete_url, edit_url) {\r
         var $container = $('#comments-container-' + post_id);\r
         var skeleton = $('#new-comment-skeleton-' + post_id).html().toString();\r
 \r
         var $container = $('#comments-container-' + post_id);\r
         var skeleton = $('#new-comment-skeleton-' + post_id).html().toString();\r
 \r
@@ -76,7 +80,8 @@ var response_commands = {
                 .replace(new RegExp('%COMMENT%', 'g'), comment)\r
                 .replace(new RegExp('%USERNAME%', 'g'), username)\r
                 .replace(new RegExp('%PROFILE_URL%', 'g'), profile_url)\r
                 .replace(new RegExp('%COMMENT%', 'g'), comment)\r
                 .replace(new RegExp('%USERNAME%', 'g'), username)\r
                 .replace(new RegExp('%PROFILE_URL%', 'g'), profile_url)\r
-                .replace(new RegExp('%DELETE_URL%', 'g'), delete_url);\r
+                .replace(new RegExp('%DELETE_URL%', 'g'), delete_url)\r
+                .replace(new RegExp('%EDIT_URL%', 'g'), edit_url);\r
 \r
         $container.append(skeleton);\r
 \r
 \r
         $container.append(skeleton);\r
 \r
@@ -100,6 +105,16 @@ var response_commands = {
         }\r
     },\r
 \r
         }\r
     },\r
 \r
+    unmark_deleted: function(post_type, post_id) {\r
+        if (post_type == 'answer') {\r
+            var $answer = $('#answer-container-' + post_id);\r
+            $answer.removeClass('deleted');\r
+        } else {\r
+            var $container = $('#question-table');\r
+            $container.removeClass('deleted');\r
+        }\r
+    },\r
+\r
     set_subscription_button: function(text) {\r
         $('.subscription_switch').html(text);\r
     },\r
     set_subscription_button: function(text) {\r
         $('.subscription_switch').html(text);\r
     },\r
@@ -109,38 +124,127 @@ var response_commands = {
     }\r
 }\r
 \r
     }\r
 }\r
 \r
-function show_message(object, msg) {\r
+function show_message(object, msg, callback) {\r
     var div = $('<div class="vote-notification"><h3>' + msg + '</h3>(' +\r
     'click to close' + ')</div>');\r
 \r
     div.click(function(event) {\r
     var div = $('<div class="vote-notification"><h3>' + msg + '</h3>(' +\r
     'click to close' + ')</div>');\r
 \r
     div.click(function(event) {\r
-        $(".vote-notification").fadeOut("fast", function() { $(this).remove(); });\r
+        $(".vote-notification").fadeOut("fast", function() {\r
+            $(this).remove();\r
+            if (callback) {\r
+                callback();\r
+            }\r
+        });\r
     });\r
 \r
     object.parent().append(div);\r
     div.fadeIn("fast");\r
 }\r
 \r
     });\r
 \r
     object.parent().append(div);\r
     div.fadeIn("fast");\r
 }\r
 \r
-function process_ajax_response(data, el) {\r
+function load_prompt(object, url) {\r
+    var $box = $('<div class="vote-notification">' +\r
+            '<img src="/m/default/media/images/indicator.gif" />' +\r
+            '</div>');\r
+\r
+\r
+    object.parent().append($box);\r
+    $box.fadeIn("fast");\r
+\r
+    $box.load(url, function() {\r
+        $box.find('.prompt-cancel').click(function() {\r
+            $box.fadeOut('fast', function() {\r
+                $box.remove();\r
+            });\r
+            return false;\r
+        });\r
+\r
+        $box.find('.prompt-submit').click(function() {\r
+            start_command();\r
+            $.post(url, {prompt: $box.find('textarea').val()}, function(data) {\r
+                $box.fadeOut('fast', function() {\r
+                    $box.remove();\r
+                });\r
+                process_ajax_response(data, object);\r
+            }, 'json');\r
+            return false;\r
+        });\r
+    });\r
+}\r
+\r
+function show_prompt(object, msg, callback) {\r
+    var div = $('<div class="vote-notification">' + msg + '<br />' +\r
+            '<textarea class="command-prompt"></textarea><br />' +\r
+            '<button class="prompt-cancel">Cancel</button>' +\r
+            '<button class="prompt-ok">OK</button>' +\r
+            '</div>');\r
+\r
+    function fade_out() {\r
+        div.fadeOut("fast", function() { div.remove(); });\r
+    }\r
+\r
+    div.find('.prompt-cancel').click(fade_out);\r
+\r
+    div.find('.prompt-ok').click(function(event) {\r
+        callback(div.find('.command-prompt').val());\r
+        fade_out();\r
+    });\r
+\r
+    object.parent().append(div);\r
+    div.fadeIn("fast");    \r
+}\r
+\r
+function process_ajax_response(data, el, callback) {\r
     if (!data.success && data['error_message'] != undefined) {\r
     if (!data.success && data['error_message'] != undefined) {\r
-        show_message(el, data.error_message)\r
+        show_message(el, data.error_message, function() {if (callback) callback(true);});\r
+        end_command(false);\r
     } else if (typeof data['commands'] != undefined){\r
         for (var command in data.commands) {\r
             response_commands[command].apply(null, data.commands[command])\r
         }\r
 \r
         if (data['message'] != undefined) {\r
     } else if (typeof data['commands'] != undefined){\r
         for (var command in data.commands) {\r
             response_commands[command].apply(null, data.commands[command])\r
         }\r
 \r
         if (data['message'] != undefined) {\r
-            show_message(el, data.message)\r
+            show_message(el, data.message, function() {if (callback) callback(false);})\r
+        } else {\r
+            if (callback) callback(false);\r
         }\r
         }\r
+        end_command(true);\r
+    }\r
+}\r
+\r
+var running = false;\r
+\r
+function start_command() {\r
+    $('body').append($('<div id="command-loader"></div>'));\r
+    running = true;\r
+}\r
+\r
+function end_command(success) {\r
+    if (success) {\r
+        $('#command-loader').addClass('success');\r
+        $('#command-loader').fadeOut("slow", function() {\r
+            $('#command-loader').remove();\r
+            running = false;\r
+        });\r
+    } else {\r
+        $('#command-loader').remove();\r
+        running = false;\r
     }\r
 }\r
 \r
 $(function() {\r
     $('a.ajax-command').live('click', function() {\r
     }\r
 }\r
 \r
 $(function() {\r
     $('a.ajax-command').live('click', function() {\r
+        if (running) return false;\r
+\r
         var el = $(this);\r
         var el = $(this);\r
-        $.getJSON(el.attr('href'), function(data) {\r
-            process_ajax_response(data, el);\r
-        });\r
+\r
+        if (el.is('.withprompt')) {\r
+            load_prompt(el, el.attr('href'));\r
+        } else {\r
+            start_command();\r
+            $.getJSON(el.attr('href'), function(data) {\r
+                process_ajax_response(data, el);\r
+            });\r
+        }\r
 \r
         return false\r
     });\r
 \r
         return false\r
     });\r
@@ -149,50 +253,105 @@ $(function() {
         var $container = $(this);\r
         var $form = $container.find('form');\r
         var $textarea = $container.find('textarea');\r
         var $container = $(this);\r
         var $form = $container.find('form');\r
         var $textarea = $container.find('textarea');\r
-        var $button = $container.find('input[type="submit"]');\r
-        var $chars_left_message = $('.comment-chars-left');\r
+        var textarea = $textarea.get(0);\r
+        var $button = $container.find('.comment-submit');\r
+        var $cancel = $container.find('.comment-cancel');\r
+        var $chars_left_message = $container.find('.comments-chars-left-msg');\r
+        var $chars_togo_message = $container.find('.comments-chars-togo-msg');\r
         var $chars_counter = $container.find('.comments-char-left-count');\r
 \r
         var $comment_tools = $container.parent().find('.comment-tools');\r
         var $add_comment_link = $comment_tools.find('.add-comment-link');\r
         var $comments_container = $container.parent().find('.comments-container');\r
 \r
         var $chars_counter = $container.find('.comments-char-left-count');\r
 \r
         var $comment_tools = $container.parent().find('.comment-tools');\r
         var $add_comment_link = $comment_tools.find('.add-comment-link');\r
         var $comments_container = $container.parent().find('.comments-container');\r
 \r
-        var max_length = parseInt($chars_counter.html());\r
+        var chars_limits = $chars_counter.html().split('|');\r
+\r
+        var min_length = parseInt(chars_limits[0]);\r
+        var max_length = parseInt(chars_limits[1]);\r
+        \r
+        var warn_length = max_length - 30;\r
+        var current_length = 0;\r
         var comment_in_form = false;\r
         var comment_in_form = false;\r
+        var interval = null;\r
+\r
+        var hcheck = !($.browser.msie || $.browser.opera);\r
+\r
+        $textarea.css("padding-top", 0).css("padding-bottom", 0).css("resize", "none");\r
+        textarea.style.overflow = 'hidden';\r
+        \r
 \r
         function cleanup_form() {\r
             $textarea.val('');\r
 \r
         function cleanup_form() {\r
             $textarea.val('');\r
+            $textarea.css('height', 80);\r
             $chars_counter.html(max_length);\r
             $chars_left_message.removeClass('warn');\r
             comment_in_form = false;\r
             $chars_counter.html(max_length);\r
             $chars_left_message.removeClass('warn');\r
             comment_in_form = false;\r
+            current_length = 0;\r
+\r
+            $chars_left_message.hide();\r
+            $chars_togo_message.show();\r
+            \r
+            $chars_counter.removeClass('warn');\r
+            $chars_counter.html(min_length);\r
+            $button.attr("disabled","disabled");\r
+            \r
+            interval = null;\r
         }\r
 \r
         cleanup_form();\r
 \r
         }\r
 \r
         cleanup_form();\r
 \r
-        function calculate_chars_left() {\r
+        function process_form_changes() {\r
             var length = $textarea.val().length;\r
             var length = $textarea.val().length;\r
-            var allow = true;\r
 \r
 \r
-            if (length < max_length) {\r
-                if (length < max_length * 0.75) {\r
-                    $chars_left_message.removeClass('warn');\r
-                } else {\r
-                    $chars_left_message.addClass('warn');\r
-                }\r
+            if (current_length == length)\r
+                return;\r
+\r
+            if (length < warn_length && current_length >= warn_length) {\r
+                $chars_counter.removeClass('warn');\r
+            } else if (current_length < warn_length && length >= warn_length){\r
+                $chars_counter.addClass('warn');\r
+            }\r
+\r
+            if (length < min_length) {\r
+                $chars_left_message.hide();\r
+                $chars_togo_message.show();\r
+                $chars_counter.html(min_length - length);\r
+            } else {\r
+                $chars_togo_message.hide();\r
+                $chars_left_message.show();\r
+                $chars_counter.html(max_length - length);\r
+            }\r
+\r
+            if (length > max_length || length < min_length) {\r
+                $button.attr("disabled","disabled");\r
             } else {\r
             } else {\r
-                allow = false;\r
+                $button.removeAttr("disabled");\r
             }\r
 \r
             }\r
 \r
-            $chars_counter.html(max_length - length);\r
-            return allow;\r
+            var current_height = textarea.style.height;\r
+            if (hcheck)\r
+                textarea.style.height = "0px";\r
+\r
+            var h = Math.max(80, textarea.scrollHeight);\r
+            textarea.style.height = current_height;\r
+            $textarea.animate({height: h + 'px'}, 50);\r
+\r
+            current_length = length;\r
         }\r
 \r
         function show_comment_form() {\r
             $container.slideDown('slow');\r
             $add_comment_link.fadeOut('slow');\r
         }\r
 \r
         function show_comment_form() {\r
             $container.slideDown('slow');\r
             $add_comment_link.fadeOut('slow');\r
+            window.setInterval(function() {\r
+                process_form_changes();\r
+            }, 200);\r
         }\r
 \r
         function hide_comment_form() {\r
         }\r
 \r
         function hide_comment_form() {\r
+            if (interval != null) {\r
+                window.clearInterval(interval);\r
+                interval = null;\r
+            }\r
             $container.slideUp('slow');\r
             $add_comment_link.fadeIn('slow');\r
         }\r
             $container.slideUp('slow');\r
             $add_comment_link.fadeIn('slow');\r
         }\r
@@ -213,37 +372,48 @@ $(function() {
         $('#' + $comments_container.attr('id') + ' .comment-edit').live('click', function() {\r
             var $link = $(this);\r
             var comment_id = /comment-(\d+)-edit/.exec($link.attr('id'))[1];\r
         $('#' + $comments_container.attr('id') + ' .comment-edit').live('click', function() {\r
             var $link = $(this);\r
             var comment_id = /comment-(\d+)-edit/.exec($link.attr('id'))[1];\r
-            var $comment = $link.parents('.comment');\r
-            var comment_text = $comment.find('.comment-text').text().trim();\r
+            var $comment = $('#comment-' + comment_id);\r
 \r
             comment_in_form = comment_id;\r
 \r
             comment_in_form = comment_id;\r
-            $textarea.val(comment_text);\r
-            calculate_chars_left();\r
+\r
+            $.get($link.attr('href'), function(data) {\r
+                $textarea.val(data);\r
+            });\r
+            \r
             $comment.slideUp('slow');\r
             show_comment_form();\r
             return false;\r
         });\r
 \r
             $comment.slideUp('slow');\r
             show_comment_form();\r
             return false;\r
         });\r
 \r
-        $textarea.keyup(calculate_chars_left);\r
-\r
         $button.click(function() {\r
         $button.click(function() {\r
-            if ($textarea.val().length > max_length) {\r
-                show_message($button, "Your comment exceeds the max number of characters allowed.");\r
-            } else {\r
-                var post_data = {\r
-                    comment: $textarea.val()\r
-                }\r
+            if (running) return false;\r
 \r
 \r
-                if (comment_in_form) {\r
-                    post_data['id'] = comment_in_form;\r
-                }\r
+            var post_data = {\r
+                comment: $textarea.val()\r
+            }\r
 \r
 \r
-                $.post($form.attr('action'), post_data, function(data) {\r
-                    process_ajax_response(data, $button);\r
-                    cleanup_form();\r
-                }, "json")\r
+            if (comment_in_form) {\r
+                post_data['id'] = comment_in_form;\r
             }\r
 \r
             }\r
 \r
+            start_command();\r
+            $.post($form.attr('action'), post_data, function(data) {\r
+                process_ajax_response(data, $button, function(error) {\r
+                    if (!error) {\r
+                        cleanup_form();\r
+                        hide_comment_form();\r
+                    }\r
+                });\r
+\r
+            }, "json");\r
+            \r
+            return false;\r
+        });\r
+\r
+        $cancel.click(function() {\r
+            if (comment_in_form) {\r
+                $comment = $('#comment-' + comment_in_form).slideDown('slow');\r
+            }\r
             hide_comment_form();\r
             return false;\r
         });\r
             hide_comment_form();\r
             return false;\r
         });\r
index 673e9b45d5742dbca004461c307dc19896ca49b0..4fcfad5d21936727382d313ccddfb9fd342a2713 100644 (file)
@@ -2,7 +2,7 @@
     border-spacing: 10px;
 }
 
     border-spacing: 10px;
 }
 
-#admin_form input[type="text"], #admin_form input[type="submit"], #admin_form textarea {
+#admin_form input[type="text"], #admin_form input[type="submit"], #admin_form textarea, .string_list_widget_button {
     line-height: 22px;
     font-size: 140%;
     font-family: sans-serif;
     line-height: 22px;
     font-size: 140%;
     font-family: sans-serif;
@@ -10,7 +10,7 @@
     color: black;
 }
 
     color: black;
 }
 
-#admin_form input[type="text"], #admin_form input[type="submit"] {
+#admin_form input[type="text"], #admin_form input[type="submit"], .string_list_widget_button {
     height: 28px;
 }
 
     height: 28px;
 }
 
 #admin_page_description {
     color: gray;
     padding-bottom: 20px;
 #admin_page_description {
     color: gray;
     padding-bottom: 20px;
+}
+
+.string_list_widget input[type=text] {
+    width: 520px;
+}
+
+.string_list_widget_button {
+    width: 28px;
+    font-size: 20px;
+    font-weight: bold;
+}
+
+.string_list_widget_button.add {
+    position: relative;
+    left: 554px;
 }
\ No newline at end of file
 }
\ No newline at end of file
index 33702758c84a361dbc3417ec9fca0891fb0415c0..0734ce02c0b524c01ecb34cdfe785b44599ed1b7 100644 (file)
@@ -3,24 +3,42 @@
     padding: 0px;
     width:600px;
     margin:0px 0px 5px 0px;
     padding: 0px;
     width:600px;
     margin:0px 0px 5px 0px;
+    clear:both;
 }
 
 .provider_logo {
 }
 
 .provider_logo {
-    display: inline-block;
-    padding: 4px;
+    display: block;
     border: 1px solid #DDD;
     text-align: center;
     border: 1px solid #DDD;
     text-align: center;
-    vertical-align: middle;
 }
 
 .provider_logo.big {
 }
 
 .provider_logo.big {
-    height: 40px;
-    width: 90px;
+    display: block;
+    border:1px solid #DDDDDD;
+    float:left;
+    height:60px;
+    margin:3px;
+    width:110px;
+}
+
+.provider_logo.big .inner {
+    display:block;
+    margin: 0px auto;
+    margin-top: 18px;
 }
 
 .provider_logo.small {
 }
 
 .provider_logo.small {
-    height: 32px;
-    width: 32px;
+    border:1px solid #DDDDDD;
+    float:left;
+    height:30px;
+    margin:3px;
+    width:30px;
+}
+
+.provider_logo.small .inner {
+    display:block;
+    margin: 0px auto;
+    margin-top: 6px;
 }
 
 .provider_logo.selected {
 }
 
 .provider_logo.selected {
     display: none;
 }
 
     display: none;
 }
 
+.signin_form {
+    clear:both;
+}
+
+.signin_form fieldset {
+    padding: 10px;
+}
+
 .signin_form input[type="text"], .signin_form input[type="password"], .signin_form input[type="submit"] {
     height: 28px;
     line-height: 22px;
     font-size: 140%;
     border: 1px solid #999;
 .signin_form input[type="text"], .signin_form input[type="password"], .signin_form input[type="submit"] {
     height: 28px;
     line-height: 22px;
     font-size: 140%;
     border: 1px solid #999;
+    padding-left:5px;
+    margin-right:5px;
+}
+
+.signin_form input[type="text"], .signin_form input[type="password"] {
+    padding-top:4px;  /* balance of alignment between firefox/safari and IE */
 }
 
 .signin_form .icon_input  {
     padding-left: 20px;
 }
 
 }
 
 .signin_form .icon_input  {
     padding-left: 20px;
 }
 
+.signin_form #openid_identifier {
+    padding-left: 18px;
+}
+
 .or_label {
     margin-top: 20px;
     margin-bottom: 10px;
 .or_label {
     margin-top: 20px;
     margin-bottom: 10px;
-}
\ No newline at end of file
+}
index dee175bdd93e048aa01f6f5ac921ebc6b7171204..99320378efe7f125007c875e0453052d590506e8 100644 (file)
@@ -663,8 +663,6 @@ ul.errorlist li {
     margin-top: 5px;
 }
 
     margin-top: 5px;
 }
 
-div.comments-container { padding: 0; }
-
 .answer {
        border-bottom: 1px solid #CCCCCE;
        padding-top: 10px;
 .answer {
        border-bottom: 1px solid #CCCCCE;
        padding-top: 10px;
@@ -677,13 +675,13 @@ div.comments-container { padding: 0; }
        min-height: 80px;
 }
 
        min-height: 80px;
 }
 
+.answered-by-owner { background: none repeat scroll 0 0 #E9E9FF; }
+
 .accepted-answer {
        background-color: #EBFFE6;
        border-bottom-color: #9BD59B;
 }
 
 .accepted-answer {
        background-color: #EBFFE6;
        border-bottom-color: #9BD59B;
 }
 
-.accepted-answer .comments-container { background-color: #CCFFBF; }
-
 .answered {
        background: none repeat scroll 0 0 #E5EBF8;
        color: #314362;
 .answered {
        background: none repeat scroll 0 0 #E5EBF8;
        color: #314362;
@@ -699,9 +697,6 @@ div.comments-container { padding: 0; }
        color: #6B2B28;
 }
 
        color: #6B2B28;
 }
 
-.answered-by-owner { background: none repeat scroll 0 0 #E9E9FF; }
-.answered-by-owner .comments-container { background-color: #E6ECFF; }
-
 .tagsList {
        list-style-type: none;
        margin: 0;
 .tagsList {
        list-style-type: none;
        margin: 0;
@@ -1111,7 +1106,6 @@ ul.form-horizontal-rows li input {
 }
 
 .post-controls {
 }
 
 .post-controls {
-       float: left;
        font-size: 11px;
        line-height: 12px;
        margin-bottom: 5px;
        font-size: 11px;
        line-height: 12px;
        margin-bottom: 5px;
@@ -1120,11 +1114,6 @@ ul.form-horizontal-rows li input {
 
 #question-controls .tags { margin: 0 0 3px; }
 
 
 #question-controls .tags { margin: 0 0 3px; }
 
-.post-update-info-container {
-       float: right;
-       min-width: 190px;
-}
-
 .post-update-info {
        display: inline-block;
        float: right;
 .post-update-info {
        display: inline-block;
        float: right;
@@ -1145,21 +1134,6 @@ ul.form-horizontal-rows li input {
        width: 32px;
 }
 
        width: 32px;
 }
 
-.comments-container { clear: both; }
-
-.admin {
-       background-color: #FFF380;
-       border: 1px solid darkred;
-       padding: 0 5px;
-}
-
-.admin p { margin-bottom: 3px; }
-
-.admin #action_status {
-       font-weight: bold;
-       text-align: center;
-}
-
 #tagSelector { padding-bottom: 2px; }
 #hideIgnoredTagsControl { margin: 5px 0 0; }
 #hideIgnoredTagsCb { margin: 0 2px 0 1px; }
 #tagSelector { padding-bottom: 2px; }
 #hideIgnoredTagsControl { margin: 5px 0 0; }
 #hideIgnoredTagsCb { margin: 0 2px 0 1px; }
@@ -1192,10 +1166,10 @@ a.post-vote.down.on,a.post-vote.down:hover { background: url("../images/vote-arr
 a.accept-answer { background: url("../images/vote-accepted.png") no-repeat scroll center center transparent; }
 a.accept-answer.on,a.accept-answer:hover { background: url("../images/vote-accepted-on.png") no-repeat scroll center center transparent; }
 
 a.accept-answer { background: url("../images/vote-accepted.png") no-repeat scroll center center transparent; }
 a.accept-answer.on,a.accept-answer:hover { background: url("../images/vote-accepted-on.png") no-repeat scroll center center transparent; }
 
-.post-score {
+.post-score, .comments-char-left-count {
        color: #777777;
        font-family: Arial;
        color: #777777;
        font-family: Arial;
-       font-size: 140%;
+       font-size: 165%;
        font-weight: bold;
        padding: 0 0 3px;
 }
        font-weight: bold;
        padding: 0 0 3px;
 }
@@ -1211,10 +1185,15 @@ a.accept-answer.on,a.accept-answer:hover { background: url("../images/vote-accep
        padding: 0;
 }
 
        padding: 0;
 }
 
+.comments-container { clear: both; }
+.comments-container { padding: 0; }
+.answered-by-owner .comments-container { background-color: #E6ECFF; }
+.accepted-answer .comments-container { background-color: #CCFFBF; }
+
 .comment {
        border-top: 1px dotted #CCCCCE;
        margin: 0;
 .comment {
        border-top: 1px dotted #CCCCCE;
        margin: 0;
-       width: 100%;
+    position: relative;
 }
 
 .comment.not_top_scorer { display: none; }
 }
 
 .comment.not_top_scorer { display: none; }
@@ -1226,27 +1205,39 @@ a.accept-answer.on,a.accept-answer:hover { background: url("../images/vote-accep
        font-weight: bold;
        padding-top: 3px;
        vertical-align: top;
        font-weight: bold;
        padding-top: 3px;
        vertical-align: top;
+    float: left;
        width: 22px;
        width: 22px;
+    height: 100%;
+    text-align: center;
 }
 
 .comment-text {
        color: #444444;
 }
 
 .comment-text {
        color: #444444;
-       font-size: 11px;
+       font-size: 12px;
        margin: 0 0 0 22px;
        padding: 0;
 }
 
        margin: 0 0 0 22px;
        padding: 0;
 }
 
+.comment-text p {
+    font-size: 12px;
+}
+
 .comment-info {
        font-size: 11px;
 .comment-info {
        font-size: 11px;
-       margin: 0;
-       text-align: right;
+       margin: 0 0 4px 0;
+    text-align: right;
+    height: 18px;
+    vertical-align: middle;
 }
 
 }
 
-a.comment-like,a.comment-delete,a.comment-edit {
-       float: right;
+.comment-info * {
+    float: right;
        height: 18px;
        height: 18px;
+       margin-left: 4px;
+}
+
+a.comment-like,a.comment-delete,a.comment-edit {
        margin-left: 2px;
        margin-left: 2px;
-       position: relative;
        width: 18px;
 }
 
        width: 18px;
 }
 
@@ -1265,8 +1256,9 @@ a.comment-edit:hover { background: url("../images/comment-edit-hover.png") no-re
 .comment-form-widgets-container input { vertical-align: top; }
 
 .comment-form-widgets-container textarea {
 .comment-form-widgets-container input { vertical-align: top; }
 
 .comment-form-widgets-container textarea {
-       height: 6em;
+       height: 80px;
        width: 80%;
        width: 80%;
+    float: left;
 }
 
 span.comment-chars-left {
 }
 
 span.comment-chars-left {
@@ -1312,3 +1304,69 @@ div.comment-tools a:hover {
 }
 
 .action-link-separator { color: #CCCCCC; }
 }
 
 .action-link-separator { color: #CCCCCC; }
+
+.deleted {background-color: #F4E7E7;}
+
+#command-loader {
+    position: fixed;
+    bottom: 0px;
+    left: 0px;
+    width: 24px;
+    height: 24px;
+    background: url('/m/default/media/images/indicator.gif')
+}
+
+#command-loader.success {
+    background: url('/m/default/media/images/vote-accepted-on.png')
+}
+
+.user-prompt {
+    width: 300px;
+}
+
+.user-prompt select, .user-prompt textarea {
+    width: 100%;
+    padding: 0;
+    border: 0;
+}
+
+.user-prompt .prompt-buttons {
+    text-align: right;
+}
+
+.comment-form-buttons {
+    width: 18%;
+    height: 100%;
+    float: right;
+}
+
+.comment-form-buttons input, .user-prompt .prompt-buttons button {
+    height: 16px;
+    line-height: 12px;
+    font-size: 110%;
+    border: 1px solid #999;
+}
+
+.comment-form-buttons input {
+    width: 100%;
+    height: 22px;
+    vertical-align: center;
+    margin-top: 6px;
+}
+
+.comments-char-left-count.warn {
+    color: orange;    
+}
+
+.moderation {
+       background-color: #FFF380;
+       border: 1px solid darkred;
+       padding: 0 5px;
+}
+
+.moderation p { margin-bottom: 3px; }
+
+.moderation #action_status {
+       font-weight: bold;
+       text-align: center;
+}
\ No newline at end of file
index a925e9dac31863fae8ff7a1dc5fcb3f3e44bf16b..35a6c2c50acd383fb971572897dbd8498fa66559 100644 (file)
@@ -25,7 +25,7 @@
                 $('#pre-collapse').text(txt);
             });
 
                 $('#pre-collapse').text(txt);
             });
 
-            // Tags autocomplete action
+            //Tags autocomplete action
                $("#id_tags").autocomplete("/matching_tags", {
                 minChars: 1,
                        matchContains: true,
                $("#id_tags").autocomplete("/matching_tags", {
                 minChars: 1,
                        matchContains: true,
index 798faeba267c4b95bfbae260149d4453f9867675..6f653f8b3073bc7e714038334f1878b6a2d555b2 100644 (file)
         <h3 class="or_label">{% trans 'Or...' %}</h3>\r
     {% endif %}\r
     <div style="width:600px;float:left;margin-bottom:5px;">\r
         <h3 class="or_label">{% trans 'Or...' %}</h3>\r
     {% endif %}\r
     <div style="width:600px;float:left;margin-bottom:5px;">\r
-        {% trans "Click to sign in through any of these services." %}\r
+    {% blocktrans %}\r
+        External login services use <b><a href="http://openid.net/">OpenID</a></b> technology, where your password always stays confidential between\r
+        you and your login provider and you don't have to remember another one.\r
+    {% endblocktrans %}\r
     </div>\r
     {% if request.user.is_anonymous %}\r
         <div style="width:600px;float:left;margin-bottom:5px;">\r
     </div>\r
     {% if request.user.is_anonymous %}\r
         <div style="width:600px;float:left;margin-bottom:5px;">\r
     <div id="bigicon_providers">\r
         {% for provider in bigicon_providers %}\r
             <div class="provider_logo big" name="{{ provider.id }}">\r
     <div id="bigicon_providers">\r
         {% for provider in bigicon_providers %}\r
             <div class="provider_logo big" name="{{ provider.id }}">\r
-                {% ifequal provider.type "DIRECT" %}\r
-                    <a class="provider_direct" href="{% url auth_provider_signin provider=provider.id %}">\r
-                        <img src="{% media provider.icon %}" />\r
-                    </a>\r
-                {% endifequal %}\r
-                {% ifequal provider.type "CUSTOM" %}\r
-                    {% include provider.code_template %}\r
-                {% endifequal %}\r
-                {% ifequal provider.type "SIMPLE_FORM" %}\r
-                    <img alt="{{ provider.simple_form_context.your_what }}" class="simple_form_provider" src="{% media provider.icon %}" />\r
-                {% endifequal %}\r
+                <div class="inner">\r
+                    {% ifequal provider.type "DIRECT" %}\r
+                        <a class="provider_direct" href="{% url auth_provider_signin provider=provider.id %}">\r
+                            <img src="{% media provider.icon %}" />\r
+                        </a>\r
+                    {% endifequal %}\r
+                    {% ifequal provider.type "CUSTOM" %}\r
+                        {% include provider.code_template %}\r
+                    {% endifequal %}\r
+                    {% ifequal provider.type "SIMPLE_FORM" %}\r
+                        <img alt="{{ provider.simple_form_context.your_what }}" class="simple_form_provider" src="{% media provider.icon %}" />\r
+                    {% endifequal %}\r
+                </div>\r
             </div>\r
         {% endfor %}\r
     </div>\r
     <div id="smallicon_providers">\r
         {% for provider in smallicon_providers %}\r
             <div class="provider_logo small" name="{{ provider.id }}">\r
             </div>\r
         {% endfor %}\r
     </div>\r
     <div id="smallicon_providers">\r
         {% for provider in smallicon_providers %}\r
             <div class="provider_logo small" name="{{ provider.id }}">\r
-                {% ifequal provider.type "DIRECT" %}\r
-                    <a class="provider_direct" href="{% url auth_provider_signin provider=provider.id %}">\r
-                        <img src="{% media provider.icon %}" />\r
-                    </a>\r
-                {% endifequal %}\r
-                {% ifequal provider.type "CUSTOM" %}\r
-                    {% include provider.code_template %}\r
-                {% endifequal %}\r
-                {% ifequal provider.type "SIMPLE_FORM" %}\r
-                    <img alt="{{ provider.simple_form_context.your_what }}" class="simple_form_provider" src="{% media provider.icon %}" />\r
-                {% endifequal %}\r
+                <div class="inner">\r
+                    {% ifequal provider.type "DIRECT" %}\r
+                        <a class="provider_direct" href="{% url auth_provider_signin provider=provider.id %}">\r
+                            <img src="{% media provider.icon %}" />\r
+                        </a>\r
+                    {% endifequal %}\r
+                    {% ifequal provider.type "CUSTOM" %}\r
+                        {% include provider.code_template %}\r
+                    {% endifequal %}\r
+                    {% ifequal provider.type "SIMPLE_FORM" %}\r
+                        <img alt="{{ provider.simple_form_context.your_what }}" class="simple_form_provider" src="{% media provider.icon %}" />\r
+                    {% endifequal %}\r
+                </div>\r
             </div>\r
         {% endfor %}\r
     </div>\r
             </div>\r
         {% endfor %}\r
     </div>\r
         </form>\r
     {% endfor %}\r
     <h3 class="or_label">{% trans 'Or...' %}</h3>\r
         </form>\r
     {% endfor %}\r
     <h3 class="or_label">{% trans 'Or...' %}</h3>\r
-    <fieldset>\r
-        {% trans 'Click' %} <a href="{% url auth_request_tempsignin %}">here</a> {% trans "if you're having troubles signing in." %}\r
-    </fieldset>\r
+    <form name="signin_form" id="dummy_form_unused" class="signin_form" method="POST" action="">\r
+        <fieldset>\r
+            {% trans 'Click' %} <a href="{% url auth_request_tempsignin %}">here</a> {% trans "if you're having troubles signing in." %}\r
+        </fieldset>\r
+    </form>\r
     <script type="text/html" id="simple_form_template">\r
         <fieldset id="slot_form">\r
               <p id="provider_name_slot">{% trans 'Enter your ' %}%%YOUR_WHAT%%</p>\r
     <script type="text/html" id="simple_form_template">\r
         <fieldset id="slot_form">\r
               <p id="provider_name_slot">{% trans 'Enter your ' %}%%YOUR_WHAT%%</p>\r
         <a href="http://openid.net/get/" target="_blank">{% trans "Get OpenID" %} </a>\r
     </p>\r
 </div>\r
         <a href="http://openid.net/get/" target="_blank">{% trans "Get OpenID" %} </a>\r
     </p>\r
 </div>\r
-{% endblock%}
\ No newline at end of file
+{% endblock%}\r
index af6aa2a2f1a5c42523b6788d0edce0f6000fcaed..fcbb660463f4c19ae26ddf95c0269c0178da2111 100644 (file)
@@ -2,6 +2,7 @@
 <!-- template badge.html -->
 {% load i18n %}
 {% load extra_tags %}
 <!-- template badge.html -->
 {% load i18n %}
 {% load extra_tags %}
+{% load user_tags %}
 {% load humanize %}
 {% block title %}{% spaceless %}{{ badge.name }} - {% trans "Badge" %}{% endspaceless %}{% endblock %}
 {% block forejs %}
 {% load humanize %}
 {% block title %}{% spaceless %}{{ badge.name }} - {% trans "Badge" %}{% endspaceless %}{% endblock %}
 {% block forejs %}
@@ -28,7 +29,7 @@
     </div>
     <div id="award-list" style="clear:both;margin-left:20px;line-height:25px;">
         {% for award in awards %}
     </div>
     <div id="award-list" style="clear:both;margin-left:20px;line-height:25px;">
         {% for award in awards %}
-        <p style="width:180px;float:left"><a href="{% url users %}{{ award.id }}/{{ award.name }}">{{ award.name }}</a> {% get_score_badge_by_details award.rep award.gold award.silver award.bronze %}</p>
+        <p style="width:220px;float:left">{% user_signature award.user "badges" %}<span class="tag-number"> &#215; {{ award.count|intcomma }}</span></p>
         {% endfor %}
     </div>
 
         {% endfor %}
     </div>
 
index aff901f70e0b1535d69748fac0ce2111e1c1ca2a..96f7a42e31827843bd474cb80e1fec90e129b588 100644 (file)
@@ -7,7 +7,7 @@
 {% block content %}
 <div class="headNormal">
 {% trans "FAQ" %}
 {% block content %}
 <div class="headNormal">
 {% trans "FAQ" %}
-</div>
+        </div>
 <div class="content">
 {{ text|markdown }}
 </div>
 <div class="content">
 {{ text|markdown }}
 </div>
index d62e521f1e774f113760665be7d526cf96b1179a..571fd24a29854dce7634bf0275f9edb7a52e87fb 100644 (file)
@@ -4,39 +4,31 @@
 <div class="comments-container" id="comments-container-{{ post.id }}">\r
     {% for comment in comments %}\r
         <a name="{{ comment.id }}"></a>\r
 <div class="comments-container" id="comments-container-{{ post.id }}">\r
     {% for comment in comments %}\r
         <a name="{{ comment.id }}"></a>\r
-        <table class="comment{% if not comment.top_scorer %} not_top_scorer{% endif %}" id="comment-{{comment.id}}">\r
-            <tr>\r
-                <td rowspan="2" class="comment-score" id="post-{{ comment.id }}-score">\r
-                    {% if comment.score %}{{ comment.score }}{% endif %}\r
-                </td>\r
-                <td class="comment-text" id="comment-{{comment.id}}-text">\r
-                    {{ comment.body }}\r
-                </td>\r
-            </tr>\r
-            <tr>\r
-                <td class="comment-info" id="comment-{{comment.id}}-info">\r
-                    <a class="comment-user userinfo" href="{{comment.user.get_profile_url}}">{{comment.user}}</a>\r
-                    <span class="comment-age">({% diff_date comment.added_at %})</span>\r
-                    {% if comment.can_like %}\r
-                        <a id="post-{{ comment.id }}-upvote" href="{% url vote_post id=comment.id,vote_type='up' %}"\r
-                         title="{% trans "I like this comment (click again to cancel)" %}" class="ajax-command comment-like{% if comment.likes %} on{% endif %}"\r
-                        rel="nofollow"> </a>\r
-                    {% endif %}\r
-                    {% if comment.can_edit %}\r
-                        <a id="comment-{{ comment.id }}-edit" href="#" title="{% trans "Edit comment" %}"\r
-                            class="comment-edit" rel="nofollow"> </a>\r
-                    {% endif %}\r
-                    {% if comment.can_delete %}\r
-                        <a id="comment-{{ comment.id }}-delete" href="{% url delete_comment id=comment.id %}" title="{% trans "Delete comment" %}"\r
-                            class="ajax-command comment-delete" rel="nofollow"> </a>\r
-                    {% endif %}\r
-                </td>\r
-            </tr>\r
-        </table>\r
+        <div class="comment{% if not comment.top_scorer %} not_top_scorer{% endif %}" id="comment-{{comment.id}}">\r
+            <div id="post-{{ comment.id }}-score" class="comment-score">{% if comment.score %}{{ comment.score }}{% endif %}</div>\r
+            <div class="comment-text">{{ comment.comment }}</div>\r
+            <div class="comment-info" id="comment-{{comment.id}}-info">\r
+                {% if comment.can_like %}\r
+                    <a id="post-{{ comment.id }}-upvote" href="{% url like_comment id=comment.id %}"\r
+                     title="{% trans "I like this comment (click again to cancel)" %}" class="ajax-command comment-like{% if comment.likes %} on{% endif %}"\r
+                    rel="nofollow"> </a>\r
+                {% endif %}\r
+                {% if comment.can_edit %}\r
+                    <a id="comment-{{ comment.id }}-edit" href="{% url node_markdown id=comment.id %}" title="{% trans "Edit comment" %}"\r
+                        class="comment-edit" rel="nofollow"> </a>\r
+                {% endif %}\r
+                {% if comment.can_delete %}\r
+                    <a id="comment-{{ comment.id }}-delete" href="{% url delete_comment id=comment.id %}" title="{% trans "Delete comment" %}"\r
+                        class="ajax-command comment-delete" rel="nofollow"> </a>\r
+                {% endif %}\r
+                <span class="comment-age">({% diff_date comment.added_at %})</span>\r
+                <a class="comment-user userinfo" href="{{comment.user.get_profile_url}}">{{comment.user}}</a>\r
+                {% if show_gravatar %}{% gravatar comment.user 18 %}{% endif %}\r
+            </div>\r
+        </div>\r
     {% endfor %}\r
 </div>\r
 <div id="comment-tools-{{ post.id }}" class="comment-tools">\r
     {% endfor %}\r
 </div>\r
 <div id="comment-tools-{{ post.id }}" class="comment-tools">\r
-    <img src="/m/default/media/images/gray-up-arrow-h18px.png" />\r
     {% ifnotequal showing total %}\r
         <span class="comments-showing">\r
             {% blocktrans %}showing {{ showing }} of {{ total }}{% endblocktrans %}\r
     {% ifnotequal showing total %}\r
         <span class="comments-showing">\r
             {% blocktrans %}showing {{ showing }} of {{ total }}{% endblocktrans %}\r
 </div>\r
 {% if can_comment %}\r
 <div id="comment-{{ post.id }}-form-container" class="comment-form-container">\r
 </div>\r
 {% if can_comment %}\r
 <div id="comment-{{ post.id }}-form-container" class="comment-form-container">\r
-    <form id="comment-{{ post.id }}-form" method="post" action="{% url comment id=post.id %}">\r
+    <form id="comment-{{ post.id }}-form" method="post" action="{% url comment id=post.id %}" accept-charset="utf-8">\r
         <div class="comment-form-widgets-container">\r
             <textarea name="comment"></textarea>\r
         <div class="comment-form-widgets-container">\r
             <textarea name="comment"></textarea>\r
-            <input type="submit" value="{% trans " add comment" %}" />\r
+            <div class="comment-form-buttons">\r
+                <span id="comment-{{ post.id }}-chars-left" class="comment-chars-left">\r
+                    <span class="comments-char-left-count">{{ min_length }}|{{ max_length }}</span>\r
+                    <span class="comments-chars-togo-msg">{% trans "characters to go" %}</span>\r
+                    <span class="comments-chars-left-msg">{% trans "characters left" %}</span>\r
+                </span>\r
+                <input type="submit" class="comment-submit" value="{% trans " comment" %}" />\r
+                <input type="submit" class="comment-cancel" value="{% trans " cancel" %}" />\r
+            </div>\r
         </div>\r
         </div>\r
-        <span id="comment-{{ post.id }}-chars-left" class="comment-chars-left">\r
-            {% blocktrans %}\r
-                have <span class="comments-char-left-count">{{ max_length }}</span> characters left\r
-            {% endblocktrans %}\r
-        </span>\r
         <script type="text/html" class="new-comment-skeleton" id="new-comment-skeleton-{{ post.id }}">\r
         <script type="text/html" class="new-comment-skeleton" id="new-comment-skeleton-{{ post.id }}">\r
-            <table class="comment" id="comment-%ID%" style="display: none">\r
-                <tr>\r
-                    <td rowspan="2" class="comment-score" id="comment-%ID%-score"></td>\r
-                    <td class="comment-text" id="comment-%ID%-text">%COMMENT%</td>\r
-                </tr>\r
-                <tr>\r
-                    <td class="comment-info" id="comment-%ID%-info">\r
-                        <a class="comment-user" href="%PROFILE_URL%">%USERNAME%</a>\r
-                        <span class="comment-age">({% trans "just now" %})</span>\r
-                        <a id="comment-%ID%-edit" href="#"\r
-                            class="comment-edit" rel="nofollow"> </a>\r
-                        <a id="comment-%ID%-delete" href="%DELETE_URL%"\r
-                            class="ajax-command comment-delete" rel="nofollow"> </a>\r
-                    </td>\r
-                </tr>\r
-            </table>\r
+            <div class="comment{% if not comment.top_scorer %} not_top_scorer{% endif %}" id="comment-%ID%">\r
+                <div id="post-%ID%-score" class="comment-score"></div>\r
+                <div class="comment-text">%COMMENT%</div>\r
+                <div class="comment-info" id="comment-{{comment.id}}-info">\r
+                    <a id="comment-%ID%-edit" href="%EDIT_URL%"\r
+                        class="comment-edit" rel="nofollow"> </a>\r
+                    <a id="comment-%ID%-delete" href="%DELETE_URL%"\r
+                        class="ajax-command comment-delete" rel="nofollow"> </a>\r
+                    <span class="comment-age">({% trans "just now" %})</span>\r
+                    <a class="comment-user" href="%PROFILE_URL%">%USERNAME%</a>\r
+                    {% if user.is_authenticated %}\r
+                        {% if show_gravatar %}{% gravatar user 18 %}{% endif %}\r
+                    {% endif %}\r
+                </div>\r
+             </div>\r
         </script>\r
     </form>\r
 </div>\r
         </script>\r
     </form>\r
 </div>\r
index 5b5fc8656d8b09a5870a34bf23724f420a5c642e..c24660b29ad9965de6ff96064ee71b1b708596c8 100644 (file)
@@ -1,7 +1,7 @@
 {% spaceless %}\r
 {% for control in controls %}\r
     <span class="action-link">\r
 {% spaceless %}\r
 {% for control in controls %}\r
     <span class="action-link">\r
-        <a title="{{ control.title }}" {% if control.command %}class="ajax-command"{% endif %}\r
+        <a title="{{ control.title }}" class="{% if control.command %}ajax-command{% endif %}{% if control.withprompt %} withprompt{% endif %}"\r
             href="{{ control.url }}">{{ control.text }}</a>\r
     </span>\r
     {% ifnotequal controls|last control %}\r
             href="{{ control.url }}">{{ control.text }}</a>\r
     </span>\r
     {% ifnotequal controls|last control %}\r
diff --git a/forum/skins/default/templates/node/report.html b/forum/skins/default/templates/node/report.html
new file mode 100644 (file)
index 0000000..dbc8630
--- /dev/null
@@ -0,0 +1,19 @@
+{% load i18n %}\r
+\r
+<div class="user-prompt">\r
+    {% trans "Please select a reason bellow or use the text box to input your own reason." %}\r
+    <select class="prompt-examples">\r
+        {% for type in types %}\r
+            <option value="{{ type }}">{{ type }}</option>\r
+        {% endfor %}\r
+    </select>\r
+    <textarea>{{ types|first }}</textarea>\r
+    <div class="prompt-buttons">\r
+        <button class="prompt-cancel">{% trans "Cancel" %}</button><button class="prompt-submit">{% trans "Send" %}</button>\r
+    </div>\r
+</div>\r
+<script>\r
+$('.user-prompt .prompt-examples').change(function() {\r
+    $('.user-prompt textarea').val($(this).val())            \r
+})\r
+</script>
\ No newline at end of file
index e35a70575f30e33296eda71cb4102c84a4ae4826..2acdc15d6ec993ea238f3d30f83fae4cea7b65df 100644 (file)
@@ -5,6 +5,7 @@
 \r
 {% block forejs %}\r
     <link rel="stylesheet" type="text/css" media="screen" href="{% media "/media/style/admin.css" %}"/>\r
 \r
 {% block forejs %}\r
     <link rel="stylesheet" type="text/css" media="screen" href="{% media "/media/style/admin.css" %}"/>\r
+    <script type="text/javascript" src="{% media "/media/js/osqa.admin.js" %}"></script>\r
     {% block adminjs %}{% endblock %}\r
 {% endblock %}\r
 \r
     {% block adminjs %}{% endblock %}\r
 {% endblock %}\r
 \r
 \r
 {% block sidebar %}\r
     <div class="boxC">\r
 \r
 {% block sidebar %}\r
     <div class="boxC">\r
-      <h3 class="subtitle">{% trans "Administration menu" %}</h3>\r
-      <ul>\r
-      {% for set in sets %}\r
-          <li><a href="{% url admin_set set.name %}">{{ set.title }}</a></li>\r
-      {% endfor %}\r
-      </ul>\r
+        <h3 class="subtitle">{% trans "Administration menu" %}</h3>\r
+        <ul>\r
+        {% for set in sets %}\r
+            <li><a href="{% url admin_set set.name %}">{{ set.title }}</a></li>\r
+        {% endfor %}\r
+        </ul>\r
     </div>\r
     {% if markdown %}\r
 \r
     </div>\r
     {% if markdown %}\r
 \r
@@ -65,5 +66,4 @@
     {% endif %}\r
 {% endblock %}\r
 \r
     {% endif %}\r
 {% endblock %}\r
 \r
-\r
-\r
+                \r
index 9e35a35c1ff3cdaae86e3be6c7c90c59cc6206b8..64670f54519c5dad58ec18186c71a84ce7c541ef 100644 (file)
@@ -65,7 +65,7 @@
                 <h3>{% trans "Recent activity" %}</h3>\r
                 <table width="100%">\r
                 {% for activity in recent_activity %}\r
                 <h3>{% trans "Recent activity" %}</h3>\r
                 <table width="100%">\r
                 {% for activity in recent_activity %}\r
-                    {% activity_item activity %}\r
+                    {% activity_item activity request.user %}\r
                 {% endfor %}\r
                 </table>\r
             </td>\r
                 {% endfor %}\r
                 </table>\r
             </td>\r
index 9997be5fbf67063c111d62f4b8a234aea4141810..27706442c221b7ae8f306c168c1bd2eabbea8360 100644 (file)
         {% get_score_badge post.author %}</p>
     {% endif %}
 {% else %}
         {% get_score_badge post.author %}</p>
     {% endif %}
 {% else %}
-    {% if post.last_edited_at %}
+    {% if post.last_edited %}
         <p style="line-height:12px;">
         {% ifequal post_type 'question' %}
         <a href="{% url question_revisions post.id %}">
         {% else %}
         <a href="{% url answer_revisions post.id %}">
         {% endifequal %}
         <p style="line-height:12px;">
         {% ifequal post_type 'question' %}
         <a href="{% url question_revisions post.id %}">
         {% else %}
         <a href="{% url answer_revisions post.id %}">
         {% endifequal %}
-        {% trans "updated" %} <strong>{% diff_date post.last_edited_at %}</strong>
+        {% trans "updated" %} <strong>{% diff_date post.last_edited.at %}</strong>
         </a>
         </p>
         </a>
         </p>
-        {% if post.author != post.last_edited_by or wiki %}
-            {% gravatar post.last_edited_by 32 %}
-            <p style="float:left">{{post.last_edited_by.get_profile_link}}<br/>
-            {% get_score_badge post.last_edited_by %}</p>
+        {% if post.author != post.last_edited.by or wiki %}
+            {% gravatar post.last_edited.by 32 %}
+            <p style="float:left">{{post.last_edited.by.get_profile_link}}<br/>
+            {% get_score_badge post.last_edited.by %}</p>
         {% endif %}
     {% endif %}
 {% endifequal %}
         {% endif %}
     {% endif %}
 {% endifequal %}
index 7a265c6370f318be29cd4e1355458df72a262da5..b6300d596ab13fda148935e97c4f74946607728b 100644 (file)
@@ -7,7 +7,7 @@
 {% load humanize %}\r
 {% load i18n %}\r
 {% load cache %}\r
 {% load humanize %}\r
 {% load i18n %}\r
 {% load cache %}\r
-{% block title %}{% spaceless %}{{ question.get_question_title }}{% endspaceless %}{% endblock %}\r
+{% block title %}{% spaceless %}{{ question.headline }}{% endspaceless %}{% endblock %}\r
 {% block forejs %}\r
         <meta name="description" content="{{question.summary}}" />\r
         <meta name="keywords" content="{{question.tagname_meta_generator}}" />\r
 {% block forejs %}\r
         <meta name="description" content="{{question.summary}}" />\r
         <meta name="keywords" content="{{question.tagname_meta_generator}}" />\r
@@ -19,8 +19,6 @@
         {% endif %}\r
 \r
         <script type="text/javascript">\r
         {% endif %}\r
 \r
         <script type="text/javascript">\r
-        // define reputation needs for comments\r
-        var repNeededForComments = 50;\r
         $().ready(function(){\r
             $("#nav_questions").attr('className',"on");\r
             var answer_sort_tab = "{{ tab_id }}";\r
         $().ready(function(){\r
             $("#nav_questions").attr('className',"on");\r
             var answer_sort_tab = "{{ tab_id }}";\r
@@ -46,7 +44,7 @@
         \r
 {% block content %}\r
 <div class="headNormal">\r
         \r
 {% block content %}\r
 <div class="headNormal">\r
-    <a href="{{ question.get_absolute_url }}">{{ question.get_question_title }}</a>\r
+    <a href="{{ question.get_absolute_url }}">{{ question.headline }}</a>\r
 </div>\r
 <div id="main-body" class="">\r
     <div id="askform">\r
 </div>\r
 <div id="main-body" class="">\r
     <div id="askform">\r
                     </td>\r
                 </tr>\r
             </table>\r
                     </td>\r
                 </tr>\r
             </table>\r
-            {% if question.closed %}\r
+            {% if question.marked %}\r
             <div class="question-status" style="margin-bottom:15px">\r
             <div class="question-status" style="margin-bottom:15px">\r
-            <h3>{% blocktrans with question.get_close_reason_display as close_reason %}The question has been closed for the following reason "{{ close_reason }}" by{% endblocktrans %} \r
-            <a href="{{ question.closed_by.get_profile_url }}">{{ question.closed_by.username }}</a> \r
-            {% blocktrans with question.closed_at as closed_at %}close date {{closed_at}}{% endblocktrans %}</h3>\r
+            <h3>\r
+                {% blocktrans with question.closed.extra as close_reason %}\r
+                    The question has been closed for the following reason "{{ close_reason }}" by\r
+                {% endblocktrans %}\r
+                <a href="{{ question.closed.by.get_profile_url }}">{{ question.closed.by.username }}</a>\r
+                 {% diff_date question.closed.at %}\r
+            </h3>\r
             </div>\r
             {% endif %}\r
             {% if answers %}\r
             </div>\r
             {% endif %}\r
             {% if answers %}\r
 \r
         {% for question in similar_questions %}\r
         <p>\r
 \r
         {% for question in similar_questions %}\r
         <p>\r
-            <a href="{{ question.get_absolute_url }}">{{ question.get_question_title }}</a>\r
+            <a href="{{ question.get_absolute_url }}">{{ question.headline }}</a>\r
         </p>\r
         {% endfor %}\r
 \r
         </p>\r
         {% endfor %}\r
 \r
index 1b634d82f5e6ee6d26de85b5e409fd99674a14ef..2fa8f1853db27ded7f4f790d259adc3fca90ef56 100644 (file)
     </div>
     <input id="ignoredTagInput" autocomplete="off" type="text"/>
     <input id="ignoredTagAdd" type="submit" value="{% trans "Add" %}"/>
     </div>
     <input id="ignoredTagInput" autocomplete="off" type="text"/>
     <input id="ignoredTagAdd" type="submit" value="{% trans "Add" %}"/>
+    {% comment %}
     <p id="hideIgnoredTagsControl">
     <input id="hideIgnoredTagsCb" type="checkbox" {% if request.user.hide_ignored_questions %}checked="checked"{% endif %} />
     <label id="hideIgnoredTagsLabel" for="hideIgnoredTagsCb">{% trans "keep ignored questions hidden" %}</label>
     <p>
     <p id="hideIgnoredTagsControl">
     <input id="hideIgnoredTagsCb" type="checkbox" {% if request.user.hide_ignored_questions %}checked="checked"{% endif %} />
     <label id="hideIgnoredTagsLabel" for="hideIgnoredTagsCb">{% trans "keep ignored questions hidden" %}</label>
     <p>
+    {% endcomment %}
 </div>
 {% endif %}
 </div>
 {% endif %}
index 04d96f347ec526bd48b56b1b25a201b3f597e5ab..cdbef86998de9d6416a92912a1329de68eb73f45 100644 (file)
@@ -59,7 +59,7 @@
     <div id="askform">
         <form id="fmretag" action="{% url edit_question question.id %}" method="post" >
             <h3>
     <div id="askform">
         <form id="fmretag" action="{% url edit_question question.id %}" method="post" >
             <h3>
-                {{ question.get_question_title }}
+                {{ question.headline }}
             </h3>
             <div id="description" class="edit-content-html">
                 {{ question.html|safe }}
             </h3>
             <div id="description" class="edit-content-html">
                 {{ question.html|safe }}
index 57685d6d7ba233226eda5f42c3b12d02e2639d4b..769aa206fd25c8fbd3c83f9b42a98c13f435c22a 100644 (file)
@@ -1,6 +1,6 @@
     <div class="qstA">
         <h2>
     <div class="qstA">
         <h2>
-            <a href="{{ question.get_absolute_url }}">{{ question.get_question_title }}</a>
+            <a href="{{ question.get_absolute_url }}">{{ question.headline }}</a>
         </h2>
         <div class="stat">
             <table>
         </h2>
         <div class="stat">
             <table>
index 37fb69c149caacb0b87478ad856332676e8bb0b3..cd0c37bc50500b76a2db50f84e3a7de707e5215a 100644 (file)
@@ -16,7 +16,7 @@
        {% trans "Reopen question" %}
 </div>
 <div id="main-body" style="width:100%">
        {% trans "Reopen question" %}
 </div>
 <div id="main-body" style="width:100%">
-    <p>{% trans "Open the previously closed question" %}: <a href="{{ question.get_absolute_url }}"><span class="big">{{ question.get_question_title }}</span></a>
+    <p>{% trans "Open the previously closed question" %}: <a href="{{ question.get_absolute_url }}"><span class="big">{{ question.headline }}</span></a>
     
     </p>   
     <p><strong>{% trans "The question was closed for the following reason " %}"{{ question.get_close_reason_display }}"{% trans "reason - leave blank in english" %} <a href="{{ question.closed_by.get_profile_url }}">{{ question.closed_by.username }}</a> {% trans "on "%} {% diff_date question.closed_at %}<font class="darkred">{% trans "date closed" %}</font>
     
     </p>   
     <p><strong>{% trans "The question was closed for the following reason " %}"{{ question.get_close_reason_display }}"{% trans "reason - leave blank in english" %} <a href="{{ question.closed_by.get_profile_url }}">{{ question.closed_by.username }}</a> {% trans "on "%} {% diff_date question.closed_at %}<font class="darkred">{% trans "date closed" %}</font>
index c2c1b2628dbbe79a15a63cfb63c116dd13c91a4f..87b5f51c6e25232468c8ccc375e4d28952517f7b 100644 (file)
@@ -6,9 +6,9 @@
          <ul class="badge-list">\r
          {% for award in awards %}\r
             <li>\r
          <ul class="badge-list">\r
          {% for award in awards %}\r
             <li>\r
-            <a href="{% url badges %}{{award.badge_id}}/{{award.badge_name}}" title="{{ award.badge_description }}" class="medal">\r
-            <span class="badge{{ award.badge_type }}">&#9679;</span>&nbsp;{{ award.badge_name }}</a>\r
-            <a href="/users/{{ award.user_id }}/{{ award.user_name|slugify }}/">{{ award.user_name }}</a>\r
+            <a href="{% url badges %}{{award.badge.id}}/{{award.badge.name|slugify}}" title="{{ award.badge.description }}" class="medal">\r
+            <span class="badge{{ award.badge.type }}">&#9679;</span>&nbsp;{{ award.badge.name }}</a>\r
+            <a href="/users/{{ award.user_id }}/{{ award.user_name|slugify }}/">{{ award.user.username }}</a>\r
             </li>\r
         {% endfor %}\r
         </ul>\r
             </li>\r
         {% endfor %}\r
         </ul>\r
index 39aa9bff8cada94054dabb84f978159507801b88..0b6a9142d859da60441008c5b48fe89f72afe876 100644 (file)
@@ -1,18 +1,12 @@
 {% load extra_tags %}\r
 {% load humanize %}\r
 {% load extra_tags %}\r
 {% load humanize %}\r
+{% load extra_tags %}\r
 \r
 \r
-<div style="clear:both;line-height:20px" >\r
-    <div style="width:180px;float:left">{% diff_date active_at 3 %}</div>\r
-    <div style="width:150px;float:left">\r
-    <span class="user-action-{{ type }}">{{ description }}</span>\r
+<div class="action_container">\r
+    <div class="action_body">\r
+        {{ describe }}\r
     </div>\r
     </div>\r
-    <div style="float:left;overflow:hidden;">\r
-        {% if badge %}\r
-        <a href="{{ url }}" title="{{ title }}" class="medal"><span class="badge{{ badge_type }}">&#9679;</span>&nbsp;{{ title }}</a>\r
-        {% else %}\r
-        <span class="post-type-{{ type }}"><a href="{{ url }}">{{ title }}</a></span>\r
-            {% if revision %}<span class="revision-summary">{{ summary }}</span>{% endif %}\r
-        {% endif %}\r
-        <div style="height:5px"></div>\r
+    <div class="action_date" style="text-align: right;">\r
+        {% diff_date action.action_date  %}\r
     </div>\r
 </div>
\ No newline at end of file
     </div>\r
 </div>
\ No newline at end of file
index faede0551fdfccbabdc7b8351a48e4af61e612e0..b86c59db24cfb3247257bb6603d144b1bffa81b6 100644 (file)
@@ -5,11 +5,15 @@
 {% load i18n %}\r
 {% block title %}{% spaceless %}{% trans "Edit user profile" %}{% endspaceless %}{% endblock %}\r
 {% block forejs %}\r
 {% load i18n %}\r
 {% block title %}{% spaceless %}{% trans "Edit user profile" %}{% endspaceless %}{% endblock %}\r
 {% block forejs %}\r
-        <script type="text/javascript">google.load("jquery", "1.4.2");google.load("jqueryui", "1.8.1");</script>\r
+        <script type="text/javascript">google.load("jquery", "1.4.2");</script>\r
 \r
         <link rel="stylesheet" href="http://jquery-ui.googlecode.com/svn/tags/latest/themes/base/jquery-ui.css" type="text/css" media="all" />\r
         <link rel="stylesheet" href="http://static.jquery.com/ui/css/demo-docs-theme/ui.theme.css" type="text/css" media="all" />\r
         \r
 \r
         <link rel="stylesheet" href="http://jquery-ui.googlecode.com/svn/tags/latest/themes/base/jquery-ui.css" type="text/css" media="all" />\r
         <link rel="stylesheet" href="http://static.jquery.com/ui/css/demo-docs-theme/ui.theme.css" type="text/css" media="all" />\r
         \r
+        <script src="{% media  "/media/js/effects.core.min.js" %}" type="text/javascript"></script>\r
+        <script src="{% media  "/media/js/ui.core.js" %}" type="text/javascript"></script>\r
+        <script src="{% media  "/media/js/ui.datepicker.js" %}" type="text/javascript"></script>\r
+\r
         <script type="text/javascript">\r
             $().ready(function(){\r
                 $("#id_birthday").datepicker({\r
         <script type="text/javascript">\r
             $().ready(function(){\r
                 $("#id_birthday").datepicker({\r
@@ -45,7 +49,7 @@
                                        title="gravatar {% trans "image associated with your email address" %}">{% blocktrans %}avatar, see {{gravatar_faq_url}}{% endblocktrans %}</a>\r
             </div>\r
         </div>\r
                                        title="gravatar {% trans "image associated with your email address" %}">{% blocktrans %}avatar, see {{gravatar_faq_url}}{% endblocktrans %}</a>\r
             </div>\r
         </div>\r
-\r
+        \r
         <div id="askform" style="float:right;width:750px;text-align:left;">\r
             <h2>{% trans "Registered user" %}</h2>\r
             <table class="user-details">\r
         <div id="askform" style="float:right;width:750px;text-align:left;">\r
             <h2>{% trans "Registered user" %}</h2>\r
             <table class="user-details">\r
index fb6fcde2ad2751e04b579668b020309e5d88e2f7..a8b2175621b7d2cd7dfe5218f47a6a95ef04a8f2 100644 (file)
@@ -4,6 +4,7 @@
 {% load humanize %}
 {% load smart_if %}
 {% load i18n %}
 {% load humanize %}
 {% load smart_if %}
 {% load i18n %}
+{% load user_tags %}
 <div id="subheader" class="headUser">
     {{view_user.username}}
 </div>
 <div id="subheader" class="headUser">
     {{view_user.username}}
 </div>
             <table class="user-details">
                 {% if view_user != request.user and request.user.is_superuser %}
                 <tr>
             <table class="user-details">
                 {% if view_user != request.user and request.user.is_superuser %}
                 <tr>
-                    <td class="admin" align="left" colspan="2">
-                        <h3>{% trans "Moderate this user" %}</h3>
-                        <form id="moderate_user_form" method="post">
-                            {{moderate_user_form.as_p}}
-                        </form>
-                        <p id="action_status"></p>
+                    <td class="moderation" align="left" colspan="2">
+                        {% comment %}{% user_moderation request.user view_user %}{% endcomment %}
                     </td>
                 </tr>
                 {% endif %}
                     </td>
                 </tr>
                 {% endif %}
@@ -83,8 +80,8 @@
                 <tr>
                                        <!--todo  - redo this with blocktrans -->
                     {% if view_user.date_of_birth.year != 1900%}
                 <tr>
                                        <!--todo  - redo this with blocktrans -->
                     {% if view_user.date_of_birth.year != 1900%}
-                        <td>{% trans "age" %}</td>
-                        <td>{% get_age view_user.date_of_birth %} {% trans "age unit" %}</td>
+                    <td>{% trans "age" %}</td>
+                    <td>{% get_age view_user.date_of_birth %} {% trans "age unit" %}</td>
                     {% endif %}
                 </tr>
                 {% endif %}
                     {% endif %}
                 </tr>
                 {% endif %}
diff --git a/forum/skins/default/templates/users/moderation.html b/forum/skins/default/templates/users/moderation.html
new file mode 100644 (file)
index 0000000..8213f18
--- /dev/null
@@ -0,0 +1,4 @@
+{% load i18n %}\r
+\r
+<h3>{% trans "Moderation tools" %}</h3>\r
+<p><a href="#" class="ajax-command">{% trans "Reputation bonus" %}</a></p>\r
index cff9e84d95a156be9b5316a37e66fd143391de0f..21c193c5f2d4e30010aed490eb04abb56341c208 100644 (file)
@@ -4,8 +4,8 @@
 {% load question_list_tags %}
 {% block usercontent %}
 <div class="user-stats-table">
 {% load question_list_tags %}
 {% block usercontent %}
 <div class="user-stats-table">
-    {% for question in questions %}
-        {% question_list_item question favorite_count=yes signature_type=badges %}
+    {% for favorite in favorites %}
+        {% question_list_item favorite.node favorite_count=yes signature_type=badges %}
     {% endfor %}
 </div>
 {% endblock %}
     {% endfor %}
 </div>
 {% endblock %}
index fc30634bc77f10d509d943f3ee557e6fa286bf2b..5098df6a6ac1a14c7c319a9e91a781dc05d1ebaa 100644 (file)
@@ -5,7 +5,7 @@
 {% block usercontent %}
         <div style="padding-top:5px;font-size:13px;">
         {% for act in activities %}
 {% block usercontent %}
         <div style="padding-top:5px;font-size:13px;">
         {% for act in activities %}
-            {% activity_item act %}   
+            {% activity_item act request.user %} 
         {% endfor %}
         </div>
 {% endblock %}
         {% endfor %}
         </div>
 {% endblock %}
index d295c21bfcf87b4db8fc034438e13fe5d53434b6..9be35c86b071ebab959c410b48dbc0ae5cbcc730 100644 (file)
@@ -34,7 +34,7 @@
                         <div style="float:left;width:20px;color:red">{{ rep.negative }}</div>
                         
                     </div>
                         <div style="float:left;width:20px;color:red">{{ rep.negative }}</div>
                         
                     </div>
-                    <a href="{{ rep.question.get_absolute_url }}">{{ rep.question.title }}</a> <span class="small">({{ rep.reputed_at }})</span>
+                    <a href="{{ rep.action.node.get_absolute_url }}">{{ rep.action.node.headline }}</a> <span class="small">({{ rep.date }})</span>
                 </p>
                 {% endfor %}
             </div>
                 </p>
                 {% endfor %}
             </div>
index 6381b59691726beee42c5537628384dbd3297737..adc6e12652151fb8728ed11d905308b0c830edf1 100644 (file)
             <table>
                 <tr>
                     <td width="180" style="line-height:35px">
             <table>
                 <tr>
                     <td width="180" style="line-height:35px">
-                        {% for award in awards %}
-                            <a href="{% url badges %}{{award.id}}/{{award.name}}" title="{{ award.description }}" class="medal"><span class="badge{{ award.type }}">&#9679;</span>&nbsp;{{ award.name }}</a><span class="tag-number"> &#215; {{ award.count|intcomma }}</span><br/>
+                        {% for award, count in awards %}
+                            <a href="{% url badges %}{{award.id}}/{{award.name}}" title="{{ award.description }}" class="medal"><span class="badge{{ award.type }}">&#9679;</span>&nbsp;{{ award.name }}</a><span class="tag-number"> &#215; {{ count|intcomma }}</span><br/>
                             {% if forloop.counter|divisibleby:"6" %}
                                 </td>
                                 <td width="180" style="line-height:35px">
                             {% if forloop.counter|divisibleby:"6" %}
                                 </td>
                                 <td width="180" style="line-height:35px">
index 9d187bf221c8e5cedbe1bff6b577f2bf00c50f72..a6cc64f365d0d2fd747ca7fff4514ca14c9ab986 100644 (file)
@@ -11,7 +11,7 @@
             <div style="clear:both;line-height:20px" >
                 <div style="width:150px;float:left">{% diff_date vote.voted_at 3 %}</div>
                 <div style="width:30px;float:left">
             <div style="clear:both;line-height:20px" >
                 <div style="width:150px;float:left">{% diff_date vote.voted_at 3 %}</div>
                 <div style="width:30px;float:left">
-                {% ifequal vote.vote 1 %}
+                {% ifequal vote.value 1 %}
                     <img src="{% media  "/media/images/vote-arrow-up-on.png" %}" title="{% trans "upvote" %}">
                 {% else %}
                     <img src="{% media  "/media/images/vote-arrow-down-on.png" %}" title="{% trans "downvote" %}">
                     <img src="{% media  "/media/images/vote-arrow-up-on.png" %}" title="{% trans "upvote" %}">
                 {% else %}
                     <img src="{% media  "/media/images/vote-arrow-down-on.png" %}" title="{% trans "downvote" %}">
index 71e33476fb34ded78fd9e96183d081d37441dc41..3a6425376a68cbd832b6b792e3420dbb28ad7796 100644 (file)
@@ -1,5 +1,3 @@
-import forum.activity
-import forum.reputation
 import forum.badges
 import forum.subscriptions
 
 import forum.badges
 import forum.subscriptions
 
index 4966ec65772c1b44683d6a770b07fccdd855acbe..e943d2d34de098f583a63465b76e4a0060c54100 100644 (file)
@@ -2,16 +2,11 @@ import os
 import re\r
 import datetime\r
 from forum.models import User, Question, Comment, QuestionSubscription, SubscriptionSettings, Answer\r
 import re\r
 import datetime\r
 from forum.models import User, Question, Comment, QuestionSubscription, SubscriptionSettings, Answer\r
-from forum.models.user import activity_record\r
-from forum.models.node import node_create\r
 from forum.utils.mail import send_email\r
 from forum.utils.mail import send_email\r
-from forum.views.readers import question_view\r
 from django.utils.translation import ugettext as _\r
 from django.utils.translation import ugettext as _\r
+from forum.actions import AskAction, AnswerAction, CommentAction, AcceptAnswerAction, UserJoinsAction, QuestionViewAction\r
 from django.conf import settings\r
 from django.db.models import Q, F\r
 from django.conf import settings\r
 from django.db.models import Q, F\r
-from django.db.models.signals import post_save\r
-from django.contrib.contenttypes.models import ContentType\r
-import const\r
 \r
 def create_subscription_if_not_exists(question, user):\r
     try:\r
 \r
 def create_subscription_if_not_exists(question, user):\r
     try:\r
@@ -26,8 +21,8 @@ def apply_default_filters(queryset, excluded_id):
 def create_recipients_dict(usr_list):\r
     return [(s['username'], s['email'], {'username': s['username']}) for s in usr_list]\r
 \r
 def create_recipients_dict(usr_list):\r
     return [(s['username'], s['email'], {'username': s['username']}) for s in usr_list]\r
 \r
-def question_posted(instance, **kwargs):\r
-    question = instance\r
+def question_posted(action, new):\r
+    question = action.node\r
 \r
     subscribers = User.objects.values('email', 'username').filter(\r
             Q(subscription_settings__enable_notifications=True, subscription_settings__new_question='i') |\r
 \r
     subscribers = User.objects.values('email', 'username').filter(\r
             Q(subscription_settings__enable_notifications=True, subscription_settings__new_question='i') |\r
@@ -56,11 +51,11 @@ def question_posted(instance, **kwargs):
     for user in new_subscribers:\r
         create_subscription_if_not_exists(question, user)\r
 \r
     for user in new_subscribers:\r
         create_subscription_if_not_exists(question, user)\r
 \r
-node_create.connect(question_posted, sender=Question)\r
+AskAction.hook(question_posted)\r
 \r
 \r
 \r
 \r
-def answer_posted(instance, **kwargs):\r
-    answer = instance\r
+def answer_posted(action, new):\r
+    answer = action.node\r
     question = answer.question\r
 \r
     subscribers = question.subscribers.values('email', 'username').filter(\r
     question = answer.question\r
 \r
     subscribers = question.subscribers.values('email', 'username').filter(\r
@@ -74,16 +69,16 @@ def answer_posted(instance, **kwargs):
                recipients, "notifications/newanswer.html", {\r
         'question': question,\r
         'answer': answer\r
                recipients, "notifications/newanswer.html", {\r
         'question': question,\r
         'answer': answer\r
-    })\r
+    }, threaded=False)\r
 \r
     if answer.author.subscription_settings.questions_answered:\r
         create_subscription_if_not_exists(question, answer.author)\r
 \r
 \r
     if answer.author.subscription_settings.questions_answered:\r
         create_subscription_if_not_exists(question, answer.author)\r
 \r
-node_create.connect(answer_posted, sender=Answer)\r
+AnswerAction.hook(answer_posted)\r
 \r
 \r
 \r
 \r
-def comment_posted(instance, **kwargs):\r
-    comment = instance\r
+def comment_posted(action, new):\r
+    comment = action.node\r
     post = comment.content_object\r
 \r
     if post.__class__ == Question:\r
     post = comment.content_object\r
 \r
     if post.__class__ == Question:\r
@@ -95,13 +90,13 @@ def comment_posted(instance, **kwargs):
 \r
     q_filter = Q(subscription_settings__notify_comments=True) | Q(subscription_settings__notify_comments_own_post=True, id=post.author.id)\r
 \r
 \r
     q_filter = Q(subscription_settings__notify_comments=True) | Q(subscription_settings__notify_comments_own_post=True, id=post.author.id)\r
 \r
-    inreply = re.search('@\w+', comment.comment)\r
-    if inreply is not None:\r
-        q_filter = q_filter | Q(subscription_settings__notify_reply_to_comments=True,\r
-                                username__istartswith=inreply.group(0)[1:],\r
-                                comments__object_id=post.id,\r
-                                comments__content_type=ContentType.objects.get_for_model(post.__class__)\r
-                                )\r
+    #inreply = re.search('@\w+', comment.comment)\r
+    #if inreply is not None:\r
+    #    q_filter = q_filter | Q(subscription_settings__notify_reply_to_comments=True,\r
+    #                            username__istartswith=inreply.group(0)[1:],\r
+    ##                            comments__object_id=post.id,\r
+    #                            comments__content_type=ContentType.objects.get_for_model(post.__class__)\r
+    #                            )\r
 \r
     subscribers = subscribers.filter(\r
             q_filter, subscription_settings__subscribed_questions='i', subscription_settings__enable_notifications=True\r
 \r
     subscribers = subscribers.filter(\r
             q_filter, subscription_settings__subscribed_questions='i', subscription_settings__enable_notifications=True\r
@@ -114,110 +109,89 @@ def comment_posted(instance, **kwargs):
                 'comment': comment,\r
                 'post': post,\r
                 'question': question,\r
                 'comment': comment,\r
                 'post': post,\r
                 'question': question,\r
-    })\r
+    }, threaded=False)\r
 \r
     if comment.user.subscription_settings.questions_commented:\r
         create_subscription_if_not_exists(question, comment.user)\r
 \r
 \r
     if comment.user.subscription_settings.questions_commented:\r
         create_subscription_if_not_exists(question, comment.user)\r
 \r
-node_create.connect(comment_posted, sender=Comment)\r
+CommentAction.hook(comment_posted)\r
 \r
 \r
 \r
 \r
-def answer_accepted(instance, created, **kwargs):\r
-    if not created and 'accepted' in instance.get_dirty_fields() and instance.accepted:\r
-        question = instance.question\r
+def answer_accepted(action, new):\r
+    question = action.node.question\r
 \r
 \r
-        subscribers = question.subscribers.values('email', 'username').filter(\r
-                subscription_settings__enable_notifications=True,\r
-                subscription_settings__notify_accepted=True,\r
-                subscription_settings__subscribed_questions='i'\r
-        ).exclude(id=instance.accepted_by.id).distinct()\r
-        recipients = create_recipients_dict(subscribers)\r
+    subscribers = question.subscribers.values('email', 'username').filter(\r
+            subscription_settings__enable_notifications=True,\r
+            subscription_settings__notify_accepted=True,\r
+            subscription_settings__subscribed_questions='i'\r
+    ).exclude(id=instance.accepted_by.id).distinct()\r
+    recipients = create_recipients_dict(subscribers)\r
 \r
 \r
-        send_email(settings.EMAIL_SUBJECT_PREFIX + _("An answer to '%(question_title)s' was accepted") % dict(question_title=question.title),\r
-                   recipients, "notifications/answeraccepted.html", {\r
-            'question': question,\r
-            'answer': instance\r
-        })\r
+    send_email(settings.EMAIL_SUBJECT_PREFIX + _("An answer to '%(question_title)s' was accepted") % dict(question_title=question.title),\r
+               recipients, "notifications/answeraccepted.html", {\r
+        'question': question,\r
+        'answer': action.node\r
+    }, threaded=False)\r
 \r
 \r
-post_save.connect(answer_accepted, sender=Answer)\r
+AcceptAnswerAction.hook(answer_accepted)\r
 \r
 \r
 \r
 \r
-def member_joined(sender, instance, created, **kwargs):\r
-    if not created:\r
-        return\r
-        \r
+def member_joined(action, new):\r
     subscribers = User.objects.values('email', 'username').filter(\r
             subscription_settings__enable_notifications=True,\r
             subscription_settings__member_joins='i'\r
     subscribers = User.objects.values('email', 'username').filter(\r
             subscription_settings__enable_notifications=True,\r
             subscription_settings__member_joins='i'\r
-    ).exclude(id=instance.id).distinct()\r
+    ).exclude(id=action.user.id).distinct()\r
 \r
     recipients = create_recipients_dict(subscribers)\r
 \r
     send_email(settings.EMAIL_SUBJECT_PREFIX + _("%(username)s is a new member on %(app_name)s") % dict(username=instance.username, app_name=settings.APP_SHORT_NAME),\r
                recipients, "notifications/newmember.html", {\r
 \r
     recipients = create_recipients_dict(subscribers)\r
 \r
     send_email(settings.EMAIL_SUBJECT_PREFIX + _("%(username)s is a new member on %(app_name)s") % dict(username=instance.username, app_name=settings.APP_SHORT_NAME),\r
                recipients, "notifications/newmember.html", {\r
-        'newmember': instance,\r
-    })\r
-\r
-    sub_settings = SubscriptionSettings(user=instance)\r
-    sub_settings.save()\r
+        'newmember': action.user,\r
+    }, threaded=False)\r
 \r
 \r
-post_save.connect(member_joined, sender=User, weak=False)\r
+UserJoinsAction.hook(member_joined)\r
 \r
 \r
-def question_viewed(instance, user, **kwargs):\r
-    if not user.is_authenticated():\r
+def question_viewed(action, new):\r
+    if not action.viewuser.is_authenticated():\r
         return\r
         return\r
-        \r
+\r
     try:\r
     try:\r
-        subscription = QuestionSubscription.objects.get(question=instance, user=user)\r
+        subscription = QuestionSubscription.objects.get(question=action.question, user=action.viewuser)\r
         subscription.last_view = datetime.datetime.now()\r
         subscription.save()\r
     except:\r
         subscription.last_view = datetime.datetime.now()\r
         subscription.save()\r
     except:\r
-        if user.subscription_settings.questions_viewed:\r
-            subscription = QuestionSubscription(question=instance, user=user)\r
+        if action.viewuser.subscription_settings.questions_viewed:\r
+            subscription = QuestionSubscription(question=action.question, user=action.viewuser)\r
             subscription.save()\r
 \r
             subscription.save()\r
 \r
-question_view.connect(question_viewed)\r
-\r
-#todo: this stuff goes temporarily here\r
-from forum.models import Award, Answer\r
-\r
-def notify_award_message(instance, created, **kwargs):\r
-    if created:\r
-        user = instance.user\r
-\r
-        msg = (u"Congratulations, you have received a badge '%s'. " \\r
-                + u"Check out <a href=\"%s\">your profile</a>.") \\r
-                % (instance.badge.name, user.get_profile_url())\r
-\r
-        user.message_set.create(message=msg)\r
+QuestionViewAction.hook(question_viewed)\r
 \r
 \r
-post_save.connect(notify_award_message, sender=Award)\r
 \r
 #todo: translate this\r
 \r
 #todo: translate this\r
-record_answer_event_re = re.compile("You have received (a|\d+) .*new response.*")\r
-def record_answer_event(instance, created, **kwargs):\r
-    if created:\r
-        q_author = instance.question.author\r
-        found_match = False\r
-        #print 'going through %d messages' % q_author.message_set.all().count()\r
-        for m in q_author.message_set.all():\r
-            #print m.message\r
-            match = record_answer_event_re.search(m.message)\r
-            if match:\r
-                found_match = True\r
-                try:\r
-                    cnt = int(match.group(1))\r
-                except:\r
-                    cnt = 1\r
-                m.message = u"You have received %d <a href=\"%s?sort=responses\">new responses</a>."\\r
-                            % (cnt+1, q_author.get_profile_url())\r
-\r
-                m.save()\r
-                break\r
-        if not found_match:\r
-            msg = u"You have received a <a href=\"%s?sort=responses\">new response</a>."\\r
-                    % q_author.get_profile_url()\r
-\r
-            q_author.message_set.create(message=msg)\r
-\r
-post_save.connect(record_answer_event, sender=Answer)
\ No newline at end of file
+#record_answer_event_re = re.compile("You have received (a|\d+) .*new response.*")\r
+#def record_answer_event(instance, created, **kwargs):\r
+#    if created:\r
+#        q_author = instance.question.author\r
+#        found_match = False\r
+#        #print 'going through %d messages' % q_author.message_set.all().count()\r
+#        for m in q_author.message_set.all():\r
+##            #print m.message\r
+# #           match = record_answer_event_re.search(m.message)\r
+#            if match:\r
+#                found_match = True\r
+#                try:\r
+#                    cnt = int(match.group(1))\r
+#                except:\r
+#                    cnt = 1\r
+##                m.message = u"You have received %d <a href=\"%s?sort=responses\">new responses</a>."\\r
+# #                           % (cnt+1, q_author.get_profile_url())\r
+#\r
+#                m.save()\r
+#                break\r
+#        if not found_match:\r
+#            msg = u"You have received a <a href=\"%s?sort=responses\">new response</a>."\\r
+#                    % q_author.get_profile_url()\r
+#\r
+#            q_author.message_set.create(message=msg)\r
+#\r
+#post_save.connect(record_answer_event, sender=Answer)
\ No newline at end of file
index 79d357d1a92e17deae769e1d8cb45bd313f32a62..822fdc1b94636c6f28c4620d08dd036b78f5b779 100644 (file)
@@ -233,6 +233,9 @@ def convert2tagname_list(question):
 
 @register.simple_tag
 def diff_date(date, limen=2):
 
 @register.simple_tag
 def diff_date(date, limen=2):
+    if not date:
+        return _('unknown')
+        
     now = datetime.datetime.now()#datetime(*time.localtime()[0:6])#???
     diff = now - date
     days = diff.days
     now = datetime.datetime.now()#datetime(*time.localtime()[0:6])#???
     diff = now - date
     days = diff.days
index a28e2c62f5299b79040fcacebba8d80927c95357..445f0cb6c3cfc1a81563129ff49bd3dff8266b08 100644 (file)
@@ -13,4 +13,4 @@ def recent_tags():
 \r
 @register.inclusion_tag('sidebar/recent_awards.html')\r
 def recent_awards():\r
 \r
 @register.inclusion_tag('sidebar/recent_awards.html')\r
 def recent_awards():\r
-    return {'awards': Award.objects.get_recent_awards()[:RECENT_AWARD_SIZE]}
\ No newline at end of file
+    return {'awards': Award.objects.order_by('-awarded_at')[:RECENT_AWARD_SIZE]}
\ No newline at end of file
index c0aeac3c7e916024282905647b8a48db6473ac39..a744d36cec83da77178458a6d0021a6fd2cab1e3 100644 (file)
@@ -1,26 +1,20 @@
 from datetime import datetime, timedelta\r
 \r
 from datetime import datetime, timedelta\r
 \r
-from forum.models import Question, FavoriteQuestion\r
+from forum.models import Question, Action\r
 from django.utils.translation import ugettext as _\r
 from django.core.urlresolvers import reverse\r
 from django import template\r
 from django.utils.translation import ugettext as _\r
 from django.core.urlresolvers import reverse\r
 from django import template\r
-from django.conf import settings\r
+from forum.actions import *\r
+from forum import settings\r
 \r
 register = template.Library()\r
 \r
 @register.inclusion_tag('node/vote_buttons.html')\r
 def vote_buttons(post, user):\r
 \r
 register = template.Library()\r
 \r
 @register.inclusion_tag('node/vote_buttons.html')\r
 def vote_buttons(post, user):\r
-    context = {\r
-        'post': post,\r
-        'user_vote': 'none'\r
-    }\r
+    context = dict(post=post, user_vote='none')\r
 \r
     if user.is_authenticated():\r
 \r
     if user.is_authenticated():\r
-        try:\r
-            vote = post.votes.get(user=user)\r
-            context['user_vote'] = vote.is_upvote() and 'up' or 'down'\r
-        except:\r
-            pass\r
+        context['user_vote'] = {1: 'up', -1: 'down', None: 'none'}[VoteAction.get_for(user, post)]\r
 \r
     return context\r
 \r
 \r
     return context\r
 \r
@@ -35,17 +29,15 @@ def accept_button(answer, user):
 @register.inclusion_tag('node/favorite_mark.html')\r
 def favorite_mark(question, user):\r
     try:\r
 @register.inclusion_tag('node/favorite_mark.html')\r
 def favorite_mark(question, user):\r
     try:\r
-        FavoriteQuestion.objects.get(question=question, user=user)\r
+        FavoriteAction.objects.get(node=question, user=user)\r
         favorited = True\r
     except:\r
         favorited = False\r
 \r
         favorited = True\r
     except:\r
         favorited = False\r
 \r
-    favorite_count = question.favorited_by.count()\r
-\r
-    return {'favorited': favorited, 'favorite_count': favorite_count, 'question': question}\r
+    return {'favorited': favorited, 'favorite_count': question.favorite_count, 'question': question}\r
 \r
 \r
-def post_control(text, url, command=False, title=""):\r
-    return {'text': text, 'url': url, 'command': command, 'title': title}\r
+def post_control(text, url, command=False, withprompt=False, title=""):\r
+    return {'text': text, 'url': url, 'command': command, 'withprompt': withprompt ,'title': title}\r
 \r
 @register.inclusion_tag('node/post_controls.html')\r
 def post_controls(post, user):\r
 \r
 @register.inclusion_tag('node/post_controls.html')\r
 def post_controls(post, user):\r
@@ -65,28 +57,32 @@ def post_controls(post, user):
 \r
         if post_type == 'question':\r
             if post.closed and user.can_reopen_question(post):\r
 \r
         if post_type == 'question':\r
             if post.closed and user.can_reopen_question(post):\r
-                controls.append(post_control(_('reopen'), reverse('reopen', kwargs={'id': post.id})))\r
+                controls.append(post_control(_('reopen'), reverse('reopen', kwargs={'id': post.id}), command=True))\r
             elif not post.closed and user.can_close_question(post):\r
             elif not post.closed and user.can_close_question(post):\r
-                controls.append(post_control(_('close'), reverse('close', kwargs={'id': post.id})))\r
+                controls.append(post_control(_('close'), reverse('close', kwargs={'id': post.id}), command=True, withprompt=True))\r
 \r
         if user.can_flag_offensive(post):\r
 \r
         if user.can_flag_offensive(post):\r
-            label = _('flag')\r
+            label = _('report')\r
             \r
             if user.can_view_offensive_flags(post):\r
             \r
             if user.can_view_offensive_flags(post):\r
-                label =  "%s (%d)" % (label, post.flaggeditems.count())\r
+                label =  "%s (%d)" % (label, post.flag_count)\r
 \r
             controls.append(post_control(label, reverse('flag_post', kwargs={'id': post.id}),\r
 \r
             controls.append(post_control(label, reverse('flag_post', kwargs={'id': post.id}),\r
-                    command=True, title=_("report as offensive (i.e containing spam, advertising, malicious text, etc.)")))\r
+                    command=True, withprompt=True, title=_("report as offensive (i.e containing spam, advertising, malicious text, etc.)")))\r
 \r
         if user.can_delete_post(post):\r
 \r
         if user.can_delete_post(post):\r
-            controls.append(post_control(_('delete'), reverse('delete_post', kwargs={'id': post.id}),\r
-                    command=True))\r
+            if post.deleted:\r
+                controls.append(post_control(_('undelete'), reverse('delete_post', kwargs={'id': post.id}),\r
+                        command=True))\r
+            else:\r
+                controls.append(post_control(_('delete'), reverse('delete_post', kwargs={'id': post.id}),\r
+                        command=True))\r
 \r
     return {'controls': controls}\r
 \r
 @register.inclusion_tag('node/comments.html')\r
 def comments(post, user):\r
 \r
     return {'controls': controls}\r
 \r
 @register.inclusion_tag('node/comments.html')\r
 def comments(post, user):\r
-    all_comments = post.comments.filter(deleted=False).order_by('added_at')\r
+    all_comments = post.comments.filter(deleted=None).order_by('added_at')\r
 \r
     if len(all_comments) <= 5:\r
         top_scorers = all_comments\r
 \r
     if len(all_comments) <= 5:\r
         top_scorers = all_comments\r
@@ -107,13 +103,10 @@ def comments(post, user):
             showing += 1\r
         \r
         if context['can_like']:\r
             showing += 1\r
         \r
         if context['can_like']:\r
-            try:\r
-                c.votes.get(user=user)\r
-                context['likes'] = True\r
-            except:\r
-                context['likes'] = False\r
+            context['likes'] = VoteAction.get_for(user, c) == 1\r
 \r
         context['user'] = c.user\r
 \r
         context['user'] = c.user\r
+        context['comment'] = c.comment\r
         context.update(dict(c.__dict__))\r
         comments.append(context)\r
 \r
         context.update(dict(c.__dict__))\r
         comments.append(context)\r
 \r
@@ -122,6 +115,9 @@ def comments(post, user):
         'post': post,\r
         'can_comment': user.can_comment(post),\r
         'max_length': settings.FORM_MAX_COMMENT_BODY,\r
         'post': post,\r
         'can_comment': user.can_comment(post),\r
         'max_length': settings.FORM_MAX_COMMENT_BODY,\r
+        'min_length': settings.FORM_MIN_COMMENT_BODY,\r
+        'show_gravatar': settings.FORM_GRAVATAR_IN_COMMENTS,\r
         'showing': showing,\r
         'total': len(all_comments),\r
         'showing': showing,\r
         'total': len(all_comments),\r
+        'user': user,\r
     }\r
     }\r
index cf35a2e0aff685e321544daa25ed84ee30f5dd57..8ab1bb2b4eb30d264ac6c83282a1ead667c90cde 100644 (file)
@@ -1,5 +1,6 @@
 from django import template\r
 from django.utils.translation import ugettext as _\r
 from django import template\r
 from django.utils.translation import ugettext as _\r
+from django.utils.safestring import mark_safe\r
 from forum import const\r
 \r
 register = template.Library()\r
 from forum import const\r
 \r
 register = template.Library()\r
@@ -30,42 +31,32 @@ def user_signature(parser, token):
 class ActivityNode(template.Node):\r
     template = template.loader.get_template('users/activity.html')\r
 \r
 class ActivityNode(template.Node):\r
     template = template.loader.get_template('users/activity.html')\r
 \r
-    def __init__(self, activity):\r
+    def __init__(self, activity, viewer):\r
         self.activity = template.Variable(activity)\r
         self.activity = template.Variable(activity)\r
+        self.viewer = template.Variable(viewer)\r
 \r
     def render(self, context):\r
         try:\r
 \r
     def render(self, context):\r
         try:\r
-            activity = self.activity.resolve(context)\r
-\r
-            context = {\r
-                'active_at': activity.active_at,\r
-                'description': activity.type_as_string,\r
-                'type': activity.activity_type,\r
-            }\r
-\r
-            if activity.activity_type == const.TYPE_ACTIVITY_PRIZE:\r
-                context['badge'] = True\r
-                context['title'] = activity.content_object.badge.name\r
-                context['url'] = activity.content_object.badge.get_absolute_url()\r
-                context['badge_type'] = activity.content_object.badge.type\r
-            else:\r
-                context['title'] = activity.node.headline\r
-                context['url'] = activity.node.get_absolute_url()\r
-\r
-            if activity.activity_type in (const.TYPE_ACTIVITY_UPDATE_ANSWER, const.TYPE_ACTIVITY_UPDATE_QUESTION):\r
-                context['revision'] = True\r
-                context['summary'] = activity.content_object.summary or \\r
-                        _('Revision n. %(rev_number)d') % {'rev_number': activity.content_object.revision}\r
-\r
-            return self.template.render(template.Context(context))\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
         except Exception, e:\r
-            return ''\r
+            #import sys, traceback\r
+            #traceback.print_exc(file=sys.stdout)\r
+            pass\r
 \r
 @register.tag\r
 def activity_item(parser, token):\r
     try:\r
 \r
 @register.tag\r
 def activity_item(parser, token):\r
     try:\r
-        tag_name, activity = token.split_contents()\r
+        tag_name, activity, viewer = token.split_contents()\r
     except ValueError:\r
     except ValueError:\r
-        raise template.TemplateSyntaxError, "%r tag requires exactly one arguments" % token.contents.split()[0]\r
+        raise template.TemplateSyntaxError, "%r tag requires exactly two arguments" % token.contents.split()[0]\r
+\r
+    return ActivityNode(activity, viewer)\r
+\r
 \r
 \r
-    return ActivityNode(activity)\r
+@register.inclusion_tag('users/moderation.html')\r
+def user_moderation(moderator, user):\r
+    \r
+    return dict(user=user)\r
index 8bf1fb47ea8875369459f21f8eeaf90067f29f02..7975644e9f6207d83d40f6005cc05e62203f75a4 100644 (file)
@@ -56,8 +56,8 @@ urlpatterns += patterns('',
     url(r'^%s%s$' % (_('questions/'), _('ask/')), app.writers.ask, name='ask'),
     url(r'^%s%s$' % (_('questions/'), _('unanswered/')), app.readers.unanswered, name='unanswered'),
     url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('edit/')), app.writers.edit_question, name='edit_question'),
     url(r'^%s%s$' % (_('questions/'), _('ask/')), app.writers.ask, name='ask'),
     url(r'^%s%s$' % (_('questions/'), _('unanswered/')), app.readers.unanswered, name='unanswered'),
     url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('edit/')), app.writers.edit_question, name='edit_question'),
-    url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('close/')), app.commands.close, name='close'),
-    url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('reopen/')), app.commands.reopen, name='reopen'),
+    url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('close/')), app.commands.close, kwargs=dict(close=True), name='close'),
+    url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('reopen/')), app.commands.close, kwargs=dict(close=False), name='reopen'),
     url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('answer/')), app.writers.answer, name='answer'),
 
     url(r'^%s(?P<id>\d+)/(?P<vote_type>[a-z]+)/' % _('vote/'), app.commands.vote_post, name='vote_post'),
     url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('answer/')), app.writers.answer, name='answer'),
 
     url(r'^%s(?P<id>\d+)/(?P<vote_type>[a-z]+)/' % _('vote/'), app.commands.vote_post, name='vote_post'),
@@ -70,9 +70,9 @@ urlpatterns += patterns('',
     url(r'^%s(?P<id>\d+)/' % _('delete/'), app.commands.delete_post, name='delete_post'),
     url(r'^%s(?P<id>\d+)/$' % _('subscribe/'), app.commands.subscribe, name="subscribe"),
     url(r'^%s' % _('matching_tags/'), app.commands.matching_tags, name='matching_tags'),
     url(r'^%s(?P<id>\d+)/' % _('delete/'), app.commands.delete_post, name='delete_post'),
     url(r'^%s(?P<id>\d+)/$' % _('subscribe/'), app.commands.subscribe, name="subscribe"),
     url(r'^%s' % _('matching_tags/'), app.commands.matching_tags, name='matching_tags'),
-    
+    url(r'^%s(?P<id>\d+)/' % _('node_markdown/'), app.commands.node_markdown, name='node_markdown'),
+
     url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('revisions/')), app.readers.revisions, name='question_revisions'),
     url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('revisions/')), app.readers.revisions, name='question_revisions'),
-    url(r'^%s$' % _('command/'), app.commands.ajax_command, name='call_ajax'),
 
     #place general question item in the end of other operations
     url(r'^%s(?P<id>\d+)/(?P<slug>[\w-]*)$' % _('question/'), app.readers.question, name='question'),
 
     #place general question item in the end of other operations
     url(r'^%s(?P<id>\d+)/(?P<slug>[\w-]*)$' % _('question/'), app.readers.question, name='question'),
@@ -94,7 +94,6 @@ urlpatterns += patterns('',
 
 
     url(r'^%s$' % _('users/'),app.users.users, name='users'),
 
 
     url(r'^%s$' % _('users/'),app.users.users, name='users'),
-    url(r'^%s(?P<id>\d+)/$' % _('moderate-user/'), app.users.moderate_user, name='moderate_user'),
     url(r'^%s(?P<id>\d+)/%s$' % (_('users/'), _('edit/')), app.users.edit_user, name='edit_user'),
 
     url(r'^%s(?P<id>\d+)/(?P<slug>.+)/%s$' % (_('users/'), _('subscriptions/')), app.users.user_subscriptions, name='user_subscriptions'),
     url(r'^%s(?P<id>\d+)/%s$' % (_('users/'), _('edit/')), app.users.edit_user, name='edit_user'),
 
     url(r'^%s(?P<id>\d+)/(?P<slug>.+)/%s$' % (_('users/'), _('subscriptions/')), app.users.user_subscriptions, name='user_subscriptions'),
@@ -105,8 +104,7 @@ urlpatterns += patterns('',
     url(r'^%s(?P<id>\d+)/(?P<slug>.+)/$' % _('users/'), app.users.user_stats, name='user_profile'),
     
     url(r'^%s$' % _('badges/'),app.meta.badges, name='badges'),
     url(r'^%s(?P<id>\d+)/(?P<slug>.+)/$' % _('users/'), app.users.user_stats, name='user_profile'),
     
     url(r'^%s$' % _('badges/'),app.meta.badges, name='badges'),
-    url(r'^%s(?P<id>\d+)//*' % _('badges/'), app.meta.badge, name='badge'),
-    url(r'^%s%s$' % (_('messages/'), _('markread/')),app.commands.read_message, name='read_message'),
+    url(r'^%s(?P<id>\d+)/(?P<slug>.+)/$' % _('badges/'), app.meta.badge, name='badge'),
     # (r'^admin/doc/' % _('admin/doc'), include('django.contrib.admindocs.urls')),
     url(r'^%s(.*)' % _('nimda/'), admin.site.root, name='osqa_admin'),
     url(r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed', {'feed_dict': feeds}, name='feeds'),
     # (r'^admin/doc/' % _('admin/doc'), include('django.contrib.admindocs.urls')),
     url(r'^%s(.*)' % _('nimda/'), admin.site.root, name='osqa_admin'),
     url(r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed', {'feed_dict': feeds}, name='feeds'),
index 421fbc94a66a8a8634948f62f61fcf7deabeac8c..490244fde8419563459c9bb80b22edef0e9d3917 100644 (file)
@@ -10,7 +10,7 @@ from django.db.models import Sum
 from forum.settings.base import Setting
 from forum.settings.forms import SettingsSetForm
 
 from forum.settings.base import Setting
 from forum.settings.forms import SettingsSetForm
 
-from forum.models import Activity, Question, Answer, User, Node
+from forum.models import Question, Answer, User, Node, Action
 from forum import const
 from forum import settings
 
 from forum import const
 from forum import settings
 
@@ -61,19 +61,16 @@ def get_all_sets():
     return sorted(Setting.sets.values(), lambda s1, s2: s1.weight - s2.weight)
 
 def get_recent_activity():
     return sorted(Setting.sets.values(), lambda s1, s2: s1.weight - s2.weight)
 
 def get_recent_activity():
-    return Activity.objects.filter(activity_type__in=(
-            const.TYPE_ACTIVITY_ASK_QUESTION, const.TYPE_ACTIVITY_ANSWER,
-            const.TYPE_ACTIVITY_COMMENT_QUESTION, const.TYPE_ACTIVITY_COMMENT_ANSWER,
-            const.TYPE_ACTIVITY_MARK_ANSWER)).order_by('-active_at')[0:10]
+    return Action.objects.order_by('-action_date')[0:30]
 
 def get_statistics():
     return {
         'total_users': User.objects.all().count(),
         'users_last_24': User.objects.filter(date_joined__gt=(datetime.now() - timedelta(days=1))).count(),
 
 def get_statistics():
     return {
         'total_users': User.objects.all().count(),
         'users_last_24': User.objects.filter(date_joined__gt=(datetime.now() - timedelta(days=1))).count(),
-        'total_questions': Question.objects.filter(deleted=False).count(),
-        'questions_last_24': Question.objects.filter(deleted=False, added_at__gt=(datetime.now() - timedelta(days=1))).count(),
-        'total_answers': Answer.objects.filter(deleted=False).count(),
-        'answers_last_24': Answer.objects.filter(deleted=False, added_at__gt=(datetime.now() - timedelta(days=1))).count(),
+        'total_questions': Question.objects.filter(deleted=None).count(),
+        'questions_last_24': Question.objects.filter(deleted=None, added_at__gt=(datetime.now() - timedelta(days=1))).count(),
+        'total_answers': Answer.objects.filter(deleted=None).count(),
+        'answers_last_24': Answer.objects.filter(deleted=None, added_at__gt=(datetime.now() - timedelta(days=1))).count(),
     }
 
 @super_user_required      
     }
 
 @super_user_required      
index cda0a81465011bcae70dc8f1d62a73c90ffad685..0d4e2f3e6813239edc18bf23d58be8d1ac1b83b6 100644 (file)
@@ -17,9 +17,10 @@ from forum.authentication.forms import SimpleRegistrationForm, SimpleEmailSubscr
 from forum.utils.mail import send_email
 
 from forum.authentication.base import InvalidAuthentication
 from forum.utils.mail import send_email
 
 from forum.authentication.base import InvalidAuthentication
-from forum.authentication import AUTH_PROVIDERS, user_logged_in
+from forum.authentication import AUTH_PROVIDERS
 
 from forum.models import AuthKeyUserAssociation, ValidationHash, Question, Answer
 
 from forum.models import AuthKeyUserAssociation, ValidationHash, Question, Answer
+from forum.actions import UserJoinsAction
 
 def signin_page(request, action=None):
     if action is None:
 
 def signin_page(request, action=None):
     if action is None:
@@ -146,6 +147,7 @@ def external_register(request):
                 user_.is_superuser = True
             
             user_.save()
                 user_.is_superuser = True
             
             user_.save()
+            UserJoinsAction(user=user_, ip=request.META['REMOTE_ADDR']).save()
 
             if not user_.email_isvalid:
                 send_validation_email(user_)
 
             if not user_.email_isvalid:
                 send_validation_email(user_)
@@ -157,7 +159,7 @@ def external_register(request):
                 request.session['auth_error'] = _("Oops, something went wrong in the middle of this process. Please try again.")
                 return HttpResponseRedirect(request.session.get('on_signin_url', reverse('auth_signin'))) 
 
                 request.session['auth_error'] = _("Oops, something went wrong in the middle of this process. Please try again.")
                 return HttpResponseRedirect(request.session.get('on_signin_url', reverse('auth_signin'))) 
 
-            uassoc = AuthKeyUserAssociation(user=user_, key=request.session['assoc_key'], provider=request.session['auth_provider'])
+            uassoc = AuthKeyUserAssociation(user=user_, key=assoc_key, provider=auth_provider)
             uassoc.save()
 
             if email_feeds_form.cleaned_data['subscribe'] == 'n':
             uassoc.save()
 
             if email_feeds_form.cleaned_data['subscribe'] == 'n':
@@ -336,7 +338,7 @@ def login_and_forward(request,  user, forward=None, message=None):
     user.backend = "django.contrib.auth.backends.ModelBackend"
     login(request,  user)
 
     user.backend = "django.contrib.auth.backends.ModelBackend"
     login(request,  user)
 
-    user_logged_in.send(user=user,old_session=old_session,sender=None)
+    #user_logged_in.send(user=user,old_session=old_session,sender=None)
 
     if not forward:
         signin_action = request.session.get('on_signin_action', None)
 
     if not forward:
         signin_action = request.session.get('on_signin_action', None)
index d0599b330af754b83d9d49163c1a06fa320d3e9f..8591930c7ae2233543ae7c3ac15d1466fbaf6cc1 100644 (file)
@@ -8,13 +8,15 @@ from django.utils.translation import ungettext, ugettext as _
 from django.template import RequestContext
 from forum.models import *
 from forum.forms import CloseForm
 from django.template import RequestContext
 from forum.models import *
 from forum.forms import CloseForm
+from forum.actions import *
 from django.core.urlresolvers import reverse
 from django.contrib.auth.decorators import login_required
 from forum.utils.decorators import ajax_method, ajax_login_required
 from django.core.urlresolvers import reverse
 from django.contrib.auth.decorators import login_required
 from forum.utils.decorators import ajax_method, ajax_login_required
-from decorators import command
+from decorators import command, CommandException
+from forum import settings
 import logging
 
 import logging
 
-class NotEnoughRepPointsException(Exception):
+class NotEnoughRepPointsException(CommandException):
     def __init__(self, action):
         super(NotEnoughRepPointsException, self).__init__(
             _("""
     def __init__(self, action):
         super(NotEnoughRepPointsException, self).__init__(
             _("""
@@ -23,7 +25,7 @@ class NotEnoughRepPointsException(Exception):
             """ % {'action': action, 'faq_url': reverse('faq')})
         )
 
             """ % {'action': action, 'faq_url': reverse('faq')})
         )
 
-class CannotDoOnOwnException(Exception):
+class CannotDoOnOwnException(CommandException):
     def __init__(self, action):
         super(CannotDoOnOwnException, self).__init__(
             _("""
     def __init__(self, action):
         super(CannotDoOnOwnException, self).__init__(
             _("""
@@ -32,7 +34,7 @@ class CannotDoOnOwnException(Exception):
             """ % {'action': action, 'faq_url': reverse('faq')})
         )
 
             """ % {'action': action, 'faq_url': reverse('faq')})
         )
 
-class AnonymousNotAllowedException(Exception):
+class AnonymousNotAllowedException(CommandException):
     def __init__(self, action):
         super(AnonymousNotAllowedException, self).__init__(
             _("""
     def __init__(self, action):
         super(AnonymousNotAllowedException, self).__init__(
             _("""
@@ -41,13 +43,13 @@ class AnonymousNotAllowedException(Exception):
             """ % {'action': action, 'signin_url': reverse('auth_signin')})
         )
 
             """ % {'action': action, 'signin_url': reverse('auth_signin')})
         )
 
-class SpamNotAllowedException(Exception):
+class SpamNotAllowedException(CommandException):
     def __init__(self, action = "comment"):
         super(SpamNotAllowedException, self).__init__(
             _("""Your %s has been marked as spam.""" % action)
         )
 
     def __init__(self, action = "comment"):
         super(SpamNotAllowedException, self).__init__(
             _("""Your %s has been marked as spam.""" % action)
         )
 
-class NotEnoughLeftException(Exception):
+class NotEnoughLeftException(CommandException):
     def __init__(self, action, limit):
         super(NotEnoughLeftException, self).__init__(
             _("""
     def __init__(self, action, limit):
         super(NotEnoughLeftException, self).__init__(
             _("""
@@ -57,7 +59,7 @@ class NotEnoughLeftException(Exception):
             """ % {'action': action, 'limit': limit, 'faq_url': reverse('faq')})
         )
 
             """ % {'action': action, 'limit': limit, 'faq_url': reverse('faq')})
         )
 
-class CannotDoubleActionException(Exception):
+class CannotDoubleActionException(CommandException):
     def __init__(self, action):
         super(CannotDoubleActionException, self).__init__(
             _("""
     def __init__(self, action):
         super(CannotDoubleActionException, self).__init__(
             _("""
@@ -70,7 +72,6 @@ class CannotDoubleActionException(Exception):
 @command
 def vote_post(request, id, vote_type):
     post = get_object_or_404(Node, id=id).leaf
 @command
 def vote_post(request, id, vote_type):
     post = get_object_or_404(Node, id=id).leaf
-    vote_score = vote_type == 'up' and 1 or -1
     user = request.user
 
     if not user.is_authenticated():
     user = request.user
 
     if not user.is_authenticated():
@@ -87,30 +88,37 @@ def vote_post(request, id, vote_type):
     if user_vote_count_today >= int(settings.MAX_VOTES_PER_DAY):
         raise NotEnoughLeftException(_('votes'), str(settings.MAX_VOTES_PER_DAY))
 
     if user_vote_count_today >= int(settings.MAX_VOTES_PER_DAY):
         raise NotEnoughLeftException(_('votes'), str(settings.MAX_VOTES_PER_DAY))
 
+    new_vote_cls = (vote_type == 'up') and VoteUpAction or VoteDownAction
+    score_inc = 0
+
     try:
     try:
-        vote = post.votes.get(canceled=False, user=user)
+        old_vote = Action.objects.get_for_types((VoteUpAction, VoteDownAction), node=post, user=user)
 
 
-        if vote.voted_at < datetime.datetime.now() - datetime.timedelta(days=int(settings.DENY_UNVOTE_DAYS)):
-            raise Exception(
+        if old_vote.action_date < datetime.datetime.now() - datetime.timedelta(days=int(settings.DENY_UNVOTE_DAYS)):
+            raise CommandException(
                     _("Sorry but you cannot cancel a vote after %(ndays)d %(tdays)s from the original vote") %
                     {'ndays': int(settings.DENY_UNVOTE_DAYS), 'tdays': ungettext('day', 'days', int(settings.DENY_UNVOTE_DAYS))}
             )
 
                     _("Sorry but you cannot cancel a vote after %(ndays)d %(tdays)s from the original vote") %
                     {'ndays': int(settings.DENY_UNVOTE_DAYS), 'tdays': ungettext('day', 'days', int(settings.DENY_UNVOTE_DAYS))}
             )
 
-        vote.cancel()
-        vote_type = 'none'
+        old_vote.cancel(ip=request.META['REMOTE_ADDR'])
+        score_inc += (old_vote.__class__ == VoteDownAction) and 1 or -1
     except ObjectDoesNotExist:
     except ObjectDoesNotExist:
-        #there is no vote yet
-        vote = Vote(user=user, node=post, vote=vote_score)
-        vote.save()
+        old_vote = None
+
+    if old_vote.__class__ != new_vote_cls:
+        new_vote_cls(user=user, node=post, ip=request.META['REMOTE_ADDR']).save()
+        score_inc += (new_vote_cls == VoteUpAction) and 1 or -1
+    else:
+        vote_type = "none"
 
     response = {
         'commands': {
 
     response = {
         'commands': {
-            'update_post_score': [id, vote.vote * (vote_type == 'none' and -1 or 1)],
+            'update_post_score': [id, score_inc],
             'update_user_post_vote': [id, vote_type]
         }
     }
 
             'update_user_post_vote': [id, vote_type]
         }
     }
 
-    votes_left = int(settings.MAX_VOTES_PER_DAY) - user_vote_count_today + (vote_type == 'none' and -1 or 1)
+    votes_left = (int(settings.MAX_VOTES_PER_DAY) - user_vote_count_today) + (vote_type == 'none' and -1 or 1)
 
     if int(settings.START_WARN_VOTES_LEFT) >= votes_left:
         response['message'] = _("You have %(nvotes)s %(tvotes)s left today.") % \
 
     if int(settings.START_WARN_VOTES_LEFT) >= votes_left:
         response['message'] = _("You have %(nvotes)s %(tvotes)s left today.") % \
@@ -120,6 +128,9 @@ def vote_post(request, id, vote_type):
 
 @command
 def flag_post(request, id):
 
 @command
 def flag_post(request, id):
+    if not request.POST:
+        return render_to_response('node/report.html', {'types': settings.FLAG_TYPES})
+
     post = get_object_or_404(Node, id=id)
     user = request.user
 
     post = get_object_or_404(Node, id=id)
     user = request.user
 
@@ -138,13 +149,17 @@ def flag_post(request, id):
         raise NotEnoughLeftException(_('flags'), str(settings.MAX_FLAGS_PER_DAY))
 
     try:
         raise NotEnoughLeftException(_('flags'), str(settings.MAX_FLAGS_PER_DAY))
 
     try:
-        post.flaggeditems.get(user=user)
-        raise CannotDoubleActionException(_('flag'))
+        current = FlagAction.objects.get(user=user, node=post)
+        raise CommandException(_("You already flagged this post with the following reason: %(reason)s") % {'reason': current.extra})
     except ObjectDoesNotExist:
     except ObjectDoesNotExist:
-        flag = FlaggedItem(user=user, content_object=post)
-        flag.save()
+        reason = request.POST.get('prompt', '').strip()
 
 
-    return {}
+        if not len(reason):
+            raise CommandException(_("Reason is empty"))
+
+        FlagAction(user=user, node=post, extra=reason, ip=request.META['REMOTE_ADDR']).save()
+
+    return {'message': _("Thank you for your report. A moderator will review your submission shortly.")}
         
 @command
 def like_comment(request, id):
         
 @command
 def like_comment(request, id):
@@ -161,18 +176,17 @@ def like_comment(request, id):
         raise NotEnoughRepPointsException( _('like comments'))    
 
     try:
         raise NotEnoughRepPointsException( _('like comments'))    
 
     try:
-        like = LikedComment.active.get(comment=comment, user=user)
-        like.cancel()
+        like = VoteUpCommentAction.objects.get(node=comment, user=user)
+        like.cancel(ip=request.META['REMOTE_ADDR'])
         likes = False
     except ObjectDoesNotExist:
         likes = False
     except ObjectDoesNotExist:
-        like = LikedComment(comment=comment, user=user)
-        like.save()
+        VoteUpCommentAction(node=comment, user=user, ip=request.META['REMOTE_ADDR']).save()
         likes = True
 
     return {
         'commands': {
         likes = True
 
     return {
         'commands': {
-            'update_comment_score': [comment.id, likes and 1 or -1],
-            'update_likes_comment_mark': [comment.id, likes and 'on' or 'off']
+            'update_post_score': [comment.id, likes and 1 or -1],
+            'update_user_post_vote': [comment.id, likes and 'up' or 'none']
         }
     }
 
         }
     }
 
@@ -187,7 +201,8 @@ def delete_comment(request, id):
     if not user.can_delete_comment(comment):
         raise NotEnoughRepPointsException( _('delete comments'))
 
     if not user.can_delete_comment(comment):
         raise NotEnoughRepPointsException( _('delete comments'))
 
-    comment.mark_deleted(user)
+    if not comment.deleted:
+        DeleteAction(node=comment, user=user, ip=request.META['REMOTE_ADDR']).save()
 
     return {
         'commands': {
 
     return {
         'commands': {
@@ -203,12 +218,11 @@ def mark_favorite(request, id):
         raise AnonymousNotAllowedException(_('mark a question as favorite'))
 
     try:
         raise AnonymousNotAllowedException(_('mark a question as favorite'))
 
     try:
-        favorite = FavoriteQuestion.objects.get(question=question, user=request.user)
-        favorite.delete()
+        favorite = FavoriteAction.objects.get(node=question, user=request.user)
+        favorite.cancel(ip=request.META['REMOTE_ADDR'])
         added = False
     except ObjectDoesNotExist:
         added = False
     except ObjectDoesNotExist:
-        favorite = FavoriteQuestion(question=question, user=request.user)
-        favorite.save()
+        FavoriteAction(node=question, user=request.user, ip=request.META['REMOTE_ADDR']).save()
         added = True
 
     return {
         added = True
 
     return {
@@ -227,33 +241,23 @@ def comment(request, id):
         raise AnonymousNotAllowedException(_('comment'))
 
     if not request.method == 'POST':
         raise AnonymousNotAllowedException(_('comment'))
 
     if not request.method == 'POST':
-        raise Exception(_("Invalid request"))
-
-    if 'id' in request.POST:
-        comment = get_object_or_404(Comment, id=request.POST['id'])
-
-        if not user.can_edit_comment(comment):
-            raise NotEnoughRepPointsException( _('edit comments'))
-    else:
-        if not user.can_comment(post):
-            raise NotEnoughRepPointsException( _('comment'))
-
-        comment = Comment(parent=post)
+        raise CommandException(_("Invalid request"))
 
     comment_text = request.POST.get('comment', '').strip()
 
     if not len(comment_text):
 
     comment_text = request.POST.get('comment', '').strip()
 
     if not len(comment_text):
-        raise Exception(_("Comment is empty"))
+        raise CommandException(_("Comment is empty"))
 
 
-    if not len(comment_text) > settings.FORM_MIN_COMMENT_BODY:
-        raise Exception(_("Comment must be at least %s characters" % settings.FORM_MIN_COMMENT_BODY))
+    if len(comment_text) < settings.FORM_MIN_COMMENT_BODY:
+        raise CommandException(_("At least %d characters required on comment body.") % settings.FORM_MIN_COMMENT_BODY)
 
 
-    comment.create_revision(user, body=comment_text)
+    if len(comment_text) > settings.FORM_MAX_COMMENT_BODY:
+        raise CommandException(_("No more than %d characters on comment body.") % settings.FORM_MAX_COMMENT_BODY)
 
     data = {
         "user_ip":request.META["REMOTE_ADDR"],
         "user_agent":request.environ['HTTP_USER_AGENT'],
 
     data = {
         "user_ip":request.META["REMOTE_ADDR"],
         "user_agent":request.environ['HTTP_USER_AGENT'],
-        "comment_author":request.user.real_name,
+        "comment_author":request.user.username,
         "comment_author_email":request.user.email,
         "comment_author_url":request.user.website,
         "comment":comment_text
         "comment_author_email":request.user.email,
         "comment_author_url":request.user.website,
         "comment":comment_text
@@ -261,11 +265,25 @@ def comment(request, id):
     if Node.isSpam(comment_text, data):
         raise SpamNotAllowedException()
 
     if Node.isSpam(comment_text, data):
         raise SpamNotAllowedException()
 
+    if 'id' in request.POST:
+        comment = get_object_or_404(Comment, id=request.POST['id'])
+
+        if not user.can_edit_comment(comment):
+            raise NotEnoughRepPointsException( _('edit comments'))
+
+        comment = ReviseAction(user=user, node=comment, ip=request.META['REMOTE_ADDR']).save(data=dict(text=comment_text)).node
+    else:
+        if not user.can_comment(post):
+            raise NotEnoughRepPointsException( _('comment'))
+
+        comment = CommentAction(user=user, ip=request.META['REMOTE_ADDR']).save(data=dict(text=comment_text, parent=post)).node
+
     if comment.active_revision.revision == 1:
         return {
             'commands': {
                 'insert_comment': [
     if comment.active_revision.revision == 1:
         return {
             'commands': {
                 'insert_comment': [
-                    id, comment.id, comment_text, user.username, user.get_profile_url(), reverse('delete_comment', kwargs={'id': comment.id})
+                    id, comment.id, comment.comment, user.username, user.get_profile_url(),
+                        reverse('delete_comment', kwargs={'id': comment.id}), reverse('node_markdown', kwargs={'id': comment.id})
                 ]
             }
         }
                 ]
             }
         }
@@ -276,6 +294,16 @@ def comment(request, id):
             }
         }
 
             }
         }
 
+@command
+def node_markdown(request, id):
+    user = request.user
+
+    if not user.is_authenticated():
+        raise AnonymousNotAllowedException(_('accept answers'))
+
+    node = get_object_or_404(Node, id=id)
+    return HttpResponse(node.body, mimetype="text/plain")
+
 
 @command
 def accept_answer(request, id):
 
 @command
 def accept_answer(request, id):
@@ -288,20 +316,20 @@ def accept_answer(request, id):
     question = answer.question
 
     if not user.can_accept_answer(answer):
     question = answer.question
 
     if not user.can_accept_answer(answer):
-        raise Exception(_("Sorry but only the question author can accept an answer"))
+        raise CommandException(_("Sorry but only the question author can accept an answer"))
 
     commands = {}
 
     if answer.accepted:
 
     commands = {}
 
     if answer.accepted:
-        answer.unmark_accepted(user)
+        answer.accepted.cancel(user, ip=request.META['REMOTE_ADDR'])
         commands['unmark_accepted'] = [answer.id]
     else:
         commands['unmark_accepted'] = [answer.id]
     else:
-        if question.accepted_answer is not None:
+        if question.answer_accepted:
             accepted = question.accepted_answer
             accepted = question.accepted_answer
-            accepted.unmark_accepted(user)
+            accepted.accepted.cancel(user, ip=request.META['REMOTE_ADDR'])
             commands['unmark_accepted'] = [accepted.id]
 
             commands['unmark_accepted'] = [accepted.id]
 
-        answer.mark_accepted(user)
+        AcceptAnswerAction(node=answer, user=user, ip=request.META['REMOTE_ADDR']).save()
         commands['mark_accepted'] = [answer.id]
 
     return {'commands': commands}
         commands['mark_accepted'] = [answer.id]
 
     return {'commands': commands}
@@ -317,12 +345,49 @@ def delete_post(request, id):
     if not (user.can_delete_post(post)):
         raise NotEnoughRepPointsException(_('delete posts'))
 
     if not (user.can_delete_post(post)):
         raise NotEnoughRepPointsException(_('delete posts'))
 
-    post.mark_deleted(user)
+    ret = {'commands': {}}
+
+    if post.deleted:
+        post.deleted.cancel(user, ip=request.META['REMOTE_ADDR'])
+        ret['commands']['unmark_deleted'] = [post.node_type, id]
+    else:
+        DeleteAction(node=post, user=user, ip=request.META['REMOTE_ADDR']).save()
+
+        ret['commands']['mark_deleted'] = [post.node_type, id]
+
+    return ret
+
+@command
+def close(request, id, close):
+    if close and not request.POST:
+        return render_to_response('node/report.html', {'types': settings.CLOSE_TYPES})
+
+    question = get_object_or_404(Question, id=id)
+    user = request.user
+
+    if not user.is_authenticated():
+        raise AnonymousNotAllowedException(_('close questions'))
+
+    if question.extra_action:
+        if not user.can_reopen_question(question):
+            raise NotEnoughRepPointsException(_('reopen questions'))
+
+        question.extra_action.cancel(user, ip=request.META['REMOTE_ADDR'])
+    else:
+        if not request.user.can_close_question(question):
+            raise NotEnoughRepPointsException(_('close questions'))
+
+        reason = request.POST.get('prompt', '').strip()
+
+        if not len(reason):
+            raise CommandException(_("Reason is empty"))
+
+        CloseAction(node=question, user=user, extra=reason, ip=request.META['REMOTE_ADDR']).save()
 
     return {
         'commands': {
 
     return {
         'commands': {
-                'mark_deleted': [post.node_type, id]
-            }
+            'refresh_page': []
+        }
     }
 
 @command
     }
 
 @command
@@ -368,7 +433,7 @@ def mark_tag(request, tag=None, **kwargs):#tagging system
 
 def matching_tags(request):
     if len(request.GET['q']) == 0:
 
 def matching_tags(request):
     if len(request.GET['q']) == 0:
-       raise Exception(_("Invalid request"))
+       raise CommandException(_("Invalid request"))
 
     possible_tags = Tag.objects.filter(name__istartswith = request.GET['q'])
     tag_output = ''
 
     possible_tags = Tag.objects.filter(name__istartswith = request.GET['q'])
     tag_output = ''
@@ -377,72 +442,9 @@ def matching_tags(request):
         
     return HttpResponse(tag_output, mimetype="text/plain")
 
         
     return HttpResponse(tag_output, mimetype="text/plain")
 
-@ajax_login_required
-def ajax_toggle_ignored_questions(request):#ajax tagging and tag-filtering system
-    if request.user.hide_ignored_questions:
-        new_hide_setting = False
-    else:
-        new_hide_setting = True
-    request.user.hide_ignored_questions = new_hide_setting
-    request.user.save()
-
-@ajax_method
-def ajax_command(request):#refactor? view processing ajax commands - note "vote" and view others do it too
-    if 'command' not in request.POST:
-        return HttpResponseForbidden(mimetype="application/json")
-    if request.POST['command'] == 'toggle-ignored-questions':
-        return ajax_toggle_ignored_questions(request)
-
-@login_required
-def close(request, id):#close question
-    """view to initiate and process 
-    question close
-    """
-    question = get_object_or_404(Question, id=id)
-    if not request.user.can_close_question(question):
-        return HttpResponseForbidden()
-    if request.method == 'POST':
-        form = CloseForm(request.POST)
-        if form.is_valid():
-            reason = form.cleaned_data['reason']
-            question.closed = True
-            question.closed_by = request.user
-            question.closed_at = datetime.datetime.now()
-            question.close_reason = reason
-            question.save()
-        return HttpResponseRedirect(question.get_absolute_url())
-    else:
-        form = CloseForm()
-        return render_to_response('close.html', {
-            'form' : form,
-            'question' : question,
-            }, context_instance=RequestContext(request))
-
-@login_required
-def reopen(request, id):#re-open question
-    """view to initiate and process 
-    question close
-    """
-    question = get_object_or_404(Question, id=id)
-    # open question
-    if not request.user.can_reopen_question(question):
-        return HttpResponseForbidden()
-    if request.method == 'POST' :
-        Question.objects.filter(id=question.id).update(closed=False,
-            closed_by=None, closed_at=None, close_reason=None)
-        return HttpResponseRedirect(question.get_absolute_url())
-    else:
-        return render_to_response('reopen.html', {
-            'question' : question,
-            }, context_instance=RequestContext(request))
-
-#osqa-user communication system
-def read_message(request):#marks message a read
-    if request.method == "POST":
-        if request.POST['formdata'] == 'required':
-            request.session['message_silent'] = 1
-            if request.user.is_authenticated():
-                request.user.delete_messages()
-    return HttpResponse('')
+
+
+
+
 
 
 
 
index cf5f90373288f5b5a7e45dca546a1bb88e0b4e67..44ec8bad9142d44ed8beb1930a20309f7a478a23 100644 (file)
@@ -3,6 +3,8 @@ from django.utils import simplejson
 from django.core.paginator import Paginator\r
 from django.shortcuts import render_to_response\r
 from django.template import RequestContext\r
 from django.core.paginator import Paginator\r
 from django.shortcuts import render_to_response\r
 from django.template import RequestContext\r
+from django.utils.translation import ungettext, ugettext as _\r
+import logging\r
 \r
 def render(template=None, tab=None):\r
     def decorator(func):\r
 \r
 def render(template=None, tab=None):\r
     def decorator(func):\r
@@ -28,7 +30,7 @@ def list(paginate, default_page_size):
             paginator = Paginator(big_list, pagesize)\r
 \r
             page_obj = paginator.page(page)\r
             paginator = Paginator(big_list, pagesize)\r
 \r
             page_obj = paginator.page(page)\r
-            context[paginate] = page_obj.object_list\r
+            context[paginate] = page_obj.object_list.lazy()\r
 \r
             base_path = context.get('base_path', None) or request.path\r
             sort = request.utils.sort_method('')\r
 \r
             base_path = context.get('base_path', None) or request.path\r
             sort = request.utils.sort_method('')\r
@@ -55,19 +57,34 @@ def list(paginate, default_page_size):
     return decorator\r
 \r
 \r
     return decorator\r
 \r
 \r
+class CommandException(Exception):\r
+    pass\r
+\r
+\r
 def command(func):\r
     def decorated(request, *args, **kwargs):\r
         try:\r
             response = func(request, *args, **kwargs)\r
 def command(func):\r
     def decorated(request, *args, **kwargs):\r
         try:\r
             response = func(request, *args, **kwargs)\r
+\r
+            if isinstance(response, HttpResponse):\r
+                return response\r
+\r
             response['success'] = True\r
         except Exception, e:\r
             response['success'] = True\r
         except Exception, e:\r
-            #import sys, traceback\r
-            #traceback.print_exc(file=sys.stdout)\r
-\r
-            response = {\r
-                'success': False,\r
-                'error_message': str(e)\r
-            }\r
+            import sys, traceback\r
+            traceback.print_exc(file=sys.stdout)\r
+\r
+            if isinstance(e, CommandException):\r
+                response = {\r
+                    'success': False,\r
+                    'error_message': str(e)\r
+                }\r
+            else:\r
+                logging.error("%s: %s" % (func.__name__, str(e)))\r
+                response = {\r
+                    'success': False,\r
+                    'error_message': _("We're sorry, but an unknown error ocurred.<br />Please try again in a while.")\r
+                }\r
 \r
         if request.is_ajax():\r
             return HttpResponse(simplejson.dumps(response), mimetype="application/json")\r
 \r
         if request.is_ajax():\r
             return HttpResponse(simplejson.dumps(response), mimetype="application/json")\r
index be41bd29530ea2702c7aedd3683d7edbcd220620..03ffc6db8994807389d541f5423cdc3f6b8cd9bd 100644 (file)
@@ -5,15 +5,14 @@ from django.http import HttpResponseRedirect, HttpResponse
 from django.conf import settings
 from forum.forms import FeedbackForm
 from django.core.urlresolvers import reverse
 from django.conf import settings
 from forum.forms import FeedbackForm
 from django.core.urlresolvers import reverse
-from django.core.mail import mail_admins
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext as _
+from django.db.models import Count
 from forum.utils.forms import get_next_url
 from forum.models import Badge, Award, User
 from forum.utils.forms import get_next_url
 from forum.models import Badge, Award, User
-from forum.badges import ALL_BADGES
+from forum.badges.base import BadgesMeta
 from forum import settings
 from forum.utils.mail import send_email
 from forum import settings
 from forum.utils.mail import send_email
-from forum.settings.settingsmarkdown import *
-
+from forum.settings.settingsmarkdown import SettingsExtension, markdown
 import re
 
 def favicon(request):
 import re
 
 def favicon(request):
@@ -36,7 +35,6 @@ def feedback(request):
 
             if not request.user.is_authenticated:
                 context['email'] = form.cleaned_data.get('email',None)
 
             if not request.user.is_authenticated:
                 context['email'] = form.cleaned_data.get('email',None)
-            
             context['message'] = form.cleaned_data['message']
             context['name'] = form.cleaned_data.get('name',None)
 
             context['message'] = form.cleaned_data['message']
             context['name'] = form.cleaned_data.get('name',None)
 
@@ -62,20 +60,13 @@ def logout(request):
         'next' : get_next_url(request),
     }, context_instance=RequestContext(request))
 
         'next' : get_next_url(request),
     }, context_instance=RequestContext(request))
 
-def badges(request):#user status/reputation system
-    badges = Badge.objects.all().order_by('name')
-
-    badges_dict = dict([(badge.badge, badge.description) for badge in ALL_BADGES])
-
-    for badge in badges:
-        if badge.description != badges_dict.get(badge.slug, badge.description):
-            badge.description = badges_dict[badge.slug]
-            badge.save()
+def badges(request):
+    badges = [b.ondb for b in sorted(BadgesMeta.by_id.values(), lambda b1, b2: cmp(b1.name, b2.name))]
     
     
-    my_badges = []
     if request.user.is_authenticated():
     if request.user.is_authenticated():
-        my_badges = Award.objects.filter(user=request.user).values('badge_id')
-        #my_badges.query.group_by = ['badge_id']
+        my_badges = Award.objects.filter(user=request.user).values('badge_id').distinct()
+    else:
+        my_badges = []
 
     return render_to_response('badges.html', {
         'badges' : badges,
 
     return render_to_response('badges.html', {
         'badges' : badges,
@@ -83,19 +74,9 @@ def badges(request):#user status/reputation system
         'feedback_faq_url' : reverse('feedback'),
     }, context_instance=RequestContext(request))
 
         'feedback_faq_url' : reverse('feedback'),
     }, context_instance=RequestContext(request))
 
-def badge(request, id):
-    badge = get_object_or_404(Badge, id=id)
-    awards = Award.objects.extra(
-        select={'id': 'auth_user.id', 
-                'name': 'auth_user.username', 
-                'rep':'forum_user.reputation',
-                'gold': 'forum_user.gold',
-                'silver': 'forum_user.silver',
-                'bronze': 'forum_user.bronze'},
-        tables=['award', 'auth_user', 'forum_user'],
-        where=['badge_id=%s AND user_id=auth_user.id AND forum_user.user_ptr_id = auth_user.id'],
-        params=[id]
-    ).distinct('id')
+def badge(request, id, slug):
+    badge = Badge.objects.get(id=id)
+    awards = Award.objects.filter(badge=badge).annotate(count=Count('user')).distinct('user').order_by('-count')
 
     return render_to_response('badge.html', {
         'awards' : awards,
 
     return render_to_response('badge.html', {
         'awards' : awards,
index 385a4bee2c7e15b4aa0bedafceb7a80c50ae953e..1714a5117f24dc233cfccf6a79f85ed2e66d1327 100644 (file)
@@ -26,7 +26,8 @@ from forum.forms import *
 from forum.models import *
 from forum.const import *
 from forum.utils.forms import get_next_url
 from forum.models import *
 from forum.const import *
 from forum.utils.forms import get_next_url
-from forum.models.question import question_view
+from forum.actions import QuestionViewAction
+from forum.modules.decorators import decoratable
 import decorators
 
 # used in index page
 import decorators
 
 # used in index page
@@ -41,26 +42,13 @@ QUESTIONS_PAGE_SIZE = 30
 # used in answers
 ANSWERS_PAGE_SIZE = 10
 
 # used in answers
 ANSWERS_PAGE_SIZE = 10
 
-#system to display main content
-def _get_tags_cache_json():#service routine used by views requiring tag list in the javascript space
-    """returns list of all tags in json format
-    no caching yet, actually
-    """
-    tags = Tag.objects.filter(deleted=False).all()
-    tags_list = []
-    for tag in tags:
-        dic = {'n': tag.name, 'c': tag.used_count}
-        tags_list.append(dic)
-    tags = simplejson.dumps(tags_list)
-    return tags
-
 @decorators.render('index.html')
 def index(request):
     return question_list(request, Question.objects.all(), sort='active', base_path=reverse('questions'))
 
 @decorators.render('questions.html', 'unanswered')
 def unanswered(request):
 @decorators.render('index.html')
 def index(request):
     return question_list(request, Question.objects.all(), sort='active', base_path=reverse('questions'))
 
 @decorators.render('questions.html', 'unanswered')
 def unanswered(request):
-    return question_list(request, Question.objects.filter(accepted_answer=None),
+    return question_list(request, Question.objects.filter(extra_ref=None),
                          _('Open questions without an accepted answer'))
 
 @decorators.render('questions.html', 'questions')
                          _('Open questions without an accepted answer'))
 
 @decorators.render('questions.html', 'questions')
@@ -74,10 +62,7 @@ def tag(request, tag):
 
 @decorators.list('questions', QUESTIONS_PAGE_SIZE)
 def question_list(request, initial, list_description=_('questions'), sort=None, base_path=None):
 
 @decorators.list('questions', QUESTIONS_PAGE_SIZE)
 def question_list(request, initial, list_description=_('questions'), sort=None, base_path=None):
-    pagesize = request.utils.page_size(QUESTIONS_PAGE_SIZE)
-    page = int(request.GET.get('page', 1))
-
-    questions = initial.filter(deleted=False)
+    questions = initial.filter(deleted=None, in_moderation=None)
 
     if request.user.is_authenticated():
         questions = questions.filter(
 
     if request.user.is_authenticated():
         questions = questions.filter(
@@ -89,7 +74,7 @@ def question_list(request, initial, list_description=_('questions'), sort=None,
         else:
             request.utils.set_sort_method(sort)
 
         else:
             request.utils.set_sort_method(sort)
 
-        view_dic = {"latest":"-added_at", "active":"-last_activity_at", "hottest":"-answer_count", "mostvoted":"-score" }
+        view_dic = {"latest":"-added_at", "active":"-last_activity_at", "hottest":"-extra_count", "mostvoted":"-score" }
 
         questions=questions.order_by(view_dic.get(sort, '-added_at'))
 
 
         questions=questions.order_by(view_dic.get(sort, '-added_at'))
 
@@ -118,15 +103,13 @@ def search(request):
     else:
         return render_to_response("search.html", context_instance=RequestContext(request))
 
     else:
         return render_to_response("search.html", context_instance=RequestContext(request))
 
+@decoratable
+def do_question_search(keywords):
+    return Question.objects.filter(Q(title__icontains=keywords) | Q(body__icontains=keywords))
+
 @decorators.render('questions.html')
 def question_search(request, keywords):
 @decorators.render('questions.html')
 def question_search(request, keywords):
-    def question_search(keywords):
-        return Question.objects.filter(Q(title__icontains=keywords) | Q(body__icontains=keywords))
-
-    from forum.modules import get_handler
-
-    question_search = get_handler('question_search', question_search)
-    initial = question_search(keywords)
+    initial = do_question_search(keywords)
 
     return question_list(request, initial, _("questions matching '%(keywords)s'") % {'keywords': keywords},
             base_path="%s?t=question&q=%s" % (reverse('search'), django_urlquote(keywords)), sort=False)
 
     return question_list(request, initial, _("questions matching '%(keywords)s'") % {'keywords': keywords},
             base_path="%s?t=question&q=%s" % (reverse('search'), django_urlquote(keywords)), sort=False)
@@ -193,7 +176,7 @@ def update_question_view_times(request, question):
     last_seen = request.session['last_seen_in_question'].get(question.id,None)
 
     if (not last_seen) or last_seen < question.last_activity_at:
     last_seen = request.session['last_seen_in_question'].get(question.id,None)
 
     if (not last_seen) or last_seen < question.last_activity_at:
-        question_view.send(sender=update_question_view_times, instance=question, user=request.user)
+        QuestionViewAction(question, request.user).save()
         request.session['last_seen_in_question'][question.id] = datetime.datetime.now()
 
     request.session['last_seen_in_question'][question.id] = datetime.datetime.now()
         request.session['last_seen_in_question'][question.id] = datetime.datetime.now()
 
     request.session['last_seen_in_question'][question.id] = datetime.datetime.now()
@@ -231,7 +214,7 @@ def question(request, id, slug):
     answers = request.user.get_visible_answers(question)
 
     if answers is not None:
     answers = request.user.get_visible_answers(question)
 
     if answers is not None:
-        answers = [a for a in answers.order_by("-accepted", order_by)
+        answers = [a for a in answers.order_by("-marked", order_by)
                    if not a.deleted or a.author == request.user]
 
     objects_list = Paginator(answers, ANSWERS_PAGE_SIZE)
                    if not a.deleted or a.author == request.user]
 
     objects_list = Paginator(answers, ANSWERS_PAGE_SIZE)
@@ -251,7 +234,6 @@ def question(request, id, slug):
         "question" : question,
         "answer" : answer_form,
         "answers" : page_objects.object_list,
         "question" : question,
         "answer" : answer_form,
         "answers" : page_objects.object_list,
-        "tags" : question.tags.all(),
         "tab_id" : view_id,
         "similar_questions" : question.get_related_questions(),
         "subscription": subscription,
         "tab_id" : view_id,
         "similar_questions" : question.get_related_questions(),
         "subscription": subscription,
index 543d5c79b47589330b7474113729372e1ac53aed..c12c391a678ceeebf4ff9a46c03abcdfacbae409 100644 (file)
@@ -15,9 +15,9 @@ from django.utils import simplejson
 from django.core.urlresolvers import reverse\r
 from forum.forms import *\r
 from forum.utils.html import sanitize_html\r
 from django.core.urlresolvers import reverse\r
 from forum.forms import *\r
 from forum.utils.html import sanitize_html\r
-from forum.authentication import user_updated\r
 from datetime import date\r
 import decorators\r
 from datetime import date\r
 import decorators\r
+from forum.actions import EditProfileAction, FavoriteAction\r
 \r
 import time\r
 \r
 \r
 import time\r
 \r
@@ -71,26 +71,6 @@ def users(request):
 \r
                                 }, context_instance=RequestContext(request))\r
 \r
 \r
                                 }, context_instance=RequestContext(request))\r
 \r
-@login_required\r
-def moderate_user(request, id):\r
-    """ajax handler of user moderation\r
-    """\r
-    if not request.user.is_superuser or request.method != 'POST':\r
-        raise Http404\r
-    if not request.is_ajax():\r
-        return HttpResponseForbidden(mimetype="application/json")\r
-\r
-    user = get_object_or_404(User, id=id)\r
-    form = ModerateUserForm(request.POST, instance=user)\r
-\r
-    if form.is_valid():\r
-        form.save()\r
-        logging.debug('data saved')\r
-        response = HttpResponse(simplejson.dumps(''), mimetype="application/json")\r
-    else:\r
-        response = HttpResponseForbidden(mimetype="application/json")\r
-    return response\r
-\r
 def set_new_email(user, new_email, nomessage=False):\r
     if new_email != user.email:\r
         user.email = new_email\r
 def set_new_email(user, new_email, nomessage=False):\r
     if new_email != user.email:\r
         user.email = new_email\r
@@ -121,10 +101,8 @@ def edit_user(request, id):
             user.about = sanitize_html(form.cleaned_data['about'])\r
 \r
             user.save()\r
             user.about = sanitize_html(form.cleaned_data['about'])\r
 \r
             user.save()\r
-            # send user updated signal if full fields have been updated\r
-            if user.email and user.real_name and user.website and user.location and \\r
-                user.date_of_birth and user.about:\r
-                user_updated.send(sender=user.__class__, instance=user, updated_by=user)\r
+            EditProfileAction(user=user, ip=request.META['REMOTE_ADDR']).save()\r
+\r
             return HttpResponseRedirect(user.get_profile_url())\r
     else:\r
         form = EditUserForm(user)\r
             return HttpResponseRedirect(user.get_profile_url())\r
     else:\r
         form = EditUserForm(user)\r
@@ -154,25 +132,21 @@ def user_view(template, tab_name, tab_description, page_title, private=False):
 \r
 @user_view('users/stats.html', 'stats', _('user profile'), _('user profile overview'))\r
 def user_stats(request, user):\r
 \r
 @user_view('users/stats.html', 'stats', _('user profile'), _('user profile overview'))\r
 def user_stats(request, user):\r
-    questions = Question.objects.filter(author=user, deleted=False).order_by('-added_at')\r
-    answers = Answer.objects.filter(author=user, deleted=False).order_by('-added_at')\r
+    questions = Question.objects.filter(author=user, deleted=None).order_by('-added_at')\r
+    answers = Answer.objects.filter(author=user, deleted=None).order_by('-added_at')\r
 \r
 \r
-    up_votes = user.get_up_vote_count()\r
-    down_votes = user.get_down_vote_count()\r
+    up_votes = user.vote_up_count\r
+    down_votes = user.vote_down_count\r
     votes_today = user.get_vote_count_today()\r
     votes_total = int(settings.MAX_VOTES_PER_DAY)\r
 \r
     user_tags = Tag.objects.filter(Q(nodes__author=user) | Q(nodes__children__author=user)) \\r
         .annotate(user_tag_usage_count=Count('name')).order_by('-user_tag_usage_count')\r
 \r
     votes_today = user.get_vote_count_today()\r
     votes_total = int(settings.MAX_VOTES_PER_DAY)\r
 \r
     user_tags = Tag.objects.filter(Q(nodes__author=user) | Q(nodes__children__author=user)) \\r
         .annotate(user_tag_usage_count=Count('name')).order_by('-user_tag_usage_count')\r
 \r
-    awards = Badge.objects.filter(award_badge__user=user).annotate(count=Count('name')).order_by('-count')\r
-\r
-    if request.user.is_superuser:\r
-        moderate_user_form = ModerateUserForm(instance=user)\r
-    else:\r
-        moderate_user_form = None\r
+    awards = [(Badge.objects.get(id=b['id']), b['count']) for b in\r
+            Badge.objects.filter(awards__user=user).values('id').annotate(count=Count('cls')).order_by('-count')]\r
 \r
 \r
-    return {'moderate_user_form': moderate_user_form,\r
+    return {\r
             "view_user" : user,\r
             "questions" : questions,\r
             "answers" : answers,\r
             "view_user" : user,\r
             "questions" : questions,\r
             "answers" : answers,\r
@@ -183,42 +157,41 @@ def user_stats(request, user):
             "votes_total_per_day": votes_total,\r
             "user_tags" : user_tags[:50],\r
             "awards": awards,\r
             "votes_total_per_day": votes_total,\r
             "user_tags" : user_tags[:50],\r
             "awards": awards,\r
-            "total_awards" : awards.count(),\r
+            "total_awards" : len(awards),\r
         }\r
 \r
 @user_view('users/recent.html', 'recent', _('recent user activity'), _('profile - recent activity'))\r
 def user_recent(request, user):\r
         }\r
 \r
 @user_view('users/recent.html', 'recent', _('recent user activity'), _('profile - recent activity'))\r
 def user_recent(request, user):\r
-    activities = Activity.objects.filter(activity_type__in=(TYPE_ACTIVITY_PRIZE,\r
-            TYPE_ACTIVITY_ASK_QUESTION, TYPE_ACTIVITY_ANSWER,\r
-            TYPE_ACTIVITY_COMMENT_QUESTION, TYPE_ACTIVITY_COMMENT_ANSWER,\r
-            TYPE_ACTIVITY_MARK_ANSWER), user=user).order_by('-active_at')[:USERS_PAGE_SIZE]\r
+    activities = user.actions.exclude(action_type__in=("voteup", "votedown", "voteupcomment", "flag")).order_by('-action_date')[:USERS_PAGE_SIZE]\r
 \r
     return {"view_user" : user, "activities" : activities}\r
 \r
 \r
 @user_view('users/votes.html', 'votes', _('user vote record'), _('profile - votes'), True)\r
 def user_votes(request, user):\r
 \r
     return {"view_user" : user, "activities" : activities}\r
 \r
 \r
 @user_view('users/votes.html', 'votes', _('user vote record'), _('profile - votes'), True)\r
 def user_votes(request, user):\r
-    votes = user.votes.exclude(node__deleted=True).order_by('-voted_at')[:USERS_PAGE_SIZE]\r
+    votes = user.votes.filter(node__deleted=None).order_by('-voted_at')[:USERS_PAGE_SIZE]\r
 \r
     return {"view_user" : user, "votes" : votes}\r
 \r
 \r
 @user_view('users/reputation.html', 'reputation', _('user reputation in the community'), _('profile - user reputation'))\r
 def user_reputation(request, user):\r
 \r
     return {"view_user" : user, "votes" : votes}\r
 \r
 \r
 @user_view('users/reputation.html', 'reputation', _('user reputation in the community'), _('profile - user reputation'))\r
 def user_reputation(request, user):\r
-    reputation = user.reputes.order_by('-reputed_at')\r
+    rep = list(user.reputes.order_by('date'))\r
+    values = [r.value for r in rep]\r
+    redux = lambda x, y: x+y     \r
 \r
     graph_data = simplejson.dumps([\r
 \r
     graph_data = simplejson.dumps([\r
-            (time.mktime(rep.reputed_at.timetuple()) * 1000, rep.reputation)\r
-            for rep in reputation\r
+            (time.mktime(rep[i].date.timetuple()) * 1000, reduce(redux, values[:i], 0))\r
+            for i in range(len(values))\r
     ])\r
 \r
     ])\r
 \r
-    return {"view_user": user, "reputation": reputation, "graph_data": graph_data}\r
+    return {"view_user": user, "reputation": reversed(rep), "graph_data": graph_data}\r
 \r
 \r
-@user_view('users/questions.html', 'favorites', _('favorite questions'),  _('profile - favorite questions'), True)\r
+@user_view('users/questions.html', 'favorites', _('favorite questions'),  _('profile - favorite questions'))\r
 def user_favorites(request, user):\r
 def user_favorites(request, user):\r
-    questions = user.favorite_questions.filter(deleted=False)\r
+    favorites = FavoriteAction.objects.filter(user=user)\r
 \r
 \r
-    return {"questions" : questions, "view_user" : user}\r
+    return {"favorites" : favorites, "view_user" : user}\r
 \r
 @user_view('users/subscriptions.html', 'subscriptions', _('subscription settings'), _('profile - subscriptions'), True)\r
 def user_subscriptions(request, user):\r
 \r
 @user_view('users/subscriptions.html', 'subscriptions', _('subscription settings'), _('profile - subscriptions'), True)\r
 def user_subscriptions(request, user):\r
index fad53c15afd73d947b041185a75e95e367d61f94..1d7214f45b542abea769defff0fac07a7f963213 100644 (file)
@@ -13,23 +13,13 @@ from django.utils.translation import ugettext as _
 from django.core.urlresolvers import reverse
 from django.core.exceptions import PermissionDenied
 
 from django.core.urlresolvers import reverse
 from django.core.exceptions import PermissionDenied
 
+from forum.actions import AskAction, AnswerAction, ReviseAction, RollbackAction, RetagAction
 from forum.forms import *
 from forum.models import *
 from forum.const import *
 from forum.utils.forms import get_next_url
 from forum.forms import *
 from forum.models import *
 from forum.const import *
 from forum.utils.forms import get_next_url
-from forum.views.readers import _get_tags_cache_json
 from forum.views.commands import SpamNotAllowedException
 
 from forum.views.commands import SpamNotAllowedException
 
-# used in index page
-INDEX_PAGE_SIZE = 20
-INDEX_AWARD_SIZE = 15
-INDEX_TAGS_SIZE = 100
-# used in tags list
-DEFAULT_PAGE_SIZE = 60
-# used in questions
-QUESTIONS_PAGE_SIZE = 10
-# used in answers
-ANSWERS_PAGE_SIZE = 10
 
 def upload(request):#ajax upload file to a question or answer
     class FileTypeNotAllow(Exception):
 
 def upload(request):#ajax upload file to a question or answer
     class FileTypeNotAllow(Exception):
@@ -77,29 +67,6 @@ def upload(request):#ajax upload file to a question or answer
     return HttpResponse(result, mimetype="application/xml")
 
 
     return HttpResponse(result, mimetype="application/xml")
 
 
-def _create_post(request, post_cls, form, parent=None):
-    post = post_cls()
-
-    if parent is not None:
-        post.parent = parent
-
-    revision_data = dict(summary=_('Initial revision'), body=form.cleaned_data['text'])
-
-    if form.cleaned_data.get('title', None):
-        revision_data['title'] = strip_tags(form.cleaned_data['title'].strip())
-
-    if form.cleaned_data.get('tags', None):
-        revision_data['tagnames'] = form.cleaned_data['tags'].strip()
-
-    post.create_revision(request.user, **revision_data)
-
-    if form.cleaned_data['wiki']:
-        post.wikify()
-
-    return HttpResponseRedirect(post.get_absolute_url())
-
-
-
 def ask(request):
     if request.method == "POST" and "text" in request.POST:
         form = AskForm(request.POST)
 def ask(request):
     if request.method == "POST" and "text" in request.POST:
         form = AskForm(request.POST)
@@ -108,7 +75,7 @@ def ask(request):
                 data = {
                     "user_ip":request.META["REMOTE_ADDR"],
                     "user_agent":request.environ['HTTP_USER_AGENT'],
                 data = {
                     "user_ip":request.META["REMOTE_ADDR"],
                     "user_agent":request.environ['HTTP_USER_AGENT'],
-                    "comment_author":request.user.real_name,
+                    "comment_author":request.user.username,
                     "comment_author_email":request.user.email,
                     "comment_author_url":request.user.website,
                     "comment":request.POST['text']
                     "comment_author_email":request.user.email,
                     "comment_author_url":request.user.website,
                     "comment":request.POST['text']
@@ -116,7 +83,8 @@ def ask(request):
                 if Node.isSpam(request.POST['text'], data):
                     raise SpamNotAllowedException("question")
 
                 if Node.isSpam(request.POST['text'], data):
                     raise SpamNotAllowedException("question")
 
-                return _create_post(request, Question, form)
+                question = AskAction(user=request.user).save(data=form.cleaned_data).node
+                return HttpResponseRedirect(question.get_absolute_url())
             else:
                 return HttpResponseRedirect(reverse('auth_action_signin', kwargs={'action': 'newquestion'}))
     elif request.method == "POST" and "go" in request.POST:
             else:
                 return HttpResponseRedirect(reverse('auth_action_signin', kwargs={'action': 'newquestion'}))
     elif request.method == "POST" and "go" in request.POST:
@@ -148,15 +116,7 @@ def _retag_question(request, question):
         form = RetagQuestionForm(question, request.POST)
         if form.is_valid():
             if form.has_changed():
         form = RetagQuestionForm(question, request.POST)
         if form.is_valid():
             if form.has_changed():
-                active = question.active_revision
-
-                question.create_revision(
-                    request.user,
-                    summary          = _('Retag'),
-                    title            = active.title,
-                    tagnames         = form.cleaned_data['tags'],
-                    body             = active.body,
-                )
+                RetagAction(user=request.user, node=question).save(data=dict(tagnames=form.cleaned_data['tags']))
 
             return HttpResponseRedirect(question.get_absolute_url())
     else:
 
             return HttpResponseRedirect(question.get_absolute_url())
     else:
@@ -180,20 +140,10 @@ def _edit_question(request, question):
 
         if not 'select_revision' in request.POST and form.is_valid():
             if form.has_changed():
 
         if not 'select_revision' in request.POST and form.is_valid():
             if form.has_changed():
-                question.create_revision(
-                    request.user,
-                    summary          = form.cleaned_data['summary'],
-                    title            = strip_tags(form.cleaned_data['title'].strip()),
-                    tagnames         = form.cleaned_data['tags'].strip(),
-                    body             = form.cleaned_data['text'],
-                )
-
-                if form.cleaned_data.get('wiki', False):
-                    question.wikify()
-
+                ReviseAction(user=request.user, node=question).save(data=form.cleaned_data)
             else:
                 if not revision == question.active_revision:
             else:
                 if not revision == question.active_revision:
-                    question.activate_revision(request.user, revision)
+                    RollbackAction(user=request.user, node=question).save(data=dict(activate=revision))
 
             return HttpResponseRedirect(question.get_absolute_url())
     else:
 
             return HttpResponseRedirect(question.get_absolute_url())
     else:
@@ -227,18 +177,10 @@ def edit_answer(request, id):
 
         if not 'select_revision' in request.POST and form.is_valid():
             if form.has_changed():
 
         if not 'select_revision' in request.POST and form.is_valid():
             if form.has_changed():
-                answer.create_revision(
-                    request.user,
-                    summary          = form.cleaned_data['summary'],
-                    body             = form.cleaned_data['text'],
-                )
-
-                if form.cleaned_data.get('wiki', False):
-                    answer.wikify()
-
+                ReviseAction(user=request.user, node=answer).save(data=form.cleaned_data)
             else:
                 if not revision == answer.active_revision:
             else:
                 if not revision == answer.active_revision:
-                    answer.activate_revision(request.user, revision)
+                    RollbackAction(user=request.user, node=answer).save(data=dict(activate=revision))
 
             return HttpResponseRedirect(answer.get_absolute_url())
 
 
             return HttpResponseRedirect(answer.get_absolute_url())
 
@@ -260,7 +202,7 @@ def answer(request, id):
                 data = {
                     "user_ip":request.META["REMOTE_ADDR"],
                     "user_agent":request.environ['HTTP_USER_AGENT'],
                 data = {
                     "user_ip":request.META["REMOTE_ADDR"],
                     "user_agent":request.environ['HTTP_USER_AGENT'],
-                    "comment_author":request.user.real_name,
+                    "comment_author":request.user.username,
                     "comment_author_email":request.user.email,
                     "comment_author_url":request.user.website,
                     "comment":request.POST['text']
                     "comment_author_email":request.user.email,
                     "comment_author_url":request.user.website,
                     "comment":request.POST['text']
@@ -268,7 +210,8 @@ def answer(request, id):
                 if Node.isSpam(request.POST['text'], data):
                     raise SpamNotAllowedException("answer")
 
                 if Node.isSpam(request.POST['text'], data):
                     raise SpamNotAllowedException("answer")
 
-                return _create_post(request, Answer, form, question)
+                answer = AnswerAction(user=request.user).save(dict(question=question, **form.cleaned_data)).node
+                return HttpResponseRedirect(answer.get_absolute_url())
             else:
                 return HttpResponseRedirect(reverse('auth_action_signin', kwargs={'action': 'newquestion'}))
 
             else:
                 return HttpResponseRedirect(reverse('auth_action_signin', kwargs={'action': 'newquestion'}))
 
index eed611c066f732e5dc8ad33e62f00c462f421eea..ba01a0fe39e7ffed3fb7f9b6193a1c777e10c39f 100644 (file)
-from datetime import timedelta
-
-from django.db.models.signals import post_save
+from datetime import datetime, timedelta
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext as _
-
-from forum.badges.base import PostCountableAbstractBadge, ActivityAbstractBadge, FirstActivityAbstractBadge, \
-        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
+from forum.badges.base import AbstractBadge
+from forum.models import Badge
+from forum.actions import *
+from forum.models import Vote, Flag
 
 import settings
 
 
 import settings
 
-class PopularQuestionBadge(PostCountableAbstractBadge):
-    type = const.BRONZE_BADGE
-    description = _('Asked a question with %s views') % str(settings.POPULAR_QUESTION_VIEWS)
+class QuestionViewBadge(AbstractBadge):
+    abstract = True
+    listen_to = (QuestionViewAction,)
 
 
-    def __init__(self):
-        super(PopularQuestionBadge, self).__init__(Question, 'view_count', settings.POPULAR_QUESTION_VIEWS)
+    @property
+    def description(self):
+        return _('Asked a question with %s views') % str(self.nviews)
 
 
-class NotableQuestionBadge(PostCountableAbstractBadge):
-    type = const.SILVER_BADGE
-    description = _('Asked a question with %s views') % str(settings.NOTABLE_QUESTION_VIEWS)
+    def award_to(self, action):
+        if action.question.extra_count == int(self.nviews):
+            return action.question.author
 
 
-    def __init__(self):
-        super(NotableQuestionBadge, self).__init__(Question, 'view_count', settings.NOTABLE_QUESTION_VIEWS)
 
 
-class FamousQuestionBadge(PostCountableAbstractBadge):
-    type = const.GOLD_BADGE
-    description = _('Asked a question with %s views') % str(settings.FAMOUS_QUESTION_VIEWS)
+class PopularQuestion(QuestionViewBadge):
+    name = _('Popular Question')
+    nviews = settings.POPULAR_QUESTION_VIEWS
 
 
-    def __init__(self):
-        super(FamousQuestionBadge, self).__init__(Question, 'view_count', settings.FAMOUS_QUESTION_VIEWS)
 
 
+class NotableQuestion(QuestionViewBadge):
+    type = Badge.SILVER
+    name = _('Notable Question')
+    nviews = settings.NOTABLE_QUESTION_VIEWS
 
 
-class NiceAnswerBadge(NodeCountableAbstractBadge):
-    type = const.BRONZE_BADGE
-    description = _('Answer voted up %s times') % str(settings.NICE_ANSWER_VOTES_UP)
+class FamousQuestion(QuestionViewBadge):
+    type = Badge.GOLD
+    name = _('Famous Question')
+    nviews = settings.FAMOUS_QUESTION_VIEWS
 
 
-    def __init__(self):
-        super(NiceAnswerBadge, self).__init__("answer", 'vote_up_count', settings.NICE_ANSWER_VOTES_UP)
 
 
-class NiceQuestionBadge(NodeCountableAbstractBadge):
-    type = const.BRONZE_BADGE
-    description = _('Question voted up %s times') % str(settings.NICE_QUESTION_VOTES_UP)
 
 
-    def __init__(self):
-        super(NiceQuestionBadge, self).__init__("question", 'vote_up_count', settings.NICE_QUESTION_VOTES_UP)
 
 
-class GoodAnswerBadge(NodeCountableAbstractBadge):
-    type = const.SILVER_BADGE
-    description = _('Answer voted up %s times') % str(settings.GOOD_ANSWER_VOTES_UP)
+class NodeScoreBadge(AbstractBadge):
+    abstract = True
+    listen_to = (VoteAction,)
 
 
-    def __init__(self):
-        super(GoodAnswerBadge, self).__init__("answer", 'vote_up_count', settings.GOOD_ANSWER_VOTES_UP)
+    @property
+    def description(self):
+        return _('Answer voted up %s times') % str(self.expected_score)
 
 
-class GoodQuestionBadge(NodeCountableAbstractBadge):
-    type = const.SILVER_BADGE
-    description = _('Question voted up %s times') % str(settings.GOOD_QUESTION_VOTES_UP)
+    def award_to(self, action):
+        if (action.node.node_type == self.node_type) and (action.node.score == int(self.expected_score)):
+            return action.node.author
+            
 
 
-    def __init__(self):
-        super(GoodQuestionBadge, self).__init__("question", 'vote_up_count', settings.GOOD_QUESTION_VOTES_UP)
+class QuestionScoreBadge(NodeScoreBadge):
+    abstract = True
+    node_type = "question"
 
 
-class GreatAnswerBadge(NodeCountableAbstractBadge):
-    type = const.GOLD_BADGE
-    description = _('Answer voted up %s times') % str(settings.GREAT_ANSWER_VOTES_UP)
+class NiceQuestion(QuestionScoreBadge):
+    expected_score = settings.NICE_QUESTION_VOTES_UP
+    name = _("Nice Question")
 
 
-    def __init__(self):
-        super(GreatAnswerBadge, self).__init__("answer", 'vote_up_count', settings.GREAT_ANSWER_VOTES_UP)
+class GoodQuestion(QuestionScoreBadge):
+    type = Badge.SILVER
+    expected_score = settings.GOOD_QUESTION_VOTES_UP
+    name = _("Good Question")
 
 
-class GreatQuestionBadge(NodeCountableAbstractBadge):
-    type = const.GOLD_BADGE
-    description = _('Question voted up %s times') % str(settings.GREAT_QUESTION_VOTES_UP)
+class GreatQuestion(QuestionScoreBadge):
+    type = Badge.GOLD
+    expected_score = settings.GREAT_QUESTION_VOTES_UP
+    name = _("Great Question")
 
 
-    def __init__(self):
-        super(GreatQuestionBadge, self).__init__("question", 'vote_up_count', settings.GREAT_QUESTION_VOTES_UP)
 
 
+class AnswerScoreBadge(NodeScoreBadge):
+    abstract = True
+    node_type = "answer"
 
 
-class FavoriteQuestionBadge(PostCountableAbstractBadge):
-    type = const.SILVER_BADGE
-    description = _('Question favorited by %s users') % str(settings.FAVORITE_QUESTION_FAVS)
+class NiceAnswer(AnswerScoreBadge):
+    expected_score = settings.NICE_ANSWER_VOTES_UP
+    name = _("Nice Answer")
 
 
-    def __init__(self):
-        super(FavoriteQuestionBadge, self).__init__(Question, 'favourite_count', settings.FAVORITE_QUESTION_FAVS)
+class GoodAnswer(AnswerScoreBadge):
+    type = Badge.SILVER
+    expected_score = settings.GOOD_ANSWER_VOTES_UP
+    name = _("Good Answer")
 
 
-class StellarQuestionBadge(PostCountableAbstractBadge):
-    type = const.GOLD_BADGE
-    description = _('Question favorited by %s users') % str(settings.STELLAR_QUESTION_FAVS)
+class GreatAnswer(AnswerScoreBadge):
+    type = Badge.GOLD
+    expected_score = settings.GREAT_ANSWER_VOTES_UP
+    name = _("Great Answer")
 
 
-    def __init__(self):
-        super(StellarQuestionBadge, self).__init__(Question, 'favourite_count', settings.STELLAR_QUESTION_FAVS)
 
 
 
 
-class DisciplinedBadge(ActivityAbstractBadge):
-    type = const.BRONZE_BADGE
-    description = _('Deleted own post with score of %s or higher') % str(settings.DISCIPLINED_MIN_SCORE)
+class FavoriteQuestionBadge(AbstractBadge):
+    abstract = True
+    listen_to = (FavoriteAction,)
 
 
-    def __init__(self):
-        def handler(instance):
-            if instance.user.id == instance.content_object.author.id and instance.content_object.score >= settings.DISCIPLINED_MIN_SCORE:
-                self.award_badge(instance.user, instance)
+    @property
+    def description(self):
+        return _('Question favorited by %s users') % str(self.expected_count)
 
 
-        super(DisciplinedBadge, self).__init__(const.TYPE_ACTIVITY_DELETE_QUESTION, handler)
+    def award_to(self, action):
+        if (action.node.node_type == "question") and (action.node.favorite_count == int(self.expected_count)):
+            return action.node.author
 
 
-class PeerPressureBadge(ActivityAbstractBadge):
-    type = const.BRONZE_BADGE
-    description = _('Deleted own post with score of %s or lower') % str(settings.PEER_PRESSURE_MAX_SCORE)
+class FavoriteQuestion(FavoriteQuestionBadge):
+    type = Badge.SILVER
+    name = _("Favorite Question")
+    expected_count = settings.FAVORITE_QUESTION_FAVS
 
 
-    def __init__(self):
-        def handler(instance):
-            if instance.user.id == instance.content_object.author.id and instance.content_object.score <= settings.PEER_PRESSURE_MAX_SCORE:
-                self.award_badge(instance.user, instance)
+class StellarQuestion(FavoriteQuestionBadge):
+    name = _("Stellar Question")
+    expected_count = settings.STELLAR_QUESTION_FAVS
 
 
-        super(PeerPressureBadge, self).__init__(const.TYPE_ACTIVITY_DELETE_QUESTION, handler)
 
 
 
 
-class CitizenPatrolBadge(FirstActivityAbstractBadge):
-    type = const.BRONZE_BADGE
-    description = _('First flagged post')
+class Disciplined(AbstractBadge):
+    listen_to = (DeleteAction,)
+    name = _("Disciplined")
+    description = _('Deleted own post with score of %s or higher') % settings.DISCIPLINED_MIN_SCORE
 
 
-    def __init__(self):
-        super(CitizenPatrolBadge, self).__init__(const.TYPE_ACTIVITY_MARK_OFFENSIVE)
+    def award_to(self, action):
+        if (action.node.author == action.user) and (action.node.score >= int(settings.DISCIPLINED_MIN_SCORE)):
+            return action.user
 
 
-class CriticBadge(FirstActivityAbstractBadge):
-    type = const.BRONZE_BADGE
-    description = _('First down vote')
+class PeerPressure(AbstractBadge):
+    listen_to = (DeleteAction,)
+    name = _("Peer Pressure")
+    description = _('Deleted own post with score of %s or lower') % settings.PEER_PRESSURE_MAX_SCORE
 
 
-    def __init__(self):
-        super(CriticBadge, self).__init__(const.TYPE_ACTIVITY_VOTE_DOWN)
+    def award_to(self, action):
+        if (action.node.author == action.user) and (action.node.score <= int(settings.PEER_PRESSURE_MAX_SCORE)):
+            return action.user
 
 
-class OrganizerBadge(FirstActivityAbstractBadge):
-    type = const.BRONZE_BADGE
-    description = _('First retag')
 
 
-    def __init__(self):
-        super(OrganizerBadge, self).__init__(const.TYPE_ACTIVITY_UPDATE_TAGS)
+class Critic(AbstractBadge):
+    award_once = True
+    listen_to = (VoteDownAction,)
+    name = _("Critic")
+    description = _('First down vote')
+
+    def award_to(self, action):
+        if (action.user.vote_down_count == 1):
+            return action.user
+
 
 
-class SupporterBadge(FirstActivityAbstractBadge):
-    type = const.BRONZE_BADGE
+class Supporter(AbstractBadge):
+    award_once = True
+    listen_to = (VoteUpAction,)
+    name = _("Supporter")
     description = _('First up vote')
 
     description = _('First up vote')
 
-    def __init__(self):
-        super(SupporterBadge, self).__init__(const.TYPE_ACTIVITY_VOTE_UP)
+    def award_to(self, action):
+        if (action.user.vote_up_count == 1):
+            return action.user
 
 
-class EditorBadge(FirstActivityAbstractBadge):
-    type = const.BRONZE_BADGE
-    description = _('First edit')
 
 
-    def __init__(self):
-        super(EditorBadge, self).__init__((const.TYPE_ACTIVITY_UPDATE_ANSWER, const.TYPE_ACTIVITY_UPDATE_QUESTION))
+class FirstActionBadge(AbstractBadge):
+    award_once = True
+    abstract = True
+    
+    def award_to(self, action):
+        if (self.listen_to[0].objects.filter(user=action.user).count() == 1):
+            return action.user
 
 
-class ScholarBadge(FirstActivityAbstractBadge):
-    type = const.BRONZE_BADGE
-    description = _('First accepted answer on your own question')
+class CitizenPatrol(FirstActionBadge):
+    listen_to = (FlagAction,)
+    name = _("Citizen Patrol")
+    description = _('First flagged post')
 
 
-    def __init__(self):
-        super(ScholarBadge, self).__init__(const.TYPE_ACTIVITY_MARK_ANSWER)
+class Organizer(FirstActionBadge):
+    listen_to = (RetagAction,)
+    name = _("Organizer")
+    description = _('First retag')
 
 
-class AutobiographerBadge(FirstActivityAbstractBadge):
-    type = const.BRONZE_BADGE
-    description = _('Completed all user profile fields')
+class Editor(FirstActionBadge):
+    listen_to = (ReviseAction,)
+    name = _("Editor")
+    description = _('First edit')
 
 
-    def __init__(self):
-        super(AutobiographerBadge, self).__init__(const.TYPE_ACTIVITY_USER_FULL_UPDATED)
+class Scholar(FirstActionBadge):
+    listen_to = (AcceptAnswerAction,)
+    name = _("Scholar")
+    description = _('First accepted answer on your own question')
 
 
-class CleanupBadge(FirstActivityAbstractBadge):
-    type = const.BRONZE_BADGE
+class Cleanup(FirstActionBadge):
+    listen_to = (RollbackAction,)
+    name = _("Cleanup")
     description = _('First rollback')
 
     description = _('First rollback')
 
-    def __init__(self):
-        super(CleanupBadge, self).__init__((const.TYPE_ACTIVITY_CANCEL_VOTE_UP, const.TYPE_ACTIVITY_CANCEL_VOTE_DOWN))
 
 
+class Autobiographer(AbstractBadge):
+    award_once = True
+    listen_to = (EditProfileAction,)
+    name = _("Autobiographer")
+    description = _('Completed all user profile fields')
+
+    def award_to(self, action):
+        user = action.user
+        if user.email and user.real_name and user.website and user.location and \
+                user.date_of_birth and user.about:
+            return user
 
 
-class CivicDutyBadge(ActivityCountAbstractBadge):
-    type = const.SILVER_BADGE
-    description = _('Voted %s times') % str(settings.CIVIC_DUTY_VOTES)
 
 
-    def __init__(self):
-        super(CivicDutyBadge, self).__init__((const.TYPE_ACTIVITY_VOTE_DOWN, const.TYPE_ACTIVITY_VOTE_UP), settings.CIVIC_DUTY_VOTES)
 
 
-class PunditBadge(ActivityCountAbstractBadge):
-    type = const.BRONZE_BADGE
-    description = _('Left %s comments') % str(settings.PUNDIT_COMMENT_COUNT)
+class CivicDuty(AbstractBadge):
+    type = Badge.SILVER
+    award_once = True
+    listen_to = (VoteUpAction, VoteDownAction)
+    name = _("Civic Duty")
+    description = _('Voted %s times') % settings.CIVIC_DUTY_VOTES
 
 
-    def __init__(self):
-        super(PunditBadge, self).__init__((const.TYPE_ACTIVITY_COMMENT_ANSWER, const.TYPE_ACTIVITY_COMMENT_QUESTION), settings.PUNDIT_COMMENT_COUNT)
+    def award_to(self, action):
+        if (action.user.vote_up_count + action.user.vote_down_count) == int(settings.CIVIC_DUTY_VOTES):
+            return action.user
 
 
 
 
-class SelfLearnerBadge(CountableAbstractBadge):
-    type = const.BRONZE_BADGE
-    description = _('Answered your own question with at least %s up votes') % str(settings.SELF_LEARNER_UP_VOTES)
+class Pundit(AbstractBadge):
+    award_once = True
+    listen_to = (CommentAction,)
+    name = _("Pundit")
+    description = _('Left %s comments') % settings.PUNDIT_COMMENT_COUNT
 
 
-    def __init__(self):
+    def award_to(self, action):
+        if (action.user.nodes.filter(node_type="comment", deleted=None)) == int(settings.CIVIC_DUTY_VOTES):
+            return action.user
 
 
-        def handler(instance):
-            if instance.node_type == "answer" and instance.author_id == instance.question.author_id:
-                self.award_badge(instance.author, instance)
 
 
-        super(SelfLearnerBadge, self).__init__(Node, 'vote_up_count', settings.SELF_LEARNER_UP_VOTES, handler)
+class SelfLearner(AbstractBadge):
+    listen_to = (VoteUpAction, )
+    name = _("Self Learner")
+    description = _('Answered your own question with at least %s up votes') % settings.SELF_LEARNER_UP_VOTES
 
 
+    def award_to(self, action):
+        if (action.node.node_type == "answer") and (action.node.author == action.node.parent.author) and (
+            action.node.score == int(settings.SELF_LEARNER_UP_VOTES)):
+            return action.node.author
 
 
-class StrunkAndWhiteBadge(ActivityCountAbstractBadge):
-    type = const.SILVER_BADGE
-    name = _('Strunk & White')
-    description = _('Edited %s entries') % str(settings.STRUNK_AND_WHITE_EDITS)
 
 
-    def __init__(self):
-        super(StrunkAndWhiteBadge, self).__init__((const.TYPE_ACTIVITY_UPDATE_ANSWER, const.TYPE_ACTIVITY_UPDATE_QUESTION), settings.STRUNK_AND_WHITE_EDITS)
+class StrunkAndWhite(AbstractBadge):
+    award_once = True
+    listen_to = (ReviseAction,)
+    name = _("Strunk & White")
+    description = _('Edited %s entries') % settings.STRUNK_AND_WHITE_EDITS
 
 
+    def award_to(self, action):
+        if (ReviseAction.objects.filter(user=action.user).count() == int(settings.STRUNK_AND_WHITE_EDITS)):
+            return action.user
 
 
-def is_user_first(post):
-    return post.__class__.objects.filter(author=post.author).order_by('added_at')[0].id == post.id
 
 
-class StudentBadge(CountableAbstractBadge):
-    type = const.BRONZE_BADGE
+class Student(AbstractBadge):
+    award_once = True
+    listen_to = (VoteUpAction,)
+    name = _("Student")
     description = _('Asked first question with at least one up vote')
 
     description = _('Asked first question with at least one up vote')
 
-    def __init__(self):
-        def handler(instance):
-            if instance.node_type == "question" and is_user_first(instance):
-                self.award_badge(instance.author, instance)
+    def award_to(self, action):
+        if (action.node.node_type == "question") and (action.node.author.nodes.filter(node_type="question", deleted=None, score=1).count() == 1):
+            return action.node.author
 
 
-        super(StudentBadge, self).__init__(Node, 'vote_up_count', 1, handler)
 
 
-class TeacherBadge(CountableAbstractBadge):
-    type = const.BRONZE_BADGE
+class Teacher(AbstractBadge):
+    award_once = True
+    listen_to = (VoteUpAction,)
+    name = _("Teacher")
     description = _('Answered first question with at least one up vote')
 
     description = _('Answered first question with at least one up vote')
 
-    def __init__(self):
-        def handler(instance):
-            if instance.node_type == "answer" and is_user_first(instance):
-                self.award_badge(instance.author, instance)
+    def award_to(self, action):
+        if (action.node.node_type == "answer") and (action.node.author.nodes.filter(node_type="answer", deleted=None, score=1).count() == 1):
+            return action.node.author
 
 
-        super(TeacherBadge, self).__init__(Node, 'vote_up_count', 1, handler)
 
 
+class Enlightened(AbstractBadge):
+    type = Badge.SILVER
+    award_once = True
+    listen_to = (VoteUpAction, AcceptAnswerAction)
+    name = _("Enlightened")
+    description = _('First answer was accepted with at least %s up votes') % settings.ENLIGHTENED_UP_VOTES
 
 
-class AcceptedAndVotedAnswerAbstractBadge(AbstractBadge):
-    def __init__(self, up_votes, handler):
-        def wrapper(sender, instance, **kwargs):
-            if sender is Answer:
-                if (not kwargs['field'] == "score") or (kwargs['new'] < kwargs['old']):
-                    return
+    def award_to(self, action):
+        if (action.node.node_type == "answer") and (action.node.accepted) and (
+            action.node.score >= int(settings.ENLIGHTENED_UP_VOTES)):
+            return action.node.author
 
 
-                answer = instance.leaf
-                vote_count = kwargs['new']
-            else:
-                answer = instance.content_object
-                vote_count = answer.vote_up_count
 
 
-            if answer.accepted and vote_count == up_votes:
-                handler(answer)
+class Guru(AbstractBadge):
+    type = Badge.SILVER
+    listen_to = (VoteUpAction, AcceptAnswerAction)
+    name = _("Guru")
+    description = _('Accepted answer and voted up %s times') % settings.GURU_UP_VOTES
 
 
-        activity_record.connect(wrapper, sender=const.TYPE_ACTIVITY_MARK_ANSWER, weak=False)
-        denorm_update.connect(wrapper, sender=Node, weak=False)
+    def award_to(self, action):
+        if (action.node.node_type == "answer") and (action.node.accepted) and (
+            action.node.score >= int(settings.ENLIGHTENED_UP_VOTES)):
+            return action.node.author
 
 
 
 
-class EnlightenedBadge(AcceptedAndVotedAnswerAbstractBadge):
-    type = const.SILVER_BADGE
-    description = _('First answer was accepted with at least %s up votes') % str(settings.ENLIGHTENED_UP_VOTES)
-
-    def __init__(self):
-        def handler(answer):
-            self.award_badge(answer.author, answer, True)
-
-        super(EnlightenedBadge, self).__init__(settings.ENLIGHTENED_UP_VOTES, handler)
-
-
-class GuruBadge(AcceptedAndVotedAnswerAbstractBadge):
-    type = const.SILVER_BADGE
-    description = _('Accepted answer and voted up %s times') % str(settings.GURU_UP_VOTES)
-
-    def __init__(self):
-        def handler(answer):
-            self.award_badge(answer.author, answer)
-
-        super(GuruBadge, self).__init__(settings.GURU_UP_VOTES, handler)
-
-
-class NecromancerBadge(CountableAbstractBadge):
-    type = const.SILVER_BADGE
+class Necromancer(AbstractBadge):
+    type = Badge.SILVER
+    listen_to = (VoteUpAction,)
+    name = _("Necromancer")
     description = _('Answered a question more than %(dif_days)s days later with at least %(up_votes)s votes') % \
     description = _('Answered a question more than %(dif_days)s days later with at least %(up_votes)s votes') % \
-            {'dif_days': str(settings.NECROMANCER_DIF_DAYS), 'up_votes': str(settings.NECROMANCER_UP_VOTES)}
-
-    def __init__(self):
-        def handler(instance):
-            if instance.node_type == "answer" and instance.added_at >= (instance.question.added_at + timedelta(days=int(settings.NECROMANCER_DIF_DAYS))):
-                self.award_badge(instance.author, instance)
+            {'dif_days': settings.NECROMANCER_DIF_DAYS, 'up_votes': settings.NECROMANCER_UP_VOTES}
 
 
-        super(NecromancerBadge, self).__init__(Node, "vote_up_count", settings.NECROMANCER_UP_VOTES, handler)
+    def award_to(self, action):
+        if (action.node.node_type == "answer") and (
+            action.node.added_at >= (action.node.question.added_at + timedelta(days=int(settings.NECROMANCER_DIF_DAYS)))):
+            return action.node.author
 
 
+class Taxonomist(AbstractBadge):
+    type = Badge.SILVER
+    listen_to = tuple()
+    name = _("Taxonomist")
+    description = _('Created a tag used by %s questions') % settings.TAXONOMIST_USE_COUNT
 
 
-class TaxonomistBadge(AbstractBadge):
-    type = const.SILVER_BADGE
-    description = _('Created a tag used by %s questions') % str(settings.TAXONOMIST_USE_COUNT)
+    def award_to(self, action):
+        return None
 
 
-    def __init__(self):
-        def handler(instance, **kwargs):
-            if instance.used_count == settings.TAXONOMIST_USE_COUNT:
-                self.award_badge(instance.created_by, instance)           
-
-        post_save.connect(handler, sender=Tag, weak=False)
-
-
-#class GeneralistTag(AbstractBadge):
-#    pass
-
-#class ExpertTag(AbstractBadge):
-#    pass
-
-#class YearlingTag(AbstractBadge):
-#    pass
-
-
-            
index 00113d5170ebc6a530270d129d849317c7e1ca21..87d98502474862132778589031961bd3d5c0e2b7 100644 (file)
@@ -1,5 +1,4 @@
 {% load i18n %}\r
 {% load i18n %}\r
-{% block title %}{% spaceless %}{% trans "Login" %}{% endspaceless %}{% endblock %}\r
 \r
 <fieldset id='local_login_fs'>\r
   <p><span class='big strong'>Enter your local user name and password</span><br/><span class='grey'>(or select your external provider above)</span></p>\r
 \r
 <fieldset id='local_login_fs'>\r
   <p><span class='big strong'>Enter your local user name and password</span><br/><span class='grey'>(or select your external provider above)</span></p>\r
@@ -29,4 +28,4 @@
         </td>\r
     </tr>\r
   </table>\r
         </td>\r
     </tr>\r
   </table>\r
-</fieldset>
\ No newline at end of file
+</fieldset>\r
index f0b4438a3fafa6d2939e5404986de9b18f818492..3767dae3fc84d40f2b57693bf86fd6ac68b1eb00 100644 (file)
@@ -6,6 +6,7 @@ from django.utils.translation import ugettext as _
 from forms import ClassicRegisterForm
 from forum.authentication.forms import SimpleEmailSubscribeForm
 from forum.views.auth import login_and_forward, send_validation_email
 from forms import ClassicRegisterForm
 from forum.authentication.forms import SimpleEmailSubscribeForm
 from forum.views.auth import login_and_forward, send_validation_email
+from forum.actions import UserJoinsAction
 
 def register(request):
     if request.method == 'POST':
 
 def register(request):
     if request.method == 'POST':
@@ -24,6 +25,7 @@ def register(request):
                 user_.is_superuser = True
 
             user_.save()
                 user_.is_superuser = True
 
             user_.save()
+            UserJoinsAction(user=user_, ip=request.META['REMOTE_ADDR']).save()
             
             send_validation_email(user_)
             if email_feeds_form.cleaned_data['subscribe'] == 'n':
             
             send_validation_email(user_)
             if email_feeds_form.cleaned_data['subscribe'] == 'n':
index c04c44b93a934786e00ecaf1800bf6a4d89ac436..c82b99eda3d7971d16e8d65823f0a6c6c0474714 100644 (file)
@@ -56,7 +56,7 @@ class MyOpenIdAuthContext(ConsumerTemplateContext):
     }
     weight = 200
     human_name = 'MyOpenID'
     }
     weight = 200
     human_name = 'MyOpenID'
-    icon = '/media/images/openid/myopenid.ico'
+    icon = '/media/images/openid/myopenid.png'
 
 
 class FlickrAuthConsumer(OpenIdAbstractAuthConsumer):
 
 
 class FlickrAuthConsumer(OpenIdAbstractAuthConsumer):
@@ -72,7 +72,7 @@ class FlickrAuthContext(ConsumerTemplateContext):
     }
     weight = 250
     human_name = 'Flickr'
     }
     weight = 250
     human_name = 'Flickr'
-    icon = '/media/images/openid/flickr.ico'
+    icon = '/media/images/openid/flickr.png'
 
 
 class TechnoratiAuthConsumer(OpenIdAbstractAuthConsumer):
 
 
 class TechnoratiAuthConsumer(OpenIdAbstractAuthConsumer):
@@ -88,7 +88,7 @@ class TechnoratiAuthContext(ConsumerTemplateContext):
     }
     weight = 260
     human_name = 'Technorati'
     }
     weight = 260
     human_name = 'Technorati'
-    icon = '/media/images/openid/technorati.ico'
+    icon = '/media/images/openid/technorati.png'
 
 
 class WordpressAuthConsumer(OpenIdAbstractAuthConsumer):
 
 
 class WordpressAuthConsumer(OpenIdAbstractAuthConsumer):
@@ -104,7 +104,7 @@ class WordpressAuthContext(ConsumerTemplateContext):
     }
     weight = 270
     human_name = 'Wordpress'
     }
     weight = 270
     human_name = 'Wordpress'
-    icon = '/media/images/openid/wordpress.ico'
+    icon = '/media/images/openid/wordpress.png'
 
 
 class BloggerAuthConsumer(OpenIdAbstractAuthConsumer):
 
 
 class BloggerAuthConsumer(OpenIdAbstractAuthConsumer):
@@ -120,7 +120,7 @@ class BloggerAuthContext(ConsumerTemplateContext):
     }
     weight = 300
     human_name = 'Blogger'
     }
     weight = 300
     human_name = 'Blogger'
-    icon = '/media/images/openid/blogger.ico'
+    icon = '/media/images/openid/blogger.png'
 
 
 class LiveJournalAuthConsumer(OpenIdAbstractAuthConsumer):
 
 
 class LiveJournalAuthConsumer(OpenIdAbstractAuthConsumer):
@@ -136,7 +136,7 @@ class LiveJournalAuthContext(ConsumerTemplateContext):
     }
     weight = 310
     human_name = 'LiveJournal'
     }
     weight = 310
     human_name = 'LiveJournal'
-    icon = '/media/images/openid/livejournal.ico'
+    icon = '/media/images/openid/livejournal.png'
 
 
 class ClaimIdAuthConsumer(OpenIdAbstractAuthConsumer):
 
 
 class ClaimIdAuthConsumer(OpenIdAbstractAuthConsumer):
@@ -152,22 +152,7 @@ class ClaimIdAuthContext(ConsumerTemplateContext):
     }
     weight = 320
     human_name = 'ClaimID'
     }
     weight = 320
     human_name = 'ClaimID'
-    icon = '/media/images/openid/claimid.ico'
-
-class VidoopAuthConsumer(OpenIdAbstractAuthConsumer):
-    def get_user_url(self, request):
-        blog_name = request.POST['input_field']
-        return "http://%s.myvidoop.com/" % blog_name
-
-class VidoopAuthContext(ConsumerTemplateContext):
-    mode = 'SMALLICON'
-    type = 'SIMPLE_FORM'
-    simple_form_context = {
-        'your_what': 'Vidoop user name'
-    }
-    weight = 330
-    human_name = 'Vidoop'
-    icon = '/media/images/openid/vidoop.ico'
+    icon = '/media/images/openid/claimid.png'
 
 class VerisignAuthConsumer(OpenIdAbstractAuthConsumer):
     def get_user_url(self, request):
 
 class VerisignAuthConsumer(OpenIdAbstractAuthConsumer):
     def get_user_url(self, request):
@@ -182,7 +167,7 @@ class VerisignAuthContext(ConsumerTemplateContext):
     }
     weight = 340
     human_name = 'Verisign'
     }
     weight = 340
     human_name = 'Verisign'
-    icon = '/media/images/openid/verisign.ico'
+    icon = '/media/images/openid/verisign.png'
 
     
 class OpenIdUrlAuthConsumer(OpenIdAbstractAuthConsumer):
 
     
 class OpenIdUrlAuthConsumer(OpenIdAbstractAuthConsumer):
@@ -193,4 +178,4 @@ class OpenIdUrlAuthContext(ConsumerTemplateContext):
     weight = 300
     human_name = 'OpenId url'
     stack_item_template = 'modules/openidauth/openidurl.html'
     weight = 300
     human_name = 'OpenId url'
     stack_item_template = 'modules/openidauth/openidurl.html'
-    icon = '/media/images/openid/openid-inputicon.gif'
\ No newline at end of file
+    icon = '/media/images/openid/openid-inputicon.gif'
index 53ab84c345519603cfa3b96321087109412d5153..cd4e77dc3d31ff28de291b31028a95dcb612cbd9 100644 (file)
@@ -12,7 +12,7 @@
             style="width: 500px; background: url('{% media provider.icon %}') no-repeat left center" />\r
         </td>\r
         <td>\r
             style="width: 500px; background: url('{% media provider.icon %}') no-repeat left center" />\r
         </td>\r
         <td>\r
-            <input type="submit" name="signin" value="{% trans 'Login' %}" />\r
+            <input type="submit" name="ssignin" value="Login" />\r
         </td>\r
     </tr>\r
     </table>\r
         </td>\r
     </tr>\r
     </table>\r
index 4cac36f9ae2d6b42bba4a79961653e253f6995d5..835c2f5dd76e463fc25fed9e5ddfc25ad61f2ab5 100644 (file)
@@ -1,11 +1,15 @@
 from forum.models import Question
 from forum.models import Question
+from forum.modules.decorators import decorate
+from forum.views.readers import do_question_search
 
 
+@decorate(do_question_search, needs_origin=False)
 def question_search(keywords):
 def question_search(keywords):
-    return Question.objects.extra(
+    return Question.objects.all().extra(
                     select={
                     select={
-                        'ranking': "node_ranking(id, %s)",
+                        'ranking': 'node_ranking("forum_node"."id", %s)',
                     },
                     },
-                    where=["node_ranking(id, %s) > 0"],
+                    where=['node_ranking("forum_node"."id", %s) > 0'],
                     params=[keywords],
                     params=[keywords],
-                    select_params=[keywords]
-                ).order_by('-ranking')
\ No newline at end of file
+                    select_params=[keywords],
+                    order_by=['-ranking']
+                )
\ No newline at end of file
index 473085cf9ede6d3e8d6907a23190cf428a8bfb60..9a6b60d20b49d5cd8affc3dd8dce6d8b508c97c5 100644 (file)
      SELECT active_revision_id INTO rev_id FROM forum_node WHERE id = node_id;
      SELECT tsv INTO v FROM forum_noderevision WHERE id = rev_id;
 
      SELECT active_revision_id INTO rev_id FROM forum_node WHERE id = node_id;
      SELECT tsv INTO v FROM forum_noderevision WHERE id = rev_id;
 
-    SELECT  count(*) INTO child_count FROM forum_node WHERE abs_parent_id = node_id AND NOT deleted;
+    SELECT  count(*) INTO child_count FROM forum_node WHERE abs_parent_id = node_id AND deleted_id IS NULL;
 
     IF child_count > 0 THEN
 
     IF child_count > 0 THEN
-       FOR r in SELECT * FROM forum_node WHERE abs_parent_id = node_id  AND NOT deleted LOOP
+       FOR r in SELECT * FROM forum_node WHERE abs_parent_id = node_id  AND deleted_id IS NULL LOOP
            SELECT tsv INTO cv FROM forum_noderevision WHERE id = r.active_revision_id;
            v :=(v || cv);
        END LOOP;
            SELECT tsv INTO cv FROM forum_noderevision WHERE id = r.active_revision_id;
            v :=(v || cv);
        END LOOP;
diff --git a/forum_modules/pgfulltext/settings.py b/forum_modules/pgfulltext/settings.py
new file mode 100644 (file)
index 0000000..b2f2497
--- /dev/null
@@ -0,0 +1,3 @@
+from forum.settings.base import Setting\r
+\r
+PG_FTSTRIGGERS_VERSION = Setting('PG_FTSTRIGGERS_VERSION', 0)
\ No newline at end of file
index 6a6f85d1791850587a44ed070c34d66568fb68d5..01c88913c8e1673fc69f297c5bf77f75d9846b48 100644 (file)
@@ -1,20 +1,11 @@
 import os\r
 from forum.models import KeyValue\r
 from django.db import connection, transaction\r
 import os\r
 from forum.models import KeyValue\r
 from django.db import connection, transaction\r
+import settings\r
 \r
 \r
-KEY = 'PG_FTSTRIGGERS_VERSION'\r
-VERSION = 3\r
-install = False\r
+VERSION = 4\r
 \r
 \r
-try:\r
-    version = KeyValue.objects.get(key=KEY).value\r
-    if version < VERSION:\r
-        install = True\r
-except:\r
-    install = True\r
-\r
-\r
-if install:\r
+if int(settings.PG_FTSTRIGGERS_VERSION) < VERSION:\r
     f = open(os.path.join(os.path.dirname(__file__), 'pg_fts_install.sql'), 'r')\r
 \r
     try:\r
     f = open(os.path.join(os.path.dirname(__file__), 'pg_fts_install.sql'), 'r')\r
 \r
     try:\r
@@ -22,13 +13,7 @@ if install:
         cursor.execute(f.read())\r
         transaction.commit_unless_managed()\r
 \r
         cursor.execute(f.read())\r
         transaction.commit_unless_managed()\r
 \r
-        try:\r
-            kv = KeyValue.objects.get(key=KEY)\r
-        except:\r
-            kv = KeyValue(key=KEY)\r
-\r
-        kv.value = VERSION\r
-        kv.save()\r
+        settings.PG_FTSTRIGGERS_VERSION.set_value(VERSION)\r
         \r
     except Exception, e:\r
         #import sys, traceback\r
         \r
     except Exception, e:\r
         #import sys, traceback\r
@@ -38,3 +23,5 @@ if install:
         cursor.close()\r
 \r
     f.close()\r
         cursor.close()\r
 \r
     f.close()\r
+\r
+import handlers\r
index f0cc4e25018645b4be7b1bb0c528243cf6f583aa..f9b6fbc75b3e7e349743e9bef0212e6b3b290ba4 100644 (file)
@@ -1,21 +1,20 @@
-from forum.badges.base import CountableAbstractBadge
-from forum.models import Question, Tag
-from forum import const
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext as _
+from forum.badges.base import AbstractBadge
+from forum.models import Badge, Tag
+from forum.actions import VoteUpAction
 import settings
 
 import settings
 
-class BugBusterBadge(CountableAbstractBadge):
-    type = const.SILVER_BADGE
-    description = _('Got %s upvotes in a question tagged with "bug"') % str(settings.BUG_BUSTER_VOTES_UP)
+class BugBuster(AbstractBadge):
+    type = Badge.SILVER
+    name = _("Bug Buster")
+    description = _('Got %s upvotes in a question tagged with "bug"') % settings.BUG_BUSTER_VOTES_UP
+    listen_to = (VoteUpAction, )
 
 
-    def __init__(self):
-
-        def handler(instance):
+    def award_to(self, action):
+        if action.node.node_type == "question" and action.node.score == settings.BUG_BUSTER_VOTES_UP:
             try:
             try:
-                bug_tag = Tag.objects.get(name='bug')
-                if bug_tag in instance.tags.all():
-                    self.award_badge(instance.author, instance)
+                bug = Tag.objects.get(name="bug")
+                if bug in action.node.tags.all():
+                    return action.node.author
             except:
                 pass
             except:
                 pass
-
-        super(BugBusterBadge, self).__init__(Question, 'vote_up_count', settings.BUG_BUSTER_VOTES_UP, handler)
\ No newline at end of file
diff --git a/mdx_limitedsyntax.py b/mdx_limitedsyntax.py
new file mode 100644 (file)
index 0000000..6a6dac5
--- /dev/null
@@ -0,0 +1,11 @@
+import markdown\r
+from django.utils.safestring import mark_safe\r
+from django.utils.html import strip_tags\r
+from forum.utils.html import sanitize_html\r
+\r
+class LimitedSyntaxExtension(markdown.Extension):\r
+    def extendMarkdown(self, md, md_globals):\r
+        del md.inlinePatterns["image_reference"]\r
+\r
+def makeExtension(configs=None) :\r
+    return LimitedSyntaxExtension(configs=configs)\r
index 866a05674548bdeaed02e97ba6edae33d77ad5dd..1a9f7e169dd56cf885ddb7a38f32bb60c01c06bf 100644 (file)
@@ -13,7 +13,7 @@ TEMPLATE_LOADERS = (
     'django.template.loaders.app_directories.load_template_source',
     'forum.modules.module_templates_loader',
     'forum.skins.load_template_source',
     'django.template.loaders.app_directories.load_template_source',
     'forum.modules.module_templates_loader',
     'forum.skins.load_template_source',
-#   'django.template.loaders.eggs.load_template_source',
+#     'django.template.loaders.eggs.load_template_source',
 )
 
 MIDDLEWARE_CLASSES = [
 )
 
 MIDDLEWARE_CLASSES = [
index 16db021f843f3bcc823c0c41ea84a83182457241..e684801612252f0b1ca22b5e0639f260bf29a626 100644 (file)
@@ -16,7 +16,7 @@ LOG_FILENAME = 'django.osqa.log'
 import logging
 logging.basicConfig(
     filename=os.path.join(SITE_SRC_ROOT, 'log', LOG_FILENAME),
 import logging
 logging.basicConfig(
     filename=os.path.join(SITE_SRC_ROOT, 'log', LOG_FILENAME),
-    level=logging.DEBUG,
+    level=logging.ERROR,
     format='%(pathname)s TIME: %(asctime)s MSG: %(filename)s:%(funcName)s:%(lineno)d %(message)s',
 )
 
     format='%(pathname)s TIME: %(asctime)s MSG: %(filename)s:%(funcName)s:%(lineno)d %(message)s',
 )