diff --git a/drakul/base/settings/common.py b/drakul/base/settings/common.py
index 0df7c39..da94826 100755
--- a/drakul/base/settings/common.py
+++ b/drakul/base/settings/common.py
@@ -165,3 +165,9 @@ DEFAULT_ATTENDANCE = {
"Stand By": 1.0,
"Confirmed": 1.0,
}
+
+ATTENDANCE_COLORS = [
+ (0.8, "#28a745"),
+ (0.5, "#ffc107"),
+ (-10, "#dc3545")
+]
diff --git a/drakul/base/templates/base.html b/drakul/base/templates/base.html
index 3380bf8..fbb4b5f 100644
--- a/drakul/base/templates/base.html
+++ b/drakul/base/templates/base.html
@@ -23,7 +23,7 @@
{% if user.is_authenticated %}
diff --git a/drakul/users/templates/users/attendance.html b/drakul/users/templates/users/attendance.html
new file mode 100644
index 0000000..e137c6b
--- /dev/null
+++ b/drakul/users/templates/users/attendance.html
@@ -0,0 +1,34 @@
+{% extends "base.html" %}
+
+{% load users_extras %}
+
+{% block title %}Attendance{% endblock %}
+
+
+{% block content %}
+
Attendance
+
+
+
+ {% for raid in raid_list %}
+ {{ raid.date | date:"d/m"}} |
+ {% endfor %}
+ Avg |
+ |
+
+
+
+ {% for user, user_responses in attendance_matrix.items %}
+
+ {% for response in user_responses.values %}
+
+ {% attendance_cell response %}
+ |
+ {% endfor %}
+ {{ user.avg_attendance | floatformat:2 }} |
+ {{ user.main }} |
+
+ {% endfor %}
+
+
+{% endblock %}
diff --git a/drakul/users/templates/users/user_list.html b/drakul/users/templates/users/user_list.html
deleted file mode 100644
index 2b67632..0000000
--- a/drakul/users/templates/users/user_list.html
+++ /dev/null
@@ -1,34 +0,0 @@
-{% extends "base.html" %}
-
-{% block title %}Users{% endblock %}
-
-
-{% block content %}
-
Users
-
-
-
-
-
- Character Name |
- Rank |
- Class |
- Role |
- Avg Attendance (Month, Total) |
-
-
-
- {% for user in user_list %}
-
- {{ user.main.name }} |
- {{ user.rank }} |
- {{ user.main.get_klass_display }} |
- {{ user.main.get_role_display }} |
- {{ user.avg_attendance_month | floatformat:2 }}, {{ user.avg_attendance_total | floatformat:2 }} |
-
- {% endfor %}
-
-
-
-
-{% endblock %}
diff --git a/drakul/users/templatetags/__init__.py b/drakul/users/templatetags/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/drakul/users/templatetags/users_extras.py b/drakul/users/templatetags/users_extras.py
new file mode 100644
index 0000000..10bf571
--- /dev/null
+++ b/drakul/users/templatetags/users_extras.py
@@ -0,0 +1,33 @@
+from decimal import Decimal
+
+from django import template
+from django.conf import settings
+from django.utils.html import format_html
+
+from drakul.raids.models import RaidResponse
+
+register = template.Library()
+
+
+@register.simple_tag
+def attendance_cell(response: RaidResponse):
+ if response is None:
+ return ""
+
+ if response.attendance == RaidResponse.STATUS_DEFAULT_ATTENDANCE[response.status]:
+ cell = ""
+ else:
+ cell = response.attendance
+
+ if response.note is not None:
+ return format_html(
+ '
{}',
+ response.note,
+ cell or "?"
+ )
+ return cell
+
+
+@register.simple_tag
+def avg_attendance_color(avg: Decimal):
+ return next(color for threshold, color in settings.ATTENDANCE_COLORS if avg >= threshold)
diff --git a/drakul/users/urls.py b/drakul/users/urls.py
index 25c9b7d..1c63951 100644
--- a/drakul/users/urls.py
+++ b/drakul/users/urls.py
@@ -3,5 +3,5 @@ from django.urls import path
from . import views
urlpatterns = [
- path("users/", views.UserListView.as_view(), name="user_list"),
+ path("attendance/", views.AttendanceView.as_view(), name="attendance"),
]
diff --git a/drakul/users/views.py b/drakul/users/views.py
index 1adada2..2c5336f 100644
--- a/drakul/users/views.py
+++ b/drakul/users/views.py
@@ -1,25 +1,43 @@
+from contextlib import suppress
+
from django.db.models import Avg, Q
from django.utils import timezone
-from django.views.generic import ListView
+from django.views.generic import TemplateView
from .models import User
+from ..raids.models import Raid, RaidResponse
-class UserListView(ListView):
- def get_queryset(self):
- return User.objects.select_related("main").annotate(
- avg_attendance_total=Avg(
- "characters__raid_responses__attendance",
- filter=Q(characters__raid_responses__raid__date__lte=timezone.now()) # only count past raids
- ),
- avg_attendance_month=Avg(
- "characters__raid_responses__attendance",
- filter=Q(
- characters__raid_responses__raid__date__lte=timezone.now(),
- characters__raid_responses__raid__date__gte=timezone.now() - timezone.timedelta(days=31)
+class AttendanceView(TemplateView):
+ template_name = "users/attendance.html"
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ users = User.objects \
+ .filter(is_active=True) \
+ .select_related("main") \
+ .annotate(
+ avg_attendance=Avg(
+ "characters__raid_responses__attendance",
+ filter=Q(
+ characters__raid_responses__raid__date__lte=timezone.now(), # only count past raids
+ characters__raid_responses__raid__is_optional=False
+ )
)
- )
- ).order_by("rank", "main__name")
+ ).order_by("main__klass", "-avg_attendance", "main__name")
+
+ # Get the last 12 non-optional raids
+ raids = Raid.objects.filter(
+ date__lte=timezone.now(),
+ is_optional=False
+ ).order_by("-date")[:12]
+ context["raid_list"] = list(reversed(raids))
+
+ context["attendance_matrix"] = {user: {raid: None for raid in reversed(raids)} for user in users}
+ for response in RaidResponse.objects.filter(raid__in=raids).select_related("raid", "character__user"):
+ with suppress(KeyError): # KeyError means user was in previous raid but is now inactive
+ context["attendance_matrix"][response.character.user][response.raid] = response
+ return context
# CharacterDetailView: