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