mod:кастомный шаблонный фильтр для статистики
This commit is contained in:
@@ -45,9 +45,9 @@
|
|||||||
|
|
||||||
{# ШАПКА и главное меню #}
|
{# ШАПКА и главное меню #}
|
||||||
<nav id="main-navbar" class="navbar navbar-expand-lg mb-4">
|
<nav id="main-navbar" class="navbar navbar-expand-lg mb-4">
|
||||||
<div class="container">
|
<div class="container p-0">
|
||||||
<a class="navbar-brand" href="/">
|
<a class="navbar-brand" href="/">
|
||||||
<img id="logo-img" class="logo-img" src=""
|
<img id="logo-img" class="logo-img p-0 m-0" src=""
|
||||||
data-src-light="{% static 'svg/logo-etpgrf-site-light.svg' %}"
|
data-src-light="{% static 'svg/logo-etpgrf-site-light.svg' %}"
|
||||||
data-src-light-compact="{% static 'svg/logo-etpgrf-site-light-compact.svg' %}"
|
data-src-light-compact="{% static 'svg/logo-etpgrf-site-light-compact.svg' %}"
|
||||||
data-src-dark="{% static 'svg/logo-etpgrf-site-dark.svg' %}"
|
data-src-dark="{% static 'svg/logo-etpgrf-site-dark.svg' %}"
|
||||||
@@ -64,12 +64,12 @@
|
|||||||
{# Футер #}
|
{# Футер #}
|
||||||
<footer class="footer mt-auto py-2 mt-4">
|
<footer class="footer mt-auto py-2 mt-4">
|
||||||
<div class="container d-flex justify-content-between align-items-center">
|
<div class="container d-flex justify-content-between align-items-center">
|
||||||
<span class="text-muted small nowrap">© Sergei Erjemin, 2025–{% now 'Y' %}.</span>
|
<span class="text-muted small nowrap me-2">© Sergei Erjemin, 2025–{% now 'Y' %}.</span>
|
||||||
|
|
||||||
<span class="text-muted small nowrap"><i class="bi bi-tags me-1" title="Версия библиотеки etpgrf / Версия сайта"></i>v0.1.3 / v0.1.3</span>
|
<nobr class="text-muted small mx-2"><i class="bi bi-tags me-1" title="Версия библиотеки etpgrf / Версия сайта"></i>v0.1.3 / v0.1.3</nobr>
|
||||||
|
|
||||||
{# Сводная статистика (HTMX) #}
|
{# Сводная статистика (HTMX) #}
|
||||||
<span class="text-muted small" hx-get="{% url 'stats_summary' %}" hx-trigger="load">
|
<span class="text-muted small ms-2" hx-get="{% url 'stats_summary' %}" hx-trigger="load">
|
||||||
...
|
...
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
<span class="me-3" title="Просмотров">
|
{% load typograph_extras %}
|
||||||
<i class="bi bi-eye me-1"></i>{{ views }}
|
<nobr class="ms-3 float-end" title="Скопировано в буфер текстов/символов">
|
||||||
</span>
|
<i class="bi bi-clipboard-check me-1"></i>{{ copied|humanize_num }} / {{ chars_copied|humanize_num }}
|
||||||
<span class="me-3 nowrap" title="На вход обработано текстов/символов">
|
</nobr>
|
||||||
<i class="bi bi-box-arrow-in-right me-1"></i>{{ processed }}/{{ chars_in }}
|
<nobr class="ms-3 float-end" title="На выход получено символов">
|
||||||
</span>
|
<i class="bi bi-box-arrow-right me-1"></i>{{ chars_out|humanize_num }}
|
||||||
<span class="me-3" title="На выход получено символов">
|
</nobr>
|
||||||
<i class="bi bi-box-arrow-right me-1"></i>{{ chars_out }}
|
<nobr class="ms-3 float-end" title="На вход обработано текстов/символов">
|
||||||
</span>
|
<i class="bi bi-box-arrow-in-right me-1"></i>{{ processed|humanize_num }} / {{ chars_in|humanize_num }}
|
||||||
<span class="nowrap" title="Скопировано в буфер текстов/символов">
|
</nobr>
|
||||||
<i class="bi bi-clipboard-check me-1"></i>{{ copied }}/{{ chars_copied }}
|
<nobr class="ms-3 float-end" title="Просмотров">
|
||||||
</span>
|
<i class="bi bi-eye me-1"></i>{{ views|humanize_num }}
|
||||||
|
</nobr>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
45
etpgrf_site/typograph/templatetags/typograph_extras.py
Normal file
45
etpgrf_site/typograph/templatetags/typograph_extras.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
from django import template
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
@register.filter(name='humanize_num')
|
||||||
|
def humanize_num(value):
|
||||||
|
"""
|
||||||
|
Форматирует число с тонкими пробелами в качестве разделителя тысяч
|
||||||
|
и сокращает большие числа до M (миллионы) или k (тысячи).
|
||||||
|
|
||||||
|
Примеры:
|
||||||
|
1234 -> 1 234
|
||||||
|
1234567 -> 1,2M
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
num = int(value)
|
||||||
|
if num > 1_000_000_000:
|
||||||
|
val = num / 1_000_000_000
|
||||||
|
suffix = " B"
|
||||||
|
elif num > 1_000_000:
|
||||||
|
val = num / 1_000_000
|
||||||
|
suffix = " M"
|
||||||
|
elif num > 1_000:
|
||||||
|
val = num / 1_000
|
||||||
|
suffix = " k"
|
||||||
|
else:
|
||||||
|
# Больше 1B -- форматируем с пробелами
|
||||||
|
return mark_safe(f"{num:,}".replace(",", " "))
|
||||||
|
|
||||||
|
# Форматируем float:
|
||||||
|
# {:,.1f} - разделитель тысяч (запятая) и 1 знак после точки
|
||||||
|
# 1234567.89 -> "1,234,567.9"
|
||||||
|
formatted = f"{val:,.2f}"
|
||||||
|
|
||||||
|
# Меняем английскую запятую (разделитель тысяч) на тонкий пробел
|
||||||
|
# Меняем английскую точку (десятичный разделитель) на запятую
|
||||||
|
# Но тут проблема: replace делает все сразу.
|
||||||
|
# "1,234.5" -> replace(",", " ") -> "1 234.5" -> replace(".", ",") -> "1 234,5"
|
||||||
|
formatted = formatted.replace(",", " ").replace(".", ",")
|
||||||
|
|
||||||
|
return mark_safe(f"{formatted}{suffix}")
|
||||||
|
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return value
|
||||||
@@ -23,35 +23,28 @@ def index(request):
|
|||||||
|
|
||||||
def get_stats_summary(request):
|
def get_stats_summary(request):
|
||||||
"""Возвращает сводную статистику."""
|
"""Возвращает сводную статистику."""
|
||||||
# Убираем try...except для отладки
|
try:
|
||||||
stats = DailyStat.objects.aggregate(
|
stats = DailyStat.objects.aggregate(
|
||||||
views=Sum('index_views'),
|
views=Sum('index_views'),
|
||||||
processed=Sum('process_requests'),
|
processed=Sum('process_requests'),
|
||||||
copied=Sum('copy_count'),
|
copied=Sum('copy_count'),
|
||||||
chars_in=Sum('chars_in'),
|
chars_in=Sum('chars_in'),
|
||||||
chars_out=Sum('chars_out'),
|
chars_out=Sum('chars_out'),
|
||||||
chars_copied=Sum('chars_copied')
|
chars_copied=Sum('chars_copied')
|
||||||
)
|
)
|
||||||
# print("Aggregated stats:", stats) # DEBUG
|
|
||||||
|
|
||||||
# Функция для форматирования чисел с сокращениями (M, k)
|
context = {
|
||||||
def format_large_number(num):
|
'views': stats['views'] or 0,
|
||||||
if num > 1_000_000:
|
'processed': stats['processed'] or 0,
|
||||||
return f"{num / 1_000_000:.3f}M".replace(".", ",")
|
'copied': stats['copied'] or 0,
|
||||||
elif num > 1_000:
|
'chars_in': stats['chars_in'] or 0,
|
||||||
return f"{num / 1_000:.2f}k".replace(".", ",")
|
'chars_out': stats['chars_out'] or 0,
|
||||||
return str(num)
|
'chars_copied': stats['chars_copied'] or 0,
|
||||||
|
}
|
||||||
|
|
||||||
context = {
|
return render(request, 'typograph/stats_summary.html', context)
|
||||||
'views': f"{(stats['views'] or 0):,}".replace(",", " "),
|
except Exception:
|
||||||
'processed': f"{(stats['processed'] or 0):,}".replace(",", " "),
|
return HttpResponse("...")
|
||||||
'copied': f"{(stats['copied'] or 0):,}".replace(",", " "),
|
|
||||||
'chars_in': format_large_number(stats['chars_in'] or 0),
|
|
||||||
'chars_out': format_large_number(stats['chars_out'] or 0),
|
|
||||||
'chars_copied': format_large_number(stats['chars_copied'] or 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
return render(request, 'typograph/stats_summary.html', context)
|
|
||||||
|
|
||||||
|
|
||||||
@require_POST
|
@require_POST
|
||||||
|
|||||||
Reference in New Issue
Block a user