]> git.openstreetmap.org Git - osqa.git/blob - forum/models/node.py
00cbbd0d71cac3a719feed84da895788f06157e1
[osqa.git] / forum / models / node.py
1 from base import *\r
2 from tag import Tag\r
3 \r
4 import markdown\r
5 from django.utils.safestring import mark_safe\r
6 from django.utils.html import strip_tags\r
7 from forum.utils.html import sanitize_html\r
8 \r
9 class NodeContent(models.Model):\r
10     title      = models.CharField(max_length=300)\r
11     tagnames   = models.CharField(max_length=125)\r
12     author     = models.ForeignKey(User, related_name='%(class)ss')\r
13     body       = models.TextField()\r
14 \r
15     @property\r
16     def user(self):\r
17         return self.author\r
18 \r
19     @property\r
20     def html(self):\r
21         return mark_safe(sanitize_html(markdown.markdown(self.body)))\r
22 \r
23     @property\r
24     def headline(self):\r
25         return self.title\r
26 \r
27     def tagname_list(self):\r
28         if self.tagnames:\r
29             return [name for name in self.tagnames.split(u' ')]\r
30         else:\r
31             return []\r
32 \r
33     def tagname_meta_generator(self):\r
34         return u','.join([unicode(tag) for tag in self.tagname_list()])\r
35 \r
36     class Meta:\r
37         abstract = True\r
38         app_label = 'forum'\r
39 \r
40 class NodeMetaClass(models.Model.__metaclass__):\r
41     types = {}\r
42 \r
43     def __new__(cls, *args, **kwargs):\r
44         new_cls = super(NodeMetaClass, cls).__new__(cls, *args, **kwargs)\r
45 \r
46         if not new_cls._meta.abstract and new_cls.__name__ is not 'Node':\r
47             NodeMetaClass.types[new_cls.__name__.lower()] = new_cls\r
48 \r
49         return new_cls\r
50 \r
51     @classmethod\r
52     def setup_relations(cls):\r
53         for node_cls in NodeMetaClass.types.values():\r
54             NodeMetaClass.setup_relation(node_cls)        \r
55 \r
56     @classmethod\r
57     def setup_relation(cls, node_cls):\r
58         name = node_cls.__name__.lower()\r
59 \r
60         def children(self):\r
61             if node_cls._meta.proxy:\r
62                 return node_cls.objects.filter(node_type=name, parent=self)\r
63             else:\r
64                 return node_cls.objects.filter(parent=self)\r
65 \r
66         def parent(self):\r
67             p = self.__dict__.get('_%s_cache' % name, None)\r
68 \r
69             if p is None and (self.parent is not None) and self.parent.node_type == name:\r
70                 p = self.parent.leaf\r
71                 self.__dict__['_%s_cache' % name] = p\r
72 \r
73             return p\r
74 \r
75         Node.add_to_class(name + 's', property(children))\r
76         Node.add_to_class(name, property(parent))\r
77 \r
78 \r
79 node_create = django.dispatch.Signal(providing_args=['instance'])\r
80 node_edit = django.dispatch.Signal(providing_args=['instance'])\r
81 \r
82 class Node(BaseModel, NodeContent, DeletableContent):\r
83     __metaclass__ = NodeMetaClass\r
84 \r
85     node_type            = models.CharField(max_length=16, default='node')\r
86     parent               = models.ForeignKey('Node', related_name='children', null=True)\r
87     abs_parent           = models.ForeignKey('Node', related_name='all_children', null=True)\r
88 \r
89     added_at             = models.DateTimeField(default=datetime.datetime.now)\r
90 \r
91     tags                 = models.ManyToManyField('Tag', related_name='%(class)ss')\r
92 \r
93     score                 = DenormalizedField(default=0)\r
94     vote_up_count         = DenormalizedField(default=0)\r
95     vote_down_count       = models.IntegerField(default=0)\r
96 \r
97     comment_count         = DenormalizedField(default=0)\r
98     offensive_flag_count  = DenormalizedField(default=0)\r
99 \r
100     last_edited_at        = models.DateTimeField(null=True, blank=True)\r
101     last_edited_by        = models.ForeignKey(User, null=True, blank=True, related_name='last_edited_%(class)ss')\r
102 \r
103     active_revision       = models.OneToOneField('NodeRevision', related_name='active', null=True)\r
104 \r
105     @property\r
106     def leaf(self):\r
107         return NodeMetaClass.types[self.node_type].objects.get(id=self.id)\r
108 \r
109     @property    \r
110     def absolute_parent(self):\r
111         if not self.abs_parent_id:\r
112             return self.leaf\r
113 \r
114         return self.abs_parent.leaf\r
115 \r
116     @property\r
117     def summary(self):\r
118         return strip_tags(self.html)[:300]\r
119 \r
120     def create_revision(self, user, **kwargs):\r
121         revision = NodeRevision(author=user, **kwargs)\r
122         \r
123         if not self.id:\r
124             self.author = user\r
125             self.save()\r
126             revision.revision = 1\r
127         else:\r
128             revision.revision = self.revisions.aggregate(last=models.Max('revision'))['last'] + 1\r
129 \r
130         revision.node_id = self.id\r
131         revision.save()\r
132         self.activate_revision(user, revision)\r
133 \r
134     def activate_revision(self, user, revision):\r
135         self.title = revision.title\r
136         self.tagnames = revision.tagnames\r
137         self.body = revision.body\r
138 \r
139         old_revision = self.active_revision\r
140         self.active_revision = revision\r
141 \r
142         if not old_revision:\r
143             signal = node_create\r
144         else:\r
145             self.last_edited_at = datetime.datetime.now()\r
146             self.last_edited_by = user\r
147             signal = node_edit\r
148 \r
149         self.save()\r
150         signal.send(sender=self.__class__, instance=self)\r
151 \r
152     def get_tag_list_if_changed(self):\r
153         dirty = self.get_dirty_fields()\r
154 \r
155         if 'tagnames' in dirty:\r
156             new_tags = self.tagname_list()\r
157             old_tags = dirty['tagnames']\r
158 \r
159             if old_tags is None or not old_tags:\r
160                 old_tags = []\r
161             else:\r
162                 old_tags = [name for name in dirty['tagnames'].split(u' ')]\r
163 \r
164             tag_list = []\r
165 \r
166             for name in new_tags:\r
167                 try:\r
168                     tag = Tag.objects.get(name=name)\r
169                 except:\r
170                     tag = Tag.objects.create(name=name, created_by=self.last_edited_by or self.author)\r
171 \r
172                 tag_list.append(tag)\r
173 \r
174                 if not name in old_tags:\r
175                     tag.used_count = tag.used_count + 1\r
176                     if tag.deleted:\r
177                         tag.unmark_deleted()\r
178                     tag.save()\r
179 \r
180             for name in [n for n in old_tags if not n in new_tags]:\r
181                 tag = Tag.objects.get(name=name)\r
182                 tag.used_count = tag.used_count - 1\r
183                 if tag.used_count == 0:\r
184                     tag.mark_deleted(self.last_edited_by or self.author)\r
185                 tag.save()\r
186 \r
187             return tag_list\r
188 \r
189         return None\r
190 \r
191     def save(self, *args, **kwargs):\r
192         if not self.id:\r
193             self.node_type = self.__class__.__name__.lower()\r
194 \r
195         if self.parent_id and not self.abs_parent_id:\r
196             self.abs_parent = self.parent.absolute_parent\r
197 \r
198         self.__dict__['score'] = self.__dict__['vote_up_count'] - self.__dict__['vote_down_count']\r
199             \r
200         tags = self.get_tag_list_if_changed()\r
201         super(Node, self).save(*args, **kwargs)\r
202         if tags is not None: self.tags = tags\r
203 \r
204     class Meta:\r
205         app_label = 'forum'\r
206 \r
207 \r
208 class NodeRevision(BaseModel, NodeContent):\r
209     node       = models.ForeignKey(Node, related_name='revisions')\r
210     summary    = models.CharField(max_length=300)\r
211     revision   = models.PositiveIntegerField()\r
212     revised_at = models.DateTimeField(default=datetime.datetime.now)\r
213 \r
214     class Meta:\r
215         unique_together = ('node', 'revision')\r
216         app_label = 'forum'\r
217 \r
218 \r
219 from user import ValidationHash\r
220 \r
221 class AnonymousNode(Node):\r
222     validation_hash = models.ForeignKey(Node, related_name='anonymous_content')\r
223     convertible_to = models.CharField(max_length=16, default='node')\r
224 \r
225     class Meta:\r
226         app_label = 'forum'