~widelands-dev/widelands-website/django_staticfiles

« back to all changes in this revision

Viewing changes to pybb/models.py

  • Committer: Holger Rapp
  • Date: 2009-02-21 18:24:02 UTC
  • Revision ID: sirver@kallisto.local-20090221182402-k3tuf5c4gjwslbjf
Main Page contains now the same informations as before

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
from datetime import datetime
2
 
from mainpage.templatetags.wl_markdown import do_wl_markdown
3
 
import os.path
4
 
import hashlib
5
 
 
6
 
from django.db import models
7
 
from django.contrib.auth.models import User
8
 
from django.urls import reverse
9
 
from django.utils.html import strip_tags
10
 
from django.utils.translation import ugettext_lazy as _
11
 
from django.conf import settings
12
 
 
13
 
from pybb.markups import mypostmarkup
14
 
from pybb.util import urlize, memoize_method, unescape
15
 
from pybb import settings as pybb_settings
16
 
 
17
 
from django.conf import settings
18
 
from notification.models import send
19
 
from django.contrib.auth.models import User
20
 
from check_input.models import SuspiciousInput
21
 
 
22
 
 
23
 
try:
24
 
    from notification import models as notification
25
 
    from django.db.models import signals
26
 
except ImportError:
27
 
    notification = None
28
 
 
29
 
MARKUP_CHOICES = (
30
 
    ('markdown', 'markdown'),
31
 
    ('bbcode', 'bbcode'),
32
 
)
33
 
 
34
 
 
35
 
class Category(models.Model):
36
 
    name = models.CharField(_('Name'), max_length=80)
37
 
    position = models.IntegerField(_('Position'), blank=True, default=0)
38
 
 
39
 
    class Meta:
40
 
        ordering = ['position']
41
 
        verbose_name = _('Category')
42
 
        verbose_name_plural = _('Categories')
43
 
 
44
 
    def __unicode__(self):
45
 
        return self.name
46
 
 
47
 
    def forum_count(self):
48
 
        return self.forums.all().count()
49
 
 
50
 
    def get_absolute_url(self):
51
 
        return reverse('pybb_category', args=[self.id])
52
 
 
53
 
    @property
54
 
    def topics(self):
55
 
        return Topic.objects.filter(forum__category=self).select_related()
56
 
 
57
 
    @property
58
 
    def posts(self):
59
 
        return Post.objects.filter(topic__forum__category=self).select_related()
60
 
 
61
 
 
62
 
class Forum(models.Model):
63
 
    category = models.ForeignKey(
64
 
        Category, related_name='forums', verbose_name=_('Category'))
65
 
    name = models.CharField(_('Name'), max_length=80)
66
 
    position = models.IntegerField(_('Position'), blank=True, default=0)
67
 
    description = models.TextField(_('Description'), blank=True, default='')
68
 
    moderators = models.ManyToManyField(
69
 
        User, blank=True, verbose_name=_('Moderators'))
70
 
    updated = models.DateTimeField(_('Updated'), null=True)
71
 
 
72
 
    class Meta:
73
 
        ordering = ['position']
74
 
        verbose_name = _('Forum')
75
 
        verbose_name_plural = _('Forums')
76
 
 
77
 
    def __unicode__(self):
78
 
        return self.name
79
 
 
80
 
    def topic_count(self):
81
 
        return self.topics.all().count()
82
 
 
83
 
    def get_absolute_url(self):
84
 
        return reverse('pybb_forum', args=[self.id])
85
 
 
86
 
    @property
87
 
    def posts(self):
88
 
        return Post.objects.filter(topic__forum=self).exclude(hidden=True).select_related()
89
 
 
90
 
    @property
91
 
    def post_count(self):
92
 
        return Post.objects.filter(topic__forum=self).exclude(hidden=True).count()
93
 
 
94
 
    @property
95
 
    def last_post(self):
96
 
        # This has better performance than using the posts manager hidden_topics
97
 
        # We search only for the last 10 topics
98
 
        topics = self.topics.order_by('-updated')[:10]
99
 
        for topic in topics:
100
 
            if topic.is_hidden:
101
 
                continue
102
 
            posts = topic.posts.exclude(hidden=True).order_by(
103
 
            '-created').select_related()
104
 
            break
105
 
 
106
 
        try:
107
 
            return posts[0]
