예제 #1
0
class PrivateTopic(models.Model):
    """
    Topic private, containing private posts.
    """

    class Meta:
        verbose_name = u'Message privé'
        verbose_name_plural = u'Messages privés'

    title = models.CharField(u'Titre', max_length=130)
    subtitle = models.CharField(u'Sous-titre', max_length=200, blank=True)
    author = models.ForeignKey(User, verbose_name=u'Auteur', related_name='author', db_index=True)
    participants = models.ManyToManyField(User, verbose_name=u'Participants', related_name='participants',
                                          db_index=True)
    last_message = models.ForeignKey('PrivatePost', null=True, related_name='last_message',
                                     verbose_name=u'Dernier message')
    pubdate = models.DateTimeField(u'Date de création', auto_now_add=True, db_index=True)
    objects = PrivateTopicManager()

    def __unicode__(self):
        """
        Human-readable representation of the PrivateTopic model.

        :return: PrivateTopic title
        :rtype: unicode
        """
        return self.title

    def get_absolute_url(self):
        """
        URL of a single PrivateTopic object.

        :return: PrivateTopic object URL
        :rtype: str
        """
        return reverse('private-posts-list', args=[self.pk, self.slug()])

    def slug(self):
        """
        PrivateTopic doesn't have a slug attribute of a private topic. To be compatible
        with older private topic, the slug is always re-calculate when we need one.
        :return: title slugify.
        """
        return slugify(self.title)

    def get_post_count(self):
        """
        Get the number of private posts in a single PrivateTopic object.

        :return: number of post in PrivateTopic object
        :rtype: int
        """
        return PrivatePost.objects.filter(privatetopic__pk=self.pk).count()

    def get_last_answer(self):
        """
        Get the last answer in the PrivateTopic written by topic's author, if exists.

        :return: PrivateTopic object last answer (PrivatePost)
        :rtype: PrivatePost object or None
        """
        last_post = PrivatePost.objects \
            .filter(privatetopic__pk=self.pk) \
            .order_by('-position_in_topic') \
            .first()

        # If the last post is the first post, there is no answer in the topic (only initial post)
        if last_post == self.first_post():
            return None

        return last_post

    def first_post(self):
        """
        Get the first answer in the PrivateTopic written by topic's author, if exists.

        :return: PrivateTopic object first answer (PrivatePost)
        :rtype: PrivatePost object or None
        """
        return PrivatePost.objects \
            .filter(privatetopic=self) \
            .order_by('position_in_topic') \
            .first()

    def last_read_post(self, user=None):
        """
        Get the last PrivatePost the user has read.

        :param user: The user is reading the PrivateTopic. If None, the current user is used.
        :type user: User object
        :return: last PrivatePost read
        :rtype: PrivatePost object or None
        """
        # If user param is not defined, we get the current user
        if user is None:
            user = get_current_user()

        try:
            post = PrivateTopicRead.objects \
                .select_related() \
                .filter(privatetopic=self, user=user)
            if len(post) == 0:
                return self.first_post()
            return post.latest('privatepost__position_in_topic').privatepost

        except (PrivatePost.DoesNotExist, TypeError):
            return self.first_post()

    def first_unread_post(self, user=None):
        """
        Get the first PrivatePost the user has unread.

        :param user: The user is reading the PrivateTopic. If None, the current user is used.
        :type user: User object
        :return: first PrivatePost unread
        :rtype: PrivatePost object or None
        """
        # If user param is not defined, we get the current user
        if user is None:
            user = get_current_user()

        try:
            last_post = PrivateTopicRead.objects \
                .select_related() \
                .filter(privatetopic=self, user=user) \
                .latest('privatepost__position_in_topic').privatepost

            next_post = PrivatePost.objects.filter(
                privatetopic__pk=self.pk,
                position_in_topic__gt=last_post.position_in_topic).first()

            return next_post
        except (PrivatePost.DoesNotExist, PrivateTopicRead.DoesNotExist):
            return self.first_post()

    def alone(self):
        """
        Check if there just one participant in the conversation (PrivateTopic).

        :return: True if there just one participant in PrivateTopic
        :rtype: bool
        """
        return self.participants.count() == 0

    def is_unread(self, user=None):
        """
        Check if an user has never read the current PrivateTopic.

        :param user: an user as Django User object. If None, the current user is used.
        :type user: User object
        :return: True if the PrivateTopic was never read
        :rtype: bool
        """
        # If user param is not defined, we get the current user
        if user is None:
            user = get_current_user()

        return is_privatetopic_unread(self, user)

    def is_author(self, user):
        """
        Check if the user given is the author of the private topic.

        :param user: User given.
        :return: true if the user is the author.
        """
        return self.author == user

    def is_participant(self, user):
        """
        Check if the user given is in participants or author of the private topic.

        :param user: User given.
        :return: true if the user is in participants
        """
        return self.author == user or user in self.participants.all()

    @staticmethod
    def has_read_permission(request):
        return request.user.is_authenticated()

    def has_object_read_permission(self, request):
        return PrivateTopic.has_read_permission(request) and self.is_participant(request.user)

    @staticmethod
    def has_write_permission(request):
        return request.user.is_authenticated()

    def has_object_write_permission(self, request):
        return PrivateTopic.has_write_permission(request) and self.is_participant(request.user)

    def has_object_update_permission(self, request):
        return PrivateTopic.has_write_permission(request) and self.is_author(request.user)
