annotate gpp/forums/models.py @ 107:e94398f5e027

Forums: implemented post delete feature.
author Brian Neal <bgneal@gmail.com>
date Tue, 22 Sep 2009 03:36:39 +0000
parents e67c4dd98db5
children d1b0b86441c0
rev   line source
bgneal@75 1 """
bgneal@75 2 Models for the forums application.
bgneal@75 3 """
bgneal@102 4 import datetime
bgneal@102 5
bgneal@75 6 from django.db import models
bgneal@100 7 from django.db.models import Q
bgneal@75 8 from django.contrib.auth.models import User, Group
bgneal@75 9 from django.template.loader import render_to_string
bgneal@75 10
bgneal@75 11
bgneal@75 12 class Category(models.Model):
bgneal@100 13 """
bgneal@100 14 Forums belong to a category, whose access may be assigned to groups.
bgneal@100 15 """
bgneal@75 16 name = models.CharField(max_length=80)
bgneal@75 17 slug = models.SlugField(max_length=80)
bgneal@75 18 position = models.IntegerField(blank=True, default=0)
bgneal@75 19 groups = models.ManyToManyField(Group, blank=True, null=True,
bgneal@75 20 help_text="If groups are assigned to this category, only members" \
bgneal@75 21 " of those groups can view this category.")
bgneal@75 22
bgneal@75 23 class Meta:
bgneal@75 24 ordering = ('position', )
bgneal@75 25 verbose_name_plural = 'Categories'
bgneal@75 26
bgneal@75 27 def __unicode__(self):
bgneal@75 28 return self.name
bgneal@75 29
bgneal@100 30 def can_access(self, user):
bgneal@100 31 """
bgneal@100 32 Checks to see if the given user has permission to access
bgneal@100 33 this category.
bgneal@100 34 If this category has no groups assigned to it, return true.
bgneal@100 35 Else, return true if the user belongs to a group that has been
bgneal@100 36 assigned to this category, and false otherwise.
bgneal@100 37 """
bgneal@100 38 if self.groups.count() == 0:
bgneal@100 39 return True
bgneal@100 40 if user.is_authenticated():
bgneal@100 41 return self.groups.filter(user__pk=user.id).count() > 0
bgneal@100 42 return False
bgneal@100 43
bgneal@100 44
bgneal@100 45 class ForumManager(models.Manager):
bgneal@100 46 """
bgneal@100 47 The manager for the Forum model. Provides a centralized place to
bgneal@100 48 put commonly used and useful queries.
bgneal@100 49 """
bgneal@100 50
bgneal@100 51 def forums_for_user(self, user):
bgneal@100 52 """
bgneal@100 53 Returns a queryset containing the forums that the given user can
bgneal@100 54 "see" due to authenticated status, superuser status and group membership.
bgneal@100 55 """
bgneal@100 56 if user.is_superuser:
bgneal@100 57 qs = self.all()
bgneal@100 58 else:
bgneal@100 59 user_groups = []
bgneal@100 60 if user.is_authenticated():
bgneal@100 61 user_groups = user.groups.all()
bgneal@100 62
bgneal@100 63 qs = self.filter(Q(category__groups__isnull=True) | \
bgneal@100 64 Q(category__groups__in=user_groups))
bgneal@100 65
bgneal@100 66 return qs.select_related('category', 'last_post', 'last_post__user')
bgneal@100 67
bgneal@75 68
bgneal@75 69 class Forum(models.Model):
bgneal@100 70 """
bgneal@100 71 A forum is a collection of topics.
bgneal@100 72 """
bgneal@75 73 category = models.ForeignKey(Category, related_name='forums')
bgneal@75 74 name = models.CharField(max_length=80)
bgneal@75 75 slug = models.SlugField(max_length=80)
bgneal@75 76 description = models.TextField(blank=True, default='')
bgneal@75 77 position = models.IntegerField(blank=True, default=0)
bgneal@75 78 moderators = models.ManyToManyField(Group, blank=True, null=True)
bgneal@75 79
bgneal@75 80 # denormalized fields to reduce database hits
bgneal@75 81 topic_count = models.IntegerField(blank=True, default=0)
bgneal@75 82 post_count = models.IntegerField(blank=True, default=0)
bgneal@75 83 last_post = models.OneToOneField('Post', blank=True, null=True,
bgneal@75 84 related_name='parent_forum')
bgneal@75 85
bgneal@100 86 objects = ForumManager()
bgneal@100 87
bgneal@75 88 class Meta:
bgneal@75 89 ordering = ('position', )
bgneal@75 90
bgneal@75 91 def __unicode__(self):
bgneal@75 92 return self.name
bgneal@75 93
bgneal@81 94 @models.permalink
bgneal@81 95 def get_absolute_url(self):
bgneal@81 96 return ('forums-forum_index', [self.slug])
bgneal@81 97
bgneal@75 98 def topic_count_update(self):
bgneal@75 99 """Call to notify the forum that its topic count has been updated."""
bgneal@75 100 self.topic_count = Topic.objects.filter(forum=self).count()
bgneal@75 101
bgneal@75 102 def post_count_update(self):
bgneal@75 103 """Call to notify the forum that its post count has been updated."""
bgneal@75 104 my_posts = Post.objects.filter(topic__forum=self)
bgneal@75 105 self.post_count = my_posts.count()
bgneal@75 106 if self.post_count > 0:
bgneal@75 107 self.last_post = my_posts[self.post_count - 1]
bgneal@75 108 else:
bgneal@75 109 self.last_post = None
bgneal@75 110
bgneal@107 111 def last_post_pre_delete(self):
bgneal@107 112 """
bgneal@107 113 Call this function prior to deleting the last post in the forum.
bgneal@107 114 A new last post will be found, if one exists.
bgneal@107 115 This is to avoid the Django cascading delete issue.
bgneal@107 116 """
bgneal@107 117 try:
bgneal@107 118 self.last_post = \
bgneal@107 119 Post.objects.filter(topic__forum=self).exclude(pk=self.last_post.pk).latest()
bgneal@107 120 except Post.DoesNotExist:
bgneal@107 121 self.last_post = None
bgneal@107 122
bgneal@75 123
bgneal@75 124 class Topic(models.Model):
bgneal@100 125 """
bgneal@100 126 A topic is a thread of discussion, consisting of a series of posts.
bgneal@100 127 """
bgneal@75 128 forum = models.ForeignKey(Forum, related_name='topics')
bgneal@75 129 name = models.CharField(max_length=255)
bgneal@75 130 creation_date = models.DateTimeField(auto_now_add=True)
bgneal@75 131 user = models.ForeignKey(User)
bgneal@75 132 view_count = models.IntegerField(blank=True, default=0)
bgneal@75 133 sticky = models.BooleanField(blank=True, default=False)
bgneal@75 134 locked = models.BooleanField(blank=True, default=False)
bgneal@75 135
bgneal@75 136 # denormalized fields to reduce database hits
bgneal@75 137 post_count = models.IntegerField(blank=True, default=0)
bgneal@102 138 update_date = models.DateTimeField()
bgneal@75 139 last_post = models.OneToOneField('Post', blank=True, null=True,
bgneal@75 140 related_name='parent_topic')
bgneal@75 141
bgneal@75 142 class Meta:
bgneal@75 143 ordering = ('-sticky', '-update_date', )
bgneal@75 144
bgneal@75 145 def __unicode__(self):
bgneal@75 146 return self.name
bgneal@75 147
bgneal@82 148 @models.permalink
bgneal@82 149 def get_absolute_url(self):
bgneal@82 150 return ('forums-topic_index', [self.pk])
bgneal@82 151
bgneal@75 152 def post_count_update(self):
bgneal@75 153 """
bgneal@75 154 Call this function to notify the topic instance that its post count
bgneal@75 155 has changed.
bgneal@75 156 """
bgneal@75 157 my_posts = Post.objects.filter(topic=self)
bgneal@75 158 self.post_count = my_posts.count()
bgneal@75 159 if self.post_count > 0:
bgneal@75 160 self.last_post = my_posts[self.post_count - 1]
bgneal@75 161 self.update_date = self.last_post.creation_date
bgneal@75 162 else:
bgneal@75 163 self.last_post = None
bgneal@75 164 self.update_date = self.creation_date
bgneal@75 165
bgneal@83 166 def reply_count(self):
bgneal@83 167 """
bgneal@83 168 Returns the number of replies to a topic. The first post
bgneal@83 169 doesn't count as a reply.
bgneal@83 170 """
bgneal@83 171 if self.post_count > 1:
bgneal@83 172 return self.post_count - 1
bgneal@83 173 return 0
bgneal@83 174
bgneal@102 175 def save(self, *args, **kwargs):
bgneal@102 176 if not self.id:
bgneal@102 177 now = datetime.datetime.now()
bgneal@102 178 self.creation_date = now
bgneal@102 179 self.update_date = now
bgneal@102 180
bgneal@102 181 super(Topic, self).save(*args, **kwargs)
bgneal@102 182
bgneal@107 183 def last_post_pre_delete(self):
bgneal@107 184 """
bgneal@107 185 Call this function prior to deleting the last post in the topic.
bgneal@107 186 A new last post will be found, if one exists.
bgneal@107 187 This is to avoid the Django cascading delete issue.
bgneal@107 188 """
bgneal@107 189 try:
bgneal@107 190 self.last_post = \
bgneal@107 191 Post.objects.filter(topic=self).exclude(pk=self.last_post.pk).latest()
bgneal@107 192 except Post.DoesNotExist:
bgneal@107 193 self.last_post = None
bgneal@107 194
bgneal@75 195
bgneal@75 196 class Post(models.Model):
bgneal@100 197 """
bgneal@100 198 A post is an instance of a user's single contribution to a topic.
bgneal@100 199 """
bgneal@75 200 topic = models.ForeignKey(Topic, related_name='posts')
bgneal@75 201 user = models.ForeignKey(User, related_name='posts')
bgneal@75 202 creation_date = models.DateTimeField(auto_now_add=True)
bgneal@75 203 update_date = models.DateTimeField(auto_now=True)
bgneal@75 204 body = models.TextField()
bgneal@75 205 html = models.TextField()
bgneal@83 206 user_ip = models.IPAddressField(blank=True, default='', null=True)
bgneal@75 207
bgneal@75 208 class Meta:
bgneal@97 209 ordering = ('creation_date', )
bgneal@107 210 get_latest_by = 'creation_date'
bgneal@75 211
bgneal@91 212 @models.permalink
bgneal@91 213 def get_absolute_url(self):
bgneal@91 214 return ('forums-goto_post', [self.pk])
bgneal@91 215
bgneal@75 216 def summary(self):
bgneal@75 217 LIMIT = 50
bgneal@75 218 if len(self.body) < LIMIT:
bgneal@75 219 return self.body
bgneal@75 220 return self.body[:LIMIT] + '...'
bgneal@75 221
bgneal@75 222 def __unicode__(self):
bgneal@75 223 return self.summary()
bgneal@75 224
bgneal@75 225 def save(self, *args, **kwargs):
bgneal@75 226 html = render_to_string('forums/post.html', {'data': self.body})
bgneal@75 227 self.html = html.strip()
bgneal@75 228 super(Post, self).save(*args, **kwargs)
bgneal@75 229
bgneal@75 230 def delete(self, *args, **kwargs):
bgneal@75 231 first_post_id = self.topic.posts.all()[0].id
bgneal@75 232 super(Post, self).delete(*args, **kwargs)
bgneal@75 233 if self.id == first_post_id:
bgneal@75 234 self.topic.delete()
bgneal@75 235
bgneal@98 236
bgneal@98 237 class FlaggedPost(models.Model):
bgneal@98 238 """This model represents a user flagging a post as inappropriate."""
bgneal@98 239 user = models.ForeignKey(User)
bgneal@98 240 post = models.ForeignKey(Post)
bgneal@98 241 flag_date = models.DateTimeField(auto_now_add=True)
bgneal@98 242
bgneal@98 243 def __unicode__(self):
bgneal@98 244 return u'Post ID %s flagged by %s' % (self.post.id, self.user.username)
bgneal@98 245
bgneal@98 246 class Meta:
bgneal@98 247 ordering = ('flag_date', )
bgneal@98 248
bgneal@98 249 def get_post_url(self):
bgneal@98 250 return '<a href="%s">Post</a>' % self.post.get_absolute_url()
bgneal@98 251 get_post_url.allow_tags = True
bgneal@98 252
bgneal@75 253 # TODO: A "read" table