예제 #1
0
class NotificationConfig(models.Model):
    user = models.OneToOneField(
        settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="notification_options"
    )
    notification_option = models.EnumField(
        NotificationMode, _("Notification options"), default=NotificationMode.ENABLED
    )
예제 #2
0
class FragmentLock(models.Model):
    """
    ForeignKey reference that prevents protected fragments from being deleted
    from the database.
    """

    fragment = models.OneToOneField(Fragment,
                                    on_delete=models.PROTECT,
                                    related_name="lock_ref")
예제 #3
0
class ConversationProgress(ProgressBase):
    """
    Tracks activity in conversation.
    """

    conversation = models.OneToOneField(
        "ej_conversations.Conversation",
        related_name="progress",
        on_delete=models.CASCADE,
    )
    conversation_level = models.EnumField(ConversationLevel,
                                          default=CommenterLevel.NONE)
    max_conversation_level = models.EnumField(ConversationLevel,
                                              default=CommenterLevel.NONE)

    # Non de-normalized fields: conversations
    n_votes = delegate_to("conversation")
    n_comments = delegate_to("conversation")
    n_rejected_comments = delegate_to("conversation")
    n_participants = delegate_to("conversation")
    n_favorites = delegate_to("conversation")
    n_tags = delegate_to("conversation")

    # Clusterization
    n_clusters = delegate_to("conversation")
    n_stereotypes = delegate_to("conversation")

    # Gamification
    n_endorsements = delegate_to("conversation")

    # Signals
    level_achievement_signal = lazy(
        lambda _: signals.conversation_level_achieved, shared=True)

    class Meta:
        verbose_name_plural = _("Conversation progress list")

    def __str__(self):
        return __('Progress for "{conversation}"').format(
            conversation=self.conversation)

    def compute_score(self):
        """
        Compute the total number of points for user contribution.

        Conversation score is based on the following rules:
            * Vote: 1 points
            * Accepted comment: 2 points
            * Rejected comment: -3 points
            * Endorsement created: 3 points

        Returns:
            Total score (int)
        """
        return (self.score_bias + self.n_votes + 2 * self.n_comments -
                3 * self.n_rejected_comments + 3 * self.n_endorsements)
예제 #4
0
class UserProgress(ProgressBase):
    """
    Tracks global user evolution.
    """

    user = models.OneToOneField(settings.AUTH_USER_MODEL,
                                related_name="progress",
                                on_delete=models.CASCADE)
    commenter_level = models.EnumField(CommenterLevel,
                                       default=CommenterLevel.NONE)
    max_commenter_level = models.EnumField(CommenterLevel,
                                           default=CommenterLevel.NONE)
    host_level = models.EnumField(HostLevel, default=HostLevel.NONE)
    max_host_level = models.EnumField(HostLevel, default=HostLevel.NONE)
    profile_level = models.EnumField(ProfileLevel, default=ProfileLevel.NONE)
    max_profile_level = models.EnumField(ProfileLevel,
                                         default=ProfileLevel.NONE)

    # Non de-normalized fields: conversations app
    n_conversations = delegate_to("user")
    n_comments = delegate_to("user")
    n_rejected_comments = delegate_to("user")
    n_votes = delegate_to("user")

    # Gamification app
    n_endorsements = delegate_to("user")
    n_given_opinion_bridge_powers = delegate_to("user")
    n_given_minority_activist_powers = delegate_to("user")

    # Level of conversations
    def _level_checker(*args):
        *_, lvl = args  # ugly trick to make static analysis happy
        return lazy(lambda p: p.user.conversations.filter(
            progress__conversation_level=lvl).count())

    n_conversation_lvl_1 = _level_checker(ConversationLevel.ALIVE)
    n_conversation_lvl_2 = _level_checker(ConversationLevel.ENGAGING)
    n_conversation_lvl_3 = _level_checker(ConversationLevel.NOTEWORTHY)
    n_conversation_lvl_4 = _level_checker(ConversationLevel.ENGAGING)
    del _level_checker

    # Aggregators
    total_conversation_score = delegate_to("user")
    total_participation_score = delegate_to("user")

    # Signals
    level_achievement_signal = lazy(lambda _: signals.user_level_achieved,
                                    shared=True)

    n_trophies = 0

    class Meta:
        verbose_name_plural = _("User progress list")

    def __str__(self):
        return __("Progress for {user}").format(user=self.user)

    def compute_score(self):
        """
        Compute the total number of points earned by user.

        User score is based on the following rules:
            * Vote: 10 points
            * Accepted comment: 30 points
            * Rejected comment: -30 points
            * Endorsement received: 15 points
            * Opinion bridge: 50 points
            * Minority activist: 50 points
            * Plus the total score of created conversations.

        Returns:
            Total score (int)
        """
        return (self.score_bias + 10 * self.n_votes + 30 * self.n_comments -
                30 * self.n_rejected_comments + 15 * self.n_endorsements +
                50 * self.n_given_opinion_bridge_powers +
                50 * self.n_given_minority_activist_powers +
                self.total_conversation_score)
