From 65dd42fc977b2235697686d0d29ded770dc66351 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Thu, 17 Jul 2025 17:07:47 +0200 Subject: [PATCH] Family views --- apps/family/forms.py | 19 ++++- apps/family/tables.py | 16 ++++- apps/family/templates/family/add_member.html | 60 ++++++++++++++++ apps/family/templates/family/base.html | 72 +++++++++++++++++++ .../templates/family/family_detail.html | 3 +- apps/family/urls.py | 4 +- apps/family/views.py | 61 ++++++++++++++-- 7 files changed, 227 insertions(+), 8 deletions(-) create mode 100644 apps/family/templates/family/add_member.html create mode 100644 apps/family/templates/family/base.html diff --git a/apps/family/forms.py b/apps/family/forms.py index 63b47f48..78c6d25f 100644 --- a/apps/family/forms.py +++ b/apps/family/forms.py @@ -3,7 +3,6 @@ from django import forms from django.forms.widgets import NumberInput -from django.utils.translation import gettext_lazy as _ from note_kfet.inputs import Autocomplete from .models import Challenge, FamilyMembership, User @@ -19,3 +18,21 @@ class ChallengeUpdateForm(forms.ModelForm): widgets = { "points": NumberInput() } + + +class FamilyMembershipForm(forms.ModelForm): + class Meta: + model = FamilyMembership + fields = ('user', ) + + widgets = { + "user": + Autocomplete( + User, + attrs={ + 'api_url': '/api/user/', + 'name_field': 'username', + 'placeholder': 'Nom ...', + }, + ) + } diff --git a/apps/family/tables.py b/apps/family/tables.py index de00b815..4172b975 100644 --- a/apps/family/tables.py +++ b/apps/family/tables.py @@ -4,7 +4,7 @@ import django_tables2 as tables from django_tables2 import A -from .models import Family, Challenge +from .models import Family, Challenge, FamilyMembership class FamilyTable(tables.Table): @@ -43,3 +43,17 @@ class ChallengeTable(tables.Table): model = Challenge template_name = 'django_tables2/bootstrap4.html' fields = ('name', 'description', 'points',) + + +class FamilyMembershipTable(tables.Table): + """ + List all family memberships. + """ + class Meta: + attrs = { + 'class': 'table table-condensed table-striped', + 'style': 'table-layout: fixed;' + } + template_name = 'django_tables2/bootstrap4.html' + fields = ('user',) + model = FamilyMembership diff --git a/apps/family/templates/family/add_member.html b/apps/family/templates/family/add_member.html new file mode 100644 index 00000000..6f77283d --- /dev/null +++ b/apps/family/templates/family/add_member.html @@ -0,0 +1,60 @@ +{% extends "family/base.html" %} +{% comment %} +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} +{% load crispy_forms_tags i18n pretty_money %} + +{% block profile_content %} +
+

+ {{ title }} +

