From 249b797d5a1cfc3f7f2ee92c854d23b986f80ffa Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Thu, 17 Jul 2025 19:08:34 +0200 Subject: [PATCH] Base template and picture --- apps/family/forms.py | 8 +- .../migrations/0002_family_display_image.py | 18 +++ apps/family/models.py | 9 ++ apps/family/templates/family/base.html | 40 ++---- .../templates/family/family_detail.html | 10 ++ apps/family/templates/family/family_info.html | 15 +++ .../templates/family/family_update.html | 21 ++++ .../templates/family/picture_update.html | 118 ++++++++++++++++++ apps/family/urls.py | 5 +- apps/family/views.py | 71 ++++++++++- 10 files changed, 279 insertions(+), 36 deletions(-) create mode 100644 apps/family/migrations/0002_family_display_image.py create mode 100644 apps/family/templates/family/family_info.html create mode 100644 apps/family/templates/family/family_update.html create mode 100644 apps/family/templates/family/picture_update.html diff --git a/apps/family/forms.py b/apps/family/forms.py index 78c6d25f..8a36d289 100644 --- a/apps/family/forms.py +++ b/apps/family/forms.py @@ -5,7 +5,7 @@ from django import forms from django.forms.widgets import NumberInput from note_kfet.inputs import Autocomplete -from .models import Challenge, FamilyMembership, User +from .models import Challenge, FamilyMembership, User, Family class ChallengeUpdateForm(forms.ModelForm): @@ -36,3 +36,9 @@ class FamilyMembershipForm(forms.ModelForm): }, ) } + + +class FamilyUpdateForm(forms.ModelForm): + class Meta: + model = Family + fields = ('description', ) \ No newline at end of file diff --git a/apps/family/migrations/0002_family_display_image.py b/apps/family/migrations/0002_family_display_image.py new file mode 100644 index 00000000..d2cf118b --- /dev/null +++ b/apps/family/migrations/0002_family_display_image.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.23 on 2025-07-17 15:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('family', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='family', + name='display_image', + field=models.ImageField(default='pic/default.png', max_length=255, upload_to='pic/', verbose_name='display image'), + ), + ] diff --git a/apps/family/models.py b/apps/family/models.py index 46e8af47..1d2d0d34 100644 --- a/apps/family/models.py +++ b/apps/family/models.py @@ -28,6 +28,15 @@ class Family(models.Model): verbose_name=_('rank'), ) + display_image = models.ImageField( + verbose_name=_('display image'), + max_length=255, + blank=False, + null=False, + upload_to='pic/', + default='pic/default.png' + ) + class Meta: verbose_name = _('Family') verbose_name_plural = _('Families') diff --git a/apps/family/templates/family/base.html b/apps/family/templates/family/base.html index 56789907..444dffed 100644 --- a/apps/family/templates/family/base.html +++ b/apps/family/templates/family/base.html @@ -13,29 +13,15 @@ SPDX-License-Identifier: GPL-3.0-or-later {% block profile_info %}

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

- {% if user_object %} - - + + - {% elif club %} - - - - {% endif %}
- {% if user_object %} - {% include "member/includes/profile_info.html" %} - {% elif club %} - {% include "member/includes/club_info.html" %} - {% endif %} + {% include "family/family_info.html" %}
{% endblock %} diff --git a/apps/family/templates/family/family_detail.html b/apps/family/templates/family/family_detail.html index 1f5f8e56..a1db566f 100644 --- a/apps/family/templates/family/family_detail.html +++ b/apps/family/templates/family/family_detail.html @@ -3,4 +3,14 @@ Copyright (C) 2018-2025 by BDE ENS Paris-Saclay SPDX-License-Identifier: GPL-3.0-or-later {% endcomment %} +{% load render_table from django_tables2 %} +{% load i18n perms %} +{% block profile_content %} +
+
+ {% trans "Family members" %} +
+ {% render_table member_list %} +
+{% endblock %} \ No newline at end of file diff --git a/apps/family/templates/family/family_info.html b/apps/family/templates/family/family_info.html new file mode 100644 index 00000000..359fe6ef --- /dev/null +++ b/apps/family/templates/family/family_info.html @@ -0,0 +1,15 @@ +{% load i18n pretty_money perms %} + +
+
{% trans 'name'|capfirst %}
+
{{ family.name }}
+ +
{% trans 'description'|capfirst %}
+
{{ family.description }}
+ +
{% trans 'score'|capfirst %}
+
{{ family.score }}
+ +
{% trans 'rank'|capfirst %}
+
{{ family.rank }}
+
diff --git a/apps/family/templates/family/family_update.html b/apps/family/templates/family/family_update.html new file mode 100644 index 00000000..27c7bed2 --- /dev/null +++ b/apps/family/templates/family/family_update.html @@ -0,0 +1,21 @@ +{% extends "base.html" %} +{% comment %} +Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} +{% load i18n crispy_forms_tags %} + +{% block content %} +
+

+ {{ title }} +

+
+
+ {% csrf_token %} + {{ form | crispy }} + +
+
+
+{% endblock %} diff --git a/apps/family/templates/family/picture_update.html b/apps/family/templates/family/picture_update.html new file mode 100644 index 00000000..e5c6749c --- /dev/null +++ b/apps/family/templates/family/picture_update.html @@ -0,0 +1,118 @@ +{% extends "family/base.html" %} +{% comment %} +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} +{% load i18n crispy_forms_tags %} + +{% block profile_content %} +
+

+ {{ title }} +

+
+
+
+ {% csrf_token %} + {{ form |crispy }} + {% if user.note.display_image != "pic/default.png" %} + + {% endif %} +
+
+ + +
+
+{% endblock %} + +{% block extracss %} + +{% endblock %} + +{% block extrajavascript%} + + + +{% endblock %} diff --git a/apps/family/urls.py b/apps/family/urls.py index 9a17a481..e86bc0b5 100644 --- a/apps/family/urls.py +++ b/apps/family/urls.py @@ -3,14 +3,15 @@ from django.urls import path -from .views import FamilyListView, FamilyDetailView, FamilyUpdateView, FamilyAddMemberView, ChallengeListView, ChallengeDetailView, ChallengeUpdateView +from .views import FamilyListView, FamilyDetailView, FamilyUpdateView, FamilyPictureUpdateView, 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('update_pic//', FamilyPictureUpdateView.as_view(), name="update_pic"), + 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 6f6e3d48..75a82e0c 100644 --- a/apps/family/views.py +++ b/apps/family/views.py @@ -3,7 +3,9 @@ from datetime import date +from django.conf import settings from django.contrib.auth.mixins import LoginRequiredMixin +from django.db import transaction from django.views.generic import DetailView, UpdateView from django.utils.translation import gettext_lazy as _ from django_tables2 import SingleTableView @@ -13,7 +15,9 @@ from django.urls import reverse_lazy from .models import Family, Challenge, FamilyMembership, User from .tables import FamilyTable, ChallengeTable, FamilyMembershipTable -from .forms import ChallengeUpdateForm, FamilyMembershipForm +from .forms import ChallengeUpdateForm, FamilyMembershipForm, FamilyUpdateForm +from member.forms import ImageForm +from member.views import PictureUpdateView class FamilyCreateView(ProtectQuerysetMixin, ProtectedCreateView): @@ -62,9 +66,12 @@ class FamilyDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): family=family, year=date.today().year, ).filter(PermissionBackend.filter_queryset(self.request, FamilyMembership, "view"))\ - .order_by("user__username").distinct("user__username") + .order_by("user__username") + family_member = family_member.distinct("user__username")\ + if settings.DATABASES["default"]["ENGINE"] == 'django.db.backends.postgresql' else family_member - membership_table = FamilyMembershipTable(data=family_member) + membership_table = FamilyMembershipTable(data=family_member, prefix="membership-") + membership_table.paginate(per_page=5, page=self.request.GET.get('membership-page', 1)) context['member_list'] = membership_table # Check if the user has the right to create a membership, to display the button. @@ -85,8 +92,43 @@ class FamilyUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): """ model = Family context_object_name = "family" + form_class = FamilyUpdateForm + template_name = 'family/family_update.html' extra_context = {"title": _('Update family')} + def get_success_url(self): + return reverse_lazy('family:family_detail', kwargs={'pk': self.object.pk}) + + +class FamilyPictureUpdateView(PictureUpdateView): + """ + Update profile picture of the family + """ + model = Family + extra_context = {"title": _("Update family picture")} + template_name = 'family/picture_update.html' + + def get_success_url(self): + """Redirect to family page after upload""" + return reverse_lazy('family:family_detail', kwargs={'pk': self.object.id}) + + @transaction.atomic + def form_valid(self, form): + """ + Save the image + """ + image = form.cleaned_data['image'] + + if image is None: + image = "pic/default.png" + else: + # Rename as PNG or GIF + extension = image.name.split(".")[-1] + if extension == "gif": + image.name = "{}_pic.gif".format(self.object.pk) + else: + image.name = "{}_pic.png".format(self.object.pk) + class FamilyAddMemberView(ProtectQuerysetMixin, ProtectedCreateView): """ @@ -108,6 +150,29 @@ class FamilyAddMemberView(ProtectQuerysetMixin, ProtectedCreateView): year=date.today().year, ) + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + form = context['form'] + + family = Family.objects.filter(PermissionBackend.filter_queryset(self.request, Family, "view"))\ + .get(pk=self.kwargs['family_pk']) + + context['family'] = family + + return context + + @transaction.atomic + def form_valid(self, form): + """ + Create family membership, check that everythinf is good + """ + family = Family.objects.filter(PermissionBackend.filter_queryset(self.request, Family, "view")) \ + .get(pk=self.kwargs["family_pk"]) + + form.instance.family = family + + return super().form_valid(form) + def get_success_url(self): return reverse_lazy('family:family_detail', kwargs={'pk': self.object.family.id})