diff --git a/Dockerfile b/Dockerfile index 787c639..6e004be 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,9 +35,16 @@ WORKDIR /app # Install runtime dependencies only RUN apt-get update && apt-get install -y \ postgresql-client \ + # weasyprint dependencies + libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz-subset0 \ + locales \ libpq5 \ && rm -rf /var/lib/apt/lists/* +RUN sed -i '/pt_BR.UTF-8/s/^# //g' /etc/locale.gen && \ + locale-gen pt_BR.UTF-8 && \ + update-locale LANG=pt_BR.UTF-8 + # Copy Python dependencies from builder COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages COPY --from=builder /usr/local/bin /usr/local/bin diff --git a/core/views.py b/core/views.py index 70a0c00..1b5df16 100644 --- a/core/views.py +++ b/core/views.py @@ -25,6 +25,7 @@ class CoreFilterView( FilterView, ): template_name = "core/list.html" + template_name_htmx = "core/includes/table_content.html" permission_action = "view" paginate_by = None table_pagination = {"per_page": 10} @@ -32,6 +33,11 @@ class CoreFilterView( def get_table_data(self): return self.object_list + def get_template_names(self): + if self.request.htmx: + return [self.template_name_htmx] + return [self.template_name] + class CoreDetailView( CoreBaseMixin, diff --git a/presente/filters.py b/presente/filters.py index 41a2d64..00d66d2 100644 --- a/presente/filters.py +++ b/presente/filters.py @@ -10,7 +10,6 @@ class ActivityFilter(django_filters.FilterSet): STATUS_CHOICES = [ - ("", "---------"), ("active", _("Ativa")), ("not_started", _("Não Iniciada")), ("expired", _("Encerrada")), @@ -26,17 +25,15 @@ class ActivityFilter(django_filters.FilterSet): choices=STATUS_CHOICES, label=_("Status"), method="filter_status", - widget=forms.Select( - attrs={"class": "form-select", "data-tom-select": "simple"} - ), + empty_label="---------", + widget=forms.Select(attrs={"class": "form-select"}), ) tags = django_filters.ModelChoiceFilter( queryset=Tag.objects.all(), label=_("Tags"), field_name="tags", - widget=forms.Select( - attrs={"class": "form-select", "data-tom-select": "simple"} - ), + empty_label="---------", + widget=forms.Select(attrs={"class": "form-select"}), ) start_time__gte = django_filters.DateFilter( field_name="start_time", @@ -89,9 +86,8 @@ class AttendanceFilter(django_filters.FilterSet): queryset=Tag.objects.all(), label=_("Tags"), field_name="activity__tags", - widget=forms.Select( - attrs={"class": "form-select", "data-tom-select": "simple"} - ), + empty_label="---------", + widget=forms.Select(attrs={"class": "form-select"}), ) activity__start_time__gte = django_filters.DateFilter( field_name="activity__start_time", @@ -125,15 +121,13 @@ class ActivityAttendanceFilter(django_filters.FilterSet): user__type = django_filters.ChoiceFilter( choices=User.UserType.choices, label=_("Tipo"), - widget=forms.Select( - attrs={"class": "form-select", "data-tom-select": "simple"} - ), + empty_label="---------", + widget=forms.Select(attrs={"class": "form-select"}), ) user__campus = django_filters.ChoiceFilter( label=_("Campus"), - widget=forms.Select( - attrs={"class": "form-select", "data-tom-select": "simple"} - ), + empty_label="---------", + widget=forms.Select(attrs={"class": "form-select"}), ) user__curso = django_filters.ChoiceFilter( label=_("Curso"), @@ -143,9 +137,8 @@ class ActivityAttendanceFilter(django_filters.FilterSet): ) user__periodo_referencia = django_filters.ChoiceFilter( label=_("Período de Referência"), - widget=forms.Select( - attrs={"class": "form-select", "data-tom-select": "simple"} - ), + empty_label="---------", + widget=forms.Select(attrs={"class": "form-select"}), ) def __init__(self, *args, **kwargs): @@ -162,9 +155,7 @@ def __init__(self, *args, **kwargs): .distinct() .order_by("user__campus") ] - self.filters["user__campus"].extra["choices"] = [ - ("", "---------") - ] + campus_choices + self.filters["user__campus"].extra["choices"] = campus_choices # Curso choices curso_choices = [ @@ -175,9 +166,7 @@ def __init__(self, *args, **kwargs): .distinct() .order_by("user__curso") ] - self.filters["user__curso"].extra["choices"] = [ - ("", "---------") - ] + curso_choices + self.filters["user__curso"].extra["choices"] = curso_choices # Periodo_referencia choices periodo_choices = [ @@ -190,9 +179,7 @@ def __init__(self, *args, **kwargs): .distinct() .order_by("user__periodo_referencia") ] - self.filters["user__periodo_referencia"].extra["choices"] = [ - ("", "---------") - ] + periodo_choices + self.filters["user__periodo_referencia"].extra["choices"] = periodo_choices class Meta: model = Attendance diff --git a/presente/models.py b/presente/models.py index c56e7e8..4e7c725 100644 --- a/presente/models.py +++ b/presente/models.py @@ -3,6 +3,7 @@ from django.utils.translation import gettext_lazy as _ from django.utils import timezone from taggit.managers import TaggableManager +from django.db.models import Case, When, Value, IntegerField User = get_user_model() @@ -41,6 +42,25 @@ class Meta: ordering = ["name"] +class ActivityQuerySet(models.QuerySet): + def with_status_order(self): + now = timezone.now() + return self.annotate( + status_order=Case( + When(end_time__lt=now, then=Value(3)), # expired + When(start_time__gt=now, then=Value(1)), # not_started + When(is_enabled=False, then=Value(2)), # not_enabled + default=Value(0), # active + output_field=IntegerField(), + ) + ) + + +class ActivityManager(models.Manager): + def get_queryset(self): + return ActivityQuerySet(self.model, using=self._db).with_status_order() + + class Activity(models.Model): owners = models.ManyToManyField( User, @@ -87,6 +107,8 @@ class Activity(models.Model): _("Modificação"), auto_now=True, null=True, blank=True ) + objects = ActivityManager() + def __str__(self): return self.title diff --git a/presente/tables.py b/presente/tables.py index 61a9d4d..44dfa00 100644 --- a/presente/tables.py +++ b/presente/tables.py @@ -16,7 +16,8 @@ class CoreTable(django_tables2.Table): class ActivityTable(CoreTable): status = django_tables2.Column( verbose_name=_("Status"), - orderable=False, + orderable=True, + order_by="status_order", empty_values=(), ) tags_list = django_tables2.TemplateColumn( @@ -87,7 +88,7 @@ class ActivityAttendanceTable(django_tables2.Table): accessor="user", verbose_name=_("Nome"), orderable=True, - order_by=("user__full_name",), + order_by=("user_name_collated", "user__full_name"), ) user_type = django_tables2.Column( accessor="user__type", diff --git a/presente/views.py b/presente/views.py index 9c8d98a..28dbea0 100644 --- a/presente/views.py +++ b/presente/views.py @@ -322,12 +322,21 @@ 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() - return ( - Attendance.objects.filter(activity=activity) - .select_related("user") - .order_by("-checked_in_at") - ) + qs = Attendance.objects.filter(activity=activity).select_related("user") + + if connection.vendor == "postgresql": + from django.contrib.postgres.fields 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): context = super().get_context_data(**kwargs) diff --git a/templates/core/includes/table_content.html b/templates/core/includes/table_content.html new file mode 100644 index 0000000..778ec76 --- /dev/null +++ b/templates/core/includes/table_content.html @@ -0,0 +1,22 @@ +{% load render_table from django_tables2 %} +{% load filter_tags %} + +{% if request.htmx %} +
+ {% if filter %} + {% has_active_filters filter as filters_active %} + + {% endif %} +
+{% endif %} + +
+
+ {% render_table table %} +
+
diff --git a/templates/core/list.html b/templates/core/list.html index 80af489..e00751b 100644 --- a/templates/core/list.html +++ b/templates/core/list.html @@ -14,7 +14,7 @@ {% block app_content %}
-
+
{% if filter %} {% has_active_filters filter as filters_active %} - + Limpar
@@ -69,10 +69,8 @@
{% endif %} -
-
- {% render_table table %} -
+
+ {% include "core/includes/table_content.html" %}
{% endblock %} diff --git a/templates/django_tables2/bootstrap5.html b/templates/django_tables2/bootstrap5.html index ac66365..6beb56d 100644 --- a/templates/django_tables2/bootstrap5.html +++ b/templates/django_tables2/bootstrap5.html @@ -10,7 +10,7 @@ {% for column in table.columns %} {% if column.orderable %} - + {{ column.header }} {% if column.is_ordered %} {% if column.order_by_alias.next == column.name %} @@ -85,7 +85,7 @@