ejplatform/ej-conversations

View on GitHub
src/ej_conversations/models/limits.py

Summary

Maintainability
A
0 mins
Test Coverage
from django.db import models
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _

from ..utils import CommentLimitStatus


class Limits(models.Model):
    """
    Configure the allowed rate users can post and vote on comments.
    """

    name = models.CharField(
        _('Name'),
        max_length=140,
        unique=True,
        help_text=_(
            'A memorable description of your limit configuration. The '
            'description is used to reference this configuration in other '
            'conversation objects.'
        ),
    )
    interval = models.IntegerField(
        _('Reference interval (in seconds)'),
        default=10 * 60,
        help_text=_(
            'We avoid spam and bots by preventing users for posting too many '
            'comments or votes in the given interval.'
        ),
    )
    max_comments_in_interval = models.IntegerField(
        _('Maximum number of comments'),
        default=1,
        help_text=_(
            'Users can post at most this number of comments in the reference '
            'interval.'
        ),
    )
    max_comments_per_conversation = models.IntegerField(
        _('Maximum number of comments (global)'),
        default=2,
        help_text=_(
            'Limit the number of comments in a single conversation.'
        ),
    )
    max_votes_in_interval = models.IntegerField(
        _('Maximum number of votes'),
        default=100,
        help_text=_(
            'Limit the number of votes. Usually this should be a much higher '
            'number than the number of comments limit.'
        ),
    )
    max_votes_per_conversation = models.IntegerField(
        _('Maximum number of votes (global)'),
        blank=True, null=True,
        help_text=_(
            'Limit the number of votes in a single conversation. No limit is'
            'enforced by default.'
        ),
    )
    timedelta = property(lambda self: timezone.timedelta(seconds=self.interval))

    class Meta:
        verbose_name_plural = _('Usage limits')

    def __str__(self):
        return self.name

    def user_status(self, user, conversation):
        """
        Verify the limits applied to a user in a conversation.
        """

        n_total = self.remaining_comments(user, conversation)
        if n_total == 0:
            return CommentLimitStatus.BLOCKED

        n_interval = self.remaining_comments(user, conversation, interval=True)
        if n_interval == 0:
            return CommentLimitStatus.TEMPORARILY_BLOCKED
        elif n_interval == 1 or n_total == 1:
            return CommentLimitStatus.ALERT
        else:
            return CommentLimitStatus.OK

    def remaining_comments(self, user, conversation, interval=False):
        """
        Return the number of comments a user can still post in a conversation.

        If interval=True, compute only comments still allowed in the reference
        interval rather than the global comment limit.
        """
        if interval:
            start_time = self._now() - self.timedelta
            filter = dict(conversation_id=conversation.id, created__gte=start_time)
            comments = user.comments.filter(**filter).count()
            return max(self.max_comments_in_interval - comments, 0)
        else:
            filter = dict(conversation_id=conversation.id)
            comments = user.comments.filter(**filter).count()
            return max(self.max_comments_per_conversation - comments, 0)

    @staticmethod
    def _now():
        # Useful for mocking
        return timezone.now()