Profile medals: refactor code

This commit is contained in:
rubenwardy 2021-07-25 23:41:34 +01:00
parent e06ac1689c
commit 034e5382ec
2 changed files with 178 additions and 173 deletions

@ -15,10 +15,12 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
import math import math
from typing import List, Optional, Tuple
from flask import * from flask import *
from flask_babel import gettext
from flask_login import current_user, login_required from flask_login import current_user, login_required
from sqlalchemy import func from sqlalchemy import func, and_, or_
from app.models import * from app.models import *
from app.tasks.forumtasks import checkForumAccount from app.tasks.forumtasks import checkForumAccount
@ -44,6 +46,159 @@ def by_forums_username(username):
return render_template("users/forums_no_such_user.html", username=username) return render_template("users/forums_no_such_user.html", username=username)
class Medal:
description: str
color: Optional[str]
icon: str
title: Optional[str]
progress: Optional[Tuple[int, int]]
def __init__(self, description: str, **kwargs):
self.description = description
self.color = kwargs.get("color", "white")
self.icon = kwargs.get("icon", None)
self.title = kwargs.get("title", None)
self.progress = kwargs.get("progress", None)
@classmethod
def make_unlocked(cls, color: str, icon: str, title: str, description: str):
return Medal(description=description, color=color, icon=icon, title=title)
@classmethod
def make_locked(cls, description: str, progress: Tuple[int, int]):
return Medal(description=description, progress=progress)
def place_to_color(place: int) -> str:
if place == 1:
return "gold"
elif place == 2:
return "#888"
elif place == 3:
return "#cd7f32"
else:
return "white"
def get_user_medals(user: User) -> Tuple[List[Medal], List[Medal]]:
unlocked = []
locked = []
#
# REVIEWS
#
users_by_reviews = db.session.query(User.username, func.count(PackageReview.id).label("count")) \
.select_from(User).join(PackageReview) \
.group_by(User.username).order_by(text("count DESC")).all()
try:
review_boundary = users_by_reviews[math.floor(len(users_by_reviews) * 0.25)][1] + 1
except IndexError:
review_boundary = None
users_by_reviews = [username for username, _ in users_by_reviews]
review_idx = None
review_percent = None
try:
review_idx = users_by_reviews.index(user.username)
review_percent = round(100 * review_idx / len(users_by_reviews), 1)
except ValueError:
pass
if review_percent and review_percent < 25:
if review_idx == 0:
title = gettext(u"Most reviews")
description = gettext(
u"%(display_name)s has written the most reviews on ContentDB.",
display_name=user.display_name)
elif review_idx <= 2:
if review_idx == 1:
title = gettext(u"2nd most reviews")
else:
title = gettext(u"3rd most reviews")
description = gettext(
u"This puts %(display_name)s in the top %(perc)s%%",
display_name=user.display_name, perc=review_percent)
else:
title = gettext(u"Top %(perc)s%% reviewer", perc=review_percent)
description = gettext(u"Only %(place)d users have written more reviews.", place=review_idx)
unlocked.append(Medal.make_unlocked(
place_to_color(review_idx + 1), "fa-star-half-alt", title, description))
else:
description = gettext(u"Consider writing more reviews to get a medal.")
if review_idx:
description += " " + gettext(u"You are in place %(place)s.", place=review_idx + 1)
locked.append(Medal.make_locked(
description, (len(user.reviews), review_boundary)))
#
# TOP PACKAGES
#
all_package_ranks = db.session.query(
Package.type,
Package.author_id,
func.rank().over(
order_by=db.desc(Package.score),
partition_by=Package.type) \
.label("rank")).order_by(db.asc(text("rank"))) \
.filter_by(state=PackageState.APPROVED).subquery()
user_package_ranks = db.session.query(all_package_ranks) \
.filter_by(author_id=user.id) \
.filter(text("rank <= 30")) \
.all()
user_package_ranks = next(
(x for x in user_package_ranks if x[0] == PackageType.MOD or x[2] <= 10),
None)
if user_package_ranks:
top_rank = user_package_ranks[2]
top_type = PackageType.coerce(user_package_ranks[0]).value
if top_rank == 1:
title = gettext(u"Top %(type)s", type=top_type.lower())
else:
title = gettext(u"Top %(group)d %(type)s", group=top_rank, type=top_type.lower())
description = gettext(u"%(display_name)s has a %(type)s placed at #%(place)d.",
display_name=user.display_name, type=top_type.lower(), place=top_rank)
unlocked.append(
Medal.make_unlocked(place_to_color(top_rank), "fa-trophy", title, description))
#
# DOWNLOADS
#
total_downloads = db.session.query(func.sum(Package.downloads)) \
.select_from(User) \
.join(User.packages) \
.filter(User.id == user.id,
Package.state == PackageState.APPROVED).scalar()
if total_downloads is None:
pass
elif total_downloads < 50000:
description = gettext(u"Your packages have %(downloads)d downloads in total.", downloads=total_downloads)
description += " " + gettext(u"First medal is at 50k.")
locked.append(Medal.make_locked(description, (total_downloads, 50000)))
else:
if total_downloads >= 300000:
place = 1
title = gettext(u">300k downloads")
elif total_downloads >= 100000:
place = 2
title = gettext(u">100k downloads")
elif total_downloads >= 75000:
place = 3
title = gettext(u">75k downloads")
else:
place = 10
title = gettext(u">50k downloads")
description = gettext(u"Has received %(downloads)d downloads across all packages.",
display_name=user.display_name, downloads=total_downloads)
unlocked.append(Medal.make_unlocked(place_to_color(place), "fa-users", title, description))
return unlocked, locked
@bp.route("/users/<username>/") @bp.route("/users/<username>/")
def profile(username): def profile(username):
user = User.query.filter_by(username=username).first() user = User.query.filter_by(username=username).first()
@ -62,49 +217,11 @@ def profile(username):
.filter(Package.author != user) \ .filter(Package.author != user) \
.order_by(db.asc(Package.title)).all() .order_by(db.asc(Package.title)).all()
users_by_reviews = db.session.query(User.username, func.count(PackageReview.id).label("count")) \ unlocked, locked = get_user_medals(user)
.select_from(User).join(PackageReview) \
.group_by(User.username).order_by(text("count DESC")).all()
try:
review_boundary = users_by_reviews[math.floor(len(users_by_reviews) * 0.25)][1] + 1
except IndexError:
review_boundary = None
users_by_reviews = [ username for username, _ in users_by_reviews ]
review_idx = None
review_percent = None
try:
review_idx = users_by_reviews.index(user.username)
review_percent = round(100 * review_idx / len(users_by_reviews), 1)
except ValueError:
pass
total_downloads = db.session.query(func.sum(Package.downloads)) \
.select_from(User) \
.join(User.packages) \
.filter(User.id == user.id, Package.state == PackageState.APPROVED).scalar() or 0
all_package_ranks = db.session.query(
Package.type,
Package.author_id,
func.rank().over(order_by=db.desc(Package.score), partition_by=Package.type) \
.label('rank')).order_by(db.asc(text("rank"))) \
.filter_by(state=PackageState.APPROVED).subquery()
user_package_ranks = db.session.query(all_package_ranks) \
.filter_by(author_id=user.id).first()
min_package_rank = None
min_package_type = None
if user_package_ranks:
min_package_rank = user_package_ranks[2]
min_package_type = PackageType.coerce(user_package_ranks[0]).value
# Process GET or invalid POST # Process GET or invalid POST
return render_template("users/profile.html", user=user, return render_template("users/profile.html", user=user,
packages=packages, maintained_packages=maintained_packages, packages=packages, maintained_packages=maintained_packages,
total_downloads=total_downloads, medals_unlocked=unlocked, medals_locked=locked)
review_idx=review_idx, review_percent=review_percent, review_boundary=review_boundary,
min_package_rank=min_package_rank, min_package_type=min_package_type)
@bp.route("/users/<username>/check/", methods=["POST"]) @bp.route("/users/<username>/check/", methods=["POST"])