예제 #5
0
class Clusterization(TimeStampedModel):
    """
    Manages clusterization tasks for a given conversation.
    """

    conversation = models.OneToOneField(
        "ej_conversations.Conversation",
        on_delete=models.CASCADE,
        related_name="clusterization",
    )
    cluster_status = EnumField(ClusterStatus, default=ClusterStatus.PENDING_DATA)
    pending_comments = models.ManyToManyField(
        "ej_conversations.Comment",
        related_name="pending_in_clusterizations",
        editable=False,
        blank=True,
    )
    pending_votes = models.ManyToManyField(
        "ej_conversations.Vote",
        related_name="pending_in_clusterizations",
        editable=False,
        blank=True,
    )

    unprocessed_comments = property(lambda self: self.pending_comments.count())
    unprocessed_votes = property(lambda self: self.pending_votes.count())
    comments = delegate_to("conversation")
    users = delegate_to("conversation")
    votes = delegate_to("conversation")
    owner = delegate_to("conversation", name="author")

    @property
    def stereotypes(self):
        return Stereotype.objects.filter(clusters__in=self.clusters.all())

    @property
    def stereotype_votes(self):
        return StereotypeVote.objects.filter(comment__in=self.comments.all())

    #
    # Statistics and annotated values
    #
    n_clusters = lazy(this.clusters.count())
    n_stereotypes = lazy(this.stereotypes.count())
    n_stereotype_votes = lazy(this.stereotype_votes.count())

    objects = ClusterizationManager()

    class Meta:
        ordering = ["conversation_id"]

    def __str__(self):
        clusters = self.clusters.count()
        return f"{self.conversation} ({clusters} clusters)"

    def get_absolute_url(self):
        return self.conversation.url("cluster:index")

    def update_clusterization(self, force=False, atomic=False):
        """
        Update clusters if necessary, unless force=True, in which it
        unconditionally updates the clusterization.
        """
        if force or rules.test_rule("ej.must_update_clusterization", self):
            log.info(f"[clusters] updating cluster: {self.conversation}")

            if self.clusters.count() == 0:
                if self.cluster_status == ClusterStatus.ACTIVE:
                    self.cluster_status = ClusterStatus.PENDING_DATA
                self.save()
                return

            with use_transaction(atomic=atomic):
                try:
                    self.clusters.clusterize_from_votes()
                except ValueError:
                    return
                self.pending_comments.all().delete()
                self.pending_votes.all().delete()
                if self.cluster_status == ClusterStatus.PENDING_DATA:
                    self.cluster_status = ClusterStatus.ACTIVE
                x = self.id
                y = self.conversation_id
                self.save()
