Skip to content

Commit

Permalink
Implement user display name override
Browse files Browse the repository at this point in the history
Cherry-pick from DMOJ/online-judge#1747
  • Loading branch information
quantum5 authored and leduythuccs committed Nov 30, 2021
1 parent cbfd1b8 commit 7ceb452
Show file tree
Hide file tree
Showing 10 changed files with 49 additions and 23 deletions.
2 changes: 1 addition & 1 deletion judge/admin/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def has_add_permission(self, request):
class ProfileAdmin(NoBatchDeleteMixin, VersionAdmin):
fields = ('user', 'display_rank', 'about', 'organizations', 'timezone', 'language', 'ace_theme',
'math_engine', 'last_access', 'ip', 'mute', 'is_unlisted', 'allow_tagging', 'notes',
'ban_reason', 'is_totp_enabled', 'user_script', 'current_contest')
'username_display_override', 'ban_reason', 'is_totp_enabled', 'user_script', 'current_contest')
readonly_fields = ('user',)
list_display = ('admin_user_admin', 'email', 'is_totp_enabled', 'timezone_full',
'date_joined', 'last_access', 'ip', 'show_public')
Expand Down
18 changes: 18 additions & 0 deletions judge/migrations/0167_profile_username_display_override.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 2.2.24 on 2021-11-30 14:58

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('judge', '0166_contest_scoreboard_cache_timeout'),
]

operations = [
migrations.AddField(
model_name='profile',
name='username_display_override',
field=models.CharField(blank=True, help_text='name displayed in place of username', max_length=100, verbose_name='display name override'),
),
]
6 changes: 6 additions & 0 deletions judge/models/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ class Profile(models.Model):
notes = models.TextField(verbose_name=_('internal notes'), null=True, blank=True,
help_text=_('Notes for administrators regarding this user.'))
data_last_downloaded = models.DateTimeField(verbose_name=_('last data download time'), null=True, blank=True)
username_display_override = models.CharField(max_length=100, blank=True, verbose_name=_('display name override'),
help_text=_('name displayed in place of username'))

@cached_property
def organization(self):
Expand All @@ -190,6 +192,10 @@ def organization(self):
def username(self):
return self.user.username

@cached_property
def display_name(self):
return self.username_display_override or self.username

@cached_property
def has_any_solves(self):
return self.submission_set.filter(points=F('problem__points')).exists()
Expand Down
3 changes: 2 additions & 1 deletion judge/views/contests.py
Original file line number Diff line number Diff line change
Expand Up @@ -677,7 +677,7 @@ def get_context_data(self, **kwargs):
ContestRankingProfile = namedtuple(
'ContestRankingProfile',
'id user css_class username points cumtime tiebreaker organization participation '
'participation_rating problem_cells result_cell virtual',
'participation_rating problem_cells result_cell virtual display_name',
)

BestSolutionData = namedtuple('BestSolutionData', 'code points time state is_pretested')
Expand Down Expand Up @@ -707,6 +707,7 @@ def display_user_problem(contest_problem):
result_cell=contest.format.display_participation_result(participation, frozen),
participation=participation,
virtual=participation.virtual,
display_name=user.display_name,
)


Expand Down
7 changes: 4 additions & 3 deletions judge/views/select2.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,18 +150,19 @@ def get(self, request, *args, **kwargs):
self.gravatar_size = request.GET.get('gravatar_size', 128)
self.gravatar_default = request.GET.get('gravatar_default', None)

self.object_list = self.get_queryset().values_list('pk', 'user__username', 'user__email', 'display_rank')
self.object_list = self.get_queryset().values_list('pk', 'user__username', 'user__email', 'display_rank',
'username_display_override')

context = self.get_context_data()

return JsonResponse({
'results': [
{
'text': username,
'text': username_override or username,
'id': username,
'gravatar_url': gravatar(email, self.gravatar_size, self.gravatar_default),
'display_rank': display_rank,
} for pk, username, email, display_rank in context['object_list']],
} for pk, username, email, display_rank, username_override in context['object_list']],
'more': context['page_obj'].has_next(),
})

Expand Down
28 changes: 14 additions & 14 deletions judge/views/submission.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def submission_related(queryset):
.only('id', 'user__user__username', 'user__display_rank', 'user__rating', 'problem__name',
'problem__code', 'problem__is_public', 'language__short_name', 'language__key', 'date', 'time', 'memory',
'points', 'result', 'status', 'case_points', 'case_total', 'current_testcase', 'contest_object',
'locked_after', 'problem__submission_source_visibility_mode') \
'locked_after', 'problem__submission_source_visibility_mode', 'user__username_display_override') \
.prefetch_related('contest_object__authors', 'contest_object__curators')


Expand All @@ -55,7 +55,7 @@ def get_title(self):
submission = self.object
return _('Submission of %(problem)s by %(user)s') % {
'problem': submission.problem.translated_name(self.request.LANGUAGE_CODE),
'user': submission.user.user.username,
'user': submission.user.display_name,
}

def get_content_title(self):
Expand All @@ -66,7 +66,7 @@ def get_content_title(self):
submission.problem.translated_name(self.request.LANGUAGE_CODE)),
'user': format_html('<a href="{0}">{1}</a>',
reverse('user_page', args=[submission.user.user.username]),
submission.user.user.username),
submission.user.display_name),
})


Expand Down Expand Up @@ -411,7 +411,7 @@ def get_context_data(self, **kwargs):
context['tab'] = 'my_submissions_tab'
else:
context['tab'] = 'user_submissions_tab'
context['tab_username'] = self.profile.user.username
context['tab_username'] = self.profile.display_name
return context


