class PrivateMessage(RenderableItem): class Meta(object): verbose_name = _('Private Message') verbose_name_plural = _('Private Messages') get_latest_by = 'sent' ordering = ['sent'] uuid = models.UUIDField(primary_key=True, default=uuid.uuid4) sender = models.ForeignKey(get_user_model_path(), related_name='sent_messages', verbose_name=_('Sender')) sender_ip = models.IPAddressField(_('Sender IP'), blank=True, default='0.0.0.0') sent = models.DateTimeField(_('Sent'), auto_now_add=True, db_index=True) subject = models.CharField(max_length=100, blank=True, default='[No Subject]') receivers = models.ManyToManyField(get_user_model_path(), through='MessageHandler', related_name='recd_messages', verbose_name=_('Recipients')) # Expected behaviour when deleting a sent message is that the recipient still has their copy sender_deleted = models.BooleanField(default=False) thread = models.ForeignKey(MessageThread, related_name='messages') def __str__(self): return '{0} {1}'.format(_('Private message from'), self.sender) def get_absolute_url(self): return reverse('private_messages:read_message', kwargs={'pk': self.uuid}) def save(self, *args, **kwargs): self.render() super(PrivateMessage, self).save(*args, **kwargs) def get_children(self): return self.thread.messages.filter(sent__gt=self.sent) def get_parent(self): try: parent = self.thread.messages.filter(sent__lt=self.sent).latest() except PrivateMessage.DoesNotExist: return None return parent def unread(self, user): try: handler = MessageHandler.objects.get(message=self, receiver=user) except MessageHandler.DoesNotExist: return False return not handler.read def get_parents(self): """ This method is just a hack for adding the Inbox to the breadcrumbs in read_message view. To get all the parents of a message, use message.thread.get_parents(message) """ return [_InboxLink()]
class Forum(models.Model): category = models.ForeignKey(Category, related_name='forums', verbose_name=_('Category')) parent = models.ForeignKey('self', related_name='child_forums', verbose_name=_('Parent forum'), blank=True, null=True) name = models.CharField(_('Name'), max_length=80) position = models.IntegerField(_('Position'), blank=True, default=0) description = models.TextField(_('Description'), blank=True) moderators = models.ManyToManyField(get_user_model_path(), blank=True, null=True, verbose_name=_('Moderators')) updated = models.DateTimeField(_('Updated'), blank=True, null=True) post_count = models.IntegerField(_('Post count'), blank=True, default=0) topic_count = models.IntegerField(_('Topic count'), blank=True, default=0) hidden = models.BooleanField(_('Hidden'), blank=False, null=False, default=False) readed_by = models.ManyToManyField(get_user_model_path(), through='ForumReadTracker', related_name='readed_forums') headline = models.TextField(_('Headline'), blank=True, null=True) class Meta(object): ordering = ['position'] verbose_name = _('Forum') verbose_name_plural = _('Forums') def __str__(self): return self.name def update_counters(self): posts = Post.objects.filter(topic__forum_id=self.id) self.post_count = posts.count() self.topic_count = Topic.objects.filter(forum=self).count() try: last_post = posts.order_by('-created', '-id')[0] self.updated = last_post.updated or last_post.created except IndexError: pass self.save() def get_absolute_url(self): return reverse('pybb:forum', kwargs={'pk': self.id}) @property def posts(self): return Post.objects.filter(topic__forum=self).select_related() @property def last_post(self): try: return self.posts.order_by('-created', '-id')[0] except IndexError: return None def get_parents(self): """ Used in templates for breadcrumb building """ parents = [self.category] parent = self.parent while parent is not None: parents.insert(1, parent) parent = parent.parent return parents
class ForumSubscription(models.Model): TYPE_NOTIFY = 1 TYPE_SUBSCRIBE = 2 TYPE_CHOICES = ( (TYPE_NOTIFY, _('be notified only when a new topic is added')), (TYPE_SUBSCRIBE, _('be auto-subscribed to topics')), ) user = models.ForeignKey(get_user_model_path(), on_delete=models.CASCADE, related_name='forum_subscriptions+', verbose_name=_('Subscriber')) forum = models.ForeignKey(Forum, on_delete=models.CASCADE, related_name='subscriptions+', verbose_name=_('Forum')) type = models.PositiveSmallIntegerField( _('Subscription type'), choices=TYPE_CHOICES, help_text= _(('The auto-subscription works like you manually subscribed to watch each topic :\n' 'you will be notified when a topic will receive an answer. \n' 'If you choose to be notified only when a new topic is added. It means' 'you will be notified only once when the topic is created : ' 'you won\'t be notified for the answers.')), ) class Meta(object): verbose_name = _('Subscription to forum') verbose_name_plural = _('Subscriptions to forums') unique_together = ( 'user', 'forum', ) def __str__(self): return '%(user)s\'s subscription to "%(forum)s"' % { 'user': self.user, 'forum': self.forum } def save(self, all_topics=False, **kwargs): if all_topics and self.type == self.TYPE_SUBSCRIBE: old = None if not self.pk else ForumSubscription.objects.get( pk=self.pk) if not old or old.type != self.type: topics = Topic.objects.filter(forum=self.forum).exclude( subscribers=self.user) self.user.subscriptions.add(*topics) super(ForumSubscription, self).save(**kwargs) def delete(self, all_topics=False, **kwargs): if all_topics: topics = Topic.objects.filter(forum=self.forum, subscribers=self.user) self.user.subscriptions.remove(*topics) super(ForumSubscription, self).delete(**kwargs)
class PollAnswerUser(models.Model): poll_answer = models.ForeignKey(PollAnswer, related_name='users', verbose_name=_('Poll answer')) user = models.ForeignKey(get_user_model_path(), related_name='poll_answers', verbose_name=_('User')) timestamp = models.DateTimeField(auto_now_add=True) class Meta: verbose_name = _('Poll answer user') verbose_name_plural = _('Polls answers users') unique_together = (('poll_answer', 'user', ), ) def __str__(self): return '%s - %s' % (self.poll_answer.topic, self.user)
class ForumReadTracker(models.Model): """ Save per user forum read tracking """ user = models.ForeignKey(get_user_model_path(), blank=False, null=False) forum = models.ForeignKey(Forum, blank=True, null=True) time_stamp = models.DateTimeField(auto_now=True) objects = ForumReadTrackerManager() class Meta(object): verbose_name = _('Forum read tracker') verbose_name_plural = _('Forum read trackers') unique_together = ('user', 'forum')
class TopicReadTracker(models.Model): """ Save per user topic read tracking """ user = models.ForeignKey(get_user_model_path(), on_delete=models.CASCADE, blank=False, null=False) topic = models.ForeignKey(Topic, on_delete=models.CASCADE, blank=True, null=True) time_stamp = models.DateTimeField(auto_now=True) objects = TopicReadTrackerManager() class Meta(object): verbose_name = _('Topic read tracker') verbose_name_plural = _('Topic read trackers') unique_together = ('user', 'topic')
class Profile(PybbProfile): """ Profile class that can be used if you doesn't have your site profile. """ user = AutoOneToOneField(get_user_model_path(), related_name='pybb_profile', verbose_name=_('User')) class Meta(object): verbose_name = _('Profile') verbose_name_plural = _('Profiles') def get_absolute_url(self): return reverse('pybb:user', kwargs={'username': getattr(self.user, get_username_field())}) def get_display_name(self): return self.user.get_username()
class CustomProfile(PybbProfile): user = models.OneToOneField( get_user_model_path(), verbose_name='linked account', related_name='pybb_customprofile', blank=False, null=False, ) class Meta(object): verbose_name = 'Profile' verbose_name_plural = 'Profiles' def get_absolute_url(self): return reverse( 'pybb:user', kwargs={'username': getattr(self.user, get_username_field())}) def get_display_name(self): return self.user.get_username()
# encoding: utf-8 import datetime from south.db import db from south.v2 import SchemaMigration from django.db import models from pybb.compat import get_image_field_full_name, get_user_model_path, get_user_frozen_models AUTH_USER = get_user_model_path() class Migration(SchemaMigration): def forwards(self, orm): # Adding field 'Category.hidden' db.add_column( 'pybb_category', 'hidden', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False) # Adding field 'Forum.hidden' db.add_column( 'pybb_forum', 'hidden', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False) def backwards(self, orm): # Deleting field 'Category.hidden' db.delete_column('pybb_category', 'hidden')
# -*- coding: utf-8 -*- from south.utils import datetime_utils as datetime from south.db import db from south.v2 import SchemaMigration from django.db import models from pybb.compat import get_image_field_full_name, get_user_model_path, get_user_frozen_models AUTH_USER = get_user_model_path() class Migration(SchemaMigration): def forwards(self, orm): if AUTH_USER == 'test_app.CustomUser': # Adding model 'CustomUser' db.create_table('test_app_customuser', ( ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), ('password', self.gf('django.db.models.fields.CharField')(max_length=128)), ('last_login', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)), ('is_superuser', self.gf('django.db.models.fields.BooleanField')(default=False)), ('username', self.gf('django.db.models.fields.CharField')(unique=True, max_length=100)), ('email', self.gf('django.db.models.fields.EmailField')(max_length=75)), ('is_staff', self.gf('django.db.models.fields.BooleanField')(default=False)), ('is_active', self.gf('django.db.models.fields.BooleanField')(default=True)), ('date_joined', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)), )) db.send_create_signal('test_app', ['CustomUser']) # Adding M2M table for field groups on 'CustomUser' m2m_table_name = db.shorten_name('test_app_customuser_groups')
class Post(RenderableItem): topic = models.ForeignKey(Topic, on_delete=models.CASCADE, related_name='posts', verbose_name=_('Topic')) user = models.ForeignKey(get_user_model_path(), on_delete=models.CASCADE, related_name='posts', verbose_name=_('User')) created = models.DateTimeField(_('Created'), blank=True, db_index=True) updated = models.DateTimeField(_('Updated'), blank=True, null=True, db_index=True) user_ip = models.GenericIPAddressField(_('User IP'), blank=True, null=True, default='0.0.0.0') on_moderation = models.BooleanField(_('On moderation'), default=False) class Meta(object): ordering = ['created'] verbose_name = _('Post') verbose_name_plural = _('Posts') def summary(self): limit = 50 tail = len(self.body) > limit and '...' or '' return self.body[:limit] + tail def __str__(self): return self.summary() @cached_property def is_topic_head(self): return self.pk and self.topic.head.pk == self.pk def save(self, *args, **kwargs): created_at = tznow() if self.created is None: self.created = created_at self.render() new = self.pk is None topic_changed = False old_post = None if not new: old_post = Post.objects.get(pk=self.pk) if old_post.topic != self.topic: topic_changed = True super(Post, self).save(*args, **kwargs) # If post is topic head and moderated, moderate topic too if self.topic.head == self and not self.on_moderation and self.topic.on_moderation: self.topic.on_moderation = False self.topic.update_counters() self.topic.forum.update_counters() if topic_changed: old_post.topic.update_counters() old_post.topic.forum.update_counters() def get_absolute_url(self): return reverse('pybb:post', kwargs={'pk': self.id}) def delete(self, *args, **kwargs): self_id = self.id head_post_id = self.topic.posts.order_by('created', 'id')[0].id if self_id == head_post_id: self.topic.delete() else: super(Post, self).delete(*args, **kwargs) self.topic.update_counters() self.topic.forum.update_counters() def get_parents(self): """ Used in templates for breadcrumb building """ return self.topic.forum.category, self.topic.forum, self.topic,
class Topic(models.Model): POLL_TYPE_NONE = 0 POLL_TYPE_SINGLE = 1 POLL_TYPE_MULTIPLE = 2 POLL_TYPE_CHOICES = ( (POLL_TYPE_NONE, _('None')), (POLL_TYPE_SINGLE, _('Single answer')), (POLL_TYPE_MULTIPLE, _('Multiple answers')), ) forum = models.ForeignKey(Forum, on_delete=models.CASCADE, related_name='topics', verbose_name=_('Forum')) name = models.CharField(_('Subject'), max_length=255) created = models.DateTimeField(_('Created'), null=True, db_index=True) updated = models.DateTimeField(_('Updated'), null=True, db_index=True) user = models.ForeignKey(get_user_model_path(), on_delete=models.CASCADE, verbose_name=_('User')) views = models.IntegerField(_('Views count'), blank=True, default=0) sticky = models.BooleanField(_('Sticky'), default=False) closed = models.BooleanField(_('Closed'), default=False) subscribers = models.ManyToManyField(get_user_model_path(), related_name='subscriptions', verbose_name=_('Subscribers'), blank=True) post_count = models.IntegerField(_('Post count'), blank=True, default=0) readed_by = models.ManyToManyField(get_user_model_path(), through='TopicReadTracker', related_name='readed_topics') on_moderation = models.BooleanField(_('On moderation'), default=False) poll_type = models.IntegerField(_('Poll type'), choices=POLL_TYPE_CHOICES, default=POLL_TYPE_NONE) poll_question = models.TextField(_('Poll question'), blank=True, null=True) slug = models.SlugField(verbose_name=_("Slug"), max_length=255) class Meta(object): ordering = ['-created'] verbose_name = _('Topic') verbose_name_plural = _('Topics') unique_together = ('forum', 'slug') def __str__(self): return self.name @cached_property def head(self): try: return self.posts.all().order_by('created', 'id')[0] except IndexError: return None @cached_property def last_post(self): try: return self.posts.order_by('-created', '-id').select_related('user')[0] except IndexError: return None def get_absolute_url(self): if defaults.PYBB_NICE_URL: return reverse('pybb:topic', kwargs={ 'slug': self.slug, 'forum_slug': self.forum.slug, 'category_slug': self.forum.category.slug }) return reverse('pybb:topic', kwargs={'pk': self.id}) def save(self, *args, **kwargs): if self.id is None: self.created = self.updated = tznow() forum_changed = False old_topic = None if self.id is not None: old_topic = Topic.objects.get(id=self.id) if self.forum != old_topic.forum: forum_changed = True super(Topic, self).save(*args, **kwargs) if forum_changed: old_topic.forum.update_counters() self.forum.update_counters() def delete(self, using=None): super(Topic, self).delete(using) self.forum.update_counters() def update_counters(self): self.post_count = self.posts.count() # force cache overwrite to get the real latest updated post if hasattr(self, 'last_post'): del self.last_post if self.last_post: self.updated = self.last_post.updated or self.last_post.created self.save() def get_parents(self): """ Used in templates for breadcrumb building """ parents = self.forum.get_parents() parents.append(self.forum) return parents def poll_votes(self): if self.poll_type != self.POLL_TYPE_NONE: return PollAnswerUser.objects.filter( poll_answer__topic=self).count() else: return None
class Topic(models.Model): POLL_TYPE_NONE = 0 POLL_TYPE_SINGLE = 1 POLL_TYPE_MULTIPLE = 2 POLL_TYPE_CHOICES = ( (POLL_TYPE_NONE, _('None')), (POLL_TYPE_SINGLE, _('Single answer')), (POLL_TYPE_MULTIPLE, _('Multiple answers')), ) forum = models.ForeignKey(Forum, related_name='topics', verbose_name=_('Forum')) name = models.CharField(_('Subject'), max_length=255) created = models.DateTimeField(_('Created'), null=True) updated = models.DateTimeField(_('Updated'), null=True) user = models.ForeignKey(get_user_model_path(), verbose_name=_('User')) views = models.IntegerField(_('Views count'), blank=True, default=0) sticky = models.BooleanField(_('Sticky'), blank=True, default=False) closed = models.BooleanField(_('Closed'), blank=True, default=False) subscribers = models.ManyToManyField(get_user_model_path(), related_name='subscriptions', verbose_name=_('Subscribers'), blank=True) post_count = models.IntegerField(_('Post count'), blank=True, default=0) readed_by = models.ManyToManyField(get_user_model_path(), through='TopicReadTracker', related_name='readed_topics') on_moderation = models.BooleanField(_('On moderation'), default=False) poll_type = models.IntegerField(_('Poll type'), choices=POLL_TYPE_CHOICES, default=POLL_TYPE_NONE) poll_question = models.TextField(_('Poll question'), blank=True, null=True) class Meta(object): ordering = ['-created'] verbose_name = _('Topic') verbose_name_plural = _('Topics') def __str__(self): return self.name @property def head(self): """ Get first post and cache it for request """ if not hasattr(self, "_head"): self._head = self.posts.all().order_by('created', 'id') if not len(self._head): return None return self._head[0] @property def last_post(self): if not getattr(self, '_last_post', None): self._last_post = self.posts.order_by('-created', '-id').select_related('user')[0] return self._last_post def get_absolute_url(self): return reverse('pybb:topic', kwargs={'pk': self.id}) def save(self, *args, **kwargs): if self.id is None: self.created = tznow() forum_changed = False old_topic = None if self.id is not None: old_topic = Topic.objects.get(id=self.id) if self.forum != old_topic.forum: forum_changed = True super(Topic, self).save(*args, **kwargs) if forum_changed: old_topic.forum.update_counters() self.forum.update_counters() def delete(self, using=None): super(Topic, self).delete(using) self.forum.update_counters() def update_counters(self): self.post_count = self.posts.count() last_post = Post.objects.filter(topic_id=self.id).order_by('-created', '-id')[0] self.updated = last_post.updated or last_post.created self.save() def get_parents(self): """ Used in templates for breadcrumb building """ parents = self.forum.get_parents() parents.append(self.forum) return parents def poll_votes(self): if self.poll_type != self.POLL_TYPE_NONE: return PollAnswerUser.objects.filter(poll_answer__topic=self).count() else: return None
class MessageHandler(models.Model): message = models.ForeignKey('PrivateMessage') receiver = models.ForeignKey(get_user_model_path()) read = models.BooleanField(default=False) deleted = models.BooleanField(default=False)