예제 #6
0
class ConversationProgress(ProgressBase):
    """
    Tracks activity in conversation.
    """

    conversation = models.OneToOneField("ej_conversations.Conversation",
                                        related_name="progress",
                                        on_delete=models.CASCADE)
    conversation_level = models.EnumField(
        ConversationLevel,
        default=CommenterLevel.NONE,
        verbose_name=_("conversation level"),
        help_text=_("Measures the level of engagement for conversation."),
    )
    max_conversation_level = models.EnumField(
        ConversationLevel,
        default=CommenterLevel.NONE,
        editable=False,
        help_text=_("maximum achieved conversation level"),
    )

    # Non de-normalized fields: conversations
    n_final_votes = delegate_to("conversation")
    n_approved_comments = delegate_to("conversation")
    n_rejected_comments = delegate_to("conversation")
    n_participants = delegate_to("conversation")
    n_favorites = delegate_to("conversation")
    n_tags = delegate_to("conversation")

    # Clusterization
    n_clusters = delegate_to("conversation")
    n_stereotypes = delegate_to("conversation")

    # Gamification
    n_endorsements = 0  # FIXME: delegate_to("conversation")

    # Signals
    level_achievement_signal = lazy(
        lambda _: signals.conversation_level_achieved, shared=True)

    # Points
    VOTE_POINTS = 1
    APPROVED_COMMENT_POINTS = 2
    REJECTED_COMMENT_POINTS = -0.125
    ENDORSEMENT_POINTS = 3

    pts_final_votes = compute_points(1)
    pts_approved_comments = compute_points(2)
    pts_rejected_comments = compute_points(-0.125)
    pts_endorsements = compute_points(3)

    objects = ProgressQuerySet.as_manager()

    class Meta:
        verbose_name = _("Conversation score")
        verbose_name_plural = _("Conversation scores")

    def __str__(self):
        return __('Progress for "{conversation}"').format(
            conversation=self.conversation)

    def compute_score(self):
        """
        Compute the total number of points for user contribution.

        Conversation score is based on the following rules:
            * Vote: 1 points
            * Accepted comment: 2 points
            * Rejected comment: -3 points
            * Endorsement created: 3 points

        Returns:
            Total score (int)
        """
        return int(
            max(
                0,
                self.score_bias + self.pts_final_votes +
                self.pts_approved_comments + self.pts_rejected_comments +
                self.pts_endorsements,
            ))