108
 
        except IndexError:
109
 
            return None
110
 
 
111
 
 
112
 
class Topic(models.Model):
113
 
    forum = models.ForeignKey(
114
 
        Forum, related_name='topics', verbose_name=_('Forum'))
115
 
    name = models.CharField(_('Subject'), max_length=255)
116
 
    created = models.DateTimeField(_('Created'), null=True)
117
 
    updated = models.DateTimeField(_('Updated'), null=True)
118
 
    user = models.ForeignKey(User, verbose_name=_('User'))
119
 
    views = models.IntegerField(_('Views count'), blank=True, default=0)
120
 
    sticky = models.BooleanField(_('Sticky'), blank=True, default=False)
121
 
    closed = models.BooleanField(_('Closed'), blank=True, default=False)
122
 
    subscribers = models.ManyToManyField(
123
 
        User, related_name='subscriptions', verbose_name=_('Subscribers'), blank=True)
124
 
 
125
 
    class Meta:
126
 
        ordering = ['-updated']
127
 
        verbose_name = _('Topic')
128
 
        verbose_name_plural = _('Topics')
129
 
 
130
 
    def __unicode__(self):
131
 
        return self.name
132
 
 
133
 
    @property
134
 
    def head(self):
135
 
        try:
136
 
            return self.posts.all().order_by('created').select_related()[0]
137
 
        except:
138
 
            return None
139
 
 
140
 
    @property
141
 
    def last_post(self):
142
 
        return self.posts.exclude(hidden=True).order_by('-created').select_related()[0]
143
 
 
144
 
    @property
145
 
    def is_hidden(self):
146
 
        # If the first post of this topic is hidden, the topic is hidden
147
 
        try:
148
 
            return self.posts.first().hidden
149
 
        except:
150
 
            return False
151
 
 
152
 
    @property
153
 
    def post_count(self):
154
 
        return Post.objects.filter(topic=self).exclude(hidden=True).count()
155
 
 
156
 
    def get_absolute_url(self):
157
 
        return reverse('pybb_topic', args=[self.id])
158
 
 
159
 
    def save(self, *args, **kwargs):
160
 
        new = self.id is None
161
 
        if new:
162
 
            self.created = datetime.now()
163
 
        super(Topic, self).save(*args, **kwargs)
164
 
 
165
 
    def update_read(self, user):
166
 
        read, new = Read.objects.get_or_create(user=user, topic=self)
167
 
        if not new:
168
 
            read.time = datetime.now()
169
 
            read.save()
170
 
 
171
 
    # def has_unreads(self, user):
172
 
        # try:
173
 
            #read = Read.objects.get(user=user, topic=self)
174
 
        # except Read.DoesNotExist:
175
 
            # return True
176
 
        # else:
177
 
            # return self.updated > read.time
178
 
 
179
 
 
180
 
class RenderableItem(models.Model):
181
 
    """Base class for models that has markup, body, body_text and body_html
182
 
    fields."""
183
 
 
184
 
    class Meta:
185
 
        abstract = True
186
 
 
187
 
    def render(self):
188
 
        if self.markup == 'bbcode':
189
 
            self.body_html = mypostmarkup.markup(self.body, auto_urls=False)
190
 
        elif self.markup == 'markdown':
191
 
            self.body_html = unicode(do_wl_markdown(
192
 
                self.body, 'bleachit'))
193
 
        else:
194
 
            raise Exception('Invalid markup property: %s' % self.markup)
195
 
 
196
 
        # Remove tags which was generated with the markup processor
197
 
        text = strip_tags(self.body_html)
198
 
 
199
 
        # Unescape entities which was generated with the markup processor
200
 
        self.body_text = unescape(text)
201
 
 
202
 
        self.body_html = urlize(self.body_html)
203
 
 
204
 
 
205
 
class HiddenTopicsManager(models.Manager):
206
 
    """Find all hidden topics by posts.
207
 
 
208
 
    A whole topic is hidden, if the first post is hidden.
209
 
    This manager returns the hidden topics and can be used to filter them out
210
 
    like so:
211
 
 
212
 
    Post.objects.exclude(topic__in=Post.hidden_topics.all()).filter(...)
213
 
 
214
 
    Use this with caution, because it affects performance, see:
215
 
    https://docs.djangoproject.com/en/dev/ref/models/querysets/#in
216
 
    """
