source: djangobb_forum/models.py @ 434:21cb6ef7649d

Last change on this file since 434:21cb6ef7649d was 434:21cb6ef7649d, checked in by slav0nic <slav0nic0@…>, 14 months ago

fix DeprecationWarning? for django 1.4; update projects

File size: 16.7 KB
Line 
1from datetime import datetime
2import os
3import os.path
4from hashlib import sha1
5
6from django.db import models
7from django.contrib.auth.models import User, Group
8from django.conf import settings
9from django.utils.translation import ugettext_lazy as _
10from django.db.models.signals import post_save
11
12from djangobb_forum.fields import AutoOneToOneField, ExtendedImageField, JSONField
13from djangobb_forum.util import smiles, convert_text_to_html
14from djangobb_forum import settings as forum_settings
15
16if 'south' in settings.INSTALLED_APPS:
17    from south.modelsinspector import add_introspection_rules
18    add_introspection_rules([], ['^djangobb_forum\.fields\.AutoOneToOneField',
19                                 '^djangobb_forum\.fields\.JSONField',
20                                 '^djangobb_forum\.fields\.ExtendedImageField'])
21
22TZ_CHOICES = [(float(x[0]), x[1]) for x in (
23    (-12, '-12'), (-11, '-11'), (-10, '-10'), (-9.5, '-09.5'), (-9, '-09'),
24    (-8.5, '-08.5'), (-8, '-08 PST'), (-7, '-07 MST'), (-6, '-06 CST'),
25    (-5, '-05 EST'), (-4, '-04 AST'), (-3.5, '-03.5'), (-3, '-03 ADT'),
26    (-2, '-02'), (-1, '-01'), (0, '00 GMT'), (1, '+01 CET'), (2, '+02'),
27    (3, '+03'), (3.5, '+03.5'), (4, '+04'), (4.5, '+04.5'), (5, '+05'),
28    (5.5, '+05.5'), (6, '+06'), (6.5, '+06.5'), (7, '+07'), (8, '+08'),
29    (9, '+09'), (9.5, '+09.5'), (10, '+10'), (10.5, '+10.5'), (11, '+11'),
30    (11.5, '+11.5'), (12, '+12'), (13, '+13'), (14, '+14'),
31)]
32
33SIGN_CHOICES = (
34    (1, 'PLUS'),
35    (-1, 'MINUS'),
36)
37
38PRIVACY_CHOICES = (
39    (0, _(u'Display your e-mail address.')),
40    (1, _(u'Hide your e-mail address but allow form e-mail.')),
41    (2, _(u'Hide your e-mail address and disallow form e-mail.')),
42)
43
44MARKUP_CHOICES = [('bbcode', 'bbcode')]
45try:
46    import markdown
47    MARKUP_CHOICES.append(("markdown", "markdown"))
48except ImportError:
49    pass
50
51path = os.path.join(settings.STATIC_ROOT, 'djangobb_forum', 'themes')
52if os.path.exists(path):
53    # fix for collectstatic
54    THEME_CHOICES = [(theme, theme) for theme in os.listdir(path) 
55                     if os.path.isdir(os.path.join(path, theme))]
56else:
57    THEME_CHOICES = []
58
59class Category(models.Model):
60    name = models.CharField(_('Name'), max_length=80)
61    groups = models.ManyToManyField(Group,blank=True, null=True, verbose_name=_('Groups'), help_text=_('Only users from these groups can see this category'))
62    position = models.IntegerField(_('Position'), blank=True, default=0)
63
64    class Meta:
65        ordering = ['position']
66        verbose_name = _('Category')
67        verbose_name_plural = _('Categories')
68
69    def __unicode__(self):
70        return self.name
71
72    def forum_count(self):
73        return self.forums.all().count()
74
75    @property
76    def topics(self):
77        return Topic.objects.filter(forum__category__id=self.id).select_related()
78
79    @property
80    def posts(self):
81        return Post.objects.filter(topic__forum__category__id=self.id).select_related()
82
83    def has_access(self, user):
84        if self.groups.exists():
85            if user.is_authenticated(): 
86                    if not self.groups.filter(user__pk=user.id).exists():
87                        return False
88            else:
89                return False
90        return True
91
92
93class Forum(models.Model):
94    category = models.ForeignKey(Category, related_name='forums', verbose_name=_('Category'))
95    name = models.CharField(_('Name'), max_length=80)
96    position = models.IntegerField(_('Position'), blank=True, default=0)
97    description = models.TextField(_('Description'), blank=True, default='')
98    moderators = models.ManyToManyField(User, blank=True, null=True, verbose_name=_('Moderators'))
99    updated = models.DateTimeField(_('Updated'), auto_now=True)
100    post_count = models.IntegerField(_('Post count'), blank=True, default=0)
101    topic_count = models.IntegerField(_('Topic count'), blank=True, default=0)
102    last_post = models.ForeignKey('Post', related_name='last_forum_post', blank=True, null=True)
103
104    class Meta:
105        ordering = ['position']
106        verbose_name = _('Forum')
107        verbose_name_plural = _('Forums')
108
109    def __unicode__(self):
110        return self.name
111
112    @models.permalink
113    def get_absolute_url(self):
114        return ('djangobb:forum', [self.id])
115
116    @property
117    def posts(self):
118        return Post.objects.filter(topic__forum__id=self.id).select_related()
119
120
121class Topic(models.Model):
122    forum = models.ForeignKey(Forum, related_name='topics', verbose_name=_('Forum'))
123    name = models.CharField(_('Subject'), max_length=255)
124    created = models.DateTimeField(_('Created'), auto_now_add=True)
125    updated = models.DateTimeField(_('Updated'), null=True)
126    user = models.ForeignKey(User, verbose_name=_('User'))
127    views = models.IntegerField(_('Views count'), blank=True, default=0)
128    sticky = models.BooleanField(_('Sticky'), blank=True, default=False)
129    closed = models.BooleanField(_('Closed'), blank=True, default=False)
130    subscribers = models.ManyToManyField(User, related_name='subscriptions', verbose_name=_('Subscribers'), blank=True)
131    post_count = models.IntegerField(_('Post count'), blank=True, default=0)
132    last_post = models.ForeignKey('Post', related_name='last_topic_post', blank=True, null=True)
133
134    class Meta:
135        ordering = ['-updated']
136        get_latest_by = 'updated'
137        verbose_name = _('Topic')
138        verbose_name_plural = _('Topics')
139
140    def __unicode__(self):
141        return self.name
142
143    def delete(self, *args, **kwargs):
144        try:
145            last_post = self.posts.latest()
146            last_post.last_forum_post.clear()
147        except Post.DoesNotExist:
148            pass
149        else:
150            last_post.last_forum_post.clear()
151        forum = self.forum
152        super(Topic, self).delete(*args, **kwargs)
153        try:
154            forum.last_post = Topic.objects.filter(forum__id=forum.id).latest().last_post
155        except Topic.DoesNotExist:
156            forum.last_post = None
157        forum.topic_count = Topic.objects.filter(forum__id=forum.id).count()
158        forum.post_count = Post.objects.filter(topic__forum__id=forum.id).count()
159        forum.save()
160
161    @property
162    def head(self):
163        try:
164            return self.posts.select_related().order_by('created')[0]
165        except IndexError:
166            return None
167
168    @property
169    def reply_count(self):
170        return self.post_count - 1
171
172    @models.permalink
173    def get_absolute_url(self):
174        return ('djangobb:topic', [self.id])
175
176    def update_read(self, user):
177        tracking = user.posttracking
178        #if last_read > last_read - don't check topics
179        if tracking.last_read and (tracking.last_read > self.last_post.created):
180            return
181        if isinstance(tracking.topics, dict):
182            #clear topics if len > 5Kb and set last_read to current time
183            if len(tracking.topics) > 5120:
184                tracking.topics = None
185                tracking.last_read = datetime.now()
186                tracking.save()
187            #update topics if exist new post or does't exist in dict
188            if self.last_post_id > tracking.topics.get(str(self.id), 0):
189                tracking.topics[str(self.id)] = self.last_post_id
190                tracking.save()
191        else:
192            #initialize topic tracking dict
193            tracking.topics = {self.id: self.last_post_id}
194            tracking.save()
195
196
197class Post(models.Model):
198    topic = models.ForeignKey(Topic, related_name='posts', verbose_name=_('Topic'))
199    user = models.ForeignKey(User, related_name='posts', verbose_name=_('User'))
200    created = models.DateTimeField(_('Created'), auto_now_add=True)
201    updated = models.DateTimeField(_('Updated'), blank=True, null=True)
202    updated_by = models.ForeignKey(User, verbose_name=_('Updated by'), blank=True, null=True)
203    markup = models.CharField(_('Markup'), max_length=15, default=forum_settings.DEFAULT_MARKUP, choices=MARKUP_CHOICES)
204    body = models.TextField(_('Message'))
205    body_html = models.TextField(_('HTML version'))
206    user_ip = models.IPAddressField(_('User IP'), blank=True, null=True)
207
208
209    class Meta:
210        ordering = ['created']
211        get_latest_by = 'created'
212        verbose_name = _('Post')
213        verbose_name_plural = _('Posts')
214
215    def save(self, *args, **kwargs):
216        self.body_html = convert_text_to_html(self.body, self.markup) 
217        if forum_settings.SMILES_SUPPORT and self.user.forum_profile.show_smilies:
218            self.body_html = smiles(self.body_html)
219        super(Post, self).save(*args, **kwargs)
220
221
222    def delete(self, *args, **kwargs):
223        self_id = self.id
224        head_post_id = self.topic.posts.order_by('created')[0].id
225        forum = self.topic.forum
226        topic = self.topic
227        profile = self.user.forum_profile
228        self.last_topic_post.clear()
229        self.last_forum_post.clear()
230        super(Post, self).delete(*args, **kwargs)
231        #if post was last in topic - remove topic
232        if self_id == head_post_id:
233            topic.delete()
234        else:
235            try:
236                topic.last_post = Post.objects.filter(topic__id=topic.id).latest()
237            except Post.DoesNotExist:
238                topic.last_post = None
239            topic.post_count = Post.objects.filter(topic__id=topic.id).count()
240            topic.save()
241        try:
242            forum.last_post = Post.objects.filter(topic__forum__id=forum.id).latest()
243        except Post.DoesNotExist:
244            forum.last_post = None
245        #TODO: for speedup - save/update only changed fields
246        forum.post_count = Post.objects.filter(topic__forum__id=forum.id).count()
247        forum.topic_count = Topic.objects.filter(forum__id=forum.id).count()
248        forum.save()
249        profile.post_count = Post.objects.filter(user__id=self.user_id).count()
250        profile.save()
251
252    @models.permalink
253    def get_absolute_url(self):
254        return ('djangobb:post', [self.id])
255
256    def summary(self):
257        LIMIT = 50
258        tail = len(self.body) > LIMIT and '...' or ''
259        return self.body[:LIMIT] + tail
260
261    __unicode__ = summary
262
263
264class Reputation(models.Model):
265    from_user = models.ForeignKey(User, related_name='reputations_from', verbose_name=_('From'))
266    to_user = models.ForeignKey(User, related_name='reputations_to', verbose_name=_('To'))
267    post = models.ForeignKey(Post, related_name='post', verbose_name=_('Post'))
268    time = models.DateTimeField(_('Time'), auto_now_add=True)
269    sign = models.IntegerField(_('Sign'), choices=SIGN_CHOICES, default=0)
270    reason = models.TextField(_('Reason'), max_length=1000)
271
272    class Meta:
273        verbose_name = _('Reputation')
274        verbose_name_plural = _('Reputations')
275        unique_together = (('from_user', 'post'),)
276
277    def __unicode__(self):
278        return u'T[%d], FU[%d], TU[%d]: %s' % (self.post.id, self.from_user.id, self.to_user.id, unicode(self.time))
279
280
281class ProfileManager(models.Manager):
282    use_for_related_fields = True
283    def get_query_set(self):
284        qs = super(ProfileManager, self).get_query_set()
285        if forum_settings.REPUTATION_SUPPORT:
286            qs = qs.extra(select={
287                'reply_total': 'SELECT SUM(sign) FROM djangobb_forum_reputation WHERE to_user_id = djangobb_forum_profile.user_id GROUP BY to_user_id',
288                'reply_count_minus': "SELECT SUM(sign) FROM djangobb_forum_reputation WHERE to_user_id = djangobb_forum_profile.user_id AND sign = '-1' GROUP BY to_user_id",
289                'reply_count_plus': "SELECT SUM(sign) FROM djangobb_forum_reputation WHERE to_user_id = djangobb_forum_profile.user_id AND sign = '1' GROUP BY to_user_id",
290                })
291        return qs
292
293class Profile(models.Model):
294    user = AutoOneToOneField(User, related_name='forum_profile', verbose_name=_('User'))
295    status = models.CharField(_('Status'), max_length=30, blank=True)
296    site = models.URLField(_('Site'), verify_exists=False, blank=True)
297    jabber = models.CharField(_('Jabber'), max_length=80, blank=True)
298    icq = models.CharField(_('ICQ'), max_length=12, blank=True)
299    msn = models.CharField(_('MSN'), max_length=80, blank=True)
300    aim = models.CharField(_('AIM'), max_length=80, blank=True)
301    yahoo = models.CharField(_('Yahoo'), max_length=80, blank=True)
302    location = models.CharField(_('Location'), max_length=30, blank=True)
303    signature = models.TextField(_('Signature'), blank=True, default='', max_length=forum_settings.SIGNATURE_MAX_LENGTH)
304    signature_html = models.TextField(_('Signature'), blank=True, default='', max_length=forum_settings.SIGNATURE_MAX_LENGTH)
305    time_zone = models.FloatField(_('Time zone'), choices=TZ_CHOICES, default=float(forum_settings.DEFAULT_TIME_ZONE))
306    language = models.CharField(_('Language'), max_length=5, default='', choices=settings.LANGUAGES)
307    avatar = ExtendedImageField(_('Avatar'), blank=True, default='', upload_to=forum_settings.AVATARS_UPLOAD_TO, width=forum_settings.AVATAR_WIDTH, height=forum_settings.AVATAR_HEIGHT)
308    theme = models.CharField(_('Theme'), choices=THEME_CHOICES, max_length=80, default='default')
309    show_avatar = models.BooleanField(_('Show avatar'), blank=True, default=True)
310    show_signatures = models.BooleanField(_('Show signatures'), blank=True, default=True)
311    show_smilies = models.BooleanField(_('Show smilies'), blank=True, default=True)
312    privacy_permission = models.IntegerField(_('Privacy permission'), choices=PRIVACY_CHOICES, default=1)
313    markup = models.CharField(_('Default markup'), max_length=15, default=forum_settings.DEFAULT_MARKUP, choices=MARKUP_CHOICES)
314    post_count = models.IntegerField(_('Post count'), blank=True, default=0)
315
316    objects = ProfileManager()
317
318    class Meta:
319        verbose_name = _('Profile')
320        verbose_name_plural = _('Profiles')
321
322    def last_post(self):
323        posts = Post.objects.filter(user__id=self.user_id).order_by('-created')
324        if posts:
325            return posts[0].created
326        else:
327            return  None
328
329class PostTracking(models.Model):
330    """
331    Model for tracking read/unread posts.
332    In topics stored ids of topics and last_posts as dict.
333    """
334
335    user = AutoOneToOneField(User)
336    topics = JSONField(null=True)
337    last_read = models.DateTimeField(null=True)
338
339    class Meta:
340        verbose_name = _('Post tracking')
341        verbose_name_plural = _('Post tracking')
342
343    def __unicode__(self):
344        return self.user.username
345
346
347class Report(models.Model):
348    reported_by = models.ForeignKey(User, related_name='reported_by', verbose_name=_('Reported by'))
349    post = models.ForeignKey(Post, verbose_name=_('Post'))
350    zapped = models.BooleanField(_('Zapped'), blank=True, default=False)
351    zapped_by = models.ForeignKey(User, related_name='zapped_by', blank=True, null=True,  verbose_name=_('Zapped by'))
352    created = models.DateTimeField(_('Created'), blank=True)
353    reason = models.TextField(_('Reason'), blank=True, default='', max_length='1000')
354
355    class Meta:
356        verbose_name = _('Report')
357        verbose_name_plural = _('Reports')
358
359    def __unicode__(self):
360        return u'%s %s' % (self.reported_by ,self.zapped)
361
362class Ban(models.Model):
363    user = models.OneToOneField(User, verbose_name=_('Banned user'), related_name='ban_users')
364    ban_start = models.DateTimeField(_('Ban start'), default=datetime.now)
365    ban_end = models.DateTimeField(_('Ban end'), blank=True, null=True)
366    reason = models.TextField(_('Reason'))
367
368    class Meta:
369        verbose_name = _('Ban')
370        verbose_name_plural = _('Bans')
371
372    def __unicode__(self):
373        return self.user.username
374
375    def save(self, *args, **kwargs):
376        self.user.is_active = False
377        self.user.save()
378        super(Ban, self).save(*args, **kwargs)
379
380    def delete(self, *args, **kwargs):
381        self.user.is_active = True
382        self.user.save()
383        super(Ban, self).delete(*args, **kwargs)
384
385
386class Attachment(models.Model):
387    post = models.ForeignKey(Post, verbose_name=_('Post'), related_name='attachments')
388    size = models.IntegerField(_('Size'))
389    content_type = models.CharField(_('Content type'), max_length=255)
390    path = models.CharField(_('Path'), max_length=255)
391    name = models.TextField(_('Name'))
392    hash = models.CharField(_('Hash'), max_length=40, blank=True, default='', db_index=True)
393
394    def __unicode__(self):
395        return self.name
396
397    def save(self, *args, **kwargs):
398        super(Attachment, self).save(*args, **kwargs)
399        if not self.hash:
400            self.hash = sha1(str(self.id) + settings.SECRET_KEY).hexdigest()
401        super(Attachment, self).save(*args, **kwargs)
402
403    @models.permalink
404    def get_absolute_url(self):
405        return ('djangobb:forum_attachment', [self.hash])
406
407    def get_absolute_path(self):
408        return os.path.join(settings.MEDIA_ROOT, forum_settings.ATTACHMENT_UPLOAD_TO,
409                            self.path)
410
411
412from .signals import post_saved, topic_saved
413
414post_save.connect(post_saved, sender=Post, dispatch_uid='djangobb_post_save')
415post_save.connect(topic_saved, sender=Topic, dispatch_uid='djangobb_topic_save')
Note: See TracBrowser for help on using the repository browser.