Expand All @@ -422,12 +422,12 @@ def get_queryset(self):
def get_title(self):
if self.is_own:
return _('All my submissions')
return _('All submissions by %s') % self.username
return _('All submissions by %s') % self.profile.display_name

def get_content_title(self):
if self.is_own:
return format_html(_('All my submissions'))
return format_html(_('All submissions by') + ' <a href="{1}">{0}</a>', self.username,
return format_html(_('All submissions by') + ' <a href="{1}">{0}</a>', self.profile.display_name,
reverse('user_page', args=[self.username]))

def get_my_submissions_page(self):
Expand Down Expand Up @@ -535,7 +535,7 @@ def get_title(self):
if self.is_own:
return _('My submissions for %(problem)s') % {'problem': self.problem_name}
return _("%(user)s's submissions for %(problem)s") % {
'user': self.username, 'problem': self.problem_name,
'user': self.profile.display_name, 'problem': self.problem_name,
}

def get_content_title(self):
Expand All @@ -544,7 +544,7 @@ def get_content_title(self):
self.username, reverse('user_page', args=[self.username]),
self.problem_name, reverse('problem_detail', args=[self.problem.code]))
return format_html(_("""<a href="{1}">{0}</a>'s submissions for <a href="{3}">{2}</a>"""),
self.username, reverse('user_page', args=[self.username]),
self.profile.display_name, reverse('user_page', args=[self.username]),
self.problem_name, reverse('problem_detail', args=[self.problem.code]))

def get_context_data(self, **kwargs):
Expand Down Expand Up @@ -656,7 +656,7 @@ def get_title(self):
if self.is_own:
return _('My submissions in %(contest)s') % {'contest': self.contest.name}
return _("%(user)s's submissions in %(contest)s") % {
'user': self.username,
'user': self.profile.display_name,
'contest': self.contest.name,
}

Expand All @@ -675,7 +675,7 @@ def get_content_title(self):
return format_html(_('My submissions in <a href="{1}">{0}</a>'),
self.contest.name, reverse('contest_view', args=[self.contest.key]))
return format_html(_('<a href="{1}">{0}</a>\'s submissions in <a href="{3}">{2}</a>'),
self.username, reverse('user_page', args=[self.username]),
self.profile.display_name, reverse('user_page', args=[self.username]),
self.contest.name, reverse('contest_view', args=[self.contest.key]))

def get_queryset(self):
Expand All @@ -689,9 +689,9 @@ def get_queryset(self):
class UserContestSubmissions(ForceContestMixin, UserProblemSubmissions):
def get_title(self):
if self.problem.is_accessible_by(self.request.user):
return "%s's submissions for %s in %s" % (self.username, self.problem_name, self.contest.name)
return "%s's submissions for %s in %s" % (self.profile.display_name, self.problem_name, self.contest.name)
return "%s's submissions for problem %s in %s" % (
self.username, self.get_problem_label(self.problem), self.contest.name)
self.profile.display_name, self.get_problem_label(self.problem), self.contest.name)

def access_check(self, request):
super(UserContestSubmissions, self).access_check(request)
Expand All @@ -702,11 +702,11 @@ def get_content_title(self):
if self.problem.is_accessible_by(self.request.user):
return format_html(_('<a href="{1}">{0}</a>\'s submissions for '
'<a href="{3}">{2}</a> in <a href="{5}">{4}</a>'),
self.username, reverse('user_page', args=[self.username]),
self.profile.display_name, reverse('user_page', args=[self.username]),
self.problem_name, reverse('problem_detail', args=[self.problem.code]),
self.contest.name, reverse('contest_view', args=[self.contest.key]))
return format_html(_('<a href="{1}">{0}</a>\'s submissions for '
'problem {2} in <a href="{4}">{3}</a>'),
self.username, reverse('user_page', args=[self.username]),
self.profile.display_name, reverse('user_page', args=[self.username]),
self.get_problem_label(self.problem),
self.contest.name, reverse('contest_view', args=[self.contest.key]))
2 changes: 1 addition & 1 deletion judge/views/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def dispatch(self, request, *args, **kwargs):

def get_title(self):
return (_('My account') if self.request.user == self.object.user else
_('User %s') % self.object.user.username)
_('User %s') % self.object.display_name)

# TODO: the same code exists in problem.py, maybe move to problems.py?
@cached_property
Expand Down
2 changes: 1 addition & 1 deletion templates/ticket/message.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div class="info">
<a href="{{ url('user_page', message.user.user.username) }}" class="user">
<img src="{{ gravatar(message.user, 135) }}" class="gravatar">
<div class="username {{ message.user.css_class }}">{{ message.user.user.username }}</div>
<div class="username {{ message.user.css_class }}">{{ message.user.display_name }}</div>
</a>
</div>
<div class="detail">
Expand Down
2 changes: 1 addition & 1 deletion templates/ticket/ticket.html
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@
<div class="info">
<a href="{{ url('user_page', request.user.username) }}" class="user">
<img src="{{ gravatar(request.user, 135) }}" class="gravatar">
<div class="username {{ request.profile.css_class }}">{{ request.user.username }}</div>
<div class="username {{ request.profile.css_class }}">{{ request.profile.display_name }}</div>
</a>
</div>
<div class="detail">
Expand Down
2 changes: 1 addition & 1 deletion templates/user/link.html
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<span class="{{ profile.css_class }}"><a href="{{ url('user_page', user.username) }}">{{ user.username }}</a></span>
<span class="{{ profile.css_class }}"><a href="{{ url('user_page', user.username) }}">{{ profile.display_name }}</a></span>

0 comments on commit 7ceb452

Please # to comment.