~widelands-dev/widelands-website/add_DISPLAY_hint

« back to all changes in this revision

Viewing changes to pybb/models.py

  • Committer: Holger Rapp
  • Date: 2009-02-25 16:55:36 UTC
  • Revision ID: sirver@kallisto.local-20090225165536-3abfhjx8qsgtzyru
- Added my hacked version of pybb. Remerging new versions is very difficult at this point :(

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
from datetime import datetime
 
2
from markdown import Markdown
 
3
import os.path
 
4
import sha
 
5
 
 
6
from django.db import models
 
7
from django.contrib.auth.models import User
 
8
from django.core.urlresolvers 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.fields import AutoOneToOneField, ExtendedImageField
 
15
from pybb.util import urlize, memoize_method, unescape
 
16
from pybb import settings as pybb_settings
 
17
 
 
18
TZ_CHOICES = [(float(x[0]), x[1]) for x in (
 
19
    (-12, '-12'), (-11, '-11'), (-10, '-10'), (-9.5, '-09.5'), (-9, '-09'),
 
20
    (-8.5, '-08.5'), (-8, '-08 PST'), (-7, '-07 MST'), (-6, '-06 CST'),
 
21
    (-5, '-05 EST'), (-4, '-04 AST'), (-3.5, '-03.5'), (-3, '-03 ADT'),
 
22
    (-2, '-02'), (-1, '-01'), (0, '00 GMT'), (1, '+01 CET'), (2, '+02'),
 
23
    (3, '+03'), (3.5, '+03.5'), (4, '+04'), (4.5, '+04.5'), (5, '+05'),
 
24
    (5.5, '+05.5'), (6, '+06'), (6.5, '+06.5'), (7, '+07'), (8, '+08'),
 
25
    (9, '+09'), (9.5, '+09.5'), (10, '+10'), (10.5, '+10.5'), (11, '+11'),
 
26
    (11.5, '+11.5'), (12, '+12'), (13, '+13'), (14, '+14'),
 
27
)]
 
28
 
 
29
MARKUP_CHOICES = (
 
30
    ('bbcode', 'bbcode'),
 
31
    ('markdown', 'markdown'),
 
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(Category, related_name='forums', verbose_name=_('Category'))
 
64
    name = models.CharField(_('Name'), max_length=80)
 
65
    position = models.IntegerField(_('Position'), blank=True, default=0)
 
66
    description = models.TextField(_('Description'), blank=True, default='')
 
67
    moderators = models.ManyToManyField(User, blank=True, null=True, verbose_name=_('Moderators'))
 
68
    updated = models.DateTimeField(_('Updated'), null=True)
 
69
    post_count = models.IntegerField(_('Post count'), blank=True, default=0)
 
70
 
 
71
    class Meta:
 
72
        ordering = ['position']
 
73
        verbose_name = _('Forum')
 
74
        verbose_name_plural = _('Forums')
 
75
 
 
76
    def __unicode__(self):
 
77
        return self.name
 
78
 
 
79
    def topic_count(self):
 
80
        return self.topics.all().count()
 
81
 
 
82
    def get_absolute_url(self):
 
83
        return reverse('pybb_forum', args=[self.id])
 
84
    
 
85
    @property
 
86
    def posts(self):
 
87
        return Post.objects.filter(topic__forum=self).select_related()
 
88
 
 
89
    @property
 
90
    def last_post(self):
 
91
        posts = self.posts.order_by('-created').select_related()
 
92
        try:
 
93
            return posts[0]
 
94
        except IndexError:
 
95
            return None
 
96
 
 
97
 
 
98
class Topic(models.Model):
 
99
    forum = models.ForeignKey(Forum, related_name='topics', verbose_name=_('Forum'))
 
100
    name = models.CharField(_('Subject'), max_length=255)
 
101
    created = models.DateTimeField(_('Created'), null=True)
 
102
    updated = models.DateTimeField(_('Updated'), null=True)
 
103
    user = models.ForeignKey(User, verbose_name=_('User'))
 
104
    views = models.IntegerField(_('Views count'), blank=True, default=0)
 
105
    sticky = models.BooleanField(_('Sticky'), blank=True, default=False)
 
106
    closed = models.BooleanField(_('Closed'), blank=True, default=False)
 
107
    subscribers = models.ManyToManyField(User, related_name='subscriptions', verbose_name=_('Subscribers'), blank=True)
 
108
    post_count = models.IntegerField(_('Post count'), blank=True, default=0)
 
109
 
 
110
    class Meta:
 
111
        ordering = ['-created']
 
112
        verbose_name = _('Topic')
 
113
        verbose_name_plural = _('Topics')
 
114
 
 
115
    def __unicode__(self):
 
116
        return self.name
 
117
    
 
118
    @property
 
119
    def head(self):
 
120
        return self.posts.all().order_by('created').select_related()[0]
 
121
 
 
122
    @property
 
123
    def last_post(self):
 
124
        return self.posts.all().order_by('-created').select_related()[0]
 
125
 
 
126
    def get_absolute_url(self):
 
127
        return reverse('pybb_topic', args=[self.id])
 
128
 
 
129
    def save(self, *args, **kwargs):
 
130
        if self.id is None:
 
131
            self.created = datetime.now()
 
132
        super(Topic, self).save(*args, **kwargs)
 
133
 
 
134
    def update_read(self, user):
 
135
        read, new = Read.objects.get_or_create(user=user, topic=self)
 
136
        if not new:
 
137
            read.time = datetime.now()
 
138
            read.save()
 
139
 
 
140
    #def has_unreads(self, user):
 
141
        #try:
 
142
            #read = Read.objects.get(user=user, topic=self)
 
143
        #except Read.DoesNotExist:
 
144
            #return True
 
145
        #else:
 
146
            #return self.updated > read.time
 
147
 
 
148
 
 
149
class RenderableItem(models.Model):
 
150
    """
 
151
    Base class for models that has markup, body, body_text and body_html fields.
 
152
    """
 
153
 
 
154
    class Meta:
 
155
        abstract = True
 
156
 
 
157
    def render(self):
 
158
        if self.markup == 'bbcode':
 
159
            self.body_html = mypostmarkup.markup(self.body, auto_urls=False)
 
160
        elif self.markup == 'markdown':
 
161
            self.body_html = unicode(Markdown(self.body, safe_mode='escape'))
 
162
        else:
 
163
            raise Exception('Invalid markup property: %s' % self.markup)
 
164
 
 
165
        # Remove tags which was generated with the markup processor
 
166
        text = strip_tags(self.body_html)
 
167
 
 
168
        # Unescape entities which was generated with the markup processor
 
169
        self.body_text = unescape(text)
 
170
 
 
171
        self.body_html = urlize(self.body_html)
 
172
 
 
173
 
 
174
class Post(RenderableItem):
 
175
    topic = models.ForeignKey(Topic, related_name='posts', verbose_name=_('Topic'))
 
176
    user = models.ForeignKey(User, related_name='posts', verbose_name=_('User'))
 
177
    created = models.DateTimeField(_('Created'), blank=True)
 
178
    updated = models.DateTimeField(_('Updated'), blank=True, null=True)
 
179
    markup = models.CharField(_('Markup'), max_length=15, default=pybb_settings.DEFAULT_MARKUP, choices=MARKUP_CHOICES)
 
180
    body = models.TextField(_('Message'))
 
181
    body_html = models.TextField(_('HTML version'))
 
182
    body_text = models.TextField(_('Text version'))
 
183
    user_ip = models.IPAddressField(_('User IP'), blank=True, default='')
 
184
 
 
185
 
 
186
    class Meta:
 
187
        ordering = ['created']
 
188
        verbose_name = _('Post')
 
189
        verbose_name_plural = _('Posts')
 
190
 
 
191
    def summary(self):
 
192
        LIMIT = 50
 
193
        tail = len(self.body) > LIMIT and '...' or '' 
 
194
        return self.body[:LIMIT] + tail
 
195
 
 
196
    __unicode__ = summary
 
197
 
 
198
    def save(self, *args, **kwargs):
 
199
        if self.created is None:
 
200
            self.created = datetime.now()
 
201
        self.render()
 
202
 
 
203
        new = self.id is None
 
204
 
 
205
        if new:
 
206
            self.topic.updated = datetime.now()
 
207
            self.topic.post_count += 1
 
208
            self.topic.save()
 
209
            self.topic.forum.updated = self.topic.updated
 
210
            self.topic.forum.post_count += 1
 
211
            self.topic.forum.save()
 
212
 
 
213
        super(Post, self).save(*args, **kwargs)
 
214
 
 
215
 
 
216
    def get_absolute_url(self):
 
217
        return reverse('pybb_post', args=[self.id])
 
218
 
 
219
 
 
220
    def delete(self, *args, **kwargs):
 
221
        self_id = self.id
 
222
        head_post_id = self.topic.posts.order_by('created')[0].id
 
223
        super(Post, self).delete(*args, **kwargs)
 
224
 
 
225
        self.topic.post_count -= 1
 
226
        self.topic.save()
 
227
        self.topic.forum.post_count -= 1
 
228
        self.topic.forum.save()
 
229
 
 
230
        if self_id == head_post_id:
 
231
            self.topic.delete()
 
232
 
 
233
 
 
234
class Profile(models.Model):
 
235
    user = AutoOneToOneField(User, related_name='pybb_profile', verbose_name=_('User'))
 
236
    site = models.URLField(_('Site'), verify_exists=False, blank=True, default='')
 
237
    jabber = models.CharField(_('Jabber'), max_length=80, blank=True, default='')
 
238
    icq = models.CharField(_('ICQ'), max_length=12, blank=True, default='')
 
239
    msn = models.CharField(_('MSN'), max_length=80, blank=True, default='')
 
240
    aim = models.CharField(_('AIM'), max_length=80, blank=True, default='')
 
241
    yahoo = models.CharField(_('Yahoo'), max_length=80, blank=True, default='')
 
242
    location = models.CharField(_('Location'), max_length=30, blank=True, default='')
 
243
    signature = models.TextField(_('Signature'), blank=True, default='', max_length=pybb_settings.SIGNATURE_MAX_LENGTH)
 
244
    time_zone = models.FloatField(_('Time zone'), choices=TZ_CHOICES, default=float(pybb_settings.DEFAULT_TIME_ZONE))
 
245
    language = models.CharField(_('Language'), max_length=10, blank=True, default='',
 
246
                                choices=settings.LANGUAGES)
 
247
    avatar = ExtendedImageField(_('Avatar'), blank=True, default='', upload_to=pybb_settings.AVATARS_UPLOAD_TO, width=pybb_settings.AVATAR_WIDTH, height=pybb_settings.AVATAR_HEIGHT)
 
248
    show_signatures = models.BooleanField(_('Show signatures'), blank=True, default=True)
 
249
    markup = models.CharField(_('Default markup'), max_length=15, default=pybb_settings.DEFAULT_MARKUP, choices=MARKUP_CHOICES)
 
250
 
 
251
    class Meta:
 
252
        verbose_name = _('Profile')
 
253
        verbose_name_plural = _('Profiles')
 
254
 
 
255
 
 
256
    @memoize_method
 
257
    def unread_pm_count(self):
 
258
        return PrivateMessage.objects.filter(dst_user=self, read=False).count()
 
259
 
 
260
    def post_count(self):
 
261
        """
 
262
        Return the nr of posts the user has. This uses djangos filter feature
 
263
        will therefore hit the database. This should maybe be reworked when the 
 
264
        database grows to not be always calculated.
 
265
        """
 
266
        return Post.objects.filter(user=self.user).count()
 
267
 
 
268
class Read(models.Model):
 
269
    """
 
270
    For each topic that user has entered the time 
 
271
    is logged to this model.
 
272
    """
 
273
 
 
274
    user = models.ForeignKey(User, verbose_name=_('User'))
 
275
    topic = models.ForeignKey(Topic, verbose_name=_('Topic'))
 
276
    time = models.DateTimeField(_('Time'), blank=True)
 
277
 
 
278
    class Meta:
 
279
        unique_together = ['user', 'topic']
 
280
        verbose_name = _('Read')
 
281
        verbose_name_plural = _('Reads')
 
282
 
 
283
    def save(self, *args, **kwargs):
 
284
        if self.time is None:
 
285
            self.time = datetime.now()
 
286
        super(Read, self).save(*args, **kwargs)
 
287
 
 
288
 
 
289
    def __unicode__(self):
 
290
        return u'T[%d], U[%d]: %s' % (self.topic.id, self.user.id, unicode(self.time))
 
291
 
 
292
 
 
293
class PrivateMessage(RenderableItem):
 
294
 
 
295
    dst_user = models.ForeignKey(User, verbose_name=_('Recipient'), related_name='dst_users')
 
296
    src_user = models.ForeignKey(User, verbose_name=_('Author'), related_name='src_users')
 
297
    read = models.BooleanField(_('Read'), blank=True, default=False)
 
298
    created = models.DateTimeField(_('Created'), blank=True)
 
299
    markup = models.CharField(_('Markup'), max_length=15, default=pybb_settings.DEFAULT_MARKUP, choices=MARKUP_CHOICES)
 
300
    subject = models.CharField(_('Subject'), max_length=255)
 
301
    body = models.TextField(_('Message'))
 
302
    body_html = models.TextField(_('HTML version'))
 
303
    body_text = models.TextField(_('Text version'))
 
304
 
 
305
    class Meta:
 
306
        ordering = ['-created']
 
307
        verbose_name = _('Private message')
 
308
        verbose_name_plural = _('Private messages')
 
309
 
 
310
    # TODO: summary and part of the save method is the same as in the Post model
 
311
    # move to common functions
 
312
    def summary(self):
 
313
        LIMIT = 50
 
314
        tail = len(self.body) > LIMIT and '...' or '' 
 
315
        return self.body[:LIMIT] + tail
 
316
 
 
317
    def __unicode__(self):
 
318
        return self.subject
 
319
 
 
320
    def save(self, *args, **kwargs):
 
321
        if self.created is None:
 
322
            self.created = datetime.now()
 
323
        self.render()
 
324
 
 
325
        new = self.id is None
 
326
        super(PrivateMessage, self).save(*args, **kwargs)
 
327
 
 
328
    def get_absolute_url(self):
 
329
        return  reverse('pybb_show_pm', args=[self.id])
 
330
 
 
331
 
 
332
class Attachment(models.Model):
 
333
    post = models.ForeignKey(Post, verbose_name=_('Post'), related_name='attachments')
 
334
    size = models.IntegerField(_('Size'))
 
335
    content_type = models.CharField(_('Content type'), max_length=255)
 
336
    path = models.CharField(_('Path'), max_length=255)
 
337
    name = models.TextField(_('Name'))
 
338
    hash = models.CharField(_('Hash'), max_length=40, blank=True, default='', db_index=True)
 
339
 
 
340
    def save(self, *args, **kwargs):
 
341
        super(Attachment, self).save(*args, **kwargs)
 
342
        if not self.hash:
 
343
            self.hash = sha.new(str(self.id) + settings.SECRET_KEY).hexdigest()
 
344
        super(Attachment, self).save(*args, **kwargs)
 
345
 
 
346
    def __unicode__(self):
 
347
        return self.name
 
348
 
 
349
    def get_absolute_url(self):
 
350
        return reverse('pybb_attachment', args=[self.hash])
 
351
 
 
352
    def size_display(self):
 
353
        size = self.size
 
354
        if size < 1024:
 
355
            return '%b' % size
 
356
        elif size < 1024 * 1024:
 
357
            return '%dKb' % int(size / 1024)
 
358
        else:
 
359
            return '%.2fMb' % (size / float(1024 * 1024))
 
360
 
 
361
 
 
362
    def get_absolute_path(self):
 
363
        return os.path.join(settings.MEDIA_ROOT, pybb_settings.ATTACHMENT_UPLOAD_TO,
 
364
                            self.path)
 
365
 
 
366
 
 
367
from pybb import signals
 
368
signals.setup_signals()