from base import *\r
+import re\r
from tag import Tag\r
\r
import markdown\r
\r
def tagname_list(self):\r
if self.tagnames:\r
- return [name for name in self.tagnames.split(u' ')]\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([unicode(tag) for tag in self.tagname_list()])\r
+ return u','.join([tag for tag in self.tagname_list()])\r
\r
class Meta:\r
abstract = True\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 = super(NodeManager, self).get_query_set()\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(self, *args, **kwargs):\r
- node = super(NodeManager, self).get(*args, **kwargs)\r
- cls = NodeMetaClass.types.get(node.node_type, None)\r
-\r
- if cls and node.__class__ is not cls:\r
- return node.leaf\r
- return node\r
-\r
def get_for_types(self, types, *args, **kwargs):\r
kwargs['node_type__in'] = [t.get_type() for t in types]\r
return self.get(*args, **kwargs)\r
\r
+ 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
added_at = models.DateTimeField(default=datetime.datetime.now)\r
score = models.IntegerField(default=0)\r
\r
- deleted = models.ForeignKey('Action', null=True, unique=True, related_name="deleted_node")\r
- in_moderation = models.ForeignKey('Action', null=True, unique=True, related_name="moderated_node")\r
+ 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
\r
extra_ref = models.ForeignKey('Node', null=True)\r
extra_count = models.IntegerField(default=0)\r
- extra_action = models.ForeignKey('Action', null=True, related_name="extra_node")\r
- \r
+\r
marked = models.BooleanField(default=False)\r
- wiki = models.BooleanField(default=False)\r
\r
comment_count = DenormalizedField("children", node_type="comment", canceled=False)\r
flag_count = DenormalizedField("flags")\r
\r
@classmethod\r
def cache_key(cls, pk):\r
- return '%s.node:%s' % (settings.APP_URL, pk)\r
+ return '%s:node:%s' % (settings.APP_URL, pk)\r
\r
@classmethod\r
def get_type(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.leaf\r
+ return self\r
\r
- return self.abs_parent.leaf\r
+ return self.abs_parent\r
\r
@property\r
def summary(self):\r
\r
self.save()\r
\r
- def get_tag_list_if_changed(self):\r
+ def _list_changes_in_tags(self):\r
dirty = self.get_dirty_fields()\r
- active_user = self.last_edited and self.last_edited.by or self.author\r
\r
- if 'tagnames' in dirty:\r
- new_tags = self.tagname_list()\r
- old_tags = dirty['tagnames']\r
-\r
- if old_tags is None or not old_tags:\r
- old_tags = []\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 = [name for name in dirty['tagnames'].split(u' ')]\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
- tag_list = []\r
+ def _process_changes_in_tags(self):\r
+ tag_changes = self._list_changes_in_tags()\r
\r
- for name in new_tags:\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=active_user or self.author)\r
+ tag = Tag.objects.create(name=name, created_by=self._last_active_user())\r
\r
- tag_list.append(tag)\r
-\r
- if not name in old_tags:\r
- tag.used_count = tag.used_count + 1\r
- if tag.deleted:\r
- tag.unmark_deleted()\r
+ if not self.nis.deleted:\r
+ tag.used_count = models.F('used_count') + 1\r
tag.save()\r
\r
- for name in [n for n in old_tags if not n in new_tags]:\r
- tag = Tag.objects.get(name=name)\r
- tag.used_count = tag.used_count - 1\r
- if tag.used_count == 0:\r
- tag.mark_deleted(active_user)\r
- tag.save()\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 tag_list\r
+ return True\r
\r
- return None\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
\r
if self.parent_id and not self.abs_parent_id:\r
self.abs_parent = self.parent.absolute_parent\r
- \r
- tags = self.get_tag_list_if_changed()\r
+\r
super(Node, self).save(*args, **kwargs)\r
- if tags is not None: self.tags = tags\r
+ if tags_changed: self.tags = list(Tag.objects.filter(name__in=self.tagname_list()))\r
\r
class Meta:\r
app_label = 'forum'\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