@ -123,153 +123,41 @@
</div> </div>
{% else %} {% else %}
<div class="row mb-5"> <div class="row mb-5">
{% if review_percent is not none and review_percent < 25 and review_idx >= 0 %} {% for medal in medals_unlocked %}
<div class="col-md-4"> <div class="col-md-4">
<div class="card"> <div class="card">
{% if review_idx == 0 %}
{% set badge_color = "gold" %}
{% elif review_idx == 1 %}
{% set badge_color = "#888" %}
{% elif review_idx == 2 %}
{% set badge_color = "#cd7f32" %}
{% else %}
{% set badge_color = "white" %}
{% endif %}
<div class="card-body media align-items-center"> <div class="card-body media align-items-center">
<i class="fas fa-star-half-alt ml-2 mr-4 text-size" style="font-size: 45px; color: {{ badge_color }};"></i> <i class="fas {{ medal.icon }} ml-2 mr-4 text-size"
style="font-size: 45px; color: {{ medal.color }};"></i>
<div class="media-body"> <div class="media-body">
{% if review_idx == 0 %}
<h5 class="mt-0"> <h5 class="mt-0">
{{ _("Most reviews", perc=review_percent) }} {{ medal.title }}
</h5> </h5>
<p class="my-0"> <p class="my-0">
{{ _("%(display_name)s has written the most reviews on ContentDB.", {{ medal.description }}
display_name=user.display_name) }}
</p> </p>
{% elif review_idx <= 2 %}
<h5 class="mt-0">
{% if review_idx == 1 %}
{{ _("2nd most reviews", perc=review_percent) }}
{% else %}
{{ _("3rd most reviews", perc=review_percent) }}
{% endif %}
</h5>
<p class="my-0">
{{ _("This puts %(display_name)s in the top %(perc)s%%",
display_name=user.display_name, perc=review_percent) }}
</p>
{% else %}
<h5 class="mt-0">
{{ _("Top %(perc)s%% reviewer", perc=review_percent) }}
</h5>
<p class="my-0">
{% if review_idx == 1 %}
{{ _("Only 1 user has written more reviews.") }}
{% else %}
{{ _("Only %(place)d users have written more reviews.", place=review_idx) }}
{% endif %}
</p>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% elif current_user == user %} {% endfor %}
{% if current_user == user %}
{% for medal in medals_locked %}
<div class="col-md-4"> <div class="col-md-4">
<div class="border border-dark rounded p-3 text-muted my-0"> <div class="border border-dark rounded p-3 text-muted my-0">
<p> <p>
{{ _("Consider writing more reviews to get a medal.") }} {{ medal.description }}
{% if review_idx %}
{{ _("You are in place %(place)s.", place=review_idx + 1) }}
{% endif %}
</p> </p>
<div class="progress"> <div class="progress">
<div class="progress-bar" role="progressbar" <div class="progress-bar" role="progressbar"
style="width: {{ [100 * num_reviews / review_boundary, 100] | min }}%;" style="width: {{ [100 * medal.progress[0] / medal.progress[1], 100] | min }}%;"
aria-valuenow="{{ num_reviews }}" aria-valuemin="0" aria-valuemax="{{ review_boundary }}"> aria-valuenow="{{ medal.progress[0] }}" aria-valuemin="0" aria-valuemax="{{ medal.progress[1] }}">
{{ _("%(value)d / %(target)d", value=num_reviews, target=review_boundary) }} {{ _("%(value)d / %(target)d", value=medal.progress[0], target=medal.progress[1]) }}
</div>
</div>
</div>
</div>
{% endif %}
{% if total_downloads >= 50000 %}
{% if total_downloads >= 300000 %}
{% set badge_color = "gold" %}
{% elif total_downloads >= 100000 %}
{% set badge_color = "#888" %}
{% elif total_downloads >= 75000 %}
{% set badge_color = "#cd7f32" %}
{% else %}
{% set badge_color = "white" %}
{% endif %}
<div class="col-md-4">
<div class="card">
<div class="card-body media align-items-center">
<i class="fas fa-users ml-2 mr-4 text-size" style="font-size: 45px; color: {{ badge_color }};"></i>
<div class="media-body">
<h5 class="mt-0">
{% if total_downloads >= 300000 %}
{{ _(">300k downloads") }}
{% elif total_downloads >= 100000 %}
{{ _(">100k downloads") }}
{% elif total_downloads >= 75000 %}
{{ _(">75k downloads") }}
{% else %}
{{ _(">50k downloads") }}
{% endif %}
</h5>
<p class="my-0">
{{ _("Has received %(downloads)d downloads across all packages.",
display_name=user.display_name, downloads=total_downloads) }}
</p>
</div>
</div>
</div>
</div>
{% elif total_downloads > 0 and current_user == user %}
<div class="col-md-4">
<div class="border border-dark rounded p-3 text-muted my-0">
<p>
{{ _("Your packages have %(downloads)d downloads in total.", downloads=total_downloads) }}
{{ _("First medal is at 50k.") }}
</p>
<div class="progress">
<div class="progress-bar" role="progressbar"
style="width: {{ [100 * total_downloads / 50000, 100] | min }}%;"
aria-valuenow="{{ total_downloads }}" aria-valuemin="0" aria-valuemax="50000">
{{ _("%(value)d / %(target)d", value=total_downloads, target=50000) }}
</div>
</div>
</div>
</div>
{% endif %}
{% if min_package_rank is not none and min_package_rank <= 20 %}
{% if min_package_rank == 1 %}
{% set badge_color = "gold" %}
{% elif min_package_rank == 2 %}
{% set badge_color = "#888" %}
{% elif min_package_rank == 3 %}
{% set badge_color = "#cd7f32" %}
{% else %}
{% set badge_color = "white" %}
{% endif %}
<div class="col-md-4">
<div class="card">
<div class="card-body media align-items-center">
<i class="fas fa-trophy ml-2 mr-4 text-size" style="font-size: 45px; color: {{ badge_color }};"></i>
<div class="media-body">
<h5 class="mt-0">
{{ _("Top %(place)d %(type)s", place=min_package_rank, type=min_package_type) }}
</h5>
<p class="my-0">
{{ _("%(display_name)s has a package placed at #%(place)d.",
display_name=user.display_name, place=min_package_rank) }}
</p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% endfor %}
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}