217
 
 
218
 
    def get_queryset(self, *args, **kwargs):
219
 
        qs = super(HiddenTopicsManager,
220
 
                   self).get_queryset().filter(hidden=True)
221
 
 
222
 
        hidden_topics = []
223
 
        try:
224
 
            for post in qs:
225
 
                if post.topic.is_hidden:
226
 
                    hidden_topics.append(post.topic)
227
 
            return hidden_topics
228
 
        except:
229
 
            return []
230
 
 
231
 
 
232
 
class Post(RenderableItem):
233
 
    topic = models.ForeignKey(
234
 
        Topic, related_name='posts', verbose_name=_('Topic'))
235
 
    user = models.ForeignKey(
236
 
        User, related_name='posts', verbose_name=_('User'))
237
 
    created = models.DateTimeField(_('Created'), blank=True)
238
 
    updated = models.DateTimeField(_('Updated'), blank=True, null=True)
239
 
    markup = models.CharField(_('Markup'), max_length=15,
240
 
                              default=pybb_settings.DEFAULT_MARKUP, choices=MARKUP_CHOICES)
241
 
    body = models.TextField(_('Message'))
242
 
    body_html = models.TextField(_('HTML version'))
243
 
    body_text = models.TextField(_('Text version'))
244
 
    hidden = models.BooleanField(_('Hidden'), blank=True, default=False)
245
 
 
246
 
    objects = models.Manager() # Normal manager 
247
 
    hidden_topics = HiddenTopicsManager() # Custom manager
248
 
 
249
 
    class Meta:
250
 
        ordering = ['created']
251
 
        verbose_name = _('Post')
252
 
        verbose_name_plural = _('Posts')
253
 
 
254
 
    def summary(self):
255
 
        LIMIT = 50
256
 
        tail = len(self.body) > LIMIT and '...' or ''
257
 
        return self.body[:LIMIT] + tail
258
 
 
259
 
    __unicode__ = summary
260
 
 
261
 
    def save(self, *args, **kwargs):
262
 
        if self.created is None:
263
 
            self.created = datetime.now()
264
 
 
265
 
        self.render()
266
 
 
267
 
        new = self.id is None
268
 
 
269
 
        if new:
270
 
            self.topic.updated = datetime.now()
271
 
            self.topic.save()
272
 
            self.topic.forum.updated = self.topic.updated
273
 
            self.topic.forum.save()
274
 
 
275
 
        super(Post, self).save(*args, **kwargs)
276
 
 
277
 
    def get_absolute_url(self):
278
 
        return reverse('pybb_post', args=[self.id])
279
 
 
280
 
    def unhide_post(self):
281
 
        """Unhide post(s) and inform subscribers."""
282
 
        self.hidden = False
283
 
        self.save()
284
 
        if self.topic.post_count == 1:
285
 
            # The topic is new
286
 
            send(User.objects.all(), 'forum_new_topic',
287
 
                 {'topic': self.topic, 'post': self, 'user': self.topic.user})
288
 
        else:
289
 
            # Inform topic subscribers
290
 
            send(self.topic.subscribers.all(), 'forum_new_post',
291
 
                 {'post': self, 'topic': self.topic, 'user': self.user})
292
 
 
293
 
    def delete(self, *args, **kwargs):
294
 
        self_id = self.id
295
 
        head_post_id = self.topic.posts.order_by('created')[0].id
296
 
        super(Post, self).delete(*args, **kwargs)
297
 
 
298
 
        self.topic.save()
299
 
        self.topic.forum.save()
300
 
 
301
 
        if self_id == head_post_id:
302
 
            self.topic.delete()
303
 
 
304
 
    def is_spam(self):
305
 
        try:
306
 
            SuspiciousInput.objects.get(object_id = self.pk)
307
 
            return True
308
 
        except:
309
 
            pass
310
 
        return False
311
 
 
312
 
 
313
 
class Read(models.Model):
314
 
    """For each topic that user has entered the time is logged to this
315
 
    model."""
316
 
 
317
 
    user = models.ForeignKey(User, verbose_name=_('User'))
318
 
    topic = models.ForeignKey(Topic, verbose_name=_('Topic'))
319
 
    time = models.DateTimeField(_('Time'), blank=True)
320
 
 
321
 
    class Meta:
