|
|
|
|
@@ -89,6 +89,44 @@ def _label(value) -> str:
|
|
|
|
|
return str(value or UNKNOWN_LABEL)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _normalize_entry_year(value) -> str:
|
|
|
|
|
if value in (None, ""):
|
|
|
|
|
return UNKNOWN_LABEL
|
|
|
|
|
raw = str(value).strip()
|
|
|
|
|
normalized = (
|
|
|
|
|
raw.translate(str.maketrans("۰۱۲۳۴۵۶۷۸۹٠١٢٣٤٥٦٧٨٩", "01234567890123456789"))
|
|
|
|
|
.replace(",", "")
|
|
|
|
|
.replace("٬", "")
|
|
|
|
|
)
|
|
|
|
|
try:
|
|
|
|
|
year = int(normalized)
|
|
|
|
|
except (TypeError, ValueError):
|
|
|
|
|
return UNKNOWN_LABEL
|
|
|
|
|
|
|
|
|
|
if 1390 <= year <= 1499:
|
|
|
|
|
return str(year)
|
|
|
|
|
if 400 <= year <= 499:
|
|
|
|
|
return str(1000 + year)
|
|
|
|
|
if 90 <= year <= 99:
|
|
|
|
|
return str(1300 + year)
|
|
|
|
|
return UNKNOWN_LABEL
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _point_group_from_rows(rows: list[dict], *, limit: int = TOP_ITEMS_LIMIT):
|
|
|
|
|
sorted_rows = sorted(rows, key=lambda item: (-int(item["value"] or 0), str(item["label"])))
|
|
|
|
|
other_rows = sorted_rows[limit:]
|
|
|
|
|
items = [
|
|
|
|
|
{"label": _label(item["label"]), "value": int(item["value"] or 0)}
|
|
|
|
|
for item in sorted_rows
|
|
|
|
|
]
|
|
|
|
|
return {
|
|
|
|
|
"items": items,
|
|
|
|
|
"top_items": items[:limit],
|
|
|
|
|
"other_count": len(other_rows),
|
|
|
|
|
"total_count": len(sorted_rows),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _point_queryset(queryset, label_field: str, *, limit: int = 12):
|
|
|
|
|
return [
|
|
|
|
|
{"label": _label(item.get(label_field)), "value": item["value"]}
|
|
|
|
|
@@ -110,18 +148,30 @@ def _point_group_queryset(queryset, label_field: str, *, limit: int = TOP_ITEMS_
|
|
|
|
|
.order_by("-value", label_field)
|
|
|
|
|
)
|
|
|
|
|
total_count = len(rows)
|
|
|
|
|
top_rows = rows[:limit]
|
|
|
|
|
other_rows = rows[limit:]
|
|
|
|
|
return {
|
|
|
|
|
"top_items": [
|
|
|
|
|
items = [
|
|
|
|
|
{"label": _label(item.get(label_field)), "value": int(item["value"] or 0)}
|
|
|
|
|
for item in top_rows
|
|
|
|
|
],
|
|
|
|
|
for item in rows
|
|
|
|
|
]
|
|
|
|
|
return {
|
|
|
|
|
"items": items,
|
|
|
|
|
"top_items": items[:limit],
|
|
|
|
|
"other_count": len(other_rows),
|
|
|
|
|
"total_count": total_count,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _entry_year_group_queryset(queryset, *, limit: int = TOP_ITEMS_LIMIT):
|
|
|
|
|
buckets: dict[str, int] = {}
|
|
|
|
|
for item in queryset.values("year_of_study").annotate(value=Count("id")):
|
|
|
|
|
label = _normalize_entry_year(item.get("year_of_study"))
|
|
|
|
|
buckets[label] = buckets.get(label, 0) + int(item["value"] or 0)
|
|
|
|
|
return _point_group_from_rows(
|
|
|
|
|
[{"label": label, "value": value} for label, value in buckets.items()],
|
|
|
|
|
limit=limit,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _trend_queryset(queryset, field: str, granularity: str):
|
|
|
|
|
trunc = GRANULARITY_TRUNC[granularity]
|
|
|
|
|
rows = (
|
|
|
|
|
@@ -263,7 +313,7 @@ def admin_users_analytics(request, date_from: str | None = None, date_to: str |
|
|
|
|
|
"signup_trend": _trend_queryset(users_qs, "date_joined", granularity),
|
|
|
|
|
"by_major": _point_group_queryset(users_qs, "major__name"),
|
|
|
|
|
"by_university": _point_group_queryset(users_qs, "university__name"),
|
|
|
|
|
"by_year": _point_group_queryset(users_qs, "year_of_study"),
|
|
|
|
|
"by_year": _entry_year_group_queryset(users_qs),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -454,6 +504,17 @@ def admin_blog_analytics(request, date_from: str | None = None, date_to: str | N
|
|
|
|
|
}
|
|
|
|
|
for post in post_popularity_all[:TOP_ITEMS_LIMIT]
|
|
|
|
|
]
|
|
|
|
|
post_popularity_items = [
|
|
|
|
|
{
|
|
|
|
|
"id": post.id,
|
|
|
|
|
"title": post.title,
|
|
|
|
|
"slug": post.slug,
|
|
|
|
|
"likes": post.likes_total,
|
|
|
|
|
"saves": post.saves_total,
|
|
|
|
|
"comments": post.comments_total,
|
|
|
|
|
}
|
|
|
|
|
for post in post_popularity_all
|
|
|
|
|
]
|
|
|
|
|
top_posts = [
|
|
|
|
|
{
|
|
|
|
|
**post,
|
|
|
|
|
@@ -489,6 +550,7 @@ def admin_blog_analytics(request, date_from: str | None = None, date_to: str | N
|
|
|
|
|
},
|
|
|
|
|
"activity_trend": list(activity_buckets.values()),
|
|
|
|
|
"post_popularity": {
|
|
|
|
|
"items": post_popularity_items,
|
|
|
|
|
"top_items": post_popularity,
|
|
|
|
|
"other_count": max(len(post_popularity_all) - TOP_ITEMS_LIMIT, 0),
|
|
|
|
|
"total_count": len(post_popularity_all),
|
|
|
|
|
@@ -742,7 +804,7 @@ def admin_dashboard(
|
|
|
|
|
"signup_trend": _trend_queryset(users_qs, "date_joined", selected_granularity),
|
|
|
|
|
"by_major": _point_queryset(users_qs, "major__name"),
|
|
|
|
|
"by_university": _point_queryset(users_qs, "university__name"),
|
|
|
|
|
"by_year": _point_queryset(users_qs, "year_of_study"),
|
|
|
|
|
"by_year": _entry_year_group_queryset(users_qs)["top_items"],
|
|
|
|
|
},
|
|
|
|
|
"events": {
|
|
|
|
|
"registration_status": registration_status,
|
|
|
|
|
|