+
+ +
+ {% csrf_token %} + {{ form|crispy }} + +
+
+
+{% endblock %} + +{% block extrajavascript %} + +{% endblock %} \ No newline at end of file diff --git a/apps/family/templates/family/base.html b/apps/family/templates/family/base.html new file mode 100644 index 00000000..56789907 --- /dev/null +++ b/apps/family/templates/family/base.html @@ -0,0 +1,72 @@ +{% extends "base.html" %} +{% comment %} +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} +{% load i18n perms %} + +{# Use a fluid-width container #} +{% block containertype %}container-fluid{% endblock %} + +{% block content %} +
+
+ {% block profile_info %} +
+

+ {% if user_object %} + {% trans "Account #" %}{{ user_object.pk }} + {% elif club %} + Club {{ club.name }} + {% endif %} +

+
+ {% if user_object %} + + + + {% elif club %} + + + + {% endif %} +
+
+ {% if user_object %} + {% include "member/includes/profile_info.html" %} + {% elif club %} + {% include "member/includes/club_info.html" %} + {% endif %} +
+ +
+ {% endblock %} +
+
+ {% block profile_content %}{% endblock %} +
+
+{% endblock %} \ No newline at end of file diff --git a/apps/family/templates/family/family_detail.html b/apps/family/templates/family/family_detail.html index b8f9d918..1f5f8e56 100644 --- a/apps/family/templates/family/family_detail.html +++ b/apps/family/templates/family/family_detail.html @@ -1,5 +1,6 @@ -{% extends "base.html" %} +{% extends "family/base.html" %} {% comment %} Copyright (C) 2018-2025 by BDE ENS Paris-Saclay SPDX-License-Identifier: GPL-3.0-or-later {% endcomment %} + diff --git a/apps/family/urls.py b/apps/family/urls.py index 622d8b54..9a17a481 100644 --- a/apps/family/urls.py +++ b/apps/family/urls.py @@ -3,12 +3,14 @@ from django.urls import path -from .views import FamilyListView, FamilyDetailView, ChallengeListView, ChallengeDetailView, ChallengeUpdateView +from .views import FamilyListView, FamilyDetailView, FamilyUpdateView, FamilyAddMemberView, ChallengeListView, ChallengeDetailView, ChallengeUpdateView app_name = 'family' urlpatterns = [ path('list/', FamilyListView.as_view(), name="family_list"), path('detail//', FamilyDetailView.as_view(), name="family_detail"), + path('update//', FamilyUpdateView.as_view(), name="family_update"), + path('/add_member', FamilyAddMemberView.as_view(), name="family_add_member"), path('challenge/list/', ChallengeListView.as_view(), name="challenge_list"), path('challenge/detail//', ChallengeDetailView.as_view(), name="challenge_detail"), path('challenge/update//', ChallengeUpdateView.as_view(), name="challenge_update"), diff --git a/apps/family/views.py b/apps/family/views.py index 4b710681..6f6e3d48 100644 --- a/apps/family/views.py +++ b/apps/family/views.py @@ -13,7 +13,7 @@ from django.urls import reverse_lazy from .models import Family, Challenge, FamilyMembership, User from .tables import FamilyTable, ChallengeTable, FamilyMembershipTable -from .forms import ChallengeUpdateForm +from .forms import ChallengeUpdateForm, FamilyMembershipForm class FamilyCreateView(ProtectQuerysetMixin, ProtectedCreateView): @@ -49,6 +49,35 @@ class FamilyDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): context_object_name = "family" extra_context = {"title": _('Family detail')} + def get_context_data(self, **kwargs): + """ + Add members list + """ + context = super().get_context_data(**kwargs) + + family = self.object + + # member list + family_member = FamilyMembership.objects.filter( + family=family, + year=date.today().year, + ).filter(PermissionBackend.filter_queryset(self.request, FamilyMembership, "view"))\ + .order_by("user__username").distinct("user__username") + + membership_table = FamilyMembershipTable(data=family_member) + context['member_list'] = membership_table + + # Check if the user has the right to create a membership, to display the button. + empty_membership = FamilyMembership( + family=family, + user=User.objects.first(), + year=date.today().year, + ) + context["can_add_members"] = PermissionBackend()\ + .has_perm(self.request.user, "family.add_membership", empty_membership) + + return context + class FamilyUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): """ @@ -59,6 +88,30 @@ class FamilyUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): extra_context = {"title": _('Update family')} +class FamilyAddMemberView(ProtectQuerysetMixin, ProtectedCreateView): + """ + Add a membership to a family + """ + model = FamilyMembership + form_class = FamilyMembershipForm + template_name = 'family/add_member.html' + extra_context = {"title": _("Add a new member to the family")} + + def get_sample_object(self): + if "family_pk" in self.kwargs: + family = Family.objects.get(pk=self.kwargs["family_pk"]) + else: + family = FamilyMembership.objects.get(pk=self.kwargs["pk"]).family + return FamilyMembership( + user=self.request.user, + family=family, + year=date.today().year, + ) + + def get_success_url(self): + return reverse_lazy('family:family_detail', kwargs={'pk': self.object.family.id}) + + class ChallengeCreateView(ProtectQuerysetMixin, ProtectedCreateView): """ Create challenge @@ -72,7 +125,7 @@ class ChallengeCreateView(ProtectQuerysetMixin, ProtectedCreateView): description="Sample challenge", points=0, ) - + def get_success_url(self): return reverse_lazy('family:challenge_list') @@ -103,7 +156,7 @@ class ChallengeDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): context["fields"] = [( Challenge._meta.get_field(field).verbose_name.capitalize(), value) for field, value in fields.items()] - context["obtained"] = getattr(self.object, "obtained") + context["obtained"] = self.object.obtained context["update"] = PermissionBackend.check_perm(self.request, "family.change_challenge") return context @@ -121,4 +174,4 @@ class ChallengeUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): def get_success_url(self, **kwargs): self.object.refresh_from_db() - return reverse_lazy('family:challenge_detail', kwargs={'pk': self.object.pk}) \ No newline at end of file + return reverse_lazy('family:challenge_detail', kwargs={'pk': self.object.pk})