From fdc241b8b04e27193686c3f75e4b9cdb2fba03e6 Mon Sep 17 00:00:00 2001 From: Diego Cirilo Date: Thu, 4 Dec 2025 01:55:28 -0300 Subject: [PATCH 1/2] fix collate --- presente/tables.py | 2 +- presente/views.py | 12 ------------ users/tables.py | 2 +- users/views.py | 14 -------------- 4 files changed, 2 insertions(+), 28 deletions(-) diff --git a/presente/tables.py b/presente/tables.py index 44dfa00..648d7d8 100644 --- a/presente/tables.py +++ b/presente/tables.py @@ -88,7 +88,7 @@ class ActivityAttendanceTable(django_tables2.Table): accessor="user", verbose_name=_("Nome"), orderable=True, - order_by=("user_name_collated", "user__full_name"), + order_by="user__full_name", ) user_type = django_tables2.Column( accessor="user__type", diff --git a/presente/views.py b/presente/views.py index fe41910..4f7e0a0 100644 --- a/presente/views.py +++ b/presente/views.py @@ -322,20 +322,8 @@ def get_page_title(self): return _("Presenças - {}").format(activity.title) def get_queryset(self): - from django.db import connection - from django.db.models import F - activity = self.get_activity() qs = Attendance.objects.filter(activity=activity).select_related("user") - - if connection.vendor == "postgresql": - from django.db.models.functions import Collate - - qs = qs.annotate(user_name_collated=Collate("user__full_name", "pt_BR")) - else: - # SQLite: just alias the field for compatibility - qs = qs.annotate(user_name_collated=F("user__full_name")) - return qs.order_by("-checked_in_at") def get_context_data(self, **kwargs): diff --git a/users/tables.py b/users/tables.py index 8af0871..616b8c7 100644 --- a/users/tables.py +++ b/users/tables.py @@ -8,7 +8,7 @@ class UserTable(CoreTable): full_name = tables.Column( verbose_name=_("Nome"), empty_values=(), - order_by=("full_name_collated", "full_name"), + order_by="full_name", ) matricula = tables.Column(verbose_name=_("Matrícula"), empty_values=()) type = tables.Column(verbose_name=_("Tipo"), empty_values=()) diff --git a/users/views.py b/users/views.py index 12d8f51..f700895 100644 --- a/users/views.py +++ b/users/views.py @@ -44,23 +44,9 @@ class UserListView(ExcludeAdminMixin, CoreFilterView): filterset_class = UserFilter def get_queryset(self): - from django.db import connection - from django.db.models import F - queryset = super().get_queryset() # Prefetch social accounts to avoid N+1 queries when accessing matricula queryset = queryset.prefetch_related("socialaccount_set") - - if connection.vendor == "postgresql": - from django.db.models.functions import Collate - - queryset = queryset.annotate( - full_name_collated=Collate("full_name", "pt_BR") - ) - else: - # SQLite: just alias the field for compatibility - queryset = queryset.annotate(full_name_collated=F("full_name")) - return queryset From 0d72f52ac9493170a3e964f6a05fdd0e9efa3861 Mon Sep 17 00:00:00 2001 From: Diego Cirilo Date: Thu, 4 Dec 2025 12:40:58 -0300 Subject: [PATCH 2/2] fix collate --- presente/tables.py | 2 +- presente/views.py | 57 +++++++++++++++---- templates/core/list.html | 2 +- .../presente/activity_attendance_list.html | 1 + templates/presente/attendance_pdf.html | 6 +- .../attendance_export_config_modal.html | 2 - .../0010_user_full_name_normalized.py | 18 ++++++ .../0011_populate_full_name_normalized.py | 34 +++++++++++ users/models.py | 21 +++++++ users/tables.py | 2 +- users/views.py | 2 +- 11 files changed, 128 insertions(+), 19 deletions(-) create mode 100644 users/migrations/0010_user_full_name_normalized.py create mode 100644 users/migrations/0011_populate_full_name_normalized.py diff --git a/presente/tables.py b/presente/tables.py index 648d7d8..17f2b11 100644 --- a/presente/tables.py +++ b/presente/tables.py @@ -88,7 +88,7 @@ class ActivityAttendanceTable(django_tables2.Table): accessor="user", verbose_name=_("Nome"), orderable=True, - order_by="user__full_name", + order_by=("user__full_name_normalized", "user__full_name"), ) user_type = django_tables2.Column( accessor="user__type", diff --git a/presente/views.py b/presente/views.py index 4f7e0a0..0a874bf 100644 --- a/presente/views.py +++ b/presente/views.py @@ -403,6 +403,7 @@ class ActivityAttendancePDFView( template_name = "presente/attendance_pdf.html" context_object_name = "attendances" pdf_filename = "relatorio_presencas.pdf" + pdf_attachment = False # Display inline in browser instead of downloading def get_pdf_filename(self): activity = self.get_activity() @@ -422,8 +423,8 @@ def get_queryset(self): if sort_by: # Map sort fields to actual model fields sort_mapping = { - "name": "user__full_name", - "-name": "-user__full_name", + "name": "user__full_name_normalized", + "-name": "-user__full_name_normalized", "type": "user__type", "-type": "-user__type", "curso": "user__curso", @@ -433,10 +434,10 @@ def get_queryset(self): "checked_in_at": "checked_in_at", "-checked_in_at": "-checked_in_at", } - sort_field = sort_mapping.get(sort_by, "user__full_name") + sort_field = sort_mapping.get(sort_by, "user__full_name_normalized") qs = qs.order_by(sort_field) else: - qs = qs.order_by("user__full_name") + qs = qs.order_by("user__full_name_normalized") return qs @@ -445,8 +446,27 @@ def get_context_data(self, **kwargs): activity = self.get_activity() context["activity"] = activity - # Get the filtered queryset (after filters are applied) - filtered_qs = context["filter"].qs + # Get the filtered queryset (after filters are applied) and apply sorting + sort_by = self.request.GET.get("sort_by", "name") + sort_mapping = { + "name": "user__full_name_normalized", + "-name": "-user__full_name_normalized", + "type": "user__type", + "-type": "-user__type", + "curso": "user__curso", + "-curso": "-user__curso", + "periodo": "user__periodo_referencia", + "-periodo": "-user__periodo_referencia", + "checked_in_at": "checked_in_at", + "-checked_in_at": "-checked_in_at", + } + sort_field = sort_mapping.get(sort_by, "user__full_name_normalized") + + # Apply sorting to the filtered queryset + filtered_qs = context["filter"].qs.order_by(sort_field) + + # Store the sorted queryset for the template + context["sorted_qs"] = filtered_qs context["total_attendances"] = filtered_qs.count() # Get column configuration @@ -499,8 +519,8 @@ def get_queryset(self): # Apply sorting sort_by = self.request.GET.get("sort_by", "name") sort_mapping = { - "name": "user__full_name", - "-name": "-user__full_name", + "name": "user__full_name_normalized", + "-name": "-user__full_name_normalized", "type": "user__type", "-type": "-user__type", "curso": "user__curso", @@ -510,7 +530,7 @@ def get_queryset(self): "checked_in_at": "checked_in_at", "-checked_in_at": "-checked_in_at", } - sort_field = sort_mapping.get(sort_by, "user__full_name") + sort_field = sort_mapping.get(sort_by, "user__full_name_normalized") qs = qs.order_by(sort_field) return qs @@ -520,6 +540,23 @@ def get(self, request, *args, **kwargs): filterset = self.filterset_class(request.GET, queryset=self.get_queryset()) queryset = filterset.qs + # Reapply sorting to ensure it's maintained after filtering + sort_by = request.GET.get("sort_by", "name") + sort_mapping = { + "name": "user__full_name_normalized", + "-name": "-user__full_name_normalized", + "type": "user__type", + "-type": "-user__type", + "curso": "user__curso", + "-curso": "-user__curso", + "periodo": "user__periodo_referencia", + "-periodo": "-user__periodo_referencia", + "checked_in_at": "checked_in_at", + "-checked_in_at": "-checked_in_at", + } + sort_field = sort_mapping.get(sort_by, "user__full_name_normalized") + queryset = queryset.order_by(sort_field) + # Get column configuration columns = request.GET.getlist("columns") if not columns: @@ -566,7 +603,7 @@ def get(self, request, *args, **kwargs): if "number" in columns: row.append(idx) if "name" in columns: - row.append(attendance.user.get_full_name()) + row.append(attendance.user.get_full_name().upper()) if "email" in columns: row.append(attendance.user.email or "-") if "matricula" in columns: diff --git a/templates/core/list.html b/templates/core/list.html index e00751b..a5f74c6 100644 --- a/templates/core/list.html +++ b/templates/core/list.html @@ -41,7 +41,7 @@
-
+ {% for field in filter.form %} {% if field.name != 'csrfmiddlewaretoken' %}
diff --git a/templates/presente/activity_attendance_list.html b/templates/presente/activity_attendance_list.html index 182e79f..c370605 100644 --- a/templates/presente/activity_attendance_list.html +++ b/templates/presente/activity_attendance_list.html @@ -5,6 +5,7 @@ hx-get="{% url 'presente:activity_attendance_export_config' activity.pk %}?{{ request.GET.urlencode }}" hx-target="#modals-here .modal-content" hx-trigger="click" + hx-include="#filter-form" data-bs-toggle="modal" data-bs-target="#modals-here"> Exportar Relatório diff --git a/templates/presente/attendance_pdf.html b/templates/presente/attendance_pdf.html index 3489d7d..514861f 100644 --- a/templates/presente/attendance_pdf.html +++ b/templates/presente/attendance_pdf.html @@ -196,7 +196,7 @@

Filtros Aplicados:

Total de Presenças: {{ total_attendances }}
- {% if filter.qs %} + {% if sorted_qs %} @@ -212,10 +212,10 @@

Filtros Aplicados:

- {% for attendance in filter.qs %} + {% for attendance in sorted_qs %} {% if "number" in columns %}{% endif %} - {% if "name" in columns %}{% endif %} + {% if "name" in columns %}{% endif %} {% if "email" in columns %}{% endif %} {% if "matricula" in columns %}{% endif %} {% if "type" in columns %}{% endif %} diff --git a/templates/presente/includes/attendance_export_config_modal.html b/templates/presente/includes/attendance_export_config_modal.html index 6d10582..98312a6 100644 --- a/templates/presente/includes/attendance_export_config_modal.html +++ b/templates/presente/includes/attendance_export_config_modal.html @@ -11,9 +11,7 @@
{{ forloop.counter }}{{ attendance.user.get_full_name }}{{ attendance.user.get_full_name|upper }}{{ attendance.user.email|default:"-" }}{{ attendance.user.matricula|default:"-" }}{{ attendance.user.get_type_display }}