feat: new “show achievement” page
This commit is contained in:
parent
12dfe11fc2
commit
4b9675bf43
|
@ -20,7 +20,7 @@ from comments.views import create_comment, edit_comment, delete_comment, show_co
|
|||
from common.feature_flags import feature_switch
|
||||
from landing.views import landing, docs, godmode_network_settings, godmode_digest_settings, godmode_settings, \
|
||||
godmode_invite
|
||||
from misc.views import stats, network, robots, generate_ical_invite, generate_google_invite
|
||||
from misc.views import stats, network, robots, generate_ical_invite, generate_google_invite, show_achievement
|
||||
from rooms.views import redirect_to_room_chat, list_rooms
|
||||
from notifications.views import render_weekly_digest, email_unsubscribe, email_confirm, render_daily_digest, \
|
||||
email_digest_switch, link_telegram
|
||||
|
@ -123,6 +123,7 @@ urlpatterns = [
|
|||
path("intro/", intro, name="intro"),
|
||||
path("people/", people, name="people"),
|
||||
path("achievements/", RedirectView.as_view(url="/stats", permanent=True), name="achievements"),
|
||||
path("achievements/<slug:achievement_code>/", show_achievement, name="show_achievement"),
|
||||
path("stats/", stats, name="stats"),
|
||||
|
||||
path("profile/tag/<str:tag_code>/toggle/", toggle_tag, name="toggle_tag"),
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
{% extends "layout.html" %}
|
||||
{% load static %}
|
||||
{% load users %}
|
||||
{% load text_filters %}
|
||||
|
||||
{% block title %}
|
||||
Ачивка «{{ achievement.name }}» — {{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block og_tags %}
|
||||
<meta property="og:title" content="Ачивка «{{ achievement.name }}» — {{ settings.APP_NAME }}">
|
||||
<meta property="og:site_name" content="{{ settings.APP_NAME }}">
|
||||
<meta property="og:url" content="{{ settings.APP_HOST }}">
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:description" content="">
|
||||
<meta property="og:image" content="{% static "images/share.png" %}">
|
||||
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="Ачивка «{{ achievement.name }}» — {{ settings.APP_NAME }}">
|
||||
<meta name="twitter:description" content="">
|
||||
<meta name="twitter:image" content="{% static "images/share.png" %}">
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<div class="stats">
|
||||
<div class="stats-achievement-details">
|
||||
{% include "achievements/widgets/achievement.html" %}
|
||||
<div class="stats-achievement-details__info">
|
||||
<span><strong>Название:</strong> {{ achievement.name }}</span>
|
||||
<span><strong>Описание:</strong> {{ achievement.description }}</span>
|
||||
<span><strong>Награждено:</strong> {{ users|length }} {{ users|length|rupluralize:"человек,человека,человек" }}</span>
|
||||
<span><strong>Редкость:</strong> {{ rarity }}%</span>
|
||||
{% if rarity > 75 %}
|
||||
<span>Легендарная и редкая ачивка. Тут только самые лучшие!</span>
|
||||
{% elif rarity > 45 %}
|
||||
<span>Довольно сложная ачивка. Нужно было постараться, чтобы ее заслужить.</span>
|
||||
{% elif rarity > 10 %}
|
||||
<span>Хорошая ачивка за хорошую работу!</span>
|
||||
{% else %}
|
||||
<span>Довольно популярная ачивка, много у кого есть.</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats-achievements-users">
|
||||
{% for user in users %}
|
||||
<a href="{% url "profile" user.slug %}" class="top-user top-user-medium zoom-on-hover">
|
||||
<span class="avatar top-user-avatar">
|
||||
<img src="{{ user.get_avatar }}" alt="Аватар {{ user.full_name }}" loading="lazy" />
|
||||
</span>
|
||||
<span class="top-user-name">{{ user.full_name }}</span>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -0,0 +1,11 @@
|
|||
<a href="{% url "show_achievement" achievement.code %}" class="user-achievement">
|
||||
<span class="user-achievement-wrapper">
|
||||
<span class="user-achievement-front" {% if achievement.style %}style="{{ achievement.style }}"{% endif %}>
|
||||
<img src="{{ achievement.image }}" class="user-badge-image" alt="{{ achievement.code }}">
|
||||
<span class="user-achievement-name">{{ achievement.name }}</span>
|
||||
</span>
|
||||
<span class="user-achievement-back" {% if achievement.style %}style="{{ achievement.style }}"{% endif %}>
|
||||
{{ achievement.description }}
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
|
@ -133,17 +133,7 @@
|
|||
{% for achievement in achievements %}
|
||||
<div class="stats-achievements">
|
||||
<div class="stats-achievements-badge">
|
||||
<span class="user-achievement">
|
||||
<span class="user-achievement-wrapper">
|
||||
<div class="user-achievement-front" {% if achievement.style %}style="{{ achievement.style }}"{% endif %}>
|
||||
<img src="{{ achievement.image }}" class="user-badge-image" alt="{{ achievement.code }}">
|
||||
<span class="user-achievement-name">{{ achievement.name }}</span>
|
||||
</div>
|
||||
<div class="user-achievement-back" {% if achievement.style %}style="{{ achievement.style }}"{% endif %}>
|
||||
{{ achievement.description }}
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
{% include "achievements/widgets/achievement.html" %}
|
||||
</div>
|
||||
<div class="stats-achievements-users">
|
||||
{% for user in achievement.achievement_users|slice:":25" %}
|
||||
|
|
|
@ -313,17 +313,7 @@
|
|||
<div class="profile-achievements">
|
||||
<div class="user-achievements">
|
||||
{% for achievement in achievements %}
|
||||
<span class="user-achievement">
|
||||
<span class="user-achievement-wrapper">
|
||||
<div class="user-achievement-front" {% if achievement.style %}style="{{ achievement.style }}"{% endif %}>
|
||||
<img src="{{ achievement.image }}" class="user-badge-image" alt="{{ achievement.code }}">
|
||||
<span class="user-achievement-name">{{ achievement.name }}</span>
|
||||
</div>
|
||||
<div class="user-achievement-back" {% if achievement.style %}style="{{ achievement.style }}"{% endif %}>
|
||||
{{ achievement.description }}
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
{% include "achievements/widgets/achievement.html" %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -243,6 +243,11 @@
|
|||
perspective: 1000px;
|
||||
}
|
||||
|
||||
.user-achievement:hover {
|
||||
color: #FFF;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.user-achievement:hover .user-achievement-wrapper {
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
|
|
|
@ -166,6 +166,30 @@
|
|||
}
|
||||
}
|
||||
|
||||
.stats-achievement-details {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.stats-achievement-details .user-achievement {
|
||||
width: 250px;
|
||||
height: 350px;
|
||||
font-size: 180%;
|
||||
}
|
||||
|
||||
.stats-achievement-details .user-badge-image {
|
||||
max-height: 180px;
|
||||
}
|
||||
|
||||
.stats-achievement-details__info {
|
||||
max-width: 60%;
|
||||
font-size: 150%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.stats-badges-latest {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
|
|
@ -3,15 +3,15 @@ from urllib.parse import urlencode
|
|||
|
||||
import pytz
|
||||
from django.db.models import Count, Q, Sum
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import render, redirect
|
||||
from django.http import HttpResponse, Http404
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.views.decorators.http import require_GET
|
||||
from icalendar import Calendar, Event
|
||||
|
||||
from authn.decorators.auth import require_auth
|
||||
from badges.models import UserBadge
|
||||
from misc.models import NetworkGroup
|
||||
from users.models.achievements import Achievement
|
||||
from users.models.achievements import Achievement, UserAchievement
|
||||
from users.models.user import User
|
||||
|
||||
|
||||
|
@ -58,6 +58,34 @@ def stats(request):
|
|||
})
|
||||
|
||||
|
||||
@require_auth
|
||||
def show_achievement(request, achievement_code):
|
||||
achievement = get_object_or_404(Achievement, code=achievement_code)
|
||||
if not achievement.is_visible:
|
||||
raise Http404()
|
||||
|
||||
users = User.objects.filter(achievements__achievement_id=achievement_code)
|
||||
|
||||
# calculate rarity of the achievement
|
||||
achievement_stats = dict(
|
||||
UserAchievement.objects.all()
|
||||
.values("achievement_id")
|
||||
.annotate(total=Count("achievement_id"))
|
||||
.order_by("-total")
|
||||
.values_list("achievement_id", "total")
|
||||
)
|
||||
|
||||
total_count = len(achievement_stats.values())
|
||||
index = list(achievement_stats.keys()).index(achievement_code)
|
||||
rarity = (index / total_count) * 100
|
||||
|
||||
return render(request, "achievements/show_achievement.html", {
|
||||
"achievement": achievement,
|
||||
"users": users,
|
||||
"rarity": round(rarity, 1)
|
||||
})
|
||||
|
||||
|
||||
@require_auth
|
||||
def network(request):
|
||||
network_groups = NetworkGroup.visible_objects()
|
||||
|
|
|
@ -5,9 +5,9 @@ from django.conf import settings
|
|||
from django.core.management import BaseCommand
|
||||
from django.db.models import Q
|
||||
from django.template import loader
|
||||
from django_q.tasks import async_task
|
||||
|
||||
from notifications.email.sender import send_mass_email, send_transactional_email
|
||||
from notifications.telegram.common import send_telegram_message, Chat
|
||||
from users.models.user import User
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
@ -18,7 +18,8 @@ class Command(BaseCommand):
|
|||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument("--users", nargs=1, type=str, required=True)
|
||||
parser.add_argument("--template", nargs=1, type=str, required=True)
|
||||
parser.add_argument("--email-template", nargs=1, type=str, required=True)
|
||||
parser.add_argument("--telegram-template", nargs=1, type=str, required=False)
|
||||
parser.add_argument("--title", nargs=1, type=str, required=True)
|
||||
parser.add_argument("--promo", nargs=1, type=bool, required=False, default=False)
|
||||
|
||||
|
@ -38,9 +39,14 @@ class Command(BaseCommand):
|
|||
self.stdout.write("Aborted")
|
||||
return
|
||||
|
||||
# find the template
|
||||
# find all necessary templates for email and telegram
|
||||
email_template = loader.get_template(options.get("email_template")[0])
|
||||
|
||||
telegram_template = None
|
||||
if options.get("telegram_template"):
|
||||
telegram_template = loader.get_template(options.get("telegram_template")[0])
|
||||
|
||||
is_promo = options.get("promo")
|
||||
template = loader.get_template(options.get("template")[0])
|
||||
|
||||
# send emails to existing users
|
||||
for user in users:
|
||||
|
@ -55,17 +61,24 @@ class Command(BaseCommand):
|
|||
sender(
|
||||
recipient=user.email,
|
||||
subject=options.get("title")[0],
|
||||
html=template.render({"user": user}),
|
||||
html=email_template.render({"user": user}),
|
||||
unsubscribe_link=f"{settings.APP_HOST}/notifications/unsubscribe/{user.id}/{secret_code}/",
|
||||
)
|
||||
|
||||
if telegram_template and user.telegram_id:
|
||||
self.stdout.write(f"Sending telegram message to {user.telegram_id}...")
|
||||
send_telegram_message(
|
||||
chat=Chat(id=user.telegram_id),
|
||||
text=telegram_template.render({"user": user}),
|
||||
)
|
||||
|
||||
# send emails to not found users
|
||||
for email in not_found_users:
|
||||
self.stdout.write(f"Sending email to {email}...")
|
||||
send_transactional_email(
|
||||
recipient=email,
|
||||
subject=options.get("title")[0],
|
||||
html=template.render(),
|
||||
html=email_template.render(),
|
||||
)
|
||||
|
||||
self.stdout.write("Done 🥙")
|
||||
|
|
|
@ -26,7 +26,7 @@ def async_create_or_update_achievement(user_achievement: UserAchievement):
|
|||
send_telegram_image(
|
||||
chat=Chat(id=user.telegram_id),
|
||||
image_url=achievement.image,
|
||||
text=render_html_message("achievement.html", user=user, achievement=achievement),
|
||||
text=render_html_message("show_achievement.html", user=user, achievement=achievement),
|
||||
)
|
||||
|
||||
# emails
|
||||
|
|
Loading…
Reference in New Issue