root/pybb/models.py

Revision 90:53cc51fbabf7, 10.9 kB (checked in by Grigoriy Petukhov <lizendir@gmail.com>, 2 weeks ago)

enchancment of the Forum.last_post method

Line 
1 from datetime import datetime
2
3 from django.db import models
4 from django.contrib.auth.models import User
5 from django.core.urlresolvers import reverse
6 from django.utils.html import escape, strip_tags
7 from django.conf import settings
8 from django.utils.translation import ugettext_lazy as _
9 #from django.contrib.markup.templatetags.markup import markdown
10 from markdown import Markdown
11
12 from pybb.markups import mypostmarkup
13 from pybb.fields import AutoOneToOneField, ExtendedImageField
14 from pybb.subscription import notify_subscribers
15 from pybb.util import urlize
16
17 LANGUAGE_CHOICES = (
18     ('en', 'English'),
19     ('ru', _('Russian')),
20 )
21
22 TZ_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
33 MARKUP_CHOICES = (
34     ('bbcode', 'bbcode'),
35     ('markdown', 'markdown'),
36 )
37
38
39 class Category(models.Model):
40     name = models.CharField(_('Name'), max_length=80)
41     position = models.IntegerField(_('Position'), blank=True, default=0)
42
43     class Meta:
44         ordering = ['position']
45         verbose_name = _('Category')
46         verbose_name_plural = _('Categories')
47
48     def __unicode__(self):
49         return self.name
50
51     def forum_count(self):
52         return self.forums.all().count()
53
54     def get_absolute_url(self):
55         return reverse('category', args=[self.id])
56
57     @property
58     def topics(self):
59         return Topic.objects.filter(forum__category=self).select_related()
60    
61     @property
62     def posts(self):
63         return Post.objects.filter(topic__forum__category=self).select_related()
64
65
66 class Forum(models.Model):
67     category = models.ForeignKey(Category, related_name='forums', verbose_name=_('Category'))
68     name = models.CharField(_('Name'), max_length=80)
69     position = models.IntegerField(_('Position'), blank=True, default=0)
70     description = models.TextField(_('Description'), blank=True, default='')
71     moderators = models.ManyToManyField(User, blank=True, null=True, verbose_name=_('Moderators'))
72     updated = models.DateTimeField(_('Updated'), null=True)
73     post_count = models.IntegerField(_('Post count'), blank=True, default=0)
74
75     class Meta:
76         ordering = ['position']
77         verbose_name = _('Forum')
78         verbose_name_plural = _('Forums')
79
80     def __unicode__(self):
81         return self.name
82
83     def topic_count(self):
84         return self.topics.all().count()
85
86     def get_absolute_url(self):
87         return reverse('forum', args=[self.id])
88    
89     @property
90     def posts(self):
91         return Post.objects.filter(topic__forum=self).select_related()
92
93     @property
94     def last_post(self):
95         posts = self.posts.order_by('-created').select_related()
96         try:
97             return posts[0]
98         except IndexError:
99             return None
100
101
102 class Topic(models.Model):
103     forum = models.ForeignKey(Forum, related_name='topics', verbose_name=_('Forum'))
104     name = models.CharField(_('Subject'), max_length=255)
105     created = models.DateTimeField(_('Created'), null=True)
106     updated = models.DateTimeField(_('Updated'), null=True)
107     user = models.ForeignKey(User, verbose_name=_('User'))
108     views = models.IntegerField(_('Views count'), blank=True, default=0)
109     sticky = models.BooleanField(_('Sticky'), blank=True, default=False)
110     closed = models.BooleanField(_('Closed'), blank=True, default=False)
111     subscribers = models.ManyToManyField(User, related_name='subscriptions', verbose_name=_('Subscribers'))
112     post_count = models.IntegerField(_('Post count'), blank=True, default=0)
113
114     class Meta:
115         ordering = ['-created']
116         verbose_name = _('Topic')
117         verbose_name_plural = _('Topics')
118
119     def __unicode__(self):
120         return self.name
121    
122     @property
123     def head(self):
124         return self.posts.all().order_by('created').select_related()[0]
125
126     @property
127     def last_post(self):
128         return self.posts.all().order_by('-created').select_related()[0]
129
130     def get_absolute_url(self):
131         return reverse('topic', args=[self.id])
132
133     def save(self, *args, **kwargs):
134         if self.id is None:
135             self.created = datetime.now()
136         super(Topic, self).save(*args, **kwargs)
137
138     def update_read(self, user):
139         read, new = Read.objects.get_or_create(user=user, topic=self)
140         if not new:
141             read.time = datetime.now()
142             read.save()
143
144     #def has_unreads(self, user):
145         #try:
146             #read = Read.objects.get(user=user, topic=self)
147         #except Read.DoesNotExist:
148             #return True
149         #else:
150             #return self.updated > read.time
151
152
153 class RenderableItem(models.Model):
154     """
155     Base class for models that has markup, body, body_text and body_html fields.
156     """
157
158     class Meta:
159         abstract = True
160
161     def render(self):
162         if self.markup == 'bbcode':
163             self.body_html = mypostmarkup.markup(self.body, auto_urls=False)
164         elif self.markup == 'markdown':
165             self.body_html = unicode(Markdown(self.body, safe_mode='escape'))
166         else:
167             raise Exception('Invalid markup property: %s' % self.markup)
168         self.body_text = strip_tags(self.body_html)
169         self.body_html = urlize(self.body_html)
170
171
172 class Post(RenderableItem):
173     topic = models.ForeignKey(Topic, related_name='posts', verbose_name=_('Topic'))
174     user = models.ForeignKey(User, related_name='posts', verbose_name=_('User'))
175     created = models.DateTimeField(_('Created'), blank=True)
176     updated = models.DateTimeField(_('Updated'), blank=True, null=True)
177     markup = models.CharField(_('Markup'), max_length=15, default=settings.PYBB_DEFAULT_MARKUP, choices=MARKUP_CHOICES)
178     body = models.TextField(_('Message'))
179     body_html = models.TextField(_('HTML version'))
180     body_text = models.TextField(_('Text version'))
181     user_ip = models.IPAddressField(_('User IP'), blank=True, default='')
182
183
184     class Meta:
185         ordering = ['created']
186         verbose_name = _('Post')
187         verbose_name_plural = _('Posts')
188
189     def summary(self):
190         LIMIT = 50
191         tail = len(self.body) > LIMIT and '...' or ''
192         return self.body[:LIMIT] + tail
193
194     __unicode__ = summary
195
196     def save(self, *args, **kwargs):
197         if self.created is None:
198             self.created = datetime.now()
199         self.render()
200
201         new = self.id is None
202
203         if new:
204             self.topic.updated = datetime.now()
205             self.topic.post_count += 1
206             self.topic.save()
207             self.topic.forum.updated = self.topic.updated
208             self.topic.forum.post_count += 1
209             self.topic.forum.save()
210
211         super(Post, self).save(*args, **kwargs)
212
213         if new:
214             notify_subscribers(self)
215
216
217
218
219     def get_absolute_url(self):
220         return reverse('post', args=[self.id])
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         super(Post, self).delete(*args, **kwargs)
226         if self_id == head_post_id:
227             self.topic.delete()
228
229
230 class Profile(models.Model):
231     user = AutoOneToOneField(User, related_name='pybb_profile', verbose_name=_('User'))
232     site = models.URLField(_('Site'), verify_exists=False, blank=True, default='')
233     jabber = models.CharField(_('Jabber'), max_length=80, blank=True, default='')
234     icq = models.CharField(_('ICQ'), max_length=12, blank=True, default='')
235     msn = models.CharField(_('MSN'), max_length=80, blank=True, default='')
236     aim = models.CharField(_('AIM'), max_length=80, blank=True, default='')
237     yahoo = models.CharField(_('Yahoo'), max_length=80, blank=True, default='')
238     location = models.CharField(_('Location'), max_length=30, blank=True, default='')
239     signature = models.TextField(_('Signature'), blank=True, default='', max_length=settings.PYBB_SIGNATURE_MAX_LENGTH)
240     time_zone = models.FloatField(_('Time zone'), choices=TZ_CHOICES, default=float(settings.PYBB_DEFAULT_TIME_ZONE))
241     language = models.CharField(_('Language'), max_length=3, blank=True, default='en', choices=LANGUAGE_CHOICES)
242     avatar = ExtendedImageField(_('Avatar'), blank=True, default='', upload_to=settings.PYBB_AVATARS_UPLOAD_TO, width=settings.PYBB_AVATAR_WIDTH, height=settings.PYBB_AVATAR_HEIGHT)
243     show_signatures = models.BooleanField(_('Show signatures'), blank=True, default=True)
244     markup = models.CharField(_('Default markup'), max_length=15, default=settings.PYBB_DEFAULT_MARKUP, choices=MARKUP_CHOICES)
245
246     class Meta:
247         verbose_name = _('Profile')
248         verbose_name_plural = _('Profiles')
249
250
251 class Read(models.Model):
252     """
253     For each topic that user has entered the time
254     is logged to this model.
255     """
256
257     user = models.ForeignKey(User, verbose_name=_('User'))
258     topic = models.ForeignKey(Topic, verbose_name=_('Topic'))
259     time = models.DateTimeField(_('Time'), blank=True)
260
261     class Meta:
262         unique_together = ['user', 'topic']
263         verbose_name = _('Read')
264         verbose_name_plural = _('Reads')
265
266     def save(self, *args, **kwargs):
267         if self.time is None:
268             self.time = datetime.now()
269         super(Read, self).save(*args, **kwargs)
270
271
272     def __unicode__(self):
273         return u'T[%d], U[%d]: %s' % (self.topic.id, self.user.id, unicode(self.time))
274
275
276 class PrivateMessage(RenderableItem):
277
278     dst_user = models.ForeignKey(User, verbose_name=_('Recipient'), related_name='dst_users')
279     src_user = models.ForeignKey(User, verbose_name=_('Author'), related_name='src_users')
280     read = models.BooleanField(_('Read'), blank=True, default=False)
281     created = models.DateTimeField(_('Created'), blank=True)
282     markup = models.CharField(_('Markup'), max_length=15, default=settings.PYBB_DEFAULT_MARKUP, choices=MARKUP_CHOICES)
283     subject = models.CharField(_('Subject'), max_length=255)
284     body = models.TextField(_('Message'))
285     body_html = models.TextField(_('HTML version'))
286     body_text = models.TextField(_('Text version'))
287
288     class Meta:
289         ordering = ['-created']
290         verbose_name = _('Private message')
291         verbose_name_plural = _('Private messages')
292
293     # TODO: summary and part of the save methid is the same as in the Post model
294     # move to common functions
295     def summary(self):
296         LIMIT = 50
297         tail = len(self.body) > LIMIT and '...' or ''
298         return self.body[:LIMIT] + tail
299
300     def __unicode__(self):
301         return self.subject
302
303     def save(self, *args, **kwargs):
304         if self.created is None:
305             self.created = datetime.now()
306         self.render()
307
308         new = self.id is None
309         super(PrivateMessage, self).save(*args, **kwargs)
310         # TODO: make email notifications
311         #if new:
312             #notify_subscribers(self)
313
314
Note: See TracBrowser for help on using the browser.