Ad profile table (#110)
This commit is contained in:
parent
ee17bc0778
commit
5335bc248f
12 changed files with 474 additions and 274 deletions
|
@ -147,6 +147,7 @@ def recalculate_ratings(ranking, old_mean, times_ranked, historical_p):
|
||||||
def rate_contest(contest):
|
def rate_contest(contest):
|
||||||
from judge.models import Rating, Profile
|
from judge.models import Rating, Profile
|
||||||
from judge.models.profile import _get_basic_info
|
from judge.models.profile import _get_basic_info
|
||||||
|
from judge.utils.users import get_contest_ratings
|
||||||
|
|
||||||
rating_subquery = Rating.objects.filter(user=OuterRef("user"))
|
rating_subquery = Rating.objects.filter(user=OuterRef("user"))
|
||||||
rating_sorted = rating_subquery.order_by("-contest__end_time")
|
rating_sorted = rating_subquery.order_by("-contest__end_time")
|
||||||
|
@ -239,6 +240,7 @@ def rate_contest(contest):
|
||||||
)
|
)
|
||||||
|
|
||||||
_get_basic_info.dirty_multi([(uid,) for uid in user_ids])
|
_get_basic_info.dirty_multi([(uid,) for uid in user_ids])
|
||||||
|
get_contest_ratings.dirty_multi([(uid,) for uid in user_ids])
|
||||||
|
|
||||||
|
|
||||||
RATING_LEVELS = [
|
RATING_LEVELS = [
|
||||||
|
|
61
judge/utils/users.py
Normal file
61
judge/utils/users.py
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.formats import date_format
|
||||||
|
from django.utils.translation import gettext as _, gettext_lazy
|
||||||
|
|
||||||
|
from judge.caching import cache_wrapper
|
||||||
|
from judge.models import Profile, Rating, Submission, Friend, ProfileInfo
|
||||||
|
|
||||||
|
|
||||||
|
def get_rating_rank(profile):
|
||||||
|
rank = None
|
||||||
|
if profile.rating:
|
||||||
|
rank = (
|
||||||
|
Profile.objects.filter(
|
||||||
|
is_unlisted=False,
|
||||||
|
rating__gt=profile.rating,
|
||||||
|
).count()
|
||||||
|
+ 1
|
||||||
|
)
|
||||||
|
return rank
|
||||||
|
|
||||||
|
|
||||||
|
def get_points_rank(profile):
|
||||||
|
return (
|
||||||
|
Profile.objects.filter(
|
||||||
|
is_unlisted=False,
|
||||||
|
performance_points__gt=profile.performance_points,
|
||||||
|
).count()
|
||||||
|
+ 1
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@cache_wrapper(prefix="gcr")
|
||||||
|
def get_contest_ratings(profile):
|
||||||
|
return (
|
||||||
|
profile.ratings.order_by("-contest__end_time")
|
||||||
|
.select_related("contest")
|
||||||
|
.defer("contest__description")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_awards(profile):
|
||||||
|
ratings = get_contest_ratings(profile)
|
||||||
|
|
||||||
|
sorted_ratings = sorted(
|
||||||
|
ratings, key=lambda x: (x.rank, -x.contest.end_time.timestamp())
|
||||||
|
)
|
||||||
|
|
||||||
|
result = [
|
||||||
|
{
|
||||||
|
"label": rating.contest.name,
|
||||||
|
"ranking": rating.rank,
|
||||||
|
"link": reverse("contest_ranking", args=(rating.contest.key,))
|
||||||
|
+ "#!"
|
||||||
|
+ profile.username,
|
||||||
|
"date": date_format(rating.contest.end_time, _("M j, Y")),
|
||||||
|
}
|
||||||
|
for rating in sorted_ratings
|
||||||
|
if rating.rank <= 3
|
||||||
|
]
|
||||||
|
|
||||||
|
return result
|
|
@ -25,6 +25,7 @@ from judge.utils.cachedict import CacheDict
|
||||||
from judge.utils.diggpaginator import DiggPaginator
|
from judge.utils.diggpaginator import DiggPaginator
|
||||||
from judge.utils.tickets import filter_visible_tickets
|
from judge.utils.tickets import filter_visible_tickets
|
||||||
from judge.utils.views import TitleMixin
|
from judge.utils.views import TitleMixin
|
||||||
|
from judge.utils.users import get_rating_rank, get_points_rank, get_awards
|
||||||
from judge.views.feed import FeedView
|
from judge.views.feed import FeedView
|
||||||
|
|
||||||
|
|
||||||
|
@ -81,6 +82,26 @@ class HomeFeedView(FeedView):
|
||||||
)
|
)
|
||||||
Profile.prefetch_profile_cache([p.id for p in context["top_rated"]])
|
Profile.prefetch_profile_cache([p.id for p in context["top_rated"]])
|
||||||
Profile.prefetch_profile_cache([p.id for p in context["top_scorer"]])
|
Profile.prefetch_profile_cache([p.id for p in context["top_scorer"]])
|
||||||
|
|
||||||
|
if self.request.user.is_authenticated:
|
||||||
|
context["rating_rank"] = get_rating_rank(self.request.profile)
|
||||||
|
context["points_rank"] = get_points_rank(self.request.profile)
|
||||||
|
|
||||||
|
medals_list = get_awards(self.request.profile)
|
||||||
|
context["awards"] = {
|
||||||
|
"medals": medals_list,
|
||||||
|
"gold_count": 0,
|
||||||
|
"silver_count": 0,
|
||||||
|
"bronze_count": 0,
|
||||||
|
}
|
||||||
|
for medal in medals_list:
|
||||||
|
if medal["ranking"] == 1:
|
||||||
|
context["awards"]["gold_count"] += 1
|
||||||
|
elif medal["ranking"] == 2:
|
||||||
|
context["awards"]["silver_count"] += 1
|
||||||
|
elif medal["ranking"] == 3:
|
||||||
|
context["awards"]["bronze_count"] += 1
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,12 @@ from judge.tasks import import_users
|
||||||
from judge.utils.problems import contest_completed_ids, user_completed_ids
|
from judge.utils.problems import contest_completed_ids, user_completed_ids
|
||||||
from judge.utils.ranker import ranker
|
from judge.utils.ranker import ranker
|
||||||
from judge.utils.unicode import utf8text
|
from judge.utils.unicode import utf8text
|
||||||
|
from judge.utils.users import (
|
||||||
|
get_rating_rank,
|
||||||
|
get_points_rank,
|
||||||
|
get_awards,
|
||||||
|
get_contest_ratings,
|
||||||
|
)
|
||||||
from judge.utils.views import (
|
from judge.utils.views import (
|
||||||
QueryStringSortMixin,
|
QueryStringSortMixin,
|
||||||
TitleMixin,
|
TitleMixin,
|
||||||
|
@ -142,22 +148,10 @@ class UserPage(TitleMixin, UserMixin, DetailView):
|
||||||
rating = self.object.ratings.order_by("-contest__end_time")[:1]
|
rating = self.object.ratings.order_by("-contest__end_time")[:1]
|
||||||
context["rating"] = rating[0] if rating else None
|
context["rating"] = rating[0] if rating else None
|
||||||
|
|
||||||
context["rank"] = (
|
context["points_rank"] = get_points_rank(self.object)
|
||||||
Profile.objects.filter(
|
|
||||||
is_unlisted=False,
|
|
||||||
performance_points__gt=self.object.performance_points,
|
|
||||||
).count()
|
|
||||||
+ 1
|
|
||||||
)
|
|
||||||
|
|
||||||
if rating:
|
if rating:
|
||||||
context["rating_rank"] = (
|
context["rating_rank"] = get_rating_rank(self.object)
|
||||||
Profile.objects.filter(
|
|
||||||
is_unlisted=False,
|
|
||||||
rating__gt=self.object.rating,
|
|
||||||
).count()
|
|
||||||
+ 1
|
|
||||||
)
|
|
||||||
context["rated_users"] = Profile.objects.filter(
|
context["rated_users"] = Profile.objects.filter(
|
||||||
is_unlisted=False, rating__isnull=False
|
is_unlisted=False, rating__isnull=False
|
||||||
).count()
|
).count()
|
||||||
|
@ -185,42 +179,9 @@ EPOCH = datetime(1970, 1, 1, tzinfo=timezone.utc)
|
||||||
class UserAboutPage(UserPage):
|
class UserAboutPage(UserPage):
|
||||||
template_name = "user/user-about.html"
|
template_name = "user/user-about.html"
|
||||||
|
|
||||||
def get_awards(self, ratings):
|
|
||||||
result = {}
|
|
||||||
|
|
||||||
sorted_ratings = sorted(
|
|
||||||
ratings, key=lambda x: (x.rank, -x.contest.end_time.timestamp())
|
|
||||||
)
|
|
||||||
|
|
||||||
result["medals"] = [
|
|
||||||
{
|
|
||||||
"label": rating.contest.name,
|
|
||||||
"ranking": rating.rank,
|
|
||||||
"link": reverse("contest_ranking", args=(rating.contest.key,))
|
|
||||||
+ "#!"
|
|
||||||
+ self.object.username,
|
|
||||||
"date": date_format(rating.contest.end_time, _("M j, Y")),
|
|
||||||
}
|
|
||||||
for rating in sorted_ratings
|
|
||||||
if rating.rank <= 3
|
|
||||||
]
|
|
||||||
|
|
||||||
num_awards = 0
|
|
||||||
for i in result:
|
|
||||||
num_awards += len(result[i])
|
|
||||||
|
|
||||||
if num_awards == 0:
|
|
||||||
result = None
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(UserAboutPage, self).get_context_data(**kwargs)
|
context = super(UserAboutPage, self).get_context_data(**kwargs)
|
||||||
ratings = context["ratings"] = (
|
ratings = context["ratings"] = get_contest_ratings(self.object)
|
||||||
self.object.ratings.order_by("-contest__end_time")
|
|
||||||
.select_related("contest")
|
|
||||||
.defer("contest__description")
|
|
||||||
)
|
|
||||||
|
|
||||||
context["rating_data"] = mark_safe(
|
context["rating_data"] = mark_safe(
|
||||||
json.dumps(
|
json.dumps(
|
||||||
|
@ -244,7 +205,7 @@ class UserAboutPage(UserPage):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
context["awards"] = self.get_awards(ratings)
|
context["awards"] = get_awards(self.object)
|
||||||
|
|
||||||
if ratings:
|
if ratings:
|
||||||
user_data = self.object.ratings.aggregate(Min("rating"), Max("rating"))
|
user_data = self.object.ratings.aggregate(Min("rating"), Max("rating"))
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -43,6 +43,10 @@
|
||||||
background-color: #f3f3f3;
|
background-color: #f3f3f3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.organization-row:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
.organization-container {
|
.organization-container {
|
||||||
border: 1px #ccc solid;
|
border: 1px #ccc solid;
|
||||||
margin-bottom: 3em;
|
margin-bottom: 3em;
|
||||||
|
@ -53,4 +57,3 @@
|
||||||
display: block;
|
display: block;
|
||||||
color: gray;
|
color: gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -512,4 +512,78 @@ a.edit-profile {
|
||||||
|
|
||||||
.user-stat-header {
|
.user-stat-header {
|
||||||
color: gray;
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-card {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: box-shadow 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
background-color: #f7f7f7;
|
||||||
|
text-align: center;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-body {
|
||||||
|
padding: 20px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info-body {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Medals Container */
|
||||||
|
.medals-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 10px;
|
||||||
|
padding-top: 0px;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Medal Item */
|
||||||
|
.medal-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 70px; /* Adjust size based on your actual image size */
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.medal-count {
|
||||||
|
background-color: hsla(30, 4%, 91%, .7);
|
||||||
|
border-radius: 50%;
|
||||||
|
bottom: .5rem;
|
||||||
|
color: black;
|
||||||
|
font-size: 1em;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 0.8em 0;
|
||||||
|
right: .5rem;
|
||||||
|
text-align: center;
|
||||||
|
width: 2.5em;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -2,16 +2,16 @@
|
||||||
{% block three_col_media %}
|
{% block three_col_media %}
|
||||||
{% include "blog/media-css.html" %}
|
{% include "blog/media-css.html" %}
|
||||||
<style>
|
<style>
|
||||||
@media (max-width: 799px) {
|
|
||||||
.title {
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-clarifications-message {
|
.no-clarifications-message {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.org-logo {
|
||||||
|
height: 2em;
|
||||||
|
width: 2em;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
@ -98,7 +98,8 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% include 'contests-countdown.html' %}
|
{% include 'contests-countdown.html' %}
|
||||||
{% include 'recent-organization.html' %}
|
{% include 'profile-table.html' %}
|
||||||
{% include 'top-users.html' %}
|
{% include 'top-users.html' %}
|
||||||
|
{% include 'recent-organization.html' %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
{% block two_col_media %}
|
{% block two_col_media %}
|
||||||
<style>
|
<style>
|
||||||
.organization-container .organization-row:last-child {
|
.organization-container {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
.org-logo {
|
.org-logo {
|
||||||
|
|
79
templates/profile-table.html
Normal file
79
templates/profile-table.html
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
{% if request.profile %}
|
||||||
|
<div class="blog-sidebox sidebox">
|
||||||
|
<h3 class="bold-text colored-text"><i class="fa fa-user"></i>{{ _('Profile') }}</h3>
|
||||||
|
<div class="toggled sidebox-content">
|
||||||
|
<div class="profile-card">
|
||||||
|
<div class="card-header">
|
||||||
|
<a href="{{url('user_page')}}">
|
||||||
|
<img class="avatar" src="{{ gravatar(request.profile) }}" alt="User Avatar">
|
||||||
|
</a>
|
||||||
|
<h4>{{ link_user(request.profile) }}</h4>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="user-info">
|
||||||
|
<div><i class="fa fa-star {{request.profile.css_class}}"></i> {{_('Rating')}}</div>
|
||||||
|
<div class="{{ request.profile.css_class }}">{{ request.profile.rating if request.profile.rating else '-' }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="user-info">
|
||||||
|
<div
|
||||||
|
title="
|
||||||
|
{%- trans trimmed counter=request.profile.problem_count %}
|
||||||
|
{{ counter }} problem solved
|
||||||
|
{% pluralize %}
|
||||||
|
{{ counter }} problems solved
|
||||||
|
{% endtrans -%}"
|
||||||
|
><i class="fa fa-slack darkcyan"></i> {{_('Problems')}}</div>
|
||||||
|
<span class="user-info-body">{{ request.profile.problem_count }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="user-info">
|
||||||
|
<div
|
||||||
|
title="{{_('Total points')}}"
|
||||||
|
><i class="green icofont-tick-mark"></i> {{_('Points')}}</div>
|
||||||
|
<div class="user-info-body"><span title="{{ request.profile.performance_points|floatformat(2) }}">
|
||||||
|
{{ request.profile.performance_points|floatformat(0) }}
|
||||||
|
</span></div>
|
||||||
|
</div>
|
||||||
|
{% if not request.profile.is_unlisted %}
|
||||||
|
<div class="user-info">
|
||||||
|
<div title="{{_('Rank by rating')}}"><i class="fa fa-globe peru" ></i> {{_('Rating')}} #</div>
|
||||||
|
<div class="user-info-body">{{rating_rank if rating_rank else '-'}}</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="user-info">
|
||||||
|
<div title="{{_('Rank by points')}}"><i class="fa fa-globe blue" ></i> {{_('Points')}} #</div>
|
||||||
|
<div class="user-info-body">{{points_rank}}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if awards.medals %}
|
||||||
|
<div class="user-info">
|
||||||
|
<div title="{{ _('Awards') }}"><i class="fa fa-trophy"></i> {{ _('Awards') }}</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if awards %}
|
||||||
|
<div class="medals-container">
|
||||||
|
{% if awards.gold_count > 0 %}
|
||||||
|
<div class="medal-item">
|
||||||
|
<img src="{{ static('awards/gold-medal.png') }}" alt="Gold Medal">
|
||||||
|
<span class="medal-count">{{ awards.gold_count }}</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if awards.silver_count > 0 %}
|
||||||
|
<div class="medal-item">
|
||||||
|
<img src="{{ static('awards/silver-medal.png') }}" alt="Silver Medal">
|
||||||
|
<span class="medal-count">{{ awards.silver_count }}</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if awards.bronze_count > 0 %}
|
||||||
|
<div class="medal-item">
|
||||||
|
<img src="{{ static('awards/bronze-medal.png') }}" alt="Bronze Medal">
|
||||||
|
<span class="medal-count">{{ awards.bronze_count }}</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
|
@ -1,19 +1,3 @@
|
||||||
{% block two_col_media %}
|
|
||||||
<style>
|
|
||||||
.org-logo {
|
|
||||||
height: 2em;
|
|
||||||
width: 2em;
|
|
||||||
margin-right: 0.5em;
|
|
||||||
}
|
|
||||||
.toggle {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.organization-row:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% if recent_organizations %}
|
{% if recent_organizations %}
|
||||||
<div class="blog-sidebox sidebox">
|
<div class="blog-sidebox sidebox">
|
||||||
<h3 class="bold-text colored-text"><i class="fa fa-users"></i>{{ _('Recent groups') }}</h3>
|
<h3 class="bold-text colored-text"><i class="fa fa-users"></i>{{ _('Recent groups') }}</h3>
|
||||||
|
|
|
@ -42,15 +42,15 @@
|
||||||
{% if not user.is_unlisted %}
|
{% if not user.is_unlisted %}
|
||||||
<div class="user-info-card">
|
<div class="user-info-card">
|
||||||
<div class="user-info">
|
<div class="user-info">
|
||||||
<div class="user-info-header" title="{{_('Rank by rating')}}"><i class="fa fa-globe peru" ></i> {{_('Rating')}}</div>
|
<div class="user-info-header" title="{{_('Rank by rating')}}"><i class="fa fa-globe peru" ></i> {{_('Rating')}} #</div>
|
||||||
<div class="user-info-body">{{rating_rank if rating_rank else '-'}}</div>
|
<div class="user-info-body">{{rating_rank if rating_rank else '-'}}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="user-info-card">
|
<div class="user-info-card">
|
||||||
<div class="user-info">
|
<div class="user-info">
|
||||||
<div class="user-info-header" title="{{_('Rank by points')}}"><i class="fa fa-globe blue" ></i> {{_('Points')}}</div>
|
<div class="user-info-header" title="{{_('Rank by points')}}"><i class="fa fa-globe blue" ></i> {{_('Points')}} #</div>
|
||||||
<div class="user-info-body">{{rank}}</div>
|
<div class="user-info-body">{{points_rank}}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -86,7 +86,7 @@
|
||||||
<br>
|
<br>
|
||||||
<div id="awards">
|
<div id="awards">
|
||||||
<h4>{{_('Awards')}}</h4>
|
<h4>{{_('Awards')}}</h4>
|
||||||
{% for medal in awards.medals %}
|
{% for medal in awards %}
|
||||||
{% if medal.ranking == 1 %}
|
{% if medal.ranking == 1 %}
|
||||||
{% set medal_url = static('awards/gold-medal.png') %}
|
{% set medal_url = static('awards/gold-medal.png') %}
|
||||||
{% elif medal.ranking == 2%}
|
{% elif medal.ranking == 2%}
|
||||||
|
|
Loading…
Reference in a new issue