322
 
        unique_together = ['user', 'topic']
323
 
        verbose_name = _('Read')
324
 
        verbose_name_plural = _('Reads')
325
 
 
326
 
    def save(self, *args, **kwargs):
327
 
        if self.time is None:
328
 
            self.time = datetime.now()
329
 
        super(Read, self).save(*args, **kwargs)
330
 
 
331
 
    def __unicode__(self):
332
 
        return u'T[%d], U[%d]: %s' % (self.topic.id, self.user.id, unicode(self.time))
333
 
 
334
 
 
335
 
class PrivateMessage(RenderableItem):
336
 
 
337
 
    dst_user = models.ForeignKey(User, verbose_name=_(
338
 
        'Recipient'), related_name='dst_users')
339
 
    src_user = models.ForeignKey(User, verbose_name=_(
340
 
        'Author'), related_name='src_users')
341
 
    read = models.BooleanField(_('Read'), blank=True, default=False)
342
 
    created = models.DateTimeField(_('Created'), blank=True)
343
 
    markup = models.CharField(_('Markup'), max_length=15,
344
 
                              default=pybb_settings.DEFAULT_MARKUP, choices=MARKUP_CHOICES)
345
 
    subject = models.CharField(_('Subject'), max_length=255)
346
 
    body = models.TextField(_('Message'))
347
 
    body_html = models.TextField(_('HTML version'))
348
 
    body_text = models.TextField(_('Text version'))
349
 
 
350
 
    class Meta:
351
 
        ordering = ['-created']
352
 
        verbose_name = _('Private message')
353
 
        verbose_name_plural = _('Private messages')
354
 
 
355
 
    # TODO: summary and part of the save method is the same as in the Post model
356
 
    # move to common functions
357
 
    def summary(self):
358
 
        LIMIT = 50
359
 
        tail = len(self.body) > LIMIT and '...' or ''
360
 
        return self.body[:LIMIT] + tail
361
 
 
362
 
    def __unicode__(self):
363
 
        return self.subject
364
 
 
365
 
    def save(self, *args, **kwargs):
366
 
        if self.created is None:
367
 
            self.created = datetime.now()
368
 
        self.render()
369
 
 
370
 
        new = self.id is None
371
 
        super(PrivateMessage, self).save(*args, **kwargs)
372
 
 
373
 
    def get_absolute_url(self):
374
 
        return reverse('pybb_show_pm', args=[self.id])
375
 
 
376
 
 
377
 
class Attachment(models.Model):
378
 
    post = models.ForeignKey(Post, verbose_name=_(
379
 
        'Post'), related_name='attachments')
380
 
    size = models.IntegerField(_('Size'))
381
 
    content_type = models.CharField(_('Content type'), max_length=255)
382
 
    path = models.CharField(_('Path'), max_length=255)
383
 
    name = models.TextField(_('Name'))
384
 
    hash = models.CharField(_('Hash'), max_length=40,
385
 
                            blank=True, default='', db_index=True)
386
 
 
387
 
    def save(self, *args, **kwargs):
388
 
        super(Attachment, self).save(*args, **kwargs)
389
 
        if not self.hash:
390
 
            self.hash = hashlib.sha1(
391
 
                str(self.id) + settings.SECRET_KEY).hexdigest()
392
 
        super(Attachment, self).save(*args, **kwargs)
393
 
 
394
 
    def __unicode__(self):
395
 
        return self.name
396
 
 
397
 
    def get_absolute_url(self):
398
 
        return reverse('pybb_attachment', args=[self.hash])
399
 
 
400
 
    def size_display(self):
401
 
        size = self.size
402
 
        if size < 1024:
403
 
            return '%b' % size
404
 
        elif size < 1024 * 1024:
405
 
            return '%dKb' % int(size / 1024)
406
 
        else:
407
 
            return '%.2fMb' % (size / float(1024 * 1024))
408
 
 
409
 
    def get_absolute_path(self):
410
 
        return os.path.join(settings.MEDIA_ROOT, pybb_settings.ATTACHMENT_UPLOAD_TO,
411
 
                            self.path)
412
 
 
413
 
 
414
 
if notification is not None:
415
 
    signals.post_save.connect(notification.handle_observations, sender=Post)
416
 
 
417
 
from pybb import signals
418
 
signals.setup_signals()