-from question import Question ,QuestionRevision, QuestionSubscription\r
-from answer import Answer, AnswerRevision\r
-from tag import Tag, MarkedTag\r
-from user import User, ValidationHash, AuthKeyUserAssociation, SubscriptionSettings\r
-from node import Node, NodeRevision, NodeState, NodeMetaClass\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 south.modelsinspector import add_introspection_rules\r
- add_introspection_rules([], [r"^forum\.models\.\w+\.\w+"])\r
-except:\r
- pass\r
-\r
-from base import *\r
-\r
-__all__ = [\r
- 'Node', 'NodeRevision', 'NodeState', \r
- 'Question', 'QuestionSubscription', 'QuestionRevision',\r
- 'Answer', 'AnswerRevision',\r
- 'Tag', 'Comment', 'MarkedTag', 'Badge', 'Award',\r
- 'ValidationHash', 'AuthKeyUserAssociation', 'SubscriptionSettings', 'KeyValue', 'User',\r
- 'Action', 'ActionRepute', 'Vote', 'Flag'\r
- ]\r
-\r
-\r
-from forum.modules import get_modules_script_classes\r
-\r
-for k, v in get_modules_script_classes('models', models.Model).items():\r
- if not k in __all__:\r
- __all__.append(k)\r
- exec "%s = v" % k\r
-\r
-NodeMetaClass.setup_relations()\r
+from question import Question ,QuestionRevision, QuestionSubscription
+from answer import Answer, AnswerRevision
+from tag import Tag, MarkedTag
+from user import User, ValidationHash, AuthKeyUserAssociation, SubscriptionSettings
+from node import Node, NodeRevision, NodeState, NodeMetaClass
+from comment import Comment
+from action import Action, ActionRepute
+from meta import Vote, Flag, Badge, Award
+from utils import KeyValue
+
+try:
+ from south.modelsinspector import add_introspection_rules
+ add_introspection_rules([], [r"^forum\.models\.\w+\.\w+"])
+except:
+ pass
+
+from base import *
+
+__all__ = [
+ 'Node', 'NodeRevision', 'NodeState',
+ 'Question', 'QuestionSubscription', 'QuestionRevision',
+ 'Answer', 'AnswerRevision',
+ 'Tag', 'Comment', 'MarkedTag', 'Badge', 'Award',
+ 'ValidationHash', 'AuthKeyUserAssociation', 'SubscriptionSettings', 'KeyValue', 'User',
+ 'Action', 'ActionRepute', 'Vote', 'Flag'
+ ]
+
+
+from forum.modules import get_modules_script_classes
+
+for k, v in get_modules_script_classes('models', models.Model).items():
+ if not k in __all__:
+ __all__.append(k)
+ exec "%s = v" % k
+
+NodeMetaClass.setup_relations()
BaseMetaClass.setup_denormalizes()
\ No newline at end of file
-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 ActionQuerySet(CachedQuerySet):\r
- def obj_from_datadict(self, datadict):\r
- cls = ActionProxyMetaClass.types.get(datadict['action_type'], None)\r
- if cls:\r
- obj = cls()\r
- obj.__dict__.update(datadict)\r
- return obj\r
- else:\r
- return super(ActionQuerySet, self).obj_from_datadict(datadict)\r
-\r
- def get(self, *args, **kwargs):\r
- return super(ActionQuerySet, self).get(*args, **kwargs).leaf()\r
-\r
-class ActionManager(CachedManager):\r
- use_for_related_fields = True\r
-\r
- def get_query_set(self):\r
- qs = ActionQuerySet(self.model)\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_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
-class Action(BaseModel):\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
- @property\r
- def verb(self):\r
- return ""\r
-\r
- def describe(self, viewer=None):\r
- return self.__class__.__name__\r
-\r
- def get_absolute_url(self):\r
- if self.node:\r
- return self.node.get_absolute_url()\r
- else:\r
- return self.user.get_profile_url()\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
- d = self._as_dict()\r
- leaf.__dict__.update(self._as_dict())\r
- l = leaf._as_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.nis.wiki):\r
- self.repute_users()\r
- self.process_action()\r
- self.trigger_hooks(True)\r
-\r
- return self\r
-\r
- def delete(self, *args, **kwargs):\r
- self.cancel_action()\r
- super(Action, self).delete(*args, **kwargs)\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
- import traceback\r
- logging.error("Error in %s hook: %s" % (cls.__name__, str(e)))\r
- logging.error(traceback.format_exc())\r
-\r
-class ActionProxyMetaClass(BaseMetaClass):\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 viewer_or_user_verb(self, viewer, user, viewer_verb, user_verb):\r
- return (viewer == user) and viewer_verb or user_verb \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 DummyActionProxyMetaClass(type):\r
- def __new__(cls, *args, **kwargs):\r
- new_cls = super(DummyActionProxyMetaClass, cls).__new__(cls, *args, **kwargs)\r
- ActionProxyMetaClass.types[new_cls.get_type()] = new_cls\r
- return new_cls\r
-\r
-class DummyActionProxy(object):\r
- __metaclass__ = DummyActionProxyMetaClass\r
-\r
- hooks = []\r
-\r
- def __init__(self, ip=None):\r
- self.ip = ip\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 += self.value\r
- self.user.save()\r
-\r
- def delete(self):\r
- self.user.reputation -= self.value\r
- self.user.save()\r
- super(ActionRepute, self).delete()\r
-\r
- class Meta:\r
- app_label = 'forum'\r
-\r
+from django.utils.translation import ugettext as _
+from utils import PickledObjectField
+from threading import Thread
+from forum.utils import html
+from base import *
+import re
+
+class ActionQuerySet(CachedQuerySet):
+ def obj_from_datadict(self, datadict):
+ cls = ActionProxyMetaClass.types.get(datadict['action_type'], None)
+ if cls:
+ obj = cls()
+ obj.__dict__.update(datadict)
+ return obj
+ else:
+ return super(ActionQuerySet, self).obj_from_datadict(datadict)
+
+ def get(self, *args, **kwargs):
+ return super(ActionQuerySet, self).get(*args, **kwargs).leaf()
+
+class ActionManager(CachedManager):
+ use_for_related_fields = True
+
+ def get_query_set(self):
+ qs = ActionQuerySet(self.model)
+
+ if self.model is not Action:
+ return qs.filter(action_type=self.model.get_type())
+ else:
+ return qs
+
+ def get_for_types(self, types, *args, **kwargs):
+ kwargs['action_type__in'] = [t.get_type() for t in types]
+ return self.get(*args, **kwargs)
+
+
+class Action(BaseModel):
+ user = models.ForeignKey('User', related_name="actions")
+ ip = models.CharField(max_length=16)
+ node = models.ForeignKey('Node', null=True, related_name="actions")
+ action_type = models.CharField(max_length=16)
+ action_date = models.DateTimeField(default=datetime.datetime.now)
+
+ extra = PickledObjectField()
+
+ canceled = models.BooleanField(default=False)
+ canceled_by = models.ForeignKey('User', null=True, related_name="canceled_actions")
+ canceled_at = models.DateTimeField(null=True)
+ canceled_ip = models.CharField(max_length=16)
+
+ hooks = {}
+
+ objects = ActionManager()
+
+ @property
+ def at(self):
+ return self.action_date
+
+ @property
+ def by(self):
+ return self.user
+
+ def repute_users(self):
+ pass
+
+ def process_data(self, **data):
+ pass
+
+ def process_action(self):
+ pass
+
+ def cancel_action(self):
+ pass
+
+ @property
+ def verb(self):
+ return ""
+
+ def describe(self, viewer=None):
+ return self.__class__.__name__
+
+ def get_absolute_url(self):
+ if self.node:
+ return self.node.get_absolute_url()
+ else:
+ return self.user.get_profile_url()
+
+ def repute(self, user, value):
+ repute = ActionRepute(action=self, user=user, value=value)
+ repute.save()
+ return repute
+
+ def cancel_reputes(self):
+ for repute in self.reputes.all():
+ cancel = ActionRepute(action=self, user=repute.user, value=(-repute.value), by_canceled=True)
+ cancel.save()
+
+ def leaf(self):
+ leaf_cls = ActionProxyMetaClass.types.get(self.action_type, None)
+
+ if leaf_cls is None:
+ return self
+
+ leaf = leaf_cls()
+ d = self._as_dict()
+ leaf.__dict__.update(self._as_dict())
+ l = leaf._as_dict()
+ return leaf
+
+ @classmethod
+ def get_type(cls):
+ return re.sub(r'action$', '', cls.__name__.lower())
+
+ def save(self, data=None, *args, **kwargs):
+ isnew = False
+
+ if not self.id:
+ self.action_type = self.__class__.get_type()
+ isnew = True
+
+ if data:
+ self.process_data(**data)
+
+ super(Action, self).save(*args, **kwargs)
+
+ if isnew:
+ if (self.node is None) or (not self.node.nis.wiki):
+ self.repute_users()
+ self.process_action()
+ self.trigger_hooks(True)
+
+ return self
+
+ def delete(self, *args, **kwargs):
+ self.cancel_action()
+ super(Action, self).delete(*args, **kwargs)
+
+ def cancel(self, user=None, ip=None):
+ if not self.canceled:
+ self.canceled = True
+ self.canceled_at = datetime.datetime.now()
+ self.canceled_by = (user is None) and self.user or user
+ if ip:
+ self.canceled_ip = ip
+ self.save()
+ self.cancel_reputes()
+ self.cancel_action()
+ #self.trigger_hooks(False)
+
+ @classmethod
+ def get_current(cls, **kwargs):
+ kwargs['canceled'] = False
+
+ try:
+ return cls.objects.get(**kwargs)
+ except cls.MultipleObjectsReturned:
+ logging.error("Got multiple values for action %s with args %s", cls.__name__,
+ ", ".join(["%s='%s'" % i for i in kwargs.items()]))
+ raise
+ except cls.DoesNotExist:
+ return None
+
+ @classmethod
+ def hook(cls, fn):
+ if not Action.hooks.get(cls, None):
+ Action.hooks[cls] = []
+
+ Action.hooks[cls].append(fn)
+
+ def trigger_hooks(self, new=True):
+ thread = Thread(target=trigger_hooks_threaded, args=[self, Action.hooks, new])
+ thread.setDaemon(True)
+ thread.start()
+
+ class Meta:
+ app_label = 'forum'
+
+def trigger_hooks_threaded(action, hooks, new):
+ for cls, hooklist in hooks.items():
+ if isinstance(action, cls):
+ for hook in hooklist:
+ try:
+ hook(action=action, new=new)
+ except Exception, e:
+ import traceback
+ logging.error("Error in %s hook: %s" % (cls.__name__, str(e)))
+ logging.error(traceback.format_exc())
+
+class ActionProxyMetaClass(BaseMetaClass):
+ types = {}
+
+ def __new__(cls, *args, **kwargs):
+ new_cls = super(ActionProxyMetaClass, cls).__new__(cls, *args, **kwargs)
+ cls.types[new_cls.get_type()] = new_cls
+
+ class Meta:
+ proxy = True
+
+ new_cls.Meta = Meta
+ return new_cls
+
+class ActionProxy(Action):
+ __metaclass__ = ActionProxyMetaClass
+
+ def friendly_username(self, viewer, user):
+ return (viewer == user) and _('You') or user.username
+
+ def friendly_ownername(self, owner, user):
+ return (owner == user) and _('your') or user.username
+
+ def viewer_or_user_verb(self, viewer, user, viewer_verb, user_verb):
+ return (viewer == user) and viewer_verb or user_verb
+
+ def hyperlink(self, url, title, **attrs):
+ return html.hyperlink(url, title, **attrs)
+
+ def describe_node(self, viewer, node):
+ node_link = self.hyperlink(node.get_absolute_url(), node.headline)
+
+ if node.parent:
+ node_desc = _("on %(link)s") % {'link': node_link}
+ else:
+ node_desc = node_link
+
+ return _("%(user)s %(node_name)s %(node_desc)s") % {
+ 'user': self.hyperlink(node.author.get_profile_url(), self.friendly_ownername(viewer, node.author)),
+ 'node_name': node.friendly_name,
+ 'node_desc': node_desc,
+ }
+
+ class Meta:
+ proxy = True
+
+class DummyActionProxyMetaClass(type):
+ def __new__(cls, *args, **kwargs):
+ new_cls = super(DummyActionProxyMetaClass, cls).__new__(cls, *args, **kwargs)
+ ActionProxyMetaClass.types[new_cls.get_type()] = new_cls
+ return new_cls
+
+class DummyActionProxy(object):
+ __metaclass__ = DummyActionProxyMetaClass
+
+ hooks = []
+
+ def __init__(self, ip=None):
+ self.ip = ip
+
+ def process_data(self, **data):
+ pass
+
+ def process_action(self):
+ pass
+
+ def save(self, data=None):
+ self.process_action()
+
+ if data:
+ self.process_data(**data)
+
+ for hook in self.__class__.hooks:
+ hook(self, True)
+
+ @classmethod
+ def get_type(cls):
+ return re.sub(r'action$', '', cls.__name__.lower())
+
+ @classmethod
+ def hook(cls, fn):
+ cls.hooks.append(fn)
+
+
+
+class ActionRepute(models.Model):
+ action = models.ForeignKey(Action, related_name='reputes')
+ date = models.DateTimeField(default=datetime.datetime.now)
+ user = models.ForeignKey('User', related_name='reputes')
+ value = models.IntegerField(default=0)
+ by_canceled = models.BooleanField(default=False)
+
+ @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
+
+ def save(self, *args, **kwargs):
+ super(ActionRepute, self).save(*args, **kwargs)
+ self.user.reputation += self.value
+ self.user.save()
+
+ def delete(self):
+ self.user.reputation -= self.value
+ self.user.save()
+ super(ActionRepute, self).delete()
+
+ class Meta:
+ app_label = 'forum'
+
-from base import *\r
-from django.utils.translation import ugettext as _\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
-\r
- def _update_parent_comment_count(self, diff):\r
- parent = self.parent\r
- parent.comment_count = parent.comment_count + diff\r
- parent.save()\r
-\r
- @property\r
- def comment(self):\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
- return self.absolute_parent.headline\r
-\r
- @property\r
- def content_object(self):\r
- return self.parent.leaf\r
-\r
- def save(self, *args, **kwargs):\r
- super(Comment,self).save(*args, **kwargs)\r
-\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
- self.parent.reset_comment_count_cache()\r
-\r
- def unmark_deleted(self):\r
- if super(Comment, self).unmark_deleted():\r
- self.parent.reset_comment_count_cache()\r
-\r
- def is_reply_to(self, user):\r
- inreply = re.search('@\w+', self.body)\r
- if inreply is not None:\r
- return user.username.startswith(inreply.group(0))\r
-\r
- return False\r
-\r
- def get_absolute_url(self):\r
- return self.abs_parent.get_absolute_url() + "#%d" % self.id\r
-\r
- def __unicode__(self):\r
- return self.body\r
-\r
+from base import *
+from django.utils.translation import ugettext as _
+import re
+
+class Comment(Node):
+ friendly_name = _("comment")
+
+ class Meta(Node.Meta):
+ ordering = ('-added_at',)
+ proxy = True
+
+ def _update_parent_comment_count(self, diff):
+ parent = self.parent
+ parent.comment_count = parent.comment_count + diff
+ parent.save()
+
+ @property
+ def comment(self):
+ if settings.FORM_ALLOW_MARKDOWN_IN_COMMENTS:
+ return self.as_markdown('limitedsyntax')
+ else:
+ return self.body
+
+ @property
+ def headline(self):
+ return self.absolute_parent.headline
+
+ @property
+ def content_object(self):
+ return self.parent.leaf
+
+ def save(self, *args, **kwargs):
+ super(Comment,self).save(*args, **kwargs)
+
+ if not self.id:
+ self.parent.reset_comment_count_cache()
+
+ def mark_deleted(self, user):
+ if super(Comment, self).mark_deleted(user):
+ self.parent.reset_comment_count_cache()
+
+ def unmark_deleted(self):
+ if super(Comment, self).unmark_deleted():
+ self.parent.reset_comment_count_cache()
+
+ def is_reply_to(self, user):
+ inreply = re.search('@\w+', self.body)
+ if inreply is not None:
+ return user.username.startswith(inreply.group(0))
+
+ return False
+
+ def get_absolute_url(self):
+ return self.abs_parent.get_absolute_url() + "#%d" % self.id
+
+ def __unicode__(self):
+ return self.body
+
-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.OneToOneField(Action, 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.OneToOneField(Action, 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 BadgesQuerySet(models.query.QuerySet):\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(BadgesQuerySet, self).get(*args, **kwargs)\r
-\r
- from forum.badges.base import BadgesMeta\r
- badge = BadgesMeta.by_id.get(int(pk), None)\r
- if not badge:\r
- return super(BadgesQuerySet, self).get(*args, **kwargs)\r
- return badge.ondb\r
-\r
-\r
-class BadgeManager(models.Manager):\r
- use_for_related_fields = True\r
-\r
- def get_query_set(self):\r
- return BadgesQuerySet(self.model)\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
- def save(self, *args, **kwargs):\r
- if isinstance(self.awarded_count, models.expressions.ExpressionNode):\r
- super(Badge, self).save(*args, **kwargs)\r
- self.awarded_count = self.__class__.objects.filter(id=self.id).values_list('awarded_count', flat=True)[0]\r
- else:\r
- super(Badge, self).save(*args, **kwargs)\r
-\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.OneToOneField(Action, related_name="award")\r
-\r
-\r
- class Meta:\r
- unique_together = ('user', 'badge', 'node')\r
+from django.utils.translation import ugettext as _
+from base import *
+
+class Vote(models.Model):
+ user = models.ForeignKey(User, related_name="votes")
+ node = models.ForeignKey(Node, related_name="votes")
+ value = models.SmallIntegerField()
+ action = models.OneToOneField(Action, related_name="vote")
+ voted_at = models.DateTimeField(default=datetime.datetime.now)
+
+ class Meta:
+ app_label = 'forum'
+ unique_together = ('user', 'node')
+
+
+class Flag(models.Model):
+ user = models.ForeignKey(User, related_name="flags")
+ node = models.ForeignKey(Node, related_name="flags")
+ reason = models.CharField(max_length=300)
+ action = models.OneToOneField(Action, related_name="flag")
+ flagged_at = models.DateTimeField(default=datetime.datetime.now)
+
+ class Meta:
+ app_label = 'forum'
+ unique_together = ('user', 'node')
+
+class BadgesQuerySet(models.query.QuerySet):
+ def get(self, *args, **kwargs):
+ try:
+ pk = [v for (k,v) in kwargs.items() if k in ('pk', 'pk__exact', 'id', 'id__exact')][0]
+ except:
+ return super(BadgesQuerySet, self).get(*args, **kwargs)
+
+ from forum.badges.base import BadgesMeta
+ badge = BadgesMeta.by_id.get(int(pk), None)
+ if not badge:
+ return super(BadgesQuerySet, self).get(*args, **kwargs)
+ return badge.ondb
+
+
+class BadgeManager(models.Manager):
+ use_for_related_fields = True
+
+ def get_query_set(self):
+ return BadgesQuerySet(self.model)
+
+class Badge(models.Model):
+ GOLD = 1
+ SILVER = 2
+ BRONZE = 3
+
+ type = models.SmallIntegerField()
+ cls = models.CharField(max_length=50, null=True)
+ awarded_count = models.PositiveIntegerField(default=0)
+
+ awarded_to = models.ManyToManyField(User, through='Award', related_name='badges')
+
+ objects = BadgeManager()
+
+ @property
+ def name(self):
+ cls = self.__dict__.get('_class', None)
+ return cls and cls.name or _("Unknown")
+
+ @property
+ def description(self):
+ cls = self.__dict__.get('_class', None)
+ return cls and cls.description or _("No description available")
+
+ @models.permalink
+ def get_absolute_url(self):
+ return ('badge', [], {'id': self.id, 'slug': slugify(self.name)})
+
+ def save(self, *args, **kwargs):
+ if isinstance(self.awarded_count, models.expressions.ExpressionNode):
+ super(Badge, self).save(*args, **kwargs)
+ self.awarded_count = self.__class__.objects.filter(id=self.id).values_list('awarded_count', flat=True)[0]
+ else:
+ super(Badge, self).save(*args, **kwargs)
+
+
+ class Meta:
+ app_label = 'forum'
+
+
+class Award(models.Model):
+ user = models.ForeignKey(User)
+ badge = models.ForeignKey('Badge', related_name="awards")
+ node = models.ForeignKey(Node, null=True)
+
+ awarded_at = models.DateTimeField(default=datetime.datetime.now)
+
+ trigger = models.ForeignKey(Action, related_name="awards", null=True)
+ action = models.OneToOneField(Action, related_name="award")
+
+
+ class Meta:
+ unique_together = ('user', 'badge', 'node')
app_label = 'forum'
\ No newline at end of file
-from base import *\r
-import re\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
-\r
-class NodeContent(models.Model):\r
- title = models.CharField(max_length=300)\r
- tagnames = models.CharField(max_length=125)\r
- author = models.ForeignKey(User, related_name='%(class)ss')\r
- body = models.TextField()\r
-\r
- @property\r
- def user(self):\r
- return self.author\r
-\r
- @property\r
- def html(self):\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
- return self.title\r
-\r
- def tagname_list(self):\r
- if self.tagnames:\r
- t = [name.strip() for name in self.tagnames.split(u' ') if name]\r
- return [name.strip() for name in self.tagnames.split(u' ') if name]\r
- else:\r
- return []\r
-\r
- def tagname_meta_generator(self):\r
- return u','.join([tag for tag in self.tagname_list()])\r
-\r
- class Meta:\r
- abstract = True\r
- app_label = 'forum'\r
-\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
- NodeMetaClass.types[new_cls.get_type()] = new_cls\r
-\r
- return new_cls\r
-\r
- @classmethod\r
- def setup_relations(cls):\r
- for node_cls in NodeMetaClass.types.values():\r
- NodeMetaClass.setup_relation(node_cls) \r
-\r
- @classmethod\r
- def setup_relation(cls, node_cls):\r
- name = node_cls.__name__.lower()\r
-\r
- def children(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
- if p is None and (self.parent is not None) and self.parent.node_type == name:\r
- p = self.parent.leaf\r
- self.__dict__['_%s_cache' % name] = p\r
-\r
- return p\r
-\r
- Node.add_to_class(name + 's', property(children))\r
- Node.add_to_class(name, property(parent))\r
-\r
-\r
-class NodeQuerySet(CachedQuerySet):\r
- def obj_from_datadict(self, datadict):\r
- cls = NodeMetaClass.types.get(datadict.get("node_type", ""), None)\r
- if cls:\r
- obj = cls()\r
- obj.__dict__.update(datadict)\r
- return obj\r
- else:\r
- return super(NodeQuerySet, self).obj_from_datadict(datadict)\r
-\r
- def get(self, *args, **kwargs):\r
- return super(NodeQuerySet, self).get(*args, **kwargs).leaf\r
-\r
- def filter_state(self, **kwargs):\r
- apply_bool = lambda q, b: b and q or ~q\r
- return self.filter(*[apply_bool(models.Q(state_string__contains="(%s)" % s), b) for s, b in kwargs.items()])\r
-\r
-\r
-class NodeManager(CachedManager):\r
- use_for_related_fields = True\r
-\r
- def get_query_set(self):\r
- qs = NodeQuerySet(self.model)\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_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
- def filter_state(self, **kwargs):\r
- return self.all().filter_state(**kwargs)\r
-\r
-\r
-class NodeStateDict(object):\r
- def __init__(self, node):\r
- self.__dict__['_node'] = node\r
-\r
- def __getattr__(self, name):\r
- if self.__dict__.get(name, None):\r
- return self.__dict__[name]\r
-\r
- try:\r
- node = self.__dict__['_node']\r
- action = NodeState.objects.get(node=node, state_type=name).action\r
- self.__dict__[name] = action\r
- return action\r
- except:\r
- return None\r
-\r
- def __setattr__(self, name, value):\r
- current = self.__getattr__(name)\r
-\r
- if value:\r
- if current:\r
- current.action = value\r
- current.save()\r
- else:\r
- node = self.__dict__['_node']\r
- state = NodeState(node=node, action=value, state_type=name)\r
- state.save()\r
- self.__dict__[name] = value\r
-\r
- if not "(%s)" % name in node.state_string:\r
- node.state_string = "%s(%s)" % (node.state_string, name)\r
- node.save()\r
- else:\r
- if current:\r
- node = self.__dict__['_node']\r
- node.state_string = "".join("(%s)" % s for s in re.findall('\w+', node.state_string) if s != name)\r
- node.save()\r
- current.node_state.delete()\r
- del self.__dict__[name]\r
-\r
-\r
-class NodeStateQuery(object):\r
- def __init__(self, node):\r
- self.__dict__['_node'] = node\r
-\r
- def __getattr__(self, name):\r
- node = self.__dict__['_node']\r
- return "(%s)" % name in node.state_string\r
-\r
-\r
-\r
-class Node(BaseModel, NodeContent):\r
- __metaclass__ = NodeMetaClass\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
- added_at = models.DateTimeField(default=datetime.datetime.now)\r
- score = models.IntegerField(default=0)\r
-\r
- state_string = models.TextField(default='')\r
- last_edited = models.ForeignKey('Action', null=True, unique=True, related_name="edited_node")\r
-\r
- last_activity_by = models.ForeignKey(User, null=True)\r
- last_activity_at = models.DateTimeField(null=True, blank=True)\r
-\r
- tags = models.ManyToManyField('Tag', related_name='%(class)ss')\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
-\r
- marked = 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
- 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 nstate(self):\r
- state = self.__dict__.get('_nstate', None)\r
-\r
- if state is None:\r
- state = NodeStateDict(self)\r
- self._nstate = state\r
-\r
- return state\r
-\r
- @property\r
- def nis(self):\r
- nis = self.__dict__.get('_nis', None)\r
-\r
- if nis is None:\r
- nis = NodeStateQuery(self)\r
- self._nis = nis\r
-\r
- return nis\r
-\r
- @property\r
- def deleted(self):\r
- return self.nis.deleted\r
-\r
- @property \r
- def absolute_parent(self):\r
- if not self.abs_parent_id:\r
- return self\r
-\r
- return self.abs_parent\r
-\r
- @property\r
- def summary(self):\r
- return strip_tags(self.html)[:300]\r
-\r
- @models.permalink\r
- def get_revisions_url(self):\r
- return ('revisions', (), {'id': self.id})\r
-\r
- def update_last_activity(self, user, save=False):\r
- self.last_activity_by = user\r
- self.last_activity_at = datetime.datetime.now()\r
-\r
- if self.parent:\r
- self.parent.update_last_activity(user, save=True)\r
-\r
- if save:\r
- self.save()\r
-\r
- def _create_revision(self, user, number, **kwargs):\r
- revision = NodeRevision(author=user, revision=number, node=self, **kwargs)\r
- revision.save()\r
- return revision\r
-\r
- def create_revision(self, user, **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)\r
- return revision\r
-\r
- def activate_revision(self, user, revision):\r
- self.title = revision.title\r
- self.tagnames = revision.tagnames\r
- self.body = revision.body\r
-\r
- self.active_revision = revision\r
- self.update_last_activity(user)\r
-\r
- self.save()\r
-\r
- def _list_changes_in_tags(self):\r
- dirty = self.get_dirty_fields()\r
-\r
- if not 'tagnames' in dirty:\r
- return None\r
- else:\r
- if self._original_state['tagnames']:\r
- old_tags = set(name for name in self._original_state['tagnames'].split(u' '))\r
- else:\r
- old_tags = set()\r
- new_tags = set(name for name in self.tagnames.split(u' ') if name)\r
-\r
- return dict(\r
- current=list(new_tags),\r
- added=list(new_tags - old_tags),\r
- removed=list(old_tags - new_tags)\r
- )\r
-\r
- def _last_active_user(self):\r
- return self.last_edited and self.last_edited.by or self.author\r
-\r
- def _process_changes_in_tags(self):\r
- tag_changes = self._list_changes_in_tags()\r
-\r
- if tag_changes is not None:\r
- for name in tag_changes['added']:\r
- try:\r
- tag = Tag.objects.get(name=name)\r
- except:\r
- tag = Tag.objects.create(name=name, created_by=self._last_active_user())\r
-\r
- if not self.nis.deleted:\r
- tag.used_count = models.F('used_count') + 1\r
- tag.save()\r
-\r
- if not self.nis.deleted:\r
- for name in tag_changes['removed']:\r
- try:\r
- tag = Tag.objects.get(name=name)\r
- tag.used_count = models.F('used_count') - 1\r
- tag.save()\r
- except:\r
- pass\r
-\r
- return True\r
-\r
- return False\r
-\r
- def mark_deleted(self, action):\r
- self.nstate.deleted = action\r
- self.save()\r
-\r
- if action:\r
- for tag in self.tags.all():\r
- tag.used_count = models.F('used_count') - 1\r
- tag.save()\r
- else:\r
- for tag in Tag.objects.filter(name__in=self.tagname_list()):\r
- tag.used_count = models.F('used_count') + 1\r
- tag.save()\r
-\r
- def save(self, *args, **kwargs):\r
- tags_changed = self._process_changes_in_tags()\r
- \r
- if not self.id:\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
- super(Node, self).save(*args, **kwargs)\r
- if tags_changed: self.tags = list(Tag.objects.filter(name__in=self.tagname_list()))\r
-\r
- class Meta:\r
- app_label = 'forum'\r
-\r
-\r
-class NodeRevision(BaseModel, NodeContent):\r
- node = models.ForeignKey(Node, related_name='revisions')\r
- summary = models.CharField(max_length=300)\r
- revision = models.PositiveIntegerField()\r
- revised_at = models.DateTimeField(default=datetime.datetime.now)\r
-\r
- class Meta:\r
- unique_together = ('node', 'revision')\r
- app_label = 'forum'\r
-\r
-\r
-class NodeState(models.Model):\r
- node = models.ForeignKey(Node, related_name='states')\r
- state_type = models.CharField(max_length=16)\r
- action = models.OneToOneField('Action', related_name="node_state")\r
-\r
- class Meta:\r
- unique_together = ('node', 'state_type')\r
- app_label = 'forum'\r
-\r
-\r
+from base import *
+import re
+from tag import Tag
+
+import markdown
+from django.utils.translation import ugettext as _
+from django.utils.safestring import mark_safe
+from django.utils.html import strip_tags
+from forum.utils.html import sanitize_html
+
+class NodeContent(models.Model):
+ title = models.CharField(max_length=300)
+ tagnames = models.CharField(max_length=125)
+ author = models.ForeignKey(User, related_name='%(class)ss')
+ body = models.TextField()
+
+ @property
+ def user(self):
+ return self.author
+
+ @property
+ def html(self):
+ return self.as_markdown()
+
+ def as_markdown(self, *extensions):
+ return mark_safe(sanitize_html(markdown.markdown(self.body, extensions=extensions)))
+
+ @property
+ def headline(self):
+ return self.title
+
+ def tagname_list(self):
+ if self.tagnames:
+ t = [name.strip() for name in self.tagnames.split(u' ') if name]
+ return [name.strip() for name in self.tagnames.split(u' ') if name]
+ else:
+ return []
+
+ def tagname_meta_generator(self):
+ return u','.join([tag for tag in self.tagname_list()])
+
+ class Meta:
+ abstract = True
+ app_label = 'forum'
+
+class NodeMetaClass(BaseMetaClass):
+ types = {}
+
+ def __new__(cls, *args, **kwargs):
+ new_cls = super(NodeMetaClass, cls).__new__(cls, *args, **kwargs)
+
+ if not new_cls._meta.abstract and new_cls.__name__ is not 'Node':
+ NodeMetaClass.types[new_cls.get_type()] = new_cls
+
+ return new_cls
+
+ @classmethod
+ def setup_relations(cls):
+ for node_cls in NodeMetaClass.types.values():
+ NodeMetaClass.setup_relation(node_cls)
+
+ @classmethod
+ def setup_relation(cls, node_cls):
+ name = node_cls.__name__.lower()
+
+ def children(self):
+ return node_cls.objects.filter(parent=self)
+
+ def parent(self):
+ p = self.__dict__.get('_%s_cache' % name, None)
+
+ if p is None and (self.parent is not None) and self.parent.node_type == name:
+ p = self.parent.leaf
+ self.__dict__['_%s_cache' % name] = p
+
+ return p
+
+ Node.add_to_class(name + 's', property(children))
+ Node.add_to_class(name, property(parent))
+
+
+class NodeQuerySet(CachedQuerySet):
+ def obj_from_datadict(self, datadict):
+ cls = NodeMetaClass.types.get(datadict.get("node_type", ""), None)
+ if cls:
+ obj = cls()
+ obj.__dict__.update(datadict)
+ return obj
+ else:
+ return super(NodeQuerySet, self).obj_from_datadict(datadict)
+
+ def get(self, *args, **kwargs):
+ return super(NodeQuerySet, self).get(*args, **kwargs).leaf
+
+ def filter_state(self, **kwargs):
+ apply_bool = lambda q, b: b and q or ~q
+ return self.filter(*[apply_bool(models.Q(state_string__contains="(%s)" % s), b) for s, b in kwargs.items()])
+
+
+class NodeManager(CachedManager):
+ use_for_related_fields = True
+
+ def get_query_set(self):
+ qs = NodeQuerySet(self.model)
+
+ if self.model is not Node:
+ return qs.filter(node_type=self.model.get_type())
+ else:
+ return qs
+
+ def get_for_types(self, types, *args, **kwargs):
+ kwargs['node_type__in'] = [t.get_type() for t in types]
+ return self.get(*args, **kwargs)
+
+ def filter_state(self, **kwargs):
+ return self.all().filter_state(**kwargs)
+
+
+class NodeStateDict(object):
+ def __init__(self, node):
+ self.__dict__['_node'] = node
+
+ def __getattr__(self, name):
+ if self.__dict__.get(name, None):
+ return self.__dict__[name]
+
+ try:
+ node = self.__dict__['_node']
+ action = NodeState.objects.get(node=node, state_type=name).action
+ self.__dict__[name] = action
+ return action
+ except:
+ return None
+
+ def __setattr__(self, name, value):
+ current = self.__getattr__(name)
+
+ if value:
+ if current:
+ current.action = value
+ current.save()
+ else:
+ node = self.__dict__['_node']
+ state = NodeState(node=node, action=value, state_type=name)
+ state.save()
+ self.__dict__[name] = value
+
+ if not "(%s)" % name in node.state_string:
+ node.state_string = "%s(%s)" % (node.state_string, name)
+ node.save()
+ else:
+ if current:
+ node = self.__dict__['_node']
+ node.state_string = "".join("(%s)" % s for s in re.findall('\w+', node.state_string) if s != name)
+ node.save()
+ current.node_state.delete()
+ del self.__dict__[name]
+
+
+class NodeStateQuery(object):
+ def __init__(self, node):
+ self.__dict__['_node'] = node
+
+ def __getattr__(self, name):
+ node = self.__dict__['_node']
+ return "(%s)" % name in node.state_string
+
+
+
+class Node(BaseModel, NodeContent):
+ __metaclass__ = NodeMetaClass
+
+ node_type = models.CharField(max_length=16, default='node')
+ parent = models.ForeignKey('Node', related_name='children', null=True)
+ abs_parent = models.ForeignKey('Node', related_name='all_children', null=True)
+
+ added_at = models.DateTimeField(default=datetime.datetime.now)
+ score = models.IntegerField(default=0)
+
+ state_string = models.TextField(default='')
+ last_edited = models.ForeignKey('Action', null=True, unique=True, related_name="edited_node")
+
+ last_activity_by = models.ForeignKey(User, null=True)
+ last_activity_at = models.DateTimeField(null=True, blank=True)
+
+ tags = models.ManyToManyField('Tag', related_name='%(class)ss')
+ active_revision = models.OneToOneField('NodeRevision', related_name='active', null=True)
+
+ extra_ref = models.ForeignKey('Node', null=True)
+ extra_count = models.IntegerField(default=0)
+
+ marked = models.BooleanField(default=False)
+
+ comment_count = DenormalizedField("children", node_type="comment", canceled=False)
+ flag_count = DenormalizedField("flags")
+
+ friendly_name = _("post")
+
+ objects = NodeManager()
+
+ def __unicode__(self):
+ return self.headline
+
+ @classmethod
+ def cache_key(cls, pk):
+ return '%s:node:%s' % (settings.APP_URL, pk)
+
+ @classmethod
+ def get_type(cls):
+ return cls.__name__.lower()
+
+ @property
+ def leaf(self):
+ leaf_cls = NodeMetaClass.types.get(self.node_type, None)
+
+ if leaf_cls is None:
+ return self
+
+ leaf = leaf_cls()
+ leaf.__dict__ = self.__dict__
+ return leaf
+
+ @property
+ def nstate(self):
+ state = self.__dict__.get('_nstate', None)
+
+ if state is None:
+ state = NodeStateDict(self)
+ self._nstate = state
+
+ return state
+
+ @property
+ def nis(self):
+ nis = self.__dict__.get('_nis', None)
+
+ if nis is None:
+ nis = NodeStateQuery(self)
+ self._nis = nis
+
+ return nis
+
+ @property
+ def deleted(self):
+ return self.nis.deleted
+
+ @property
+ def absolute_parent(self):
+ if not self.abs_parent_id:
+ return self
+
+ return self.abs_parent
+
+ @property
+ def summary(self):
+ return strip_tags(self.html)[:300]
+
+ @models.permalink
+ def get_revisions_url(self):
+ return ('revisions', (), {'id': self.id})
+
+ def update_last_activity(self, user, save=False):
+ self.last_activity_by = user
+ self.last_activity_at = datetime.datetime.now()
+
+ if self.parent:
+ self.parent.update_last_activity(user, save=True)
+
+ if save:
+ self.save()
+
+ def _create_revision(self, user, number, **kwargs):
+ revision = NodeRevision(author=user, revision=number, node=self, **kwargs)
+ revision.save()
+ return revision
+
+ def create_revision(self, user, **kwargs):
+ number = self.revisions.aggregate(last=models.Max('revision'))['last'] + 1
+ revision = self._create_revision(user, number, **kwargs)
+ self.activate_revision(user, revision)
+ return revision
+
+ def activate_revision(self, user, revision):
+ self.title = revision.title
+ self.tagnames = revision.tagnames
+ self.body = revision.body
+
+ self.active_revision = revision
+ self.update_last_activity(user)
+
+ self.save()
+
+ def _list_changes_in_tags(self):
+ dirty = self.get_dirty_fields()
+
+ if not 'tagnames' in dirty:
+ return None
+ else:
+ if self._original_state['tagnames']:
+ old_tags = set(name for name in self._original_state['tagnames'].split(u' '))
+ else:
+ old_tags = set()
+ new_tags = set(name for name in self.tagnames.split(u' ') if name)
+
+ return dict(
+ current=list(new_tags),
+ added=list(new_tags - old_tags),
+ removed=list(old_tags - new_tags)
+ )
+
+ def _last_active_user(self):
+ return self.last_edited and self.last_edited.by or self.author
+
+ def _process_changes_in_tags(self):
+ tag_changes = self._list_changes_in_tags()
+
+ if tag_changes is not None:
+ for name in tag_changes['added']:
+ try:
+ tag = Tag.objects.get(name=name)
+ except:
+ tag = Tag.objects.create(name=name, created_by=self._last_active_user())
+
+ if not self.nis.deleted:
+ tag.used_count = models.F('used_count') + 1
+ tag.save()
+
+ if not self.nis.deleted:
+ for name in tag_changes['removed']:
+ try:
+ tag = Tag.objects.get(name=name)
+ tag.used_count = models.F('used_count') - 1
+ tag.save()
+ except:
+ pass
+
+ return True
+
+ return False
+
+ def mark_deleted(self, action):
+ self.nstate.deleted = action
+ self.save()
+
+ if action:
+ for tag in self.tags.all():
+ tag.used_count = models.F('used_count') - 1
+ tag.save()
+ else:
+ for tag in Tag.objects.filter(name__in=self.tagname_list()):
+ tag.used_count = models.F('used_count') + 1
+ tag.save()
+
+ def save(self, *args, **kwargs):
+ tags_changed = self._process_changes_in_tags()
+
+ if not self.id:
+ self.node_type = self.get_type()
+ super(BaseModel, self).save(*args, **kwargs)
+ self.active_revision = self._create_revision(self.author, 1, title=self.title, tagnames=self.tagnames, body=self.body)
+ self.update_last_activity(self.author)
+
+ if self.parent_id and not self.abs_parent_id:
+ self.abs_parent = self.parent.absolute_parent
+
+ super(Node, self).save(*args, **kwargs)
+ if tags_changed: self.tags = list(Tag.objects.filter(name__in=self.tagname_list()))
+
+ class Meta:
+ app_label = 'forum'
+
+
+class NodeRevision(BaseModel, NodeContent):
+ node = models.ForeignKey(Node, related_name='revisions')
+ summary = models.CharField(max_length=300)
+ revision = models.PositiveIntegerField()
+ revised_at = models.DateTimeField(default=datetime.datetime.now)
+
+ class Meta:
+ unique_together = ('node', 'revision')
+ app_label = 'forum'
+
+
+class NodeState(models.Model):
+ node = models.ForeignKey(Node, related_name='states')
+ state_type = models.CharField(max_length=16)
+ action = models.OneToOneField('Action', related_name="node_state")
+
+ class Meta:
+ unique_together = ('node', 'state_type')
+ app_label = 'forum'
+
+
def __unicode__(self):
return self.name
+ @models.permalink
+ def get_absolute_url(self):
+ return ('tag_questions', (), {'tag': self.name})
+
class MarkedTag(models.Model):
TAG_MARK_REASONS = (('good',_('interesting')),('bad',_('ignored')))
tag = models.ForeignKey(Tag, related_name='user_selections')
-from base import *\r
-from django.contrib.contenttypes.models import ContentType\r
-from django.contrib.auth.models import User as DjangoUser, AnonymousUser as DjangoAnonymousUser\r
-from django.db.models import Q\r
-try:\r
- from hashlib import md5\r
-except:\r
- from md5 import new as md5\r
-\r
-import string\r
-from random import Random\r
-\r
-from django.utils.translation import ugettext as _\r
-import django.dispatch\r
-\r
-\r
-QUESTIONS_PER_PAGE_CHOICES = (\r
- (10, u'10'),\r
- (30, u'30'),\r
- (50, u'50'),\r
-)\r
-\r
-class UserManager(CachedManager):\r
- def get_site_owner(self):\r
- return self.all().order_by('date_joined')[0]\r
-\r
-class AnonymousUser(DjangoAnonymousUser):\r
- def get_visible_answers(self, question):\r
- return question.answers.filter_state(deleted=False)\r
-\r
- def can_view_deleted_post(self, post):\r
- return False\r
-\r
- def can_vote_up(self):\r
- return False\r
-\r
- def can_vote_down(self):\r
- return False\r
-\r
- def can_flag_offensive(self, post=None):\r
- return False\r
-\r
- def can_view_offensive_flags(self, post=None):\r
- return False\r
-\r
- def can_comment(self, post):\r
- return False\r
-\r
- def can_like_comment(self, comment):\r
- return False\r
-\r
- def can_edit_comment(self, comment):\r
- return False\r
-\r
- def can_delete_comment(self, comment):\r
- return False\r
-\r
- def can_convert_to_comment(self, answer):\r
- return False\r
-\r
- def can_accept_answer(self, answer):\r
- return False\r
-\r
- def can_create_tags(self):\r
- return False\r
-\r
- def can_edit_post(self, post):\r
- return False\r
-\r
- def can_wikify(self, post):\r
- return False\r
-\r
- def can_cancel_wiki(self, post):\r
- return False\r
-\r
- def can_retag_questions(self):\r
- return False\r
-\r
- def can_close_question(self, question):\r
- return False\r
-\r
- def can_reopen_question(self, question):\r
- return False\r
-\r
- def can_delete_post(self, post):\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
-\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
- website = models.URLField(max_length=200, 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
- 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
- def gravatar(self):\r
- return md5(self.email).hexdigest()\r
-\r
- def save(self, *args, **kwargs):\r
- if self.reputation < 0:\r
- self.reputation = 0\r
-\r
- new = not bool(self.id)\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_messages(self):\r
- messages = []\r
- for m in self.message_set.all():\r
- messages.append(m.message)\r
- return messages\r
-\r
- def delete_messages(self):\r
- self.message_set.all().delete()\r
-\r
- @models.permalink\r
- def get_profile_url(self):\r
- return ('user_profile', (), {'id': self.id, 'slug': slugify(self.username)})\r
-\r
- def get_profile_link(self):\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_state(deleted=False)\r
-\r
- def get_vote_count_today(self):\r
- today = datetime.date.today()\r
- return self.actions.filter(canceled=False, action_type__in=("voteup", "votedown"),\r
- action_date__gte=(today - datetime.timedelta(days=1))).count()\r
-\r
- def get_reputation_by_upvoted_today(self):\r
- today = datetime.datetime.now()\r
- sum = self.reputes.filter(reputed_at__range=(today - datetime.timedelta(days=1), today)).aggregate(models.Sum('value'))\r
- #todo: redo this, maybe transform in the daily cap\r
- #if sum.get('value__sum', None) is not None: return sum['value__sum']\r
- return 0\r
-\r
- def get_flagged_items_count_today(self):\r
- today = datetime.date.today()\r
- return self.actions.filter(canceled=False, action_type="flag",\r
- action_date__gte=(today - datetime.timedelta(days=1))).count()\r
-\r
- @true_if_is_super_or_staff\r
- def can_view_deleted_post(self, post):\r
- return post.author == self\r
-\r
- @true_if_is_super_or_staff\r
- def can_vote_up(self):\r
- return self.reputation >= int(settings.REP_TO_VOTE_UP)\r
-\r
- @true_if_is_super_or_staff\r
- def can_vote_down(self):\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
- return self.is_superuser or self.is_staff or self.reputation >= int(settings.REP_TO_FLAG)\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
- return self.reputation >= int(settings.REP_TO_VIEW_FLAGS)\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
- ) or (post.__class__.__name__ == "Answer" and self == post.question.author)\r
-\r
- @true_if_is_super_or_staff\r
- def can_like_comment(self, comment):\r
- return self != comment.author and (self.reputation >= int(settings.REP_TO_LIKE_COMMENT))\r
-\r
- @true_if_is_super_or_staff\r
- def can_edit_comment(self, comment):\r
- return (comment.author == self and comment.added_at >= datetime.datetime.now() - datetime.timedelta(minutes=60)\r
- ) or self.is_superuser\r
-\r
- @true_if_is_super_or_staff\r
- def can_delete_comment(self, comment):\r
- return self == comment.author or self.reputation >= int(settings.REP_TO_DELETE_COMMENTS)\r
-\r
- def can_convert_to_comment(self, answer):\r
- return (not answer.marked) and (self.is_superuser or self.is_staff or answer.author == self or self.reputation >= int(settings.REP_TO_CONVERT_TO_COMMENT))\r
-\r
- @true_if_is_super_or_staff\r
- def can_accept_answer(self, answer):\r
- return self == answer.question.author\r
-\r
- @true_if_is_super_or_staff\r
- def can_create_tags(self):\r
- return self.reputation >= int(settings.REP_TO_CREATE_TAGS)\r
-\r
- @true_if_is_super_or_staff\r
- def can_edit_post(self, post):\r
- return self == post.author or self.reputation >= int(settings.REP_TO_EDIT_OTHERS\r
- ) or (post.nis.wiki and self.reputation >= int(settings.REP_TO_EDIT_WIKI))\r
-\r
- @true_if_is_super_or_staff\r
- def can_wikify(self, post):\r
- return self == post.author or self.reputation >= int(settings.REP_TO_WIKIFY)\r
-\r
- @true_if_is_super_or_staff\r
- def can_cancel_wiki(self, post):\r
- return self == post.author\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
- @true_if_is_super_or_staff\r
- def can_close_question(self, question):\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
- @true_if_is_super_or_staff\r
- def can_reopen_question(self, question):\r
- return self == question.author and self.reputation >= settings.REP_TO_REOPEN_OWN\r
-\r
- @true_if_is_super_or_staff\r
- def can_delete_post(self, post):\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
- @true_if_is_super_or_staff\r
- def can_upload_files(self):\r
- return self.reputation >= int(settings.REP_TO_UPLOAD)\r
-\r
- def check_password(self, old_passwd):\r
- self.__dict__.update(self.__class__.objects.filter(id=self.id).values('password')[0])\r
- return DjangoUser.check_password(self, old_passwd)\r
-\r
-\r
- class Meta:\r
- app_label = 'forum'\r
-\r
-class SubscriptionSettings(models.Model):\r
- user = models.OneToOneField(User, related_name='subscription_settings')\r
-\r
- enable_notifications = models.BooleanField(default=True)\r
-\r
- #notify if\r
- member_joins = models.CharField(max_length=1, default='n')\r
- new_question = models.CharField(max_length=1, default='d')\r
- new_question_watched_tags = models.CharField(max_length=1, default='i')\r
- subscribed_questions = models.CharField(max_length=1, default='i')\r
- \r
- #auto_subscribe_to\r
- all_questions = models.BooleanField(default=False)\r
- all_questions_watched_tags = models.BooleanField(default=False)\r
- questions_asked = models.BooleanField(default=True)\r
- questions_answered = models.BooleanField(default=True)\r
- questions_commented = models.BooleanField(default=False)\r
- questions_viewed = models.BooleanField(default=False)\r
-\r
- #notify activity on subscribed\r
- notify_answers = models.BooleanField(default=True)\r
- notify_reply_to_comments = models.BooleanField(default=True)\r
- notify_comments_own_post = models.BooleanField(default=True)\r
- notify_comments = models.BooleanField(default=False)\r
- notify_accepted = models.BooleanField(default=False)\r
-\r
- class Meta:\r
- app_label = 'forum'\r
-\r
-from forum.utils.time import one_day_from_now\r
-\r
-class ValidationHashManager(models.Manager):\r
- def _generate_md5_hash(self, user, type, hash_data, seed):\r
- return md5("%s%s%s%s" % (seed, "".join(map(str, hash_data)), user.id, type)).hexdigest()\r
-\r
- def create_new(self, user, type, hash_data=[], expiration=None):\r
- seed = ''.join(Random().sample(string.letters+string.digits, 12))\r
- hash = self._generate_md5_hash(user, type, hash_data, seed)\r
-\r
- obj = ValidationHash(hash_code=hash, seed=seed, user=user, type=type)\r
-\r
- if expiration is not None:\r
- obj.expiration = expiration\r
-\r
- try:\r
- obj.save()\r
- except:\r
- return None\r
- \r
- return obj\r
-\r
- def validate(self, hash, user, type, hash_data=[]):\r
- try:\r
- obj = self.get(hash_code=hash)\r
- except:\r
- return False\r
-\r
- if obj.type != type:\r
- return False\r
-\r
- if obj.user != user:\r
- return False\r
-\r
- valid = (obj.hash_code == self._generate_md5_hash(obj.user, type, hash_data, obj.seed))\r
-\r
- if valid:\r
- if obj.expiration < datetime.datetime.now():\r
- obj.delete()\r
- return False\r
- else:\r
- obj.delete()\r
- return True\r
-\r
- return False\r
-\r
-class ValidationHash(models.Model):\r
- hash_code = models.CharField(max_length=255,unique=True)\r
- seed = models.CharField(max_length=12)\r
- expiration = models.DateTimeField(default=one_day_from_now)\r
- type = models.CharField(max_length=12)\r
- user = models.ForeignKey(User)\r
-\r
- objects = ValidationHashManager()\r
-\r
- class Meta:\r
- unique_together = ('user', 'type')\r
- app_label = 'forum'\r
-\r
- def __str__(self):\r
- return self.hash_code\r
-\r
-class AuthKeyUserAssociation(models.Model):\r
- key = models.CharField(max_length=255,null=False,unique=True)\r
- provider = models.CharField(max_length=64)\r
- user = models.ForeignKey(User, related_name="auth_keys")\r
- added_at = models.DateTimeField(default=datetime.datetime.now)\r
-\r
- class Meta:\r
- app_label = 'forum'\r
+from base import *
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.auth.models import User as DjangoUser, AnonymousUser as DjangoAnonymousUser
+from django.db.models import Q
+try:
+ from hashlib import md5
+except:
+ from md5 import new as md5
+
+import string
+from random import Random
+
+from django.utils.translation import ugettext as _
+import django.dispatch
+
+
+QUESTIONS_PER_PAGE_CHOICES = (
+ (10, u'10'),
+ (30, u'30'),
+ (50, u'50'),
+)
+
+class UserManager(CachedManager):
+ def get_site_owner(self):
+ return self.all().order_by('date_joined')[0]
+
+class AnonymousUser(DjangoAnonymousUser):
+ def get_visible_answers(self, question):
+ return question.answers.filter_state(deleted=False)
+
+ def can_view_deleted_post(self, post):
+ return False
+
+ def can_vote_up(self):
+ return False
+
+ def can_vote_down(self):
+ return False
+
+ def can_flag_offensive(self, post=None):
+ return False
+
+ def can_view_offensive_flags(self, post=None):
+ return False
+
+ def can_comment(self, post):
+ return False
+
+ def can_like_comment(self, comment):
+ return False
+
+ def can_edit_comment(self, comment):
+ return False
+
+ def can_delete_comment(self, comment):
+ return False
+
+ def can_convert_to_comment(self, answer):
+ return False
+
+ def can_accept_answer(self, answer):
+ return False
+
+ def can_create_tags(self):
+ return False
+
+ def can_edit_post(self, post):
+ return False
+
+ def can_wikify(self, post):
+ return False
+
+ def can_cancel_wiki(self, post):
+ return False
+
+ def can_retag_questions(self):
+ return False
+
+ def can_close_question(self, question):
+ return False
+
+ def can_reopen_question(self, question):
+ return False
+
+ def can_delete_post(self, post):
+ return False
+
+ def can_upload_files(self):
+ return False
+
+def true_if_is_super_or_staff(fn):
+ def decorated(self, *args, **kwargs):
+ return self.is_superuser or self.is_staff or fn(self, *args, **kwargs)
+ return decorated
+
+class User(BaseModel, DjangoUser):
+ is_approved = models.BooleanField(default=False)
+ email_isvalid = models.BooleanField(default=False)
+
+ reputation = models.PositiveIntegerField(default=0)
+ gold = models.PositiveIntegerField(default=0)
+ silver = models.PositiveIntegerField(default=0)
+ bronze = models.PositiveIntegerField(default=0)
+
+ last_seen = models.DateTimeField(default=datetime.datetime.now)
+ real_name = models.CharField(max_length=100, blank=True)
+ website = models.URLField(max_length=200, blank=True)
+ location = models.CharField(max_length=100, blank=True)
+ date_of_birth = models.DateField(null=True, blank=True)
+ about = models.TextField(blank=True)
+
+ subscriptions = models.ManyToManyField('Node', related_name='subscribers', through='QuestionSubscription')
+
+ vote_up_count = DenormalizedField("actions", canceled=False, action_type="voteup")
+ vote_down_count = DenormalizedField("actions", canceled=False, action_type="votedown")
+
+ objects = UserManager()
+
+ def __unicode__(self):
+ return self.username
+
+ @property
+ def gravatar(self):
+ return md5(self.email).hexdigest()
+
+ def save(self, *args, **kwargs):
+ if self.reputation < 0:
+ self.reputation = 0
+
+ new = not bool(self.id)
+
+ super(User, self).save(*args, **kwargs)
+
+ if new:
+ sub_settings = SubscriptionSettings(user=self)
+ sub_settings.save()
+
+ def get_absolute_url(self):
+ return self.get_profile_url()
+
+ def get_messages(self):
+ messages = []
+ for m in self.message_set.all():
+ messages.append(m.message)
+ return messages
+
+ def delete_messages(self):
+ self.message_set.all().delete()
+
+ @models.permalink
+ def get_profile_url(self):
+ return ('user_profile', (), {'id': self.id, 'slug': slugify(self.username)})
+
+ def get_absolute_url(self):
+ return self.get_profile_url()
+
+ def get_profile_link(self):
+ profile_link = u'<a href="%s">%s</a>' % (self.get_profile_url(),self.username)
+ return mark_safe(profile_link)
+
+ def get_visible_answers(self, question):
+ return question.answers.filter_state(deleted=False)
+
+ def get_vote_count_today(self):
+ today = datetime.date.today()
+ return self.actions.filter(canceled=False, action_type__in=("voteup", "votedown"),
+ action_date__gte=(today - datetime.timedelta(days=1))).count()
+
+ def get_reputation_by_upvoted_today(self):
+ today = datetime.datetime.now()
+ sum = self.reputes.filter(reputed_at__range=(today - datetime.timedelta(days=1), today)).aggregate(models.Sum('value'))
+ #todo: redo this, maybe transform in the daily cap
+ #if sum.get('value__sum', None) is not None: return sum['value__sum']
+ return 0
+
+ def get_flagged_items_count_today(self):
+ today = datetime.date.today()
+ return self.actions.filter(canceled=False, action_type="flag",
+ action_date__gte=(today - datetime.timedelta(days=1))).count()
+
+ @true_if_is_super_or_staff
+ def can_view_deleted_post(self, post):
+ return post.author == self
+
+ @true_if_is_super_or_staff
+ def can_vote_up(self):
+ return self.reputation >= int(settings.REP_TO_VOTE_UP)
+
+ @true_if_is_super_or_staff
+ def can_vote_down(self):
+ return self.reputation >= int(settings.REP_TO_VOTE_DOWN)
+
+ def can_flag_offensive(self, post=None):
+ if post is not None and post.author == self:
+ return False
+ return self.is_superuser or self.is_staff or self.reputation >= int(settings.REP_TO_FLAG)
+
+ @true_if_is_super_or_staff
+ def can_view_offensive_flags(self, post=None):
+ if post is not None and post.author == self:
+ return True
+ return self.reputation >= int(settings.REP_TO_VIEW_FLAGS)
+
+ @true_if_is_super_or_staff
+ def can_comment(self, post):
+ return self == post.author or self.reputation >= int(settings.REP_TO_COMMENT
+ ) or (post.__class__.__name__ == "Answer" and self == post.question.author)
+
+ @true_if_is_super_or_staff
+ def can_like_comment(self, comment):
+ return self != comment.author and (self.reputation >= int(settings.REP_TO_LIKE_COMMENT))
+
+ @true_if_is_super_or_staff
+ def can_edit_comment(self, comment):
+ return (comment.author == self and comment.added_at >= datetime.datetime.now() - datetime.timedelta(minutes=60)
+ ) or self.is_superuser
+
+ @true_if_is_super_or_staff
+ def can_delete_comment(self, comment):
+ return self == comment.author or self.reputation >= int(settings.REP_TO_DELETE_COMMENTS)
+
+ def can_convert_to_comment(self, answer):
+ return (not answer.marked) and (self.is_superuser or self.is_staff or answer.author == self or self.reputation >= int(settings.REP_TO_CONVERT_TO_COMMENT))
+
+ @true_if_is_super_or_staff
+ def can_accept_answer(self, answer):
+ return self == answer.question.author
+
+ @true_if_is_super_or_staff
+ def can_create_tags(self):
+ return self.reputation >= int(settings.REP_TO_CREATE_TAGS)
+
+ @true_if_is_super_or_staff
+ def can_edit_post(self, post):
+ return self == post.author or self.reputation >= int(settings.REP_TO_EDIT_OTHERS
+ ) or (post.nis.wiki and self.reputation >= int(settings.REP_TO_EDIT_WIKI))
+
+ @true_if_is_super_or_staff
+ def can_wikify(self, post):
+ return self == post.author or self.reputation >= int(settings.REP_TO_WIKIFY)
+
+ @true_if_is_super_or_staff
+ def can_cancel_wiki(self, post):
+ return self == post.author
+
+ @true_if_is_super_or_staff
+ def can_retag_questions(self):
+ return self.reputation >= int(settings.REP_TO_RETAG)
+
+ @true_if_is_super_or_staff
+ def can_close_question(self, question):
+ return (self == question.author and self.reputation >= int(settings.REP_TO_CLOSE_OWN)
+ ) or self.reputation >= int(settings.REP_TO_CLOSE_OTHERS)
+
+ @true_if_is_super_or_staff
+ def can_reopen_question(self, question):
+ return self == question.author and self.reputation >= settings.REP_TO_REOPEN_OWN
+
+ @true_if_is_super_or_staff
+ def can_delete_post(self, post):
+ if post.node_type == "comment":
+ return self.can_delete_comment(post)
+
+ return (self == post.author and (post.__class__.__name__ == "Answer" or
+ not post.answers.exclude(author=self).count()))
+
+ @true_if_is_super_or_staff
+ def can_upload_files(self):
+ return self.reputation >= int(settings.REP_TO_UPLOAD)
+
+ def check_password(self, old_passwd):
+ self.__dict__.update(self.__class__.objects.filter(id=self.id).values('password')[0])
+ return DjangoUser.check_password(self, old_passwd)
+
+
+ class Meta:
+ app_label = 'forum'
+
+class SubscriptionSettings(models.Model):
+ user = models.OneToOneField(User, related_name='subscription_settings')
+
+ enable_notifications = models.BooleanField(default=True)
+
+ #notify if
+ member_joins = models.CharField(max_length=1, default='n')
+ new_question = models.CharField(max_length=1, default='d')
+ new_question_watched_tags = models.CharField(max_length=1, default='i')
+ subscribed_questions = models.CharField(max_length=1, default='i')
+
+ #auto_subscribe_to
+ all_questions = models.BooleanField(default=False)
+ all_questions_watched_tags = models.BooleanField(default=False)
+ questions_asked = models.BooleanField(default=True)
+ questions_answered = models.BooleanField(default=True)
+ questions_commented = models.BooleanField(default=False)
+ questions_viewed = models.BooleanField(default=False)
+
+ #notify activity on subscribed
+ notify_answers = models.BooleanField(default=True)
+ notify_reply_to_comments = models.BooleanField(default=True)
+ notify_comments_own_post = models.BooleanField(default=True)
+ notify_comments = models.BooleanField(default=False)
+ notify_accepted = models.BooleanField(default=False)
+
+ class Meta:
+ app_label = 'forum'
+
+from forum.utils.time import one_day_from_now
+
+class ValidationHashManager(models.Manager):
+ def _generate_md5_hash(self, user, type, hash_data, seed):
+ return md5("%s%s%s%s" % (seed, "".join(map(str, hash_data)), user.id, type)).hexdigest()
+
+ def create_new(self, user, type, hash_data=[], expiration=None):
+ seed = ''.join(Random().sample(string.letters+string.digits, 12))
+ hash = self._generate_md5_hash(user, type, hash_data, seed)
+
+ obj = ValidationHash(hash_code=hash, seed=seed, user=user, type=type)
+
+ if expiration is not None:
+ obj.expiration = expiration
+
+ try:
+ obj.save()
+ except:
+ return None
+
+ return obj
+
+ def validate(self, hash, user, type, hash_data=[]):
+ try:
+ obj = self.get(hash_code=hash)
+ except:
+ return False
+
+ if obj.type != type:
+ return False
+
+ if obj.user != user:
+ return False
+
+ valid = (obj.hash_code == self._generate_md5_hash(obj.user, type, hash_data, obj.seed))
+
+ if valid:
+ if obj.expiration < datetime.datetime.now():
+ obj.delete()
+ return False
+ else:
+ obj.delete()
+ return True
+
+ return False
+
+class ValidationHash(models.Model):
+ hash_code = models.CharField(max_length=255,unique=True)
+ seed = models.CharField(max_length=12)
+ expiration = models.DateTimeField(default=one_day_from_now)
+ type = models.CharField(max_length=12)
+ user = models.ForeignKey(User)
+
+ objects = ValidationHashManager()
+
+ class Meta:
+ unique_together = ('user', 'type')
+ app_label = 'forum'
+
+ def __str__(self):
+ return self.hash_code
+
+class AuthKeyUserAssociation(models.Model):
+ key = models.CharField(max_length=255,null=False,unique=True)
+ provider = models.CharField(max_length=64)
+ user = models.ForeignKey(User, related_name="auth_keys")
+ added_at = models.DateTimeField(default=datetime.datetime.now)
+
+ class Meta:
+ app_label = 'forum'
-{% extends "email_base.html" %}\r
-{% load i18n %}\r
-{% load extra_tags %}\r
-\r
-{% block content %}\r
- <p>{% trans "Greetings from the Q&A forum" %},</p>\r
-\r
- <p>{% trans "To make use of the Forum, please follow the link below:" %}</p>\r
-\r
- <a href="{% fullurl auth_validate_email user=user.id,code=validation_code %}">{% fullurl auth_validate_email user=user.id,code=validation_code %}</a>\r
-\r
- <p>{% trans "Following the link above will help us verify your email address." %}</p>\r
-\r
- <p>{% blocktrans %}If you beleive that this message was sent in mistake -\r
- no further action is needed. Just ingore this email, we apologize\r
- for any inconvenience{% endblocktrans %}</p>\r
-\r
- <p>{% blocktrans %}Sincerely,<br />\r
- Forum Administrator{% endblocktrans %}</p>\r
-{% endblock %}\r
+{% extends "email_base.html" %}
+{% load i18n %}
+{% load extra_tags %}
+{% load email_tags %}
+
+{% block content %}
+ <p>{% trans "Greetings from the Q&A forum" %},</p>
+
+ <p>{% trans "To make use of the Forum, please follow the link below:" %}</p>
+
+ <a href="{% fullurl auth_validate_email user=user.id,code=validation_code %}">{% fullurl auth_validate_email user=user.id,code=validation_code %}</a>
+
+ <p>{% trans "Following the link above will help us verify your email address." %}</p>
+
+ <p>{% blocktrans %}If you beleive that this message was sent in mistake -
+ no further action is needed. Just ingore this email, we apologize
+ for any inconvenience{% endblocktrans %}</p>
+
+ <p>{% blocktrans %}Sincerely,<br />
+ Forum Administrator{% endblocktrans %}</p>
+{% endblock %}
-{% extends "email_base.html" %}\r
-{% load i18n %}\r
-{% load extra_tags %}\r
-\r
-{% block content %}\r
- <p>{% trans "Greetings from the Q&A forum" %},</p>\r
-\r
- <p>{% trans "You're seeing this because someone requested a temporary login link" %}</p>\r
-\r
- <a href="{% fullurl auth_tempsignin user=user.id,code=temp_login_code %}">{% fullurl auth_tempsignin user=user.id,code=temp_login_code %}</a>\r
-\r
- <p>{% trans "Following the link above will give you access to your account." %}</p>\r
-\r
- <p>{% blocktrans %}If you beleive that this message was sent in mistake -\r
- no further action is needed. Just ingore this email, we apologize\r
- for any inconvenience{% endblocktrans %}</p>\r
-\r
- <p>{% blocktrans %}Sincerely,<br />\r
- Forum Administrator{% endblocktrans %}</p>\r
-{% endblock %}\r
+{% extends "email_base.html" %}
+{% load i18n %}
+{% load extra_tags %}
+{% load email_tags %}
+
+{% block content %}
+ <p>{% trans "Greetings from the Q&A forum" %},</p>
+
+ <p>{% trans "You're seeing this because someone requested a temporary login link" %}</p>
+
+ <a href="{% fullurl auth_tempsignin user=user.id,code=temp_login_code %}">{% fullurl auth_tempsignin user=user.id,code=temp_login_code %}</a>
+
+ <p>{% trans "Following the link above will give you access to your account." %}</p>
+
+ <p>{% blocktrans %}If you beleive that this message was sent in mistake -
+ no further action is needed. Just ingore this email, we apologize
+ for any inconvenience{% endblocktrans %}</p>
+
+ <p>{% blocktrans %}Sincerely,<br />
+ Forum Administrator{% endblocktrans %}</p>
+{% endblock %}
-{% load extra_filters %}\r
-{% load extra_tags %}\r
-{% load i18n %}\r
-\r
-<html xmlns="http://www.w3.org/1999/xhtml">\r
- <head>\r
- <style type="text/css">\r
- body {\r
- background: #FFF;\r
- font-size: 12px;\r
- line-height: 150%;\r
- margin: 0;\r
- padding: 0;\r
- color: #000;\r
- font-family: sans-serif;\r
- }\r
-\r
- #wrapper {\r
- width: 600px;\r
- margin: auto;\r
- padding: 0;\r
- }\r
-\r
- a img {\r
- border: none;\r
- }\r
- </style>\r
- </head>\r
- <body>\r
- <a href="{% fullurl index %}">\r
- <img src="cid:logo" title="{% trans "home" %}" alt="{{settings.APP_TITLE}} logo"/>\r
- </a>\r
- <br />\r
- <p>{{ settings.APP_TITLE }}</p>\r
- <br /><br />\r
- <div id="wrapper">\r
- <div id="room">\r
- <div id="CALeft">\r
- {% block content%}\r
- {% endblock%}\r
- </div>\r
- </div>\r
- <div class="spacer3"></div>\r
- </div>\r
- </body>\r
+{% load extra_filters %}
+{% load extra_tags %}
+{% load email_tags %}
+{% load i18n %}
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <style type="text/css">
+ body {
+ background: #FFF;
+ font-size: 12px;
+ line-height: 150%;
+ margin: 0;
+ padding: 0;
+ color: #000;
+ font-family: sans-serif;
+ }
+
+ #wrapper {
+ width: 600px;
+ margin: auto;
+ padding: 0;
+ }
+
+ a img {
+ border: none;
+ }
+ </style>
+ </head>
+ <body>
+ <a href="{% fullurl index %}">
+ <img src="cid:logo" title="{% trans "home" %}" alt="{{settings.APP_TITLE}} logo"/>
+ </a>
+ <br />
+ <p>{{ settings.APP_TITLE }}</p>
+ <br /><br />
+ <div id="wrapper">
+ <div id="room">
+ <div id="CALeft">
+ {% block content%}
+ {% endblock%}
+ </div>
+ </div>
+ <div class="spacer3"></div>
+ </div>
+ </body>
</html>
\ No newline at end of file
{% block content %}
<div id="main-wrapper"><br /><br />
<div id="heading">
- <h1>Markdown Syntax</h1>
- <p>This document describes some of the more important parts of Markdown (for writers, that is). There's a lot more to the syntax than is mentioned here, though. To get the full syntax documentation, go to John Gruber's <a href="http://daringfireball.net/projects/markdown/syntax" rel="nofollow">Markdown Syntax</a> page</p>
+ <h1>{% trans "Markdown Syntax" %}</h1>
+ <p>{% blocktrans %}This document describes some of the more important parts of Markdown (for writers, that is). There's a lot more to the syntax than is mentioned here, though. To get the full syntax documentation, go to John Gruber's <a href="http://daringfireball.net/projects/markdown/syntax" rel="nofollow">Markdown Syntax</a> page{% endblocktrans %}</p>
</div>
<div class="section">
- <h2 class="section-title">Headers</h2>
+ <h2 class="section-title">{% trans "Headers" %}</h2>
<div class="section-description">
- For top-level headers underline the text with equal signs. For second-level headers use dashes to underline.
+ {% trans "For top-level headers underline the text with equal signs. For second-level headers use dashes to underline." %}
</div>
<table class="section-example"><tr>
<td>
- <code>This is an H1 </code><br />
+ <code>{% trans "This is an H1" %}</code><br />
<code>============= </code>
</td>
<td>
- <h1>This is an H1</h1>
+ <h1>{% trans "This is an H1" %}</h1>
</td>
</tr></table>
<table class="section-example"><tr>
<td>
- <code>This is an H2</code><br />
+ <code>{% trans "This is an H2" %}</code><br />
<code>-------------</code>
</td>
<td>
- <h2>This is an H2</h2>
+ <h2>{% trans "This is an H2" %}</h2>
</td>
</tr></table>
<div class="section-description">
- If you would rather, you can prefix headers with a hash (#) symbol instead. The number of hash symbols indicates the header level. For example, a single hash indicates a header level of one while two indicates the second header level:
+ {% blocktrans %}If you would rather, you can prefix headers with a hash (#) symbol instead. The number of hash symbols indicates the header level. For example, a single hash indicates a header level of one while two indicates the second header level:{% endblocktrans %}
</div>
<table class="section-example"><tr>
<td>
- <code># This is H1</code>
+ <code># {% trans "This is an H1" %}</code>
</td>
<td>
- <h1>This is an H1</h1>
+ <h1>{% trans "This is an H1" %}</h1>
</td>
</tr></table>
<table class="section-example"><tr>
<td>
- <code>## This is H2</code>
+ <code>## {% trans "This is an H2" %}</code>
</td>
<td>
- <h2>This is an H2</h2>
+ <h2>{% trans "This is an H2" %}</h2>
</td>
</tr></table>
<table class="section-example"><tr>
<td>
- <code>### This is H3</code>
+ <code>### {% trans "This is an H3" %}</code>
</td>
<td>
- <h3>This is an H3</h3>
+ <h3>{% trans "This is an H3" %}</h3>
</td>
</tr></table>
<div class="section-description">
- Which you choose is a matter of style. Whichever you thinks looks better in the text document. In both cases, the final, fully formatted, document looks the same.
+ {% trans "Which you choose is a matter of style. Whichever you thinks looks better in the text document. In both cases, the final, fully formatted, document looks the same." %}
</div>
</div>
<div class="section">
- <h2 class="section-title">Paragraphs</h2>
+ <h2 class="section-title">{% trans "Paragraphs" %}</h2>
<div class="section-description">
- Paragraphs are surrounded by blank lines.
+ {% trans "Paragraphs are surrounded by blank lines." %}
</div>
<div class="section-example">
- <code>This is paragraph one.</code>
+ <code>{% trans "This is paragraph one." %}</code>
</div><br />
<div class="section-example">
- <code>This is paragraph two.</code>
+ <code>{% trans "This is paragraph two." %}</code>
</div>
</div>
<div class="section">
- <h2 class="section-title">Links</h2>
+ <h2 class="section-title">{% trans "Links" %}</h2>
<div class="section-description">
+ {% blocktrans %}
There are two parts to every link.
The first is the actual text that the user will see and it is surrounded by brackets.
The second is address of the page you wish to link to and it is surrounded in parenthesis.
+ {% endblocktrans %}
</div>
<table class="section-example"><tr>
<td>
- <code>[link text](http://example.com/)</code>
+ <code>[{% trans "link text" %}]({% trans "http://example.com/" %})</code>
</td>
<td>
- <a>link text</a>
+ <a>{% trans "link text" %}</a>
</td>
</tr></table>
</div>
<div class="section">
- <h2 class="section-title">Formatting</h2>
+ <h2 class="section-title">{% trans "Formatting" %}</h2>
<div class="section-description">
- To indicate bold text surround the text with two star (*) symbols or two underscore (_) symbols:
+ {% trans "To indicate bold text surround the text with two star (*) symbols or two underscore (_) symbols:" %}
</div>
<table class="section-example"><tr>
<td>
- <code>**This is bold**</code>
+ <code>**{% trans "This is bold" %}**</code>
</td>
<td>
- <strong>This is bold</strong>
+ <strong>{% trans "This is bold" %}</strong>
</td>
</tr></table>
<table class="section-example"><tr>
<td>
- <code>__This is also bold__</code>
+ <code>__{% trans "This is also bold" %}__</code>
</td>
<td>
- <strong>This is also bold</strong>
+ <strong>{% trans "This is also bold" %}</strong>
</td>
</tr></table>
<div class="section-description">
- To indicate italicized text surround the text with a single star (*) symbol or underscore (_) symbol:
+ {% trans "To indicate italicized text surround the text with a single star (*) symbol or underscore (_) symbol:" %}
</div>
<table class="section-example"><tr>
<td>
- <code>*This is italics*</code>
+ <code>*{% trans "This is italics" %}*</code>
</td>
<td>
- <i>This is italics</i>
+ <i>{% trans "This is italics" %}</i>
</td>
</tr></table>
<table class="section-example"><tr>
<td>
- <code>_This is also italics_</code>
+ <code>_{% trans "This is also italics" %}_</code>
</td>
<td>
- <i>This is also italics</i>
+ <i>{% trans "This is also italics" %}</i>
</td>
</tr></table>
<div class="section-description">
- To indicate italicized and bold text surround the text with three star (*) symbol or underscore (_) symbol:
+ {% trans "To indicate italicized and bold text surround the text with three star (*) symbol or underscore (_) symbol:" %}
</div>
<table class="section-example"><tr>
<td>
- <code>***This is bold and italics***</code>
+ <code>***{% trans "This is bold and italics" %}***</code>
</td>
<td>
- <strong><i>This is italics</i></strong>
+ <strong><i>{% trans "This is italics" %}</i></strong>
</td>
</tr></table>
<table class="section-example"><tr>
<td>
- <code>___This is also bold and italics___</code>
+ <code>___{% trans "This is also bold and italics" %}___</code>
</td>
<td>
- <strong><i>This is italics</i></strong>
+ <strong><i>{% trans "This is italics" %}</i></strong>
</td>
</tr></table>
</div>
<div class="section">
- <h2 class="section-title">Blockquotes</h2>
+ <h2 class="section-title">{% trans "Blockquotes" %}</h2>
<div class="section-description">
- To create an indented area use the right angle bracket (>) character before each line to be included in the blockquote.
+ {% trans "To create an indented area use the right angle bracket (>) character before each line to be included in the blockquote." %}
</div>
<table class="section-example"><tr>
<td>
- <code>> This is part of a blockquote.</code><br />
- <code>> This is part of the same blockquote.</code>
+ <code>> {% trans "This is part of a blockquote." %}</code><br />
+ <code>> {% trans "This is part of the same blockquote." %}</code>
</td>
<td>
- <p style="padding-left:15px;">This is part of a blockquote.<br />This is part of the same blockquote.</p>
+ <p style="padding-left:15px;">{% trans "This is part of a blockquote." %}<br />{% trans "This is part of the same blockquote." %}</p>
</td>
</tr></table>
<div class="section-description">
- Rather than putting it in front of each line to include in the block quote you can put it at the beginning and end the quote with a newline.
+ {% trans "Rather than putting it in front of each line to include in the block quote you can put it at the beginning and end the quote with a newline." %}
</div>
<table class="section-example"><tr>
<td>
- <code>> This is part of a blockquote.</code><br />
- <code>This continues the blockquote even though there's no bracket.</code><br /><br />
- <code>The blank line ends the blockquote.</code>
+ <code>> {% trans "This is part of a blockquote." %}</code><br />
+ <code>{% trans "This continues the blockquote even though there's no bracket." %}</code><br /><br />
+ <code>{% trans "The blank line ends the blockquote." %}</code>
</td>
<td>
- <p style="padding-left:15px;">This is part of a blockquote. <br /> This continues the blockquote even though there's no bracket.</p>
- <p>The blank line ends the blockquote.</p>
+ <p style="padding-left:15px;">{% trans "This is part of a blockquote." %} <br /> {% trans "This continues the blockquote even though there's no bracket." %}</p>
+ <p>{% trans "The blank line ends the blockquote." %}</p>
</td>
</tr></table>
</div>
<div class="section">
- <h2 class="section-title">Lists</h2>
+ <h2 class="section-title">{% trans "Lists" %}</h2>
<div class="section-description">
- To create a numbered list in Markdown, prefix each item in the list with a number followed by a period and space. The number you use actually doesn't matter.
+ {% trans "To create a numbered list in Markdown, prefix each item in the list with a number followed by a period and space. The number you use actually doesn't matter." %}
</div>
<table class="section-example"><tr>
<td>
- <code>1. Item 1</code><br />
- <code>2. Item 2</code><br />
- <code>3. Item 3</code>
+ <code>1. {% trans "Item" %} 1</code><br />
+ <code>2. {% trans "Item" %} 2</code><br />
+ <code>3. {% trans "Item" %} 3</code>
</td>
<td>
<ol>
- <li>Item 1</li>
- <li>Item 2</li>
- <li>Item 3</li>
+ <li>{% trans "Item" %} 1</li>
+ <li>{% trans "Item" %} 2</li>
+ <li>{% trans "Item" %} 3</li>
</ol>
</td>
</tr></table>
<div class="section-description">
- To create a bulleted list, prefix each item in the list with a star (*) character.
+ {% trans "To create a bulleted list, prefix each item in the list with a star (*) character." %}
</div>
<table class="section-example"><tr>
<td>
- <code>* A list item</code><br />
- <code>* Another list item</code><br />
- <code>* A third list item</code>
+ <code>* {% trans "A list item" %}</code><br />
+ <code>* {% trans "Another list item" %}</code><br />
+ <code>* {% trans "A third list item" %}</code>
</td>
<td>
<ul>
- <li>A list item</li>
- <li>Another list item</li>
- <li>A third list item</li>
+ <li>{% trans "A list item" %}</li>
+ <li>{% trans "Another list item" %}</li>
+ <li>{% trans "A third list item" %}</li>
</ul>
</td>
</tr></table>
</div>
<div class="section">
- <h2 class="section-title">A Lot More</h2>
- <div class="section-description">There's a lot more to the Markdown syntax than is mentioned here. But for creative writers, this covers a lot of the necessities. To find out more about Markdown than you'd ever want to really know, <a href="http://daringfireball.net/projects/markdown/syntax" target="_blank" rel="nofollow">go to the Markdown page where it all started</a>.</div>
+ <h2 class="section-title">{% trans "A Lot More" %}</h2>
+ <div class="section-description">{% blocktrans %}There's a lot more to the Markdown syntax than is mentioned here. But for creative writers, this covers a lot of the necessities. To find out more about Markdown than you'd ever want to really know, <a href="http://daringfireball.net/projects/markdown/syntax" target="_blank" rel="nofollow">go to the Markdown page where it all started</a>.{% endblocktrans %}</div>
</div>
</div>
{% endblock %}
\ No newline at end of file
-{% load i18n extra_tags email_tags %}\r
-\r
-{% declare %}\r
- prefix = settings.EMAIL_SUBJECT_PREFIX\r
- app_name = settings.APP_SHORT_NAME\r
- app_url = settings.APP_URL\r
- answer_author = answer.author.username\r
- question = answer.question\r
- question_url = question.get_absolute_url()\r
- question_title = question.title\r
- accepted_by = answer.nstate.accepted.by.username\r
-{% enddeclare %}\r
-\r
-{% email %}\r
- {% subject %}{% blocktrans %}{{ prefix }} New answer to {{ question_title }}{% endblocktrans %}{% endsubject %}\r
-\r
- {% htmlcontent notifications/base.html %}\r
- <p style="{{ p_style }}">\r
- {% blocktrans %}\r
- {{ accepted_by }} has just accepted {{ answer_author }}'s answer on his question\r
- <a href="{{ app_url }}{{ question_url }}">{{ question_title }}</a>.\r
- {% endblocktrans %}\r
- </p>\r
- {% endhtmlcontent %}\r
-\r
- {% textcontent notifications/base_text.html %}\r
- {% blocktrans %}\r
- {{ accepted_by }} has just accepted {{ answer_author }}'s answer on his question\r
- "{{ question_title }}".\r
- {% endblocktrans %}\r
- {% endtextcontent %}\r
-\r
-{% endemail %}\r
+{% load i18n extra_tags email_tags %}
+
+{% declare %}
+ prefix = settings.EMAIL_SUBJECT_PREFIX
+ app_name = settings.APP_SHORT_NAME
+ answer_author = answer.author.username
+ question = answer.question
+ question_title = question.title
+ accepted_by = answer.nstate.accepted.by.username
+{% enddeclare %}
+
+{% email %}
+ {% subject %}{% blocktrans %}{{ prefix }} New answer to {{ question_title }}{% endblocktrans %}{% endsubject %}
+
+ {% htmlcontent notifications/base.html %}
+ {% declare %}
+ accepted_by_link = html.objlink(answer.nstate.accepted.by, style=a_style)
+ answer_author_link = html.objlink(answer.author, style=a_style)
+ question_link = html.objlink(question, style=a_style)
+ {% enddeclare %}
+ <p style="{{ p_style }}">
+ {% blocktrans %}
+ {{ accepted_by_link }} has just accepted {{ answer_author_link }}'s answer on his question
+ {{ question_link }}.
+ {% endblocktrans %}
+ </p>
+ {% endhtmlcontent %}
+
+{% textcontent notifications/base_text.html %}
+{% blocktrans %}
+{{ accepted_by }} has just accepted {{ answer_author }}'s answer on his question
+"{{ question_title }}".
+{% endblocktrans %}
+{% endtextcontent %}
+
+{% endemail %}
{% block content %}
{% endblock%}
-Thanks,
+{% trans "Thanks" %},
{{settings.APP_SHORT_NAME}}
-P.S. You can always fine-tune which notifications you receive here:
+{% trans "P.S. You can always fine-tune which notifications you receive here:" %}
{{ settings.APP_URL }}{% url user_subscriptions id=recipient.id %}
{{ postal_address }}
\ No newline at end of file
-{% extends "email_base.html" %}\r
-{% load i18n %}\r
-{% load humanize %}\r
-{% load extra_tags %}\r
-\r
-{% block content %}\r
- <p>{% trans "Hello" %} {{ user.username }},</p>\r
-\r
- <p>{% blocktrans with settings.APP_SHORT_NAME as app_title %}\r
- This is the {{ digest_type }} activity digest for {{ app_title }}\r
- {% endblocktrans %}</p>\r
-\r
- {% if new_users %}\r
- <h3>\r
- {% blocktrans with new_users|length as nusers_count and new_users|length|pluralize as nusers_count_pluralize and settings.APP_SHORT_NAME as app_title %}\r
- {{ nusers_count }} new user{{ nusers_count_pluralize }} joined the {{ app_title }} community:\r
- {% endblocktrans %}\r
- </h3>\r
- <ul>\r
- {% for nuser in new_users %}\r
- <li><a href="{{ settings.APP_URL }}{{ nuser.get_profile_url }}">{{ nuser.username }}</a></li> \r
- {% endfor %}\r
- </ul>\r
- {% endif %}\r
-\r
- {% if activity_in_subscriptions %}\r
- <h3>\r
- {% blocktrans with activity_in_subscriptions|length as question_count and activity_in_subscriptions|length|pluralize as question_count_pluralize %}\r
- {{ question_count }} of your subscriptions have updates:\r
- {% endblocktrans %}\r
- </h3>\r
- <ul>\r
- {% for record in activity_in_subscriptions %}\r
- <li>\r
- {% trans "On question " %}<a href="{{ settings.APP_URL }}{{ record.question.get_absolute_url }}">{{ question_title }}" %}</a> -\r
- {% if record.activity.answers %}\r
- {% blocktrans with record.activity.answers|length as answer_count and record.activity.answers|length|pluralize as answer_count_pluralize %}\r
- {{ answer_count }} new answer{{ answer_count_pluralize }}\r
- {% endblocktrans %},\r
- {% endif %}\r
- {% if record.activity.comments %}\r
- {% blocktrans with record.activity.comments|length as comment_count and record.activity.comments|length|pluralize as comment_count_pluralize %}\r
- {{ comment_count }} new comment{{ comment_count_pluralize }}\r
- {% endblocktrans %}\r
- {% if own_comments_only %}\r
- {% trans "on your own post(s)" %}\r
- {% endif %},\r
- {% endif %}\r
- {% if record.accepted %}\r
- {% trans "an answer was accepted" %}\r
- {% endif %}\r
- </li>\r
- {% endfor %}\r
- </ul>\r
- {% endif %}\r
-\r
- {% if new_questions %}\r
- <h3>\r
- {% blocktrans with new_questions|length as question_count and new_questions|length|pluralize as question_count_pluralize%}\r
- {{ question_count }} new question{{ question_count_pluralize }}\r
- {% endblocktrans %}\r
- {% if watched_tags_only %}\r
- {% trans "matching your interesting tags" %}\r
- {% endif %}\r
- {% trans "posted :" %}\r
- </h3>\r
- <ul>\r
- {% for question in new_questions %}\r
- <li>\r
- <a href="{{ settings.APP_URL }}{{ question.get_absolute_url }}">{{ question.title }}</a> -\r
- {% blocktrans with question.author.username as author_name and question.added_at|date:"D d M Y" as question_time %}\r
- Posted by {{ author_name }} in {{ question_time }}\r
- {% endblocktrans %}\r
- </li>\r
- {% endfor %}\r
- </ul>\r
- {% endif %}\r
-\r
+{% extends "email_base.html" %}
+{% load i18n %}
+{% load humanize %}
+{% load extra_tags %}
+
+{% block content %}
+ <p>{% trans "Hello" %} {{ user.username }},</p>
+
+ <p>{% blocktrans with settings.APP_SHORT_NAME as app_title %}
+ This is the {{ digest_type }} activity digest for {{ app_title }}
+ {% endblocktrans %}</p>
+
+ {% if new_users %}
+ <h3>
+ {% blocktrans with new_users|length as nusers_count and new_users|length|pluralize as nusers_count_pluralize and settings.APP_SHORT_NAME as app_title %}
+ {{ nusers_count }} new user{{ nusers_count_pluralize }} joined the {{ app_title }} community:
+ {% endblocktrans %}
+ </h3>
+ <ul>
+ {% for nuser in new_users %}
+ <li><a href="{{ settings.APP_URL }}{{ nuser.get_profile_url }}">{{ nuser.username }}</a></li>
+ {% endfor %}
+ </ul>
+ {% endif %}
+
+ {% if activity_in_subscriptions %}
+ <h3>
+ {% blocktrans with activity_in_subscriptions|length as question_count and activity_in_subscriptions|length|pluralize as question_count_pluralize %}
+ {{ question_count }} of your subscriptions have updates:
+ {% endblocktrans %}
+ </h3>
+ <ul>
+ {% for record in activity_in_subscriptions %}
+ <li>
+ {% trans "On question " %}<a href="{{ settings.APP_URL }}{{ record.question.get_absolute_url }}">{{ question_title }}" %}</a> -
+ {% if record.activity.answers %}
+ {% blocktrans with record.activity.answers|length as answer_count and record.activity.answers|length|pluralize as answer_count_pluralize %}
+ {{ answer_count }} new answer{{ answer_count_pluralize }}
+ {% endblocktrans %},
+ {% endif %}
+ {% if record.activity.comments %}
+ {% blocktrans with record.activity.comments|length as comment_count and record.activity.comments|length|pluralize as comment_count_pluralize %}
+ {{ comment_count }} new comment{{ comment_count_pluralize }}
+ {% endblocktrans %}
+ {% if own_comments_only %}
+ {% trans "on your own post(s)" %}
+ {% endif %},
+ {% endif %}
+ {% if record.accepted %}
+ {% trans "an answer was accepted" %}
+ {% endif %}
+ </li>
+ {% endfor %}
+ </ul>
+ {% endif %}
+
+ {% if new_questions %}
+ <h3>
+ {% blocktrans with new_questions|length as question_count and new_questions|length|pluralize as question_count_pluralize%}
+ {{ question_count }} new question{{ question_count_pluralize }}
+ {% endblocktrans %}
+ {% if watched_tags_only %}
+ {% trans "matching your interesting tags" %}
+ {% endif %}
+ {% trans "posted :" %}
+ </h3>
+ <ul>
+ {% for question in new_questions %}
+ <li>
+ <a href="{{ settings.APP_URL }}{{ question.get_absolute_url }}">{{ question.title }}</a> -
+ {% blocktrans with question.author.username as author_name and question.added_at|date:"D d M Y" as question_time %}
+ Posted by {{ author_name }} in {{ question_time }}
+ {% endblocktrans %}
+ </li>
+ {% endfor %}
+ </ul>
+ {% endif %}
+
{% endblock %}
\ No newline at end of file
{% extends "email_base.html" %}
{% load i18n %}
{% load extra_tags %}
+{% load email_tags %}
{% block content %}
<p>{% spaceless %}
-{% load i18n extra_tags email_tags %}\r
-\r
-{% declare %}\r
- prefix = settings.EMAIL_SUBJECT_PREFIX\r
- app_name = settings.APP_SHORT_NAME\r
- app_url = settings.APP_URL\r
- answer_author = answer.author.username\r
- question = answer.question\r
- question_url = question.get_absolute_url()\r
- question_title = question.title\r
-{% enddeclare %}\r
-\r
-{% email %}\r
- {% subject %}{% blocktrans %}{{ prefix }} New answer to {{ question_title }}{% endblocktrans %}{% endsubject %}\r
-\r
- {% htmlcontent notifications/base.html %}\r
- <p style="{{ p_style }}">\r
- {% blocktrans %}\r
- {{ answer_author }} has just posted a new answer on {{ app_name }} to the question\r
- <a href="{{ app_url }}{{ question_url }}">{{ question_title }}</a>:\r
- {% endblocktrans %}\r
- </p>\r
-\r
- <blockquote>\r
- {{ answer.html|safe }}\r
- </blockquote>\r
-\r
- <p style="{{ p_style }}">{% trans "Don't forget to come over and cast your vote." %}</p>\r
- {% endhtmlcontent %}\r
-\r
- {% textcontent notifications/base_text.html %}\r
- {% blocktrans %}\r
- {{ answer_author }} has just posted a new answer on {{ app_name }} to the question\r
- "{{ question_title }}":\r
- {% endblocktrans %}\r
-\r
-\r
- {{ answer.body|safe }}\r
-\r
- {% trans "Don't forget to come over and cast your vote." %} \r
- {% endtextcontent %}\r
-\r
-{% endemail %}\r
-\r
+{% load i18n extra_tags email_tags %}
+
+{% declare %}
+ prefix = settings.EMAIL_SUBJECT_PREFIX
+ app_name = settings.APP_SHORT_NAME
+ answer_author = answer.author.username
+ question = answer.question
+ question_title = question.title
+ safe_body = html.html2text(answer.html)
+{% enddeclare %}
+
+{% email %}
+ {% subject %}{% blocktrans %}{{ prefix }} New answer to {{ question_title }}{% endblocktrans %}{% endsubject %}
+
+ {% htmlcontent notifications/base.html %}
+ {% declare %}
+ author_link = html.objlink(answer.author, style=a_style)
+ question_link = html.objlink(question, style=a_style)
+ {% enddeclare %}
+ <p style="{{ p_style }}">
+ {% blocktrans %}
+ {{ author_link }} has just posted a new answer on {{ app_name }} to the question
+ {{ question_link }}:
+ {% endblocktrans %}
+ </p>
+
+ <blockquote>
+ {{ answer.html|safe }}
+ </blockquote>
+
+ <p style="{{ p_style }}">{% trans "Don't forget to come over and cast your vote." %}</p>
+ {% endhtmlcontent %}
+
+{% textcontent notifications/base_text.html %}
+{% blocktrans %}
+{{ answer_author }} has just posted a new answer on {{ app_name }} to the question
+"{{ question_title }}":
+{% endblocktrans %}
+{{ safe_body }}
+
+{% trans "Don't forget to come over and cast your vote." %}
+{% endtextcontent %}
+
+{% endemail %}
+
-{% load i18n extra_tags email_tags %}\r
-\r
-{% declare %}\r
- prefix = settings.EMAIL_SUBJECT_PREFIX\r
- app_name = settings.APP_SHORT_NAME\r
- app_url = settings.APP_URL\r
- post = comment.parent\r
- question = post.question and post.question or post\r
- post_author = post.author.username\r
- comment_author = comment.author\r
- question_url = question.get_absolute_url()\r
- question_title = question.title\r
-{% enddeclare %}\r
-\r
-{% email %}\r
- {% subject %}{% blocktrans %}{{ prefix }} New comment on {{ question_title }}{% endblocktrans %}{% endsubject %}\r
-\r
- {% htmlcontent notifications/base.html %}\r
- <p style="{{ p_style }}">\r
- {% blocktrans %}{{ comment_author }} has just posted a comment on {% endblocktrans %}\r
- {% ifnotequal post question %}\r
- {% blocktrans %}the answer posted by {{ post_author }} to {% endblocktrans %}\r
- {% endifnotequal %}\r
- {% blocktrans %}the question <a href="{{ app_url }}{{ question_url }}">{{ question_title }}</a>{% endblocktrans %}\r
- </p>\r
-\r
- <blockquote>\r
- {{ comment.comment }}\r
- </blockquote>\r
-\r
- <p style="{{ p_style }}">{% trans "Don't forget to come over and cast your vote." %}</p>\r
- {% endhtmlcontent %}\r
-\r
- {% textcontent notifications/base_text.html %}\r
- {% blocktrans %}{{ comment_author }} has just posted a comment on {% endblocktrans %}\r
- {% ifnotequal post question %}\r
- {% blocktrans %}the answer posted by {{ post_author }} to {% endblocktrans %}\r
- {% endifnotequal %}\r
- {% blocktrans %}the question "{{ question_title }}"{% endblocktrans %}\r
-\r
-\r
- {{ comment.body }}\r
-\r
- {% trans "Don't forget to come over and cast your vote." %}\r
- {% endtextcontent %}\r
-\r
-{% endemail %}\r
+{% load i18n extra_tags email_tags %}
+
+{% declare %}
+ prefix = settings.EMAIL_SUBJECT_PREFIX
+ app_name = settings.APP_SHORT_NAME
+ post = comment.parent
+ question = post.question and post.question or post
+ post_author = post.author.username
+ comment_author = comment.author
+ question_url = question.get_absolute_url()
+ question_title = question.title
+ safe_body = html.html2text(comment.comment)
+{% enddeclare %}
+
+{% email %}
+ {% subject %}{% blocktrans %}{{ prefix }} New comment on {{ question_title }}{% endblocktrans %}{% endsubject %}
+
+ {% htmlcontent notifications/base.html %}
+ {% declare %}
+ author_link = html.objlink(comment.author, style=a_style)
+ question_link = html.objlink(question, style=a_style)
+ {% enddeclare %}
+ <p style="{{ p_style }}">
+ {% blocktrans %}{{ author_link }} has just posted a comment on {% endblocktrans %}
+ {% ifnotequal post question %}
+ {% blocktrans %}the answer posted by {{ post_author }} to {% endblocktrans %}
+ {% endifnotequal %}
+ {% blocktrans %}the question {{ question_link }}{% endblocktrans %}
+ </p>
+
+ <blockquote>
+ {{ comment.comment }}
+ </blockquote>
+
+ <p style="{{ p_style }}">{% trans "Don't forget to come over and cast your vote." %}</p>
+ {% endhtmlcontent %}
+
+{% textcontent notifications/base_text.html %}
+{% blocktrans %}{{ comment_author }} has just posted a comment on {% endblocktrans %}
+{% ifnotequal post question %}
+{% blocktrans %}the answer posted by {{ post_author }} to {% endblocktrans %}
+{% endifnotequal %}
+{% blocktrans %}the question "{{ question_title }}"{% endblocktrans %}
+{{ safe_body }}
+
+{% trans "Don't forget to come over and cast your vote." %}
+{% endtextcontent %}
+
+{% endemail %}
-{% load i18n extra_tags email_tags %}\r
-\r
-{% declare %}\r
- prefix = settings.EMAIL_SUBJECT_PREFIX\r
- app_name = settings.APP_SHORT_NAME\r
- app_url = settings.APP_URL\r
- newmember_name = newmember.username\r
- newmember_url = newmember.get_profile_url\r
-{% enddeclare %}\r
-\r
-{% email %}\r
- {% subject %}{% blocktrans %}{{ newmember_name }} is a new member on {{ app_name }}{% endblocktrans %}{% endsubject %}\r
-\r
- {% htmlcontent notifications/base.html %}\r
- <p style="{{ p_style }}">\r
- {% blocktrans %}\r
- {{ newmember_name }} has just joined {{ app_name }}. You can visit {{ newmember_name }}'s profile using the following link: <br />\r
- <a href="{{ app_url }}{{ newmember_url }}">{{ newmember_name }} profile</a>\r
- {% endblocktrans %}\r
- </p>\r
- {% endhtmlcontent %}\r
-\r
- {% textcontent notifications/base_text.html %}\r
- {% blocktrans %}\r
- {{ newmember_name }} has just joined {{ app_name }}. You can visit {{ newmember_name }}'s profile using the following url: <br />\r
- {{ app_url }}{{ newmember_url }}\r
- {% endblocktrans %}\r
- {% endtextcontent %}\r
-\r
-{% endemail %}\r
-\r
+{% load i18n extra_tags email_tags %}
+
+{% declare %}
+ prefix = settings.EMAIL_SUBJECT_PREFIX
+ app_name = settings.APP_SHORT_NAME
+ app_url = settings.APP_URL
+ newmember_name = newmember.username
+ newmember_url = newmember.get_profile_url()
+{% enddeclare %}
+
+{% email %}
+ {% subject %}{% blocktrans %}{{ prefix }}{{ newmember_name }} is a new member on {{ app_name }}{% endblocktrans %}{% endsubject %}
+
+ {% htmlcontent notifications/base.html %}
+ {% declare %}
+ newmember_link = html.objlink(newmember, style=a_style)
+ {% enddeclare %}
+ <p style="{{ p_style }}">
+ {% blocktrans %}
+ {{ newmember_link }} has just joined {{ app_name }}. You can visit {{ newmember_name }}'s profile using the following link: <br />
+ <a href="{{ app_url }}{{ newmember_url }}">{{ newmember_name }} profile</a>
+ {% endblocktrans %}
+ </p>
+ {% endhtmlcontent %}
+
+{% textcontent notifications/base_text.html %}
+{% blocktrans %}
+{{ newmember_name }} has just joined {{ app_name }}. You can visit {{ newmember_name }}'s profile using the following url: <br />
+{{ app_url }}{{ newmember_url }}
+{% endblocktrans %}
+{% endtextcontent %}
+
+{% endemail %}
+
-{% load i18n extra_tags email_tags %}\r
-\r
-{% declare %}\r
- prefix = settings.EMAIL_SUBJECT_PREFIX\r
- app_name = settings.APP_SHORT_NAME\r
- question_author = question.author.username\r
- app_url = settings.APP_URL\r
- question_url = question.get_absolute_url()\r
- question_title = question.title\r
- question_tags = question.tagnames\r
-{% enddeclare %}\r
-\r
-{% email %}\r
- {% subject %}{% blocktrans %}{{ prefix }} New question on {{ app_name }}{% endblocktrans %}{% endsubject %}\r
-\r
- {% htmlcontent notifications/base.html %}\r
- <p style="{{ p_style }}">\r
- {% blocktrans %}\r
- {{ question_author }} has just posted a new question on {{ app_name }}, with title\r
- <a style="{{ a_style }}" href="{{ app_url }}{{ question_url }}">{{ question_title }}</a> and tagged <em>{{ question_tags }}</em>:\r
- {% endblocktrans %}\r
- </p>\r
-\r
- <blockquote>\r
- {{ question.html|safe }}\r
- </blockquote>\r
-\r
- <p style="{{ p_style }}">{% trans "Don't forget to come over and cast your vote." %}</p> \r
- {% endhtmlcontent %}\r
-\r
-{% textcontent notifications/base_text.html %}\r
- {% blocktrans %}\r
- {{ question_author }} has just posted a new question on {{ app_name }}, with title\r
- "{{ question_title }}" and tagged {{ question_tags }}:\r
- {% endblocktrans %}\r
-\r
- {{ question.body|safe }}\r
-\r
- {% trans "Don't forget to come over and cast your vote." %}\r
-{% endtextcontent %}\r
-\r
-{% endemail %}\r
-\r
+{% load i18n extra_tags email_tags %}
+
+{% declare %}
+ prefix = settings.EMAIL_SUBJECT_PREFIX
+ app_name = settings.APP_SHORT_NAME
+ question_author = question.author.username
+ question_url = settings.APP_URL + question.get_absolute_url()
+ question_title = question.title
+ question_tags = question.tagnames
+ safe_body = html.html2text(question.html)
+{% enddeclare %}
+
+{% email %}
+ {% subject %}{% blocktrans %}{{ prefix }} New question on {{ app_name }}{% endblocktrans %}{% endsubject %}
+
+ {% htmlcontent notifications/base.html %}
+ {% declare %}
+ author_link = html.objlink(question.author, style=a_style)
+ question_link = html.objlink(question, style=a_style)
+ tag_links = html.mark_safe(" ".join([html.objlink(t, style=a_style) for t in question.tags.all()]))
+ {% enddeclare %}
+
+ <p style="{{ p_style }}">
+ {% blocktrans %}
+ {{ author_link }} has just posted a new question on {{ app_name }}, entitled
+ {{ question_link }}
+ and tagged "<em>{{ tag_links }}</em>". Here's what it says:
+ {% endblocktrans %}
+ </p>
+
+ <blockquote>
+ {{ question.html|safe }}
+ </blockquote>
+
+ <p style="{{ p_style }}">{% trans "Don't forget to come over and cast your vote." %}</p>
+ {% endhtmlcontent %}
+
+{% textcontent notifications/base_text.html %}
+{% blocktrans %}
+{{ question_author }} has just posted a new question on {{ app_name }}, entitled
+"{{ question_title }}" and tagged {{ question_tags }}:
+{% endblocktrans %}
+{{ safe_body }}
+
+{% trans "Don't forget to come over and cast your vote." %}
+{% endtextcontent %}
+
+{% endemail %}
+
from django import template
from forum import settings
from forum.utils.mail import create_and_send_mail_messages
+from django.template.defaulttags import url as default_url
register = template.Library()
return EmbedMediaNode(location, alias)
+class FullUrlNode(template.Node):
+ def __init__(self, default_node):
+ self.default_node = default_node
+
+ def render(self, context):
+ domain = settings.APP_URL
+ path = self.default_node.render(context)
+ return "%s%s" % (domain, path)
+
+@register.tag(name='fullurl')
+def fullurl(parser, token):
+ default_node = default_url(parser, token)
+ return FullUrlNode(default_node)
+
+
from forum import settings
from django.template.defaulttags import url as default_url
from forum import skins
+from forum.utils import html
+from django.core.urlresolvers import reverse
register = template.Library()
'username': template.defaultfilters.urlencode(username),
})
-MAX_FONTSIZE = 18
-MIN_FONTSIZE = 12
-@register.simple_tag
-def tag_font_size(max_size, min_size, current_size):
- """
- do a logarithmic mapping calcuation for a proper size for tagging cloud
- Algorithm from http://blogs.dekoh.com/dev/2007/10/29/choosing-a-good-font-size-variation-algorithm-for-your-tag-cloud/
- """
- #avoid invalid calculation
- if current_size == 0:
- current_size = 1
- try:
- weight = (math.log10(current_size) - math.log10(min_size)) / (math.log10(max_size) - math.log10(min_size))
- except:
- weight = 0
- return MIN_FONTSIZE + round((MAX_FONTSIZE - MIN_FONTSIZE) * weight)
+#MAX_FONTSIZE = 18
+#MIN_FONTSIZE = 12
+#@register.simple_tag
+#def tag_font_size(max_size, min_size, current_size):
+# """
+# do a logarithmic mapping calcuation for a proper size for tagging cloud
+# Algorithm from http://blogs.dekoh.com/dev/2007/10/29/choosing-a-good-font-size-variation-algorithm-for-your-tag-cloud/
+# """
+# #avoid invalid calculation
+# if current_size == 0:
+# current_size = 1
+# try:
+# weight = (math.log10(current_size) - math.log10(min_size)) / (math.log10(max_size) - math.log10(min_size))
+# except:
+# weight = 0
+# return MIN_FONTSIZE + round((MAX_FONTSIZE - MIN_FONTSIZE) * weight)
LEADING_PAGE_RANGE_DISPLAYED = TRAILING_PAGE_RANGE_DISPLAYED = 5
'reputationword' : _('reputation points'),
})
-@register.simple_tag
-def get_score_badge_by_details(rep, gold, silver, bronze):
- BADGE_TEMPLATE = '<span class="reputation-score" title="%(reputation)s %(repword)s">%(reputation)s</span>'
- if gold > 0 :
- BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(gold)s %(badgeword)s">'
- '<span class="badge1">●</span>'
- '<span class="badgecount">%(gold)s</span>'
- '</span>')
- if silver > 0:
- BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(silver)s %(badgeword)s">'
- '<span class="badge2">●</span>'
- '<span class="badgecount">%(silver)s</span>'
- '</span>')
- if bronze > 0:
- BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(bronze)s %(badgeword)s">'
- '<span class="badge3">●</span>'
- '<span class="badgecount">%(bronze)s</span>'
- '</span>')
- BADGE_TEMPLATE = smart_unicode(BADGE_TEMPLATE, encoding='utf-8', strings_only=False, errors='strict')
- return mark_safe(BADGE_TEMPLATE % {
- 'reputation' : rep,
- 'gold' : gold,
- 'silver' : silver,
- 'bronze' : bronze,
- 'repword' : _('reputation points'),
- 'badgeword' : _('badges'),
- })
+#@register.simple_tag
+#def get_score_badge_by_details(rep, gold, silver, bronze):
+# BADGE_TEMPLATE = '<span class="reputation-score" title="%(reputation)s %(repword)s">%(reputation)s</span>'
+# if gold > 0 :
+# BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(gold)s %(badgeword)s">'
+# '<span class="badge1">●</span>'
+# '<span class="badgecount">%(gold)s</span>'
+# '</span>')
+# if silver > 0:
+# BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(silver)s %(badgeword)s">'
+# '<span class="badge2">●</span>'
+# '<span class="badgecount">%(silver)s</span>'
+# '</span>')
+# if bronze > 0:
+# BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(bronze)s %(badgeword)s">'
+# '<span class="badge3">●</span>'
+# '<span class="badgecount">%(bronze)s</span>'
+# '</span>')
+# BADGE_TEMPLATE = smart_unicode(BADGE_TEMPLATE, encoding='utf-8', strings_only=False, errors='strict')
+# return mark_safe(BADGE_TEMPLATE % {
+# 'reputation' : rep,
+# 'gold' : gold,
+# 'silver' : silver,
+# 'bronze' : bronze,
+# 'repword' : _('reputation points'),
+# 'badgeword' : _('badges'),
+# })
@register.simple_tag
def get_age(birthday):
diff = current_time - datetime.datetime(year,month,day,0,0,0)
return diff.days / 365
-@register.simple_tag
-def get_total_count(up_count, down_count):
- return up_count + down_count
-
-@register.simple_tag
-def format_number(value):
- strValue = str(value)
- if len(strValue) <= 3:
- return strValue
- result = ''
- first = ''
- pattern = re.compile('(-?\d+)(\d{3})')
- m = re.match(pattern, strValue)
- while m != None:
- first = m.group(1)
- second = m.group(2)
- result = ',' + second + result
- strValue = first + ',' + second
- m = re.match(pattern, strValue)
- return first + result
-
-@register.simple_tag
-def convert2tagname_list(question):
- question['tagnames'] = [name for name in question['tagnames'].split(u' ')]
- return ''
+#@register.simple_tag
+#def get_total_count(up_count, down_count):
+# return up_count + down_count
+
+#@register.simple_tag
+#def format_number(value):
+# strValue = str(value)
+# if len(strValue) <= 3:
+# return strValue
+# result = ''
+# first = ''
+# pattern = re.compile('(-?\d+)(\d{3})')
+# m = re.match(pattern, strValue)
+# while m != None:
+# first = m.group(1)
+# second = m.group(2)
+# result = ',' + second + result
+# strValue = first + ',' + second
+# m = re.match(pattern, strValue)
+# return first + result
+
+#@register.simple_tag
+#def convert2tagname_list(question):
+# question['tagnames'] = [name for name in question['tagnames'].split(u' ')]
+# return ''
@register.simple_tag
def diff_date(date, limen=2):
else:
return ungettext('%(min)d min ago','%(min)d mins ago',minutes) % {'min':minutes}
-@register.simple_tag
-def get_latest_changed_timestamp():
- try:
- from time import localtime, strftime
- from os import path
- root = settings.SITE_SRC_ROOT
- dir = (
- root,
- '%s/forum' % root,
- '%s/templates' % root,
- )
- stamp = (path.getmtime(d) for d in dir)
- latest = max(stamp)
- timestr = strftime("%H:%M %b-%d-%Y %Z", localtime(latest))
- except:
- timestr = ''
- return timestr
+#@register.simple_tag
+#def get_latest_changed_timestamp():
+# try:
+# from time import localtime, strftime
+# from os import path
+# root = settings.SITE_SRC_ROOT
+# dir = (
+# root,
+# '%s/forum' % root,
+# '%s/templates' % root,
+# )
+# stamp = (path.getmtime(d) for d in dir)
+# latest = max(stamp)
+# timestr = strftime("%H:%M %b-%d-%Y %Z", localtime(latest))
+# except:
+# timestr = ''
+# return timestr
@register.simple_tag
def media(url):
def render(self,context):
return self.content
-class JoinItemListNode(template.Node):
- def __init__(self,separator=ItemSeparatorNode("''"), items=()):
- self.separator = separator
- self.items = items
- def render(self,context):
- out = []
- empty_re = re.compile(r'^\s*$')
- for item in self.items:
- bit = item.render(context)
- if not empty_re.search(bit):
- out.append(bit)
- return self.separator.render(context).join(out)
-
-@register.tag(name="joinitems")
-def joinitems(parser,token):
- try:
- tagname,junk,sep_token = token.split_contents()
- except ValueError:
- raise template.TemplateSyntaxError("joinitems tag requires 'using \"separator html\"' parameters")
- if junk == 'using':
- sep_node = ItemSeparatorNode(sep_token)
- else:
- raise template.TemplateSyntaxError("joinitems tag requires 'using \"separator html\"' parameters")
- nodelist = []
- while True:
- nodelist.append(parser.parse(('separator','endjoinitems')))
- next = parser.next_token()
- if next.contents == 'endjoinitems':
- break
-
- return JoinItemListNode(separator=sep_node,items=nodelist)
+#class JoinItemListNode(template.Node):
+# def __init__(self,separator=ItemSeparatorNode("''"), items=()):
+# self.separator = separator
+# self.items = items
+# def render(self,context):
+# out = []
+# empty_re = re.compile(r'^\s*$')
+# for item in self.items:
+# bit = item.render(context)
+# if not empty_re.search(bit):
+# out.append(bit)
+# return self.separator.render(context).join(out)
+#
+#@register.tag(name="joinitems")
+#def joinitems(parser,token):
+# try:
+# tagname,junk,sep_token = token.split_contents()
+# except ValueError:
+# raise template.TemplateSyntaxError("joinitems tag requires 'using \"separator html\"' parameters")
+# if junk == 'using':
+# sep_node = ItemSeparatorNode(sep_token)
+# else:
+# raise template.TemplateSyntaxError("joinitems tag requires 'using \"separator html\"' parameters")
+# nodelist = []
+# while True:
+# nodelist.append(parser.parse(('separator','endjoinitems')))
+# next = parser.next_token()
+# if next.contents == 'endjoinitems':
+# break
+
+# return JoinItemListNode(separator=sep_node,items=nodelist)
class BlockMediaUrlNode(template.Node):
def __init__(self,nodelist):
break
return BlockMediaUrlNode(nodelist)
-class FullUrlNode(template.Node):
- def __init__(self, default_node):
- self.default_node = default_node
-
- def render(self, context):
- domain = settings.APP_URL
- #protocol = getattr(settings, "PROTOCOL", "http")
- path = self.default_node.render(context)
- return "%s%s" % (domain, path)
-
-@register.tag(name='fullurl')
-def fullurl(parser, token):
- default_node = default_url(parser, token)
- return FullUrlNode(default_node)
@register.simple_tag
def fullmedia(url):
path = media(url)
return "%s%s" % (domain, path)
-class UserVarNode(template.Node):
- def __init__(self, tokens):
- self.tokens = tokens
-
- def render(self, context):
- return "{{ %s }}" % self.tokens
-
-@register.tag(name='user_var')
-def user_var(parser, token):
- tokens = " ".join(token.split_contents()[1:])
- return UserVarNode(tokens)
-
class SimpleVarNode(template.Node):
def __init__(self, name, value):
d = {}
d['_'] = _
d['os'] = os
+ d['html'] = html
+ d['reverse'] = reverse
for c in clist:
d.update(c)
try:
"""Utilities for working with HTML."""
import html5lib
from html5lib import sanitizer, serializer, tokenizer, treebuilders, treewalkers
+from forum.utils.html2text import HTML2Text
+from django.template import mark_safe
+from forum import settings
class HTMLSanitizerMixin(sanitizer.HTMLSanitizerMixin):
acceptable_elements = ('a', 'abbr', 'acronym', 'address', 'b', 'big',
quote_attr_values=True)
output_generator = s.serialize(stream)
return u''.join(output_generator)
+
+
+def html2text(s, ignore_tags=(), indent_width=4, page_width=80):
+ ignore_tags = [t.lower() for t in ignore_tags]
+ parser = HTML2Text(ignore_tags, indent_width, page_width)
+ parser.feed(s)
+ parser.close()
+ parser.generate()
+ return parser.result
+
+def buildtag(name, content, **attrs):
+ return mark_safe('<%s %s>%s</a>' % (name, " ".join('%s="%s"' % i for i in attrs.items()), content))
+
+def hyperlink(url, title, **attrs):
+ return mark_safe('<a href="%s" %s>%s</a>' % (url, " ".join('%s="%s"' % i for i in attrs.items()), title))
+
+def objlink(obj, **attrs):
+ return hyperlink(settings.APP_URL + obj.get_absolute_url(), unicode(obj), **attrs)
+
+
+
+
-import email\r
-import socket\r
-import os\r
-\r
-from email.mime.multipart import MIMEMultipart\r
-from email.mime.text import MIMEText\r
-from email.mime.image import MIMEImage\r
-\r
-from django.core.mail import DNS_NAME\r
-from smtplib import SMTP\r
-import email.Charset\r
-from forum import settings\r
-from django.template import loader, Context, Template\r
-from forum.utils.html import sanitize_html\r
-from forum.context import application_settings\r
-from forum.utils.html2text import HTML2Text\r
-from threading import Thread\r
-\r
-def send_msg_list(msgs, sender=None):\r
- if len(msgs):\r
- connection = SMTP(str(settings.EMAIL_HOST), str(settings.EMAIL_PORT),\r
- local_hostname=DNS_NAME.get_fqdn())\r
-\r
- try:\r
- if (bool(settings.EMAIL_USE_TLS)):\r
- connection.ehlo()\r
- connection.starttls()\r
- connection.ehlo()\r
-\r
- if settings.EMAIL_HOST_USER and settings.EMAIL_HOST_PASSWORD:\r
- connection.login(str(settings.EMAIL_HOST_USER), str(settings.EMAIL_HOST_PASSWORD))\r
-\r
- if sender is None:\r
- sender = str(settings.DEFAULT_FROM_EMAIL)\r
-\r
- for email, msg in msgs:\r
- try:\r
- connection.sendmail(sender, [email], msg)\r
- except Exception, e:\r
- pass\r
- try:\r
- connection.quit()\r
- except socket.sslerror:\r
- connection.close()\r
- except Exception, e:\r
- pass\r
-\r
-def html2text(s, ignore_tags=(), indent_width=4, page_width=80):\r
- ignore_tags = [t.lower() for t in ignore_tags]\r
- parser = HTML2Text(ignore_tags, indent_width, page_width)\r
- parser.feed(s)\r
- parser.close()\r
- parser.generate()\r
- return parser.result\r
-\r
-def named(data):\r
- if isinstance(data, (tuple, list)) and len(data) == 2:\r
- return '%s <%s>' % data\r
-\r
- return str(data)\r
-\r
-def create_msg(subject, sender, recipient, html, text, images):\r
- msgRoot = MIMEMultipart('related')\r
- msgRoot['Subject'] = subject\r
- msgRoot['From'] = named(sender)\r
- msgRoot['To'] = named(recipient)\r
- msgRoot.preamble = 'This is a multi-part message from %s.' % unicode(settings.APP_SHORT_NAME).encode('utf8')\r
-\r
- msgAlternative = MIMEMultipart('alternative')\r
- msgRoot.attach(msgAlternative)\r
-\r
- msgAlternative.attach(MIMEText(text, _charset='utf-8'))\r
- msgAlternative.attach(MIMEText(html, 'html', _charset='utf-8'))\r
-\r
- for img in images:\r
- try:\r
- fp = open(img[0], 'rb')\r
- msgImage = MIMEImage(fp.read())\r
- fp.close()\r
- msgImage.add_header('Content-ID', '<'+img[1]+'>')\r
- msgRoot.attach(msgImage)\r
- except:\r
- pass\r
-\r
- return msgRoot.as_string()\r
-\r
-def send_email(subject, recipients, template, context={}, sender=None, images=[], threaded=True):\r
- if sender is None:\r
- sender = (unicode(settings.APP_SHORT_NAME), unicode(settings.DEFAULT_FROM_EMAIL))\r
-\r
- if not len(images):\r
- images = [(os.path.join(str(settings.UPFILES_FOLDER), os.path.basename(str(settings.APP_LOGO))), 'logo')]\r
-\r
- context.update(application_settings(None))\r
- html_body = loader.get_template(template).render(Context(context))\r
- txt_body = html2text(html_body)\r
-\r
- if isinstance(recipients, str):\r
- recipients = [recipients]\r
-\r
- msgs = []\r
-\r
- for recipient in recipients:\r
- if isinstance(recipient, str):\r
- recipient_data = ('recipient', recipient)\r
- recipient_context = None\r
- elif isinstance(recipient, (list, tuple)) and len(recipient) == 2:\r
- name, email = recipient\r
- recipient_data = (name, email)\r
- recipient_context = None\r
- elif isinstance(recipient, (list, tuple)) and len(recipient) == 3:\r
- name, email, recipient_context = recipient\r
- recipient_data = (name, email)\r
- else:\r
- raise Exception('bad argument for recipients')\r
-\r
- if recipient_context is not None:\r
- recipient_context = Context(recipient_context)\r
- msg_html = Template(html_body).render(recipient_context)\r
- msg_txt = Template(txt_body).render(recipient_context)\r
- else:\r
- msg_html = html_body\r
- msg_txt = txt_body\r
-\r
- msg = create_msg(subject, sender, recipient_data, msg_html, msg_txt, images)\r
- msgs.append((email, msg))\r
-\r
- if threaded:\r
- thread = Thread(target=send_msg_list, args=[msgs])\r
- thread.setDaemon(True)\r
- thread.start()\r
- else:\r
- send_msg_list(msgs)\r
-\r
-\r
-def send_template_email(recipients, template, context):\r
- t = loader.get_template(template)\r
- context.update(dict(recipients=recipients, settings=settings))\r
- t.render(Context(context))\r
-\r
-def create_and_send_mail_messages(messages):\r
- sender = '%s <%s>' % (unicode(settings.APP_SHORT_NAME), unicode(settings.DEFAULT_FROM_EMAIL))\r
-\r
- connection = SMTP(str(settings.EMAIL_HOST), str(settings.EMAIL_PORT),\r
- local_hostname=DNS_NAME.get_fqdn())\r
-\r
- try:\r
- if (bool(settings.EMAIL_USE_TLS)):\r
- connection.ehlo()\r
- connection.starttls()\r
- connection.ehlo()\r
-\r
- if settings.EMAIL_HOST_USER and settings.EMAIL_HOST_PASSWORD:\r
- connection.login(str(settings.EMAIL_HOST_USER), str(settings.EMAIL_HOST_PASSWORD))\r
-\r
- if sender is None:\r
- sender = str(settings.DEFAULT_FROM_EMAIL)\r
-\r
- for recipient, subject, html, text, media in messages:\r
- msgRoot = MIMEMultipart('related')\r
- msgRoot['Subject'] = subject\r
- msgRoot['From'] = sender\r
- msgRoot['To'] = '%s <%s>' % (recipient.username, recipient.email)\r
- msgRoot.preamble = 'This is a multi-part message from %s.' % unicode(settings.APP_SHORT_NAME).encode('utf8')\r
-\r
- msgAlternative = MIMEMultipart('alternative')\r
- msgRoot.attach(msgAlternative)\r
-\r
- msgAlternative.attach(MIMEText(text, _charset='utf-8'))\r
- msgAlternative.attach(MIMEText(html, 'html', _charset='utf-8'))\r
-\r
- for alias, location in media.items():\r
- fp = open(location, 'rb')\r
- msgImage = MIMEImage(fp.read())\r
- fp.close()\r
- msgImage.add_header('Content-ID', '<'+alias+'>')\r
- msgRoot.attach(msgImage)\r
-\r
- try:\r
- connection.sendmail(sender, [recipient.email], msgRoot.as_string())\r
- except Exception, e:\r
- pass\r
-\r
- try:\r
- connection.quit()\r
- except socket.sslerror:\r
- connection.close()\r
- except Exception, e:\r
- print e\r
+import email
+import socket
+import os
+
+from email.mime.multipart import MIMEMultipart
+from email.mime.text import MIMEText
+from email.mime.image import MIMEImage
+
+from django.core.mail import DNS_NAME
+from smtplib import SMTP
+import email.Charset
+from forum import settings
+from django.template import loader, Context, Template
+from forum.utils.html import sanitize_html
+from forum.context import application_settings
+from forum.utils.html2text import HTML2Text
+from threading import Thread
+
+def send_msg_list(msgs, sender=None):
+ if len(msgs):
+ connection = SMTP(str(settings.EMAIL_HOST), str(settings.EMAIL_PORT),
+ local_hostname=DNS_NAME.get_fqdn())
+
+ try:
+ if (bool(settings.EMAIL_USE_TLS)):
+ connection.ehlo()
+ connection.starttls()
+ connection.ehlo()
+
+ if settings.EMAIL_HOST_USER and settings.EMAIL_HOST_PASSWORD:
+ connection.login(str(settings.EMAIL_HOST_USER), str(settings.EMAIL_HOST_PASSWORD))
+
+ if sender is None:
+ sender = str(settings.DEFAULT_FROM_EMAIL)
+
+ for email, msg in msgs:
+ try:
+ connection.sendmail(sender, [email], msg)
+ except Exception, e:
+ pass
+ try:
+ connection.quit()
+ except socket.sslerror:
+ connection.close()
+ except Exception, e:
+ pass
+
+def html2text(s, ignore_tags=(), indent_width=4, page_width=80):
+ ignore_tags = [t.lower() for t in ignore_tags]
+ parser = HTML2Text(ignore_tags, indent_width, page_width)
+ parser.feed(s)
+ parser.close()
+ parser.generate()
+ return parser.result
+
+def named(data):
+ if isinstance(data, (tuple, list)) and len(data) == 2:
+ return '%s <%s>' % data
+
+ return str(data)
+
+def create_msg(subject, sender, recipient, html, text, images):
+ msgRoot = MIMEMultipart('related')
+ msgRoot['Subject'] = subject
+ msgRoot['From'] = named(sender)
+ msgRoot['To'] = named(recipient)
+ msgRoot.preamble = 'This is a multi-part message from %s.' % unicode(settings.APP_SHORT_NAME).encode('utf8')
+
+ msgAlternative = MIMEMultipart('alternative')
+ msgRoot.attach(msgAlternative)
+
+ msgAlternative.attach(MIMEText(text, _charset='utf-8'))
+ msgAlternative.attach(MIMEText(html, 'html', _charset='utf-8'))
+
+ for img in images:
+ try:
+ fp = open(img[0], 'rb')
+ msgImage = MIMEImage(fp.read())
+ fp.close()
+ msgImage.add_header('Content-ID', '<'+img[1]+'>')
+ msgRoot.attach(msgImage)
+ except:
+ pass
+
+ return msgRoot.as_string()
+
+def send_email(subject, recipients, template, context={}, sender=None, images=[], threaded=True):
+ if sender is None:
+ sender = (unicode(settings.APP_SHORT_NAME), unicode(settings.DEFAULT_FROM_EMAIL))
+
+ if not len(images):
+ images = [(os.path.join(str(settings.UPFILES_FOLDER), os.path.basename(str(settings.APP_LOGO))), 'logo')]
+
+ context.update(application_settings(None))
+ html_body = loader.get_template(template).render(Context(context))
+ txt_body = html2text(html_body)
+
+ if isinstance(recipients, str):
+ recipients = [recipients]
+
+ msgs = []
+
+ for recipient in recipients:
+ if isinstance(recipient, str):
+ recipient_data = ('recipient', recipient)
+ recipient_context = None
+ elif isinstance(recipient, (list, tuple)) and len(recipient) == 2:
+ name, email = recipient
+ recipient_data = (name, email)
+ recipient_context = None
+ elif isinstance(recipient, (list, tuple)) and len(recipient) == 3:
+ name, email, recipient_context = recipient
+ recipient_data = (name, email)
+ else:
+ raise Exception('bad argument for recipients')
+
+ if recipient_context is not None:
+ recipient_context = Context(recipient_context)
+ msg_html = Template(html_body).render(recipient_context)
+ msg_txt = Template(txt_body).render(recipient_context)
+ else:
+ msg_html = html_body
+ msg_txt = txt_body
+
+ msg = create_msg(subject, sender, recipient_data, msg_html, msg_txt, images)
+ msgs.append((email, msg))
+
+ if threaded:
+ thread = Thread(target=send_msg_list, args=[msgs])
+ thread.setDaemon(True)
+ thread.start()
+ else:
+ send_msg_list(msgs)
+
+
+def send_template_email(recipients, template, context):
+ t = loader.get_template(template)
+ context.update(dict(recipients=recipients, settings=settings))
+ t.render(Context(context))
+
+def create_and_send_mail_messages(messages):
+ sender = '%s <%s>' % (unicode(settings.APP_SHORT_NAME), unicode(settings.DEFAULT_FROM_EMAIL))
+
+ connection = SMTP(str(settings.EMAIL_HOST), str(settings.EMAIL_PORT),
+ local_hostname=DNS_NAME.get_fqdn())
+
+ try:
+ if (bool(settings.EMAIL_USE_TLS)):
+ connection.ehlo()
+ connection.starttls()
+ connection.ehlo()
+
+ if settings.EMAIL_HOST_USER and settings.EMAIL_HOST_PASSWORD:
+ connection.login(str(settings.EMAIL_HOST_USER), str(settings.EMAIL_HOST_PASSWORD))
+
+ if sender is None:
+ sender = str(settings.DEFAULT_FROM_EMAIL)
+
+ for recipient, subject, html, text, media in messages:
+ msgRoot = MIMEMultipart('related')
+ msgRoot.set_charset('utf-8')
+ msgRoot['Subject'] = subject
+ msgRoot['From'] = sender
+ msgRoot['To'] = '%s <%s>' % (recipient.username, recipient.email)
+ msgRoot.preamble = 'This is a multi-part message from %s.' % unicode(settings.APP_SHORT_NAME).encode('utf8')
+
+ msgAlternative = MIMEMultipart('alternative')
+ msgRoot.attach(msgAlternative)
+
+ msgAlternative.attach(MIMEText(text))
+ msgAlternative.attach(MIMEText(html, 'html'))
+
+ for alias, location in media.items():
+ fp = open(location, 'rb')
+ msgImage = MIMEImage(fp.read())
+ fp.close()
+ msgImage.add_header('Content-ID', '<'+alias+'>')
+ msgRoot.attach(msgImage)
+
+ try:
+ connection.sendmail(sender, [recipient.email], msgRoot.as_string())
+ except Exception, e:
+ pass
+
+ try:
+ connection.quit()
+ except socket.sslerror:
+ connection.close()
+ except:
+ pass