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)
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 )
class User(models.Model): name = models.CharField(max_length=100) age = models.IntegerField(blank=True, null=True) created = models.DateTimeField(default=now) modified = models.DateTimeField(auto_now=True) gender = models.EnumField(Gender, blank=True, null=True) __str__ = (lambda self: self.name)
class Channel(TimeStampedModel): name = models.CharField(max_length=100) users = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name="channels", blank=True) purpose = models.EnumField(Purpose, _("Purpose"), default=Purpose.GENERAL) owner = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True, related_name="owned_channels" ) slug = AutoSlugField(unique=True, populate_from="name") class Meta: ordering = ["slug"]
class Comment(StatusModel, TimeStampedModel): """ A comment on a conversation. """ STATUS = Choices(("pending", _("awaiting moderation")), ("approved", _("approved")), ("rejected", _("rejected"))) STATUS_MAP = { "pending": STATUS.pending, "approved": STATUS.approved, "rejected": STATUS.rejected } conversation = models.ForeignKey("Conversation", related_name="comments", on_delete=models.CASCADE) author = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="comments", on_delete=models.CASCADE) content = models.TextField( _("Content"), max_length=252, validators=[MinLengthValidator(2), is_not_empty], help_text=_("Body of text for the comment"), ) rejection_reason = models.EnumField(RejectionReason, _("Rejection reason"), default=RejectionReason.USER_PROVIDED) rejection_reason_text = models.TextField( _("Rejection reason (free-form)"), blank=True, help_text=_( "You must provide a reason to reject a comment. Users will receive " "this feedback."), ) moderator = models.ForeignKey( settings.AUTH_USER_MODEL, related_name="moderated_comments", on_delete=models.SET_NULL, blank=True, null=True, ) is_approved = property(lambda self: self.status == self.STATUS.approved) is_pending = property(lambda self: self.status == self.STATUS.pending) is_rejected = property(lambda self: self.status == self.STATUS.rejected) @property def has_rejection_explanation(self): return self.rejection_reason != RejectionReason.USER_PROVIDED or ( self.rejection_reason.USER_PROVIDED and self.rejection_reason_text) # # Annotations # author_name = lazy(lambda self: self.author.name, name="author_name") missing_votes = lazy( lambda self: self.conversation.users.count() - self.n_votes, name="missing_votes") agree_count = lazy(lambda self: votes_counter(self, choice=Choice.AGREE), name="agree_count") skip_count = lazy(lambda self: votes_counter(self, choice=Choice.SKIP), name="skip_count") disagree_count = lazy( lambda self: votes_counter(self, choice=Choice.DISAGREE), name="disagree_count") n_votes = lazy(lambda self: votes_counter(self), name="n_votes") @property def rejection_reason_display(self): if self.status == self.STATUS.approved: return _("Comment is approved") elif self.status == self.STATUS.pending: return _("Comment is pending moderation") elif self.rejection_reason_text: return self.rejection_reason_text elif self.rejection_reason is not None: return self.rejection_reason.description else: raise AssertionError objects = CommentQuerySet.as_manager() class Meta: unique_together = ("conversation", "content") def __str__(self): return self.content def clean(self): super().clean() if self.status == self.STATUS.rejected and not self.has_rejection_explanation: raise ValidationError({ "rejection_reason": _("Must give a reason to reject a comment") }) def vote(self, author, choice, commit=True): """ Cast a vote for the current comment. Vote must be one of 'agree', 'skip' or 'disagree'. >>> comment.vote(user, 'agree') # doctest: +SKIP """ choice = normalize_choice(choice) # We do not full_clean since the uniqueness constraint will only be # enforced when strictly necessary. vote = Vote(author=author, comment=self, choice=choice) vote.clean_fields() # Check if vote exists and if its existence represents an error is_changed = False try: saved_vote = Vote.objects.get(author=author, comment=self) except Vote.DoesNotExist: pass else: if saved_vote.choice == choice: commit = False elif saved_vote.choice == Choice.SKIP: vote.id = saved_vote.id vote.created = now() is_changed = True else: raise ValidationError("Cannot change user vote") # Send possibly saved vote if commit: vote.save() log.debug(f"Registered vote: {author} - {choice}") vote_cast.send( Comment, vote=vote, comment=self, choice=choice, is_update=is_changed, is_final=choice != Choice.SKIP, ) return vote def statistics(self, ratios=False): """ Return full voting statistics for comment. Args: ratios (bool): If True, also include 'agree_ratio', 'disagree_ratio', etc fields each original value. Ratios count the percentage of votes in each category. >>> comment.statistics() # doctest: +SKIP { 'agree': 42, 'disagree': 10, 'skip': 25, 'total': 67, 'missing': 102, } """ stats = { "agree": self.agree_count, "disagree": self.disagree_count, "skip": self.skip_count, "total": self.n_votes, "missing": self.missing_votes, } if ratios: e = 1e-50 # prevents ZeroDivisionErrors stats.update( agree_ratio=self.agree_count / (self.n_votes + e), disagree_ratio=self.disagree_count / (self.n_votes + e), skip_ratio=self.skip_count / (self.n_votes + e), missing_ratio=self.missing_votes / (self.missing_votes + self.n_votes + e), ) return stats
class Classroom(DescriptiveModel): """ One specific occurrence of a course for a given teacher in a given period. """ long_description = models.LongDescriptionField(blank=True) discipline = models.ForeignKey( 'Discipline', blank=True, null=True, on_delete=models.SET_NULL, ) location = models.CharField( _('location'), blank=True, max_length=140, help_text=_('Physical location of classroom, if applicable.'), ) teacher = models.ForeignKey( User, related_name='classrooms_as_teacher', on_delete=models.PROTECT ) students = models.ManyToManyField( User, verbose_name=_('students'), related_name='classrooms_as_student', blank=True, ) staff = models.ManyToManyField( User, verbose_name=_('staff'), related_name='classrooms_as_staff', blank=True, ) is_accepting_subscriptions = models.BooleanField( _('accept subscriptions'), default=True, help_text=_( 'Set it to false to prevent new student subscriptions.' ), ) is_public = models.BooleanField( _('is it public?'), default=False, help_text=_( 'If true, all students will be able to see the contents of the ' 'course. Most activities will not be available to non-subscribed ' 'students.' ), ) comments_policy = models.EnumField( CommentPolicyEnum, blank=True, null=True, ) subscription_passphrase = models.CharField( _('subscription passphrase'), default=phrase_lower, max_length=140, help_text=_( 'A passphrase/word that students must enter to subscribe in the ' 'course. Leave empty if no passphrase should be necessary.' ), blank=True, ) objects = ClassroomManager() def register_student(self, user): """ Register a new student in the course. """ if user == self.teacher: raise ValidationError(_('Teacher cannot enroll as student.')) elif self.staff.filter(id=user.id): raise ValidationError(_('Staff member cannot enroll as student.')) self.students.add(user) def register_staff(self, user): """ Register a new user as staff. """ if user == self.teacher: raise ValidationError(_('Teacher cannot enroll as staff.')) self.students.add(user)
class ParticipationProgress(ProgressBase): """ Tracks user evolution in conversation. """ user = models.ForeignKey( settings.AUTH_USER_MODEL, related_name="participation_progresses", on_delete=models.CASCADE, ) conversation = models.ForeignKey( "ej_conversations.Conversation", related_name="participation_progresses", on_delete=models.CASCADE, ) voter_level = models.EnumField(VoterLevel, default=VoterLevel.NONE) max_voter_level = models.EnumField(VoterLevel, default=VoterLevel.NONE) is_owner = models.BooleanField(default=False) is_focused = models.BooleanField(default=False) # Non de-normalized fields: conversations is_favorite = lazy( lambda p: p.conversation.favorites.filter(user=p.user).exists()) n_votes = lazy(lambda p: p.user.votes.filter(comment__conversation=p. conversation).count()) n_comments = lazy( lambda p: p.user.comments.filter(conversation=p.conversation).count()) n_rejected_comments = lazy(lambda p: p.user.rejected_comments.filter( conversation=p.conversation).count()) n_conversation_comments = delegate_to("conversation", name="n_comments") n_conversation_rejected_comments = delegate_to("conversation", name="n_rejected_comments") votes_ratio = lazy(this.n_votes / (this.n_conversation_comments + 1e-50)) # Gamification # n_endorsements = lazy(lambda p: Endorsement.objects.filter(comment__author=p.user).count()) # n_given_opinion_bridge_powers = delegate_to('user') # n_given_minority_activist_powers = delegate_to('user') n_endorsements = 0 n_given_opinion_bridge_powers = 0 n_given_minority_activist_powers = 0 # Leaderboard @lazy def n_conversation_scores(self): db = ParticipationProgress.objects return db.filter(conversation=self.conversation).count() @lazy def n_higher_scores(self): db = ParticipationProgress.objects return db.filter(conversation=self.conversation, score__gt=self.score).count() n_lower_scores = lazy(this.n_conversation_scores - this.n_higher_scores) # Signals level_achievement_signal = lazy( lambda _: signals.participation_level_achieved, shared=True) def __str__(self): msg = __("Progress for user: {user} at {conversation}") return msg.format(user=self.user, conversation=self.conversation) def sync(self): self.is_owner = self.conversation.author == self.user # You cannot receive a focused achievement in your own conversation! if not self.is_owner: n_comments = self.conversation.n_comments self.is_focused = n_comments == self.n_votes >= 20 return super().sync() 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. * Got a focused badge: 50 points. 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 + 50 * self.is_focused)
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)
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, ))
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, )
class ParticipationProgress(ProgressBase): """ Tracks user evolution in conversation. """ user = models.ForeignKey( settings.AUTH_USER_MODEL, related_name="participation_progresses", on_delete=models.CASCADE, editable=False, ) conversation = models.ForeignKey( "ej_conversations.Conversation", related_name="participation_progresses", on_delete=models.CASCADE, editable=False, ) voter_level = models.EnumField( VoterLevel, default=VoterLevel.NONE, verbose_name=_("voter level"), help_text=_("Measure how many votes user has given in conversation"), ) max_voter_level = models.EnumField( VoterLevel, default=VoterLevel.NONE, editable=False, verbose_name=_("maximum achieved voter level")) is_owner = models.BooleanField( _("owner?"), default=False, help_text=_( "Total score is computed differently if user owns conversation."), ) is_focused = models.BooleanField( _("focused?"), default=False, help_text=_( "User received a focused badge (i.e., voted on all comments)"), ) # Non de-normalized fields: conversations is_favorite = lazy( lambda p: p.conversation.favorites.filter(user=p.user).exists()) n_final_votes = lazy( lambda p: p.user.votes.filter(comment__conversation=p.conversation ).exclude(choice=Choice.SKIP).count()) n_approved_comments = lazy(lambda p: p.user.approved_comments.filter( conversation=p.conversation).count()) n_rejected_comments = lazy(lambda p: p.user.rejected_comments.filter( conversation=p.conversation).count()) n_conversation_comments = delegate_to("conversation", name="n_approved_comments") n_conversation_approved_comments = delegate_to("conversation", name="n_approved_comments") n_conversation_rejected_comments = delegate_to("conversation", name="n_rejected_comments") votes_ratio = lazy(this.n_final_votes / (this.n_conversation_comments + 1e-50)) # FIXME: Gamification # n_endorsements = lazy(lambda p: Endorsement.objects.filter(comment__author=p.user).count()) # n_given_opinion_bridge_powers = delegate_to('user') # n_given_minority_activist_powers = delegate_to('user') n_endorsements = 0 n_given_opinion_bridge_powers = 0 n_given_minority_activist_powers = 0 # Points VOTE_POINTS = 10 APPROVED_COMMENT_POINTS = 30 REJECTED_COMMENT_POINTS = -15 ENDORSEMENT_POINTS = 15 OPINION_BRIDGE_POINTS = 50 MINORITY_ACTIVIST_POINTS = 50 IS_FOCUSED_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) pts_is_focused = compute_points(IS_FOCUSED_POINTS, name="is_focused") # Leaderboard @lazy def n_conversation_scores(self): db = ParticipationProgress.objects return db.filter(conversation=self.conversation).count() @lazy def n_higher_scores(self): db = ParticipationProgress.objects return db.filter(conversation=self.conversation, score__gt=self.score).count() n_lower_scores = lazy(this.n_conversation_scores - this.n_higher_scores) # Signals level_achievement_signal = lazy( lambda _: signals.participation_level_achieved, shared=True) objects = ProgressQuerySet.as_manager() class Meta: verbose_name = _("User score (per conversation)") verbose_name_plural = _("User scores (per conversation)") def __str__(self): msg = __("Progress for user: {user} at {conversation}") return msg.format(user=self.user, conversation=self.conversation) def sync(self): self.is_owner = self.conversation.author == self.user # You cannot receive a focused achievement in your own conversation! if not self.is_owner: n_comments = self.conversation.n_approved_comments self.is_focused = (self.n_final_votes >= 20) and (n_comments == self.n_final_votes) return super().sync() 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. * Got a focused badge: 50 points. Observation: Owner do not receive any points for its own 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.pts_is_focused, )
class Poll(AbstractTask): kind = models.EnumField(PollType)