~widelands-dev/widelands-website/trunk

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'