예제 #7
0
class UserProgress(ProgressBase):
    """
    Tracks global user evolution.
    """

    user = models.OneToOneField(settings.AUTH_USER_MODEL,
                                related_name="progress",
                                on_delete=models.CASCADE,
                                editable=False)
    commenter_level = models.EnumField(
        CommenterLevel,
        default=CommenterLevel.NONE,
        verbose_name=_("commenter level"),
        help_text=
        _("Measures how much user participated by contributing comments to conversations"
          ),
    )
    max_commenter_level = models.EnumField(
        CommenterLevel,
        default=CommenterLevel.NONE,
        editable=False,
        verbose_name=_("maximum achieved commenter level"),
    )
    host_level = models.EnumField(
        HostLevel,
        default=HostLevel.NONE,
        verbose_name=_("host level"),
        help_text=
        _("Measures how much user participated by creating engaging conversations."
          ),
    )
    max_host_level = models.EnumField(
        HostLevel,
        default=HostLevel.NONE,
        editable=False,
        verbose_name=_("maximum achieved host level"))
    profile_level = models.EnumField(
        ProfileLevel,
        default=ProfileLevel.NONE,
        verbose_name=_("profile level"),
        help_text=_("Measures how complete is the user profile."),
    )
    max_profile_level = models.EnumField(
        ProfileLevel,
        default=ProfileLevel.NONE,
        editable=False,
        verbose_name=_("maximum achieved profile level"),
    )

    # Non de-normalized fields: conversations app
    n_conversations = delegate_to("user")
    n_pending_comments = delegate_to("user")
    n_rejected_comments = delegate_to("user")

    @lazy
    def n_final_votes(self):
        return (Vote.objects.filter(author=self.user).exclude(
            comment__conversation__author=self.user).count())

    @lazy
    def n_approved_comments(self):
        return Comment.objects.filter(author=self.user).exclude(
            conversation__author=self.user).count()

    # Gamification app
    n_endorsements = 0  # FIXME: delegate_to("user")
    n_given_opinion_bridge_powers = delegate_to("user")
    n_given_minority_activist_powers = delegate_to("user")

    # Level of conversations
    def _conversation_level_checker(*args):
        *_, lvl = args  # ugly trick to make static analysis happy
        return lazy(lambda p: p.user.conversations.filter(
            progress__conversation_level__gte=lvl).count())

    def _participation_level_checker(*args):
        *_, lvl = args  # ugly trick to make static analysis happy
        return lazy(lambda p: p.user.participation_progresses.filter(
            voter_level__gte=lvl).count())

    n_conversation_lvl_1 = _conversation_level_checker(
        ConversationLevel.CONVERSATION_LVL1)
    n_conversation_lvl_2 = _conversation_level_checker(
        ConversationLevel.CONVERSATION_LVL2)
    n_conversation_lvl_3 = _conversation_level_checker(
        ConversationLevel.CONVERSATION_LVL3)
    n_conversation_lvl_4 = _conversation_level_checker(
        ConversationLevel.CONVERSATION_LVL2)
    n_participation_lvl_1 = _participation_level_checker(VoterLevel.VOTER_LVL1)
    n_participation_lvl_2 = _participation_level_checker(VoterLevel.VOTER_LVL2)
    n_participation_lvl_3 = _participation_level_checker(VoterLevel.VOTER_LVL3)
    n_participation_lvl_4 = _participation_level_checker(VoterLevel.VOTER_LVL4)

    del _conversation_level_checker
    del _participation_level_checker

    # Aggregators
    total_conversation_score = delegate_to("user")
    total_participation_score = delegate_to("user")

    # Score points
    VOTE_POINTS = 10
    APPROVED_COMMENT_POINTS = 30
    REJECTED_COMMENT_POINTS = -15
    ENDORSEMENT_POINTS = 15
    OPINION_BRIDGE_POINTS = 50
    MINORITY_ACTIVIST_POINTS = 50

    pts_final_votes = compute_points(VOTE_POINTS)
    pts_approved_comments = compute_points(APPROVED_COMMENT_POINTS)
    pts_rejected_comments = compute_points(REJECTED_COMMENT_POINTS)
    pts_endorsements = compute_points(ENDORSEMENT_POINTS)
    pts_given_opinion_bridge_powers = compute_points(OPINION_BRIDGE_POINTS)
    pts_given_minority_activist_powers = compute_points(
        MINORITY_ACTIVIST_POINTS)

    # Signals
    level_achievement_signal = lazy(lambda _: signals.user_level_achieved,
                                    shared=True)

    score_level = property(lambda self: ScoreLevel.from_score(self.score))

    @property
    def n_trophies(self):
        n = 0
        n += bool(self.score_level)
        n += bool(self.profile_level)
        n += bool(self.host_level)
        n += bool(self.commenter_level)
        n += self.n_conversation_lvl_1
        n += self.n_participation_lvl_1
        return n

    objects = ProgressQuerySet.as_manager()

    class Meta:
        verbose_name = _("User score")
        verbose_name_plural = _("User scores")

    def __str__(self):
        return str(self.user)

    def compute_score(self):
        """
        Compute the total number of points earned by user.

        User score is based on the following rules:
            * Vote: 10 points
            * Accepted comment: 30 points
            * Rejected comment: -30 points
            * Endorsement received: 15 points
            * Opinion bridge: 50 points
            * Minority activist: 50 points
            * Plus the total score of created conversations.

        Returns:
            Total score (int)
        """
        return max(
            0,
            self.score_bias + self.pts_final_votes +
            self.pts_approved_comments + self.pts_rejected_comments +
            self.pts_endorsements + self.pts_given_opinion_bridge_powers +
            self.pts_given_minority_activist_powers +
            self.total_conversation_score,
        )