404.2.24
by franku
added the old threadedcomments app as wildelands app |
1 |
from django.db import models |
2 |
from django.contrib.contenttypes.models import ContentType |
|
3 |
from django.contrib.contenttypes.fields import GenericForeignKey |
|
4 |
from django.contrib.auth.models import User |
|
5 |
from datetime import datetime |
|
6 |
from django.db.models import Q |
|
7 |
from django.utils.translation import ugettext_lazy as _ |
|
8 |
from django.conf import settings |
|
532.1.2
by franku
replaced renamed functions and __unicode__ with __str__ |
9 |
from django.utils.encoding import force_text |
404.2.24
by franku
added the old threadedcomments app as wildelands app |
10 |
|
438.1.6
by franku
run the script |
11 |
DEFAULT_MAX_COMMENT_LENGTH = getattr( |
12 |
settings, 'DEFAULT_MAX_COMMENT_LENGTH', 1000) |
|
404.2.24
by franku
added the old threadedcomments app as wildelands app |
13 |
DEFAULT_MAX_COMMENT_DEPTH = getattr(settings, 'DEFAULT_MAX_COMMENT_DEPTH', 8) |
14 |
||
15 |
MARKDOWN = 1 |
|
16 |
TEXTILE = 2 |
|
17 |
REST = 3 |
|
18 |
PLAINTEXT = 5 |
|
19 |
MARKUP_CHOICES = ( |
|
438.1.6
by franku
run the script |
20 |
(MARKDOWN, _('markdown')), |
21 |
(TEXTILE, _('textile')), |
|
22 |
(REST, _('restructuredtext')), |
|
23 |
(PLAINTEXT, _('plaintext')), |
|
404.2.24
by franku
added the old threadedcomments app as wildelands app |
24 |
)
|
25 |
||
26 |
DEFAULT_MARKUP = getattr(settings, 'DEFAULT_MARKUP', PLAINTEXT) |
|
27 |
||
438.1.6
by franku
run the script |
28 |
|
404.2.24
by franku
added the old threadedcomments app as wildelands app |
29 |
def dfs(node, all_nodes, depth): |
30 |
"""
|
|
31 |
Performs a recursive depth-first search starting at ``node``. This function
|
|
32 |
also annotates an attribute, ``depth``, which is an integer that represents
|
|
33 |
how deeply nested this node is away from the original object.
|
|
34 |
"""
|
|
35 |
node.depth = depth |
|
438.1.6
by franku
run the script |
36 |
to_return = [node, ] |
404.2.24
by franku
added the old threadedcomments app as wildelands app |
37 |
for subnode in all_nodes: |
38 |
if subnode.parent and subnode.parent.id == node.id: |
|
438.1.6
by franku
run the script |
39 |
to_return.extend(dfs(subnode, all_nodes, depth + 1)) |
404.2.24
by franku
added the old threadedcomments app as wildelands app |
40 |
return to_return |
41 |
||
438.1.6
by franku
run the script |
42 |
|
404.2.24
by franku
added the old threadedcomments app as wildelands app |
43 |
class ThreadedCommentManager(models.Manager): |
438.1.6
by franku
run the script |
44 |
"""A ``Manager`` which will be attached to each comment model.
|
45 |
||
46 |
It helps to facilitate the retrieval of comments in tree form and
|
|
47 |
also has utility methods for creating and retrieving objects related
|
|
48 |
to a specific content object.
|
|
49 |
||
50 |
"""
|
|
51 |
||
404.2.24
by franku
added the old threadedcomments app as wildelands app |
52 |
def get_tree(self, content_object, root=None): |
53 |
"""
|
|
54 |
Runs a depth-first search on all comments related to the given content_object.
|
|
55 |
This depth-first search adds a ``depth`` attribute to the comment which
|
|
56 |
signifies how how deeply nested the comment is away from the original object.
|
|
438.1.6
by franku
run the script |
57 |
|
404.2.24
by franku
added the old threadedcomments app as wildelands app |
58 |
If root is specified, it will start the tree from that comment's ID.
|
438.1.6
by franku
run the script |
59 |
|
404.2.24
by franku
added the old threadedcomments app as wildelands app |
60 |
Ideally, one would use this ``depth`` attribute in the display of the comment to
|
61 |
offset that comment by some specified length.
|
|
438.1.6
by franku
run the script |
62 |
|
404.2.24
by franku
added the old threadedcomments app as wildelands app |
63 |
The following is a (VERY) simple example of how the depth property might be used in a template:
|
438.1.6
by franku
run the script |
64 |
|
404.2.24
by franku
added the old threadedcomments app as wildelands app |
65 |
{% for comment in comment_tree %}
|
66 |
<p style="margin-left: {{ comment.depth }}em">{{ comment.comment }}</p>
|
|
67 |
{% endfor %}
|
|
68 |
"""
|
|
69 |
content_type = ContentType.objects.get_for_model(content_object) |
|
70 |
children = list(self.get_query_set().filter( |
|
438.1.6
by franku
run the script |
71 |
content_type=content_type, |
72 |
object_id=getattr(content_object, 'pk', |
|
73 |
getattr(content_object, 'id')), |
|
404.2.24
by franku
added the old threadedcomments app as wildelands app |
74 |
).select_related().order_by('date_submitted')) |
75 |
to_return = [] |
|
76 |
if root: |
|
77 |
if isinstance(root, int): |
|
78 |
root_id = root |
|
79 |
else: |
|
80 |
root_id = root.id |
|
81 |
to_return = [c for c in children if c.id == root_id] |
|
82 |
if to_return: |
|
83 |
to_return[0].depth = 0 |
|
84 |
for child in children: |
|
85 |
if child.parent_id == root_id: |
|
86 |
to_return.extend(dfs(child, children, 1)) |
|
87 |
else: |
|
88 |
for child in children: |
|
89 |
if not child.parent: |
|
90 |
to_return.extend(dfs(child, children, 0)) |
|
91 |
return to_return |
|
92 |
||
93 |
def _generate_object_kwarg_dict(self, content_object, **kwargs): |
|
438.1.6
by franku
run the script |
94 |
"""Generates the most comment keyword arguments for a given
|
95 |
``content_object``."""
|
|
96 |
kwargs['content_type'] = ContentType.objects.get_for_model( |
|
97 |
content_object) |
|
98 |
kwargs['object_id'] = getattr( |
|
99 |
content_object, 'pk', getattr(content_object, 'id')) |
|
404.2.24
by franku
added the old threadedcomments app as wildelands app |
100 |
return kwargs |
101 |
||
102 |
def create_for_object(self, content_object, **kwargs): |
|
438.1.6
by franku
run the script |
103 |
"""A simple wrapper around ``create`` for a given
|
104 |
``content_object``."""
|
|
404.2.24
by franku
added the old threadedcomments app as wildelands app |
105 |
return self.create(**self._generate_object_kwarg_dict(content_object, **kwargs)) |
438.1.6
by franku
run the script |
106 |
|
404.2.24
by franku
added the old threadedcomments app as wildelands app |
107 |
def get_or_create_for_object(self, content_object, **kwargs): |
438.1.6
by franku
run the script |
108 |
"""A simple wrapper around ``get_or_create`` for a given
|
109 |
``content_object``."""
|
|
404.2.24
by franku
added the old threadedcomments app as wildelands app |
110 |
return self.get_or_create(**self._generate_object_kwarg_dict(content_object, **kwargs)) |
438.1.6
by franku
run the script |
111 |
|
404.2.24
by franku
added the old threadedcomments app as wildelands app |
112 |
def get_for_object(self, content_object, **kwargs): |
438.1.6
by franku
run the script |
113 |
"""A simple wrapper around ``get`` for a given ``content_object``."""
|
404.2.24
by franku
added the old threadedcomments app as wildelands app |
114 |
return self.get(**self._generate_object_kwarg_dict(content_object, **kwargs)) |
115 |
||
116 |
def all_for_object(self, content_object, **kwargs): |
|
438.1.6
by franku
run the script |
117 |
"""Prepopulates a QuerySet with all comments related to the given
|
118 |
``content_object``."""
|
|
404.2.24
by franku
added the old threadedcomments app as wildelands app |
119 |
return self.filter(**self._generate_object_kwarg_dict(content_object, **kwargs)) |
120 |
||
438.1.6
by franku
run the script |
121 |
|
404.2.24
by franku
added the old threadedcomments app as wildelands app |
122 |
class PublicThreadedCommentManager(ThreadedCommentManager): |
123 |
"""
|
|
124 |
A ``Manager`` which borrows all of the same methods from ``ThreadedCommentManager``,
|
|
125 |
but which also restricts the queryset to only the published methods
|
|
126 |
(in other words, ``is_public = True``).
|
|
127 |
"""
|
|
438.1.6
by franku
run the script |
128 |
|
404.2.24
by franku
added the old threadedcomments app as wildelands app |
129 |
def get_query_set(self): |
130 |
return super(ThreadedCommentManager, self).get_queryset().filter( |
|
438.1.6
by franku
run the script |
131 |
Q(is_public=True) | Q(is_approved=True) |
404.2.24
by franku
added the old threadedcomments app as wildelands app |
132 |
)
|
133 |
||
438.1.6
by franku
run the script |
134 |
|
404.2.24
by franku
added the old threadedcomments app as wildelands app |
135 |
class ThreadedComment(models.Model): |
438.1.6
by franku
run the script |
136 |
"""A threaded comment which must be associated with an instance of
|
137 |
``django.contrib.auth.models.User``. It is given its hierarchy by a
|
|
138 |
nullable relationship back on itself named ``parent``.
|
|
139 |
||
404.2.24
by franku
added the old threadedcomments app as wildelands app |
140 |
This ``ThreadedComment`` supports several kinds of markup languages,
|
141 |
including Textile, Markdown, and ReST.
|
|
438.1.6
by franku
run the script |
142 |
|
404.2.24
by franku
added the old threadedcomments app as wildelands app |
143 |
It also includes two Managers: ``objects``, which is the same as the normal
|
144 |
``objects`` Manager with a few added utility functions (see above), and
|
|
145 |
``public``, which has those same utility functions but limits the QuerySet to
|
|
146 |
only those values which are designated as public (``is_public=True``).
|
|
438.1.6
by franku
run the script |
147 |
|
404.2.24
by franku
added the old threadedcomments app as wildelands app |
148 |
"""
|
149 |
# Generic Foreign Key Fields
|
|
150 |
content_type = models.ForeignKey(ContentType) |
|
151 |
object_id = models.PositiveIntegerField(_('object ID')) |
|
152 |
content_object = GenericForeignKey() |
|
438.1.6
by franku
run the script |
153 |
|
404.2.24
by franku
added the old threadedcomments app as wildelands app |
154 |
# Hierarchy Field
|
438.1.6
by franku
run the script |
155 |
parent = models.ForeignKey( |
156 |
'self', null=True, blank=True, default=None, related_name='children') |
|
157 |
||
404.2.24
by franku
added the old threadedcomments app as wildelands app |
158 |
# User Field
|
159 |
user = models.ForeignKey(User) |
|
438.1.6
by franku
run the script |
160 |
|
404.2.24
by franku
added the old threadedcomments app as wildelands app |
161 |
# Date Fields
|
438.1.6
by franku
run the script |
162 |
date_submitted = models.DateTimeField( |
163 |
_('date/time submitted'), default=datetime.now) |
|
164 |
date_modified = models.DateTimeField( |
|
165 |
_('date/time modified'), default=datetime.now) |
|
166 |
date_approved = models.DateTimeField( |
|
167 |
_('date/time approved'), default=None, null=True, blank=True) |
|
168 |
||
404.2.24
by franku
added the old threadedcomments app as wildelands app |
169 |
# Meat n' Potatoes
|
170 |
comment = models.TextField(_('comment')) |
|
438.1.6
by franku
run the script |
171 |
markup = models.IntegerField( |
172 |
choices=MARKUP_CHOICES, default=DEFAULT_MARKUP, null=True, blank=True) |
|
173 |
||
404.2.24
by franku
added the old threadedcomments app as wildelands app |
174 |
# Status Fields
|
438.1.6
by franku
run the script |
175 |
is_public = models.BooleanField(_('is public'), default=True) |
176 |
is_approved = models.BooleanField(_('is approved'), default=False) |
|
177 |
||
404.2.24
by franku
added the old threadedcomments app as wildelands app |
178 |
objects = ThreadedCommentManager() |
179 |
public = PublicThreadedCommentManager() |
|
438.1.6
by franku
run the script |
180 |
|
532.1.2
by franku
replaced renamed functions and __unicode__ with __str__ |
181 |
def __str__(self): |
404.2.24
by franku
added the old threadedcomments app as wildelands app |
182 |
if len(self.comment) > 50: |
438.1.6
by franku
run the script |
183 |
return self.comment[:50] + '...' |
404.2.24
by franku
added the old threadedcomments app as wildelands app |
184 |
return self.comment[:50] |
438.1.6
by franku
run the script |
185 |
|
404.2.24
by franku
added the old threadedcomments app as wildelands app |
186 |
def save(self, **kwargs): |
187 |
if not self.markup: |
|
188 |
self.markup = DEFAULT_MARKUP |
|
189 |
self.date_modified = datetime.now() |
|
190 |
if not self.date_approved and self.is_approved: |
|
191 |
self.date_approved = datetime.now() |
|
192 |
super(ThreadedComment, self).save(**kwargs) |
|
438.1.6
by franku
run the script |
193 |
|
404.2.24
by franku
added the old threadedcomments app as wildelands app |
194 |
def get_content_object(self): |
438.1.6
by franku
run the script |
195 |
"""Wrapper around the GenericForeignKey due to compatibility reasons
|
196 |
and due to ``list_display`` limitations."""
|
|
404.2.24
by franku
added the old threadedcomments app as wildelands app |
197 |
return self.content_object |
438.1.6
by franku
run the script |
198 |
|
404.2.24
by franku
added the old threadedcomments app as wildelands app |
199 |
class Meta: |
200 |
ordering = ('-date_submitted',) |
|
438.1.6
by franku
run the script |
201 |
verbose_name = _('Threaded Comment') |
202 |
verbose_name_plural = _('Threaded Comments') |
|
203 |
get_latest_by = 'date_submitted' |