-
Notifications
You must be signed in to change notification settings - Fork 391
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Add problem points vote functionality #1645
Changes from all commits
c803bf7
d654bec
b73368f
c1bea23
c07b4ba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import django.core.validators | ||
import django.db.models.deletion | ||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('judge', '0133_add_problem_data_hints'), | ||
] | ||
|
||
operations = [ | ||
migrations.AddField( | ||
model_name='profile', | ||
name='is_banned_from_problem_voting', | ||
field=models.BooleanField(default=False, | ||
help_text="User will not be able to vote on problems' point values.", | ||
verbose_name='banned from voting on problem point values'), | ||
), | ||
migrations.CreateModel( | ||
name='ProblemPointsVote', | ||
fields=[ | ||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
('points', models.IntegerField(help_text='The amount of points the voter thinks this problem deserves.', | ||
validators=[django.core.validators.MinValueValidator(1), | ||
django.core.validators.MaxValueValidator(50)], | ||
verbose_name='proposed points')), | ||
('note', models.TextField(blank=True, default='', help_text='Justification for problem point value.', | ||
max_length=8192, verbose_name='note')), | ||
('problem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, | ||
related_name='problem_points_votes', to='judge.Problem', | ||
verbose_name='problem')), | ||
('voter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, | ||
related_name='problem_points_votes', to='judge.Profile', | ||
verbose_name='voter')), | ||
('vote_time', models.DateTimeField(auto_now_add=True, help_text='The time this vote was cast.', | ||
verbose_name='vote time')), | ||
], | ||
options={ | ||
'verbose_name': 'problem vote', | ||
'verbose_name_plural': 'problem votes', | ||
}, | ||
), | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
from enum import IntEnum | ||
from operator import attrgetter | ||
|
||
from django.conf import settings | ||
|
@@ -93,6 +94,18 @@ class SubmissionSourceAccess: | |
FOLLOW = 'F' | ||
|
||
|
||
class VotePermission(IntEnum): | ||
NONE = 0 | ||
VIEW = 1 | ||
VOTE = 2 | ||
|
||
def can_view(self): | ||
return self >= VotePermission.VIEW | ||
|
||
def can_vote(self): | ||
return self >= VotePermission.VOTE | ||
Comment on lines
+102
to
+106
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I cared I'd ask for this to be |
||
|
||
|
||
class Problem(models.Model): | ||
SUBMISSION_SOURCE_ACCESS = ( | ||
(SubmissionSourceAccess.FOLLOW, _('Follow global setting')), | ||
|
@@ -434,6 +447,32 @@ def save(self, *args, **kwargs): | |
|
||
save.alters_data = True | ||
|
||
def is_solved_by(self, user): | ||
# Return true if a full AC submission to the problem from the user exists. | ||
return self.submission_set.filter(user=user.profile, result='AC', points__gte=F('problem__points')).exists() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is going to have issues with 9999/10000 in a 5 point problem or something, but I couldn't be bothered to care. |
||
|
||
def vote_permission_for_user(self, user): | ||
if not user.is_authenticated: | ||
return VotePermission.NONE | ||
|
||
# If the user is in contest, nothing should be shown. | ||
if user.profile.current_contest: | ||
return VotePermission.NONE | ||
|
||
# If the user is not allowed to vote. | ||
if user.profile.is_unlisted or user.profile.is_banned_from_problem_voting: | ||
return VotePermission.VIEW | ||
|
||
# If the user is banned from submitting to the problem. | ||
if self.banned_users.filter(pk=user.pk).exists(): | ||
return VotePermission.VIEW | ||
|
||
# If the user has not solved the problem. | ||
if not self.is_solved_by(user): | ||
return VotePermission.VIEW | ||
|
||
return VotePermission.VOTE | ||
kiritofeng marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
class Meta: | ||
permissions = ( | ||
('see_private_problem', _('See hidden problems')), | ||
|
@@ -522,3 +561,28 @@ class Meta: | |
) | ||
verbose_name = _('solution') | ||
verbose_name_plural = _('solutions') | ||
|
||
|
||
class ProblemPointsVote(models.Model): | ||
lakshy-gupta marked this conversation as resolved.
Show resolved
Hide resolved
|
||
points = models.IntegerField( | ||
verbose_name=_('proposed points'), | ||
help_text=_('The amount of points the voter thinks this problem deserves.'), | ||
validators=[ | ||
MinValueValidator(settings.DMOJ_PROBLEM_MIN_USER_POINTS_VOTE), | ||
MaxValueValidator(settings.DMOJ_PROBLEM_MAX_USER_POINTS_VOTE), | ||
], | ||
kiritofeng marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) | ||
voter = models.ForeignKey(Profile, verbose_name=_('voter'), related_name='problem_points_votes', on_delete=CASCADE) | ||
problem = models.ForeignKey(Problem, verbose_name=_('problem'), related_name='problem_points_votes', | ||
on_delete=CASCADE) | ||
vote_time = models.DateTimeField(verbose_name=_('vote time'), help_text=_('The time this vote was cast.'), | ||
auto_now_add=True) | ||
note = models.TextField(verbose_name=_('note'), help_text=_('Justification for problem point value.'), | ||
max_length=8192, blank=True, default='') | ||
|
||
class Meta: | ||
verbose_name = _('problem vote') | ||
verbose_name_plural = _('problem votes') | ||
|
||
def __str__(self): | ||
return _('Points vote by %(voter)s for %(problem)s') % {'voter': self.voter, 'problem': self.problem} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't expose comment votes in the sidebar, probably don't need to do so here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was done to give access to
ProblemPointsVote.note
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, should we surface a link to a filtered admin view in the UI, or embed justifications in it? It seems like we can view problem vote distributions in the problem UI, but then an admin has to go to admin,
ProblemPointsVote
view, search by the same problem ID, and then read notes.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh wait, this filtered link already exists...