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()]
Exemple #2
0
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
Exemple #3
0
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)
Exemple #4
0
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)
Exemple #5
0
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')
Exemple #6
0
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')
Exemple #7
0
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()
Exemple #8
0
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')
Exemple #10
0
# -*- 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')
Exemple #11
0
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,
Exemple #12
0
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
Exemple #13
0
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)