]> git.openstreetmap.org Git - osqa.git/blob - forum/models/node.py
Remove old method. Proably causing model validation errors?
[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.translation import ugettext as _\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 self.as_markdown()\r
23 \r
24     def as_markdown(self, *extensions):\r
25         return mark_safe(sanitize_html(markdown.markdown(self.body, extensions=extensions)))\r
26 \r
27     @property\r
28     def headline(self):\r
29         return self.title\r
30 \r
31     def tagname_list(self):\r
32         if self.tagnames:\r
33             return [name for name in self.tagnames.split(u' ')]\r
34         else:\r
35             return []\r
36 \r
37     def tagname_meta_generator(self):\r
38         return u','.join([unicode(tag) for tag in self.tagname_list()])\r
39 \r
40     class Meta:\r
41         abstract = True\r
42         app_label = 'forum'\r
43 \r
44 class NodeMetaClass(BaseMetaClass):\r
45     types = {}\r
46 \r
47     def __new__(cls, *args, **kwargs):\r
48         new_cls = super(NodeMetaClass, cls).__new__(cls, *args, **kwargs)\r
49 \r
50         if not new_cls._meta.abstract and new_cls.__name__ is not 'Node':\r
51             NodeMetaClass.types[new_cls.get_type()] = new_cls\r
52 \r
53         return new_cls\r
54 \r
55     @classmethod\r
56     def setup_relations(cls):\r
57         for node_cls in NodeMetaClass.types.values():\r
58             NodeMetaClass.setup_relation(node_cls)        \r
59 \r
60     @classmethod\r
61     def setup_relation(cls, node_cls):\r
62         name = node_cls.__name__.lower()\r
63 \r
64         def children(self):\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 class NodeManager(CachedManager):\r
81     use_for_related_fields = True\r
82 \r
83     def get_query_set(self):\r
84         qs = super(NodeManager, self).get_query_set()\r
85 \r
86         if self.model is not Node:\r
87             return qs.filter(node_type=self.model.get_type())\r
88         else:\r
89             return qs\r
90 \r
91     def get(self, *args, **kwargs):\r
92         node = super(NodeManager, self).get(*args, **kwargs)\r
93         cls = NodeMetaClass.types.get(node.node_type, None)\r
94 \r
95         if cls and node.__class__ is not cls:\r
96             return node.leaf\r
97         return node\r
98 \r
99     def get_for_types(self, types, *args, **kwargs):\r
100         kwargs['node_type__in'] = [t.get_type() for t in types]\r
101         return self.get(*args, **kwargs)\r
102 \r
103 \r
104 class Node(BaseModel, NodeContent):\r
105     __metaclass__ = NodeMetaClass\r
106 \r
107     node_type            = models.CharField(max_length=16, default='node')\r
108     parent               = models.ForeignKey('Node', related_name='children', null=True)\r
109     abs_parent           = models.ForeignKey('Node', related_name='all_children', null=True)\r
110 \r
111     added_at             = models.DateTimeField(default=datetime.datetime.now)\r
112     score                 = models.IntegerField(default=0)\r
113 \r
114     deleted               = models.ForeignKey('Action', null=True, unique=True, related_name="deleted_node")\r
115     in_moderation         = models.ForeignKey('Action', null=True, unique=True, related_name="moderated_node")\r
116     last_edited           = models.ForeignKey('Action', null=True, unique=True, related_name="edited_node")\r
117 \r
118     last_activity_by       = models.ForeignKey(User, null=True)\r
119     last_activity_at       = models.DateTimeField(null=True, blank=True)\r
120 \r
121     tags                 = models.ManyToManyField('Tag', related_name='%(class)ss')\r
122     active_revision       = models.OneToOneField('NodeRevision', related_name='active', null=True)\r
123 \r
124     extra_ref = models.ForeignKey('Node', null=True)\r
125     extra_count = models.IntegerField(default=0)\r
126     extra_action = models.ForeignKey('Action', null=True, related_name="extra_node")\r
127     \r
128     marked = models.BooleanField(default=False)\r
129     wiki = models.BooleanField(default=False)\r
130 \r
131     comment_count = DenormalizedField("children", node_type="comment", canceled=False)\r
132     flag_count = DenormalizedField("flags")\r
133 \r
134     friendly_name = _("post")\r
135 \r
136     objects = NodeManager()\r
137 \r
138     @classmethod\r
139     def cache_key(cls, pk):\r
140         return '%s.node:%s' % (settings.APP_URL, pk)\r
141 \r
142     @classmethod\r
143     def get_type(cls):\r
144         return cls.__name__.lower()\r
145 \r
146     @property\r
147     def leaf(self):\r
148         leaf_cls = NodeMetaClass.types.get(self.node_type, None)\r
149 \r
150         if leaf_cls is None:\r
151             return self\r
152 \r
153         leaf = leaf_cls()\r
154         leaf.__dict__ = self.__dict__\r
155         return leaf\r
156 \r
157     @property    \r
158     def absolute_parent(self):\r
159         if not self.abs_parent_id:\r
160             return self.leaf\r
161 \r
162         return self.abs_parent.leaf\r
163 \r
164     @property\r
165     def summary(self):\r
166         return strip_tags(self.html)[:300]\r
167 \r
168     @models.permalink\r
169     def get_revisions_url(self):\r
170         return ('revisions', (), {'id': self.id})\r
171 \r
172     def update_last_activity(self, user, save=False):\r
173         self.last_activity_by = user\r
174         self.last_activity_at = datetime.datetime.now()\r
175 \r
176         if self.parent:\r
177             self.parent.update_last_activity(user, save=True)\r
178 \r
179         if save:\r
180             self.save()\r
181 \r
182     def _create_revision(self, user, number, **kwargs):\r
183         revision = NodeRevision(author=user, revision=number, node=self, **kwargs)\r
184         revision.save()\r
185         return revision\r
186 \r
187     def create_revision(self, user, **kwargs):\r
188         number = self.revisions.aggregate(last=models.Max('revision'))['last'] + 1\r
189         revision = self._create_revision(user, number, **kwargs)\r
190         self.activate_revision(user, revision)\r
191         return revision\r
192 \r
193     def activate_revision(self, user, revision):\r
194         self.title = revision.title\r
195         self.tagnames = revision.tagnames\r
196         self.body = revision.body\r
197 \r
198         self.active_revision = revision\r
199         self.update_last_activity(user)\r
200 \r
201         self.save()\r
202 \r
203     def get_tag_list_if_changed(self):\r
204         dirty = self.get_dirty_fields()\r
205         active_user = self.last_edited and self.last_edited.by or self.author\r
206 \r
207         if 'tagnames' in dirty:\r
208             new_tags = self.tagname_list()\r
209             old_tags = dirty['tagnames']\r
210 \r
211             if old_tags is None or not old_tags:\r
212                 old_tags = []\r
213             else:\r
214                 old_tags = [name for name in dirty['tagnames'].split(u' ')]\r
215 \r
216             tag_list = []\r
217 \r
218             for name in new_tags:\r
219                 try:\r
220                     tag = Tag.objects.get(name=name)\r
221                 except:\r
222                     tag = Tag.objects.create(name=name, created_by=active_user or self.author)\r
223 \r
224                 tag_list.append(tag)\r
225 \r
226                 if not name in old_tags:\r
227                     tag.used_count = tag.used_count + 1\r
228                     if tag.deleted:\r
229                         tag.unmark_deleted()\r
230                     tag.save()\r
231 \r
232             for name in [n for n in old_tags if not n in new_tags]:\r
233                 tag = Tag.objects.get(name=name)\r
234                 tag.used_count = tag.used_count - 1\r
235                 if tag.used_count == 0:\r
236                     tag.mark_deleted(active_user)\r
237                 tag.save()\r
238 \r
239             return tag_list\r
240 \r
241         return None\r
242 \r
243     def save(self, *args, **kwargs):\r
244         if not self.id:\r
245             self.node_type = self.get_type()\r
246             super(BaseModel, self).save(*args, **kwargs)\r
247             self.active_revision = self._create_revision(self.author, 1, title=self.title, tagnames=self.tagnames, body=self.body)\r
248             self.update_last_activity(self.author)\r
249 \r
250         if self.parent_id and not self.abs_parent_id:\r
251             self.abs_parent = self.parent.absolute_parent\r
252         \r
253         tags = self.get_tag_list_if_changed()\r
254         super(Node, self).save(*args, **kwargs)\r
255         if tags is not None: self.tags = tags\r
256 \r
257     class Meta:\r
258         app_label = 'forum'\r
259 \r
260 \r
261 class NodeRevision(BaseModel, NodeContent):\r
262     node       = models.ForeignKey(Node, related_name='revisions')\r
263     summary    = models.CharField(max_length=300)\r
264     revision   = models.PositiveIntegerField()\r
265     revised_at = models.DateTimeField(default=datetime.datetime.now)\r
266 \r
267     class Meta:\r
268         unique_together = ('node', 'revision')\r
269         app_label = 'forum'\r
270 \r
271 \r