예제 #2
0
class PrivateTopic(models.Model):
    """
    Private topic, containing private posts.

    We maintain the following invariants :
        * all participants are reachable,
        * no duplicate participant.

    A participant is either the author or a mere participant.
    """
    class Meta:
        verbose_name = "Message privé"
        verbose_name_plural = "Messages privés"

    title = models.CharField("Titre", max_length=130)
    subtitle = models.CharField("Sous-titre", max_length=200, blank=True)
    author = models.ForeignKey(User,
                               verbose_name="Auteur",
                               related_name="author",
                               db_index=True,
                               on_delete=models.SET_NULL,
                               null=True)
    participants = models.ManyToManyField(User,
                                          verbose_name="Participants",
                                          related_name="participants",
                                          db_index=True)
    last_message = models.ForeignKey("PrivatePost",
                                     null=True,
                                     related_name="last_message",
                                     verbose_name="Dernier message",
                                     on_delete=models.SET_NULL)
    pubdate = models.DateTimeField("Date de création",
                                   auto_now_add=True,
                                   db_index=True)
    objects = PrivateTopicManager()

    @staticmethod
    def create(title, subtitle, author, recipients):
        limit = PrivateTopic._meta.get_field("title").max_length
        topic = PrivateTopic()
        topic.title = title[:limit]
        topic.subtitle = subtitle
        topic.pubdate = datetime.now()
        topic.author = author
        topic.save()

        for participant in recipients:
            topic.add_participant(participant, silent=True)
        topic.save()

        return topic

    def __str__(self):
        """
        Human-readable representation of the PrivateTopic model.

        :return: PrivateTopic title
        :rtype: unicode
        """
        return self.title

    def get_absolute_url(self):
        """
        URL of a single PrivateTopic object.

        :return: PrivateTopic object URL
        :rtype: str
        """
        return reverse("private-posts-list", args=[self.pk, self.slug()])

    def slug(self):
        """
        PrivateTopic doesn't have a slug attribute of a private topic. To be compatible
        with older private topic, the slug is always re-calculated when we need one.
        :return: title slugify.
        """
        return old_slugify(self.title)

    def get_post_count(self):
        """
        Get the number of private posts in a single PrivateTopic object.

        :return: number of posts in PrivateTopic object
        :rtype: int
        """
        return PrivatePost.objects.filter(privatetopic__pk=self.pk).count()

    def get_last_answer(self):
        """
        Get the last answer in the PrivateTopic written by topic's author, if exists.

        :return: PrivateTopic object last answer (PrivatePost)
        :rtype: PrivatePost object or None
        """
        last_post = PrivatePost.objects.filter(
            privatetopic__pk=self.pk).order_by("-position_in_topic").first()

        # If the last post is the first post, there is no answer in the topic (only initial post)
        if last_post == self.first_post():
            return None

        return last_post

    def first_post(self):
        """
        Get the first answer in the PrivateTopic written by topic's author, if exists.

        :return: PrivateTopic object first answer (PrivatePost)
        :rtype: PrivatePost object or None
        """
        return PrivatePost.objects.filter(
            privatetopic=self).order_by("position_in_topic").first()

    def last_read_post(self, user=None):
        """
        Get the last PrivatePost the user has read.

        :param user: The user is reading the PrivateTopic. If None, the current user is used.
        :type user: User object
        :return: last PrivatePost read
        :rtype: PrivatePost object or None
        """
        # If user param is not defined, we get the current user
        if user is None:
            user = get_current_user()

        try:
            post = PrivateTopicRead.objects.select_related().filter(
                privatetopic=self, user=user)
            if len(post) == 0:
                return self.first_post()
            return post.latest("privatepost__position_in_topic").privatepost

        except (PrivatePost.DoesNotExist, TypeError):
            return self.first_post()

    def first_unread_post(self, user=None):
        """
        Get the first PrivatePost the user has unread.

        :param user: The user is reading the PrivateTopic. If None, the current user is used.
        :type user: User object
        :return: first PrivatePost unread
        :rtype: PrivatePost object or None
        """
        # If user param is not defined, we get the current user
        if user is None:
            user = get_current_user()

        try:
            last_post = (PrivateTopicRead.objects.select_related().filter(
                privatetopic=self,
                user=user).latest("privatepost__position_in_topic").privatepost
                         )

            next_post = PrivatePost.objects.filter(
                privatetopic__pk=self.pk,
                position_in_topic__gt=last_post.position_in_topic).first()

            return next_post
        except (PrivatePost.DoesNotExist, PrivateTopicRead.DoesNotExist):
            return self.first_post()

    def resolve_last_read_post_absolute_url(self, user=None):
        """resolve the url that leads to the last post the current user has read.

        :return: the url
        :rtype: str
        """
        if user is None:
            user = get_current_user()

        try:
            pk, pos = self.resolve_last_post_pk_and_pos_read_by_user(user)
            page_nb = 1
            if pos > settings.ZDS_APP["forum"]["posts_per_page"]:
                page_nb += (pos -
                            1) // settings.ZDS_APP["forum"]["posts_per_page"]
            return f"{self.get_absolute_url()}?page={page_nb}#p{pk}"
        except PrivateTopicRead.DoesNotExist:
            return self.first_unread_post().get_absolute_url()

    def resolve_last_post_pk_and_pos_read_by_user(self, user):
        """Determine the primary ey of position of the last post read by a user.

        :param user: the current (authenticated) user. Please do not try with unauthenticated user, il would lead to a \
        useless request.
        :return: the primary key
        :rtype: int
        """
        t_read = (
            PrivateTopicRead.objects.select_related("privatepost").filter(
                privatetopic__pk=self.pk,
                user__pk=user.pk).latest("privatepost__position_in_topic"))
        if t_read:
            return t_read.privatepost.pk, t_read.privatepost.position_in_topic
        return list(
            PrivatePost.objects.filter(
                topic__pk=self.pk).order_by("position").values(
                    "pk", "position").first().values())

    def one_participant_remaining(self):
        """
        Check if there is only one participant remaining in the private topic.

        :return: True if there is only one participant remaining, False otherwise.
        :rtype: bool
        """
        return self.participants.count() == 0

    def is_unread(self, user=None):
        """
        Check if a user has never read the current PrivateTopic.

        :param user: a user as Django User object. If None, the current user is used.
        :type user: User object
        :return: True if the PrivateTopic was never read
        :rtype: bool
        """
        # If user param is not defined, we get the current user
        if user is None:
            user = get_current_user()

        return is_privatetopic_unread(self, user)

    def is_author(self, user):
        """
        Check if a user is the author of the private topic.

        :param user: a given user.
        :return: True if the user is the author, False otherwise.
        """
        return self.author == user

    def set_as_author(self, user):
        """
        Set a participant as the author of the private topic.

        The previous author becomes a mere participant. If the user is already the author, nothing happens.

        :param user: a given user.
        :raise NotParticipatingError: if the user is not already participating in the private topic.
        """
        if not self.is_participant(user):
            raise NotParticipatingError
        if not self.is_author(
                user):  # nothing to do if user is already the author
            self.participants.add(self.author)
            self.participants.remove(user)
            self.author = user

    def is_participant(self, user):
        """
        Check if a given user is participating in the private topic.

        :param user: a given user.
        :return: True if the user is the author or a mere participant, False otherwise.
        """
        return self.is_author(user) or user in self.participants.all()

    def add_participant(self, user, silent=False):
        """
        Add a participant to the private topic.
        If the user is already participating, do nothing.
        Send the `participant_added` signal if successful.

        :param user: the user to add to the private topic
        :param silent: specify if the `participant_added` signal should be silent (e.g. no notification)
        :raise NotReachableError: if the user cannot receive private messages (e.g. a bot)
        """
        if not is_reachable(user):
            raise NotReachableError
        if not self.is_participant(user):
            self.participants.add(user)
            signals.participant_added.send(sender=PrivateTopic,
                                           topic=self,
                                           silent=silent)

    def remove_participant(self, user):
        """
        Remove a participant from the private topic.
        If the removed participant is the author, set the first mere participant as the author.
        If the given user is not a participant, do nothing.
        Send the `participant_removed` signal if successful.

        :param user: the user to remove from the private topic.
        """
        if self.is_participant(user):
            if self.is_author(user):
                self.set_as_author(self.participants.first())
            self.participants.remove(user)
            signals.participant_removed.send(sender=PrivateTopic, topic=self)

    @staticmethod
    def has_read_permission(request):
        return request.user.is_authenticated

    def has_object_read_permission(self, request):
        return PrivateTopic.has_read_permission(
            request) and self.is_participant(request.user)

    @staticmethod
    def has_write_permission(request):
        return request.user.is_authenticated

    def has_object_write_permission(self, request):
        return PrivateTopic.has_write_permission(
            request) and self.is_participant(request.user)

    def has_object_update_permission(self, request):
        return PrivateTopic.has_write_permission(request) and self.is_author(
            request.user)