mirror of
				https://gitlab.crans.org/bde/nk20
				synced 2025-10-30 23:39:54 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			470 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			470 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
 | |
| # SPDX-License-Identifier: GPL-3.0-or-later
 | |
| 
 | |
| from datetime import date
 | |
| 
 | |
| from django.conf import settings
 | |
| from django.shortcuts import redirect
 | |
| from django.contrib.auth.mixins import LoginRequiredMixin
 | |
| from django.core.exceptions import PermissionDenied
 | |
| from django.db import transaction
 | |
| from django.views.generic import DetailView, UpdateView, ListView
 | |
| from django.views.generic.edit import DeleteView, FormMixin
 | |
| from django.views.generic.base import TemplateView
 | |
| from django.utils.translation import gettext_lazy as _
 | |
| from django_tables2 import SingleTableView, MultiTableMixin
 | |
| from permission.backends import PermissionBackend
 | |
| from permission.views import ProtectQuerysetMixin, ProtectedCreateView
 | |
| from django.urls import reverse_lazy
 | |
| from member.forms import ImageForm
 | |
| import phonenumbers
 | |
| 
 | |
| from .models import Family, Challenge, FamilyMembership, User, Achievement
 | |
| from .tables import FamilyTable, ChallengeTable, FamilyMembershipTable, AchievementTable, FamilyAchievementTable
 | |
| from .forms import ChallengeForm, FamilyMembershipForm, FamilyForm
 | |
| 
 | |
| 
 | |
| class FamilyCreateView(ProtectQuerysetMixin, ProtectedCreateView):
 | |
|     """
 | |
|     Create family
 | |
|     """
 | |
|     model = Family
 | |
|     extra_context = {"title": _('Create family')}
 | |
|     form_class = FamilyForm
 | |
| 
 | |
|     def get_sample_object(self):
 | |
|         return Family(
 | |
|             name="",
 | |
|             description="Sample family",
 | |
|             score=0,
 | |
|             rank=0,
 | |
|         )
 | |
| 
 | |
|     def get_success_url(self):
 | |
|         self.object.refresh_from_db()
 | |
|         return reverse_lazy("family:manage")
 | |
| 
 | |
| 
 | |
| class FamilyListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
 | |
|     """
 | |
|     List existing Families
 | |
|     """
 | |
|     model = Family
 | |
|     table_class = FamilyTable
 | |
|     extra_context = {"title": _('Families list')}
 | |
| 
 | |
|     def get_context_data(self, **kwargs):
 | |
|         context = super().get_context_data(**kwargs)
 | |
| 
 | |
|         fake_family = Family(name="", description="")
 | |
|         fake_challenge = Challenge(name="", description="", points=0)
 | |
|         can_add_family = PermissionBackend.check_perm(self.request, "family.add_family", fake_family)
 | |
|         can_add_challenge = PermissionBackend.check_perm(self.request, "family.add_challenge", fake_challenge)
 | |
| 
 | |
|         if Family.objects.exists() and Challenge.objects.exists():
 | |
|             fake_achievement = Achievement(family=Family.objects.first(), challenge=Challenge.objects.first(), valid=False)
 | |
|             can_add_achievement = PermissionBackend.check_perm(self.request, "family.add_achievement", fake_achievement)
 | |
|         else:
 | |
|             can_add_achievement = False
 | |
| 
 | |
|         context["can_manage"] = can_add_family or can_add_challenge or can_add_achievement
 | |
| 
 | |
|         return context
 | |
| 
 | |
| 
 | |
| class FamilyDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
 | |
|     """
 | |
|     Display details of a family
 | |
|     """
 | |
|     model = Family
 | |
|     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")
 | |
|         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, 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.
 | |
|         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)
 | |
| 
 | |
|         # Défis réalisé par la famille
 | |
|         achievements = Achievement.objects.filter(family=family)
 | |
|         achievements_table = FamilyAchievementTable(data=achievements, prefix="achievement-")
 | |
|         achievements_table.paginate(per_page=5, page=self.request.GET.get('achievement-page', 1))
 | |
|         context["achievement_list"] = achievements_table
 | |
| 
 | |
|         return context
 | |
| 
 | |
| 
 | |
| class FamilyUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
 | |
|     """
 | |
|     Update the information of a family.
 | |
|     """
 | |
|     model = Family
 | |
|     context_object_name = "family"
 | |
|     form_class = FamilyForm
 | |
|     extra_context = {"title": _('Update family')}
 | |
| 
 | |
|     def get_success_url(self):
 | |
|         return reverse_lazy('family:family_detail', kwargs={'pk': self.object.pk})
 | |
| 
 | |
| 
 | |
| class FamilyPictureUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, DetailView):
 | |
|     """
 | |
|     Update profile picture of the family
 | |
|     """
 | |
|     model = Family
 | |
|     extra_context = {"title": _("Update family picture")}
 | |
|     template_name = 'family/picture_update.html'
 | |
|     form_class = ImageForm
 | |
| 
 | |
|     def get_context_data(self, **kwargs):
 | |
|         context = super().get_context_data(**kwargs)
 | |
|         context['form'] = self.form_class(self.request.POST, self.request.FILES)
 | |
|         return context
 | |
| 
 | |
|     def get_success_url(self):
 | |
|         """Redirect to family page after upload"""
 | |
|         return reverse_lazy('family:family_detail', kwargs={'pk': self.object.pk})
 | |
| 
 | |
|     def post(self, request, *args, **kwargs):
 | |
|         form = self.get_form()
 | |
|         self.object = self.get_object()
 | |
|         return self.form_valid(form) if form.is_valid() else self.form_invalid(form)
 | |
| 
 | |
|     @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)
 | |
| 
 | |
|         # Save
 | |
|         self.object.display_image = image
 | |
|         self.object.save()
 | |
|         return super().form_valid(form)
 | |
| 
 | |
| 
 | |
| 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_context_data(self, **kwargs):
 | |
|         context = super().get_context_data(**kwargs)
 | |
| 
 | |
|         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})
 | |
| 
 | |
| 
 | |
| class ChallengeCreateView(ProtectQuerysetMixin, ProtectedCreateView):
 | |
|     """
 | |
|     Create challenge
 | |
|     """
 | |
|     model = Challenge
 | |
|     extra_context = {"title": _('Create challenge')}
 | |
|     form_class = ChallengeForm
 | |
| 
 | |
|     def get_sample_object(self):
 | |
|         return Challenge(
 | |
|             name="",
 | |
|             description="Sample challenge",
 | |
|             points=0,
 | |
|         )
 | |
| 
 | |
|     def get_success_url(self):
 | |
|         return reverse_lazy('family:manage')
 | |
| 
 | |
| 
 | |
| class ChallengeListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
 | |
|     """
 | |
|     List all challenges
 | |
|     """
 | |
|     model = Challenge
 | |
|     table_class = ChallengeTable
 | |
|     extra_context = {"title": _('Challenges list')}
 | |
| 
 | |
|     def get_context_data(self, **kwargs):
 | |
|         context = super().get_context_data(**kwargs)
 | |
| 
 | |
|         fake_family = Family(name="", description="")
 | |
|         fake_challenge = Challenge(name="", description="", points=0)
 | |
|         can_add_family = PermissionBackend.check_perm(self.request, "family.add_family", fake_family)
 | |
|         can_add_challenge = PermissionBackend.check_perm(self.request, "family.add_challenge", fake_challenge)
 | |
| 
 | |
|         if Family.objects.exists() and Challenge.objects.exists():
 | |
|             fake_achievement = Achievement(family=Family.objects.first(), challenge=Challenge.objects.first(), valid=False)
 | |
|             can_add_achievement = PermissionBackend.check_perm(self.request, "family.add_achievement", fake_achievement)
 | |
|         else:
 | |
|             can_add_achievement = False
 | |
| 
 | |
|         context["can_manage"] = can_add_family or can_add_challenge or can_add_achievement
 | |
| 
 | |
|         return context
 | |
| 
 | |
| 
 | |
| class ChallengeDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
 | |
|     """
 | |
|     Display details of a challenge
 | |
|     """
 | |
|     model = Challenge
 | |
|     context_object_name = "challenge"
 | |
|     extra_context = {"title": _('Details of:')}
 | |
| 
 | |
|     def get_context_data(self, **kwargs):
 | |
|         context = super().get_context_data(**kwargs)
 | |
|         fields = ["name", "description", "points",]
 | |
| 
 | |
|         fields = dict([(field, getattr(self.object, field)) for field in fields])
 | |
| 
 | |
|         context["fields"] = [(
 | |
|             Challenge._meta.get_field(field).verbose_name.capitalize(),
 | |
|             value) for field, value in fields.items()]
 | |
|         context["obtained"] = self.object.obtained
 | |
|         context["update"] = PermissionBackend.check_perm(self.request, "family.change_challenge")
 | |
| 
 | |
|         return context
 | |
| 
 | |
| 
 | |
| class ChallengeUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
 | |
|     """
 | |
|     Update the information of a challenge
 | |
|     """
 | |
|     model = Challenge
 | |
|     context_object_name = "challenge"
 | |
|     extra_context = {"title": _('Update challenge')}
 | |
|     form_class = ChallengeForm
 | |
| 
 | |
|     def get_success_url(self, **kwargs):
 | |
|         self.object.refresh_from_db()
 | |
|         return reverse_lazy('family:challenge_detail', kwargs={'pk': self.object.pk})
 | |
| 
 | |
| 
 | |
| class FamilyManageView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
 | |
|     """
 | |
|     Manage families and challenges
 | |
|     """
 | |
|     model = Achievement
 | |
|     template_name = 'family/manage.html'
 | |
|     table_class = AchievementTable
 | |
|     extra_context = {'title': _('Manage families and challenges')}
 | |
| 
 | |
|     def dispatch(self, request, *args, **kwargs):
 | |
|         # Check that the user is authenticated
 | |
|         if not request.user.is_authenticated:
 | |
|             return self.handle_no_permission()
 | |
| 
 | |
|         perm = PermissionBackend.has_model_perm(self.request, Achievement(), "add")
 | |
|         perm = perm or PermissionBackend.has_model_perm(self.request, Challenge(), "add")
 | |
|         perm = perm or PermissionBackend.has_model_perm(self.request, Family(), "add")
 | |
|         if not perm:
 | |
|             raise PermissionDenied(_("You are not able to manage families and challenges."))
 | |
|         return super().dispatch(request, *args, **kwargs)
 | |
| 
 | |
|     def get_queryset(self, **kwargs):
 | |
|         # retrieves only Transaction that user has the right to see.
 | |
|         return Achievement.objects.filter(
 | |
|             PermissionBackend.filter_queryset(self.request, Achievement, "view")
 | |
|         ).order_by("-obtained_at").all()
 | |
| 
 | |
|     def get_context_data(self, **kwargs):
 | |
|         context = super().get_context_data(**kwargs)
 | |
| 
 | |
|         context['all_challenges'] = Challenge.objects.filter(
 | |
|             PermissionBackend.filter_queryset(self.request, Challenge, "view")
 | |
|         ).order_by('name')
 | |
| 
 | |
|         context["can_add_family"] = PermissionBackend.has_model_perm(self.request, Family(), "add")
 | |
|         context["can_add_challenge"] = PermissionBackend.has_model_perm(self.request, Challenge(), "add")
 | |
|         context["can_add_achievement"] = PermissionBackend.has_model_perm(self.request, Achievement(), "add")
 | |
| 
 | |
|         # Get the user's family if they have one
 | |
|         try:
 | |
|             user_family_membership = FamilyMembership.objects.get(user=self.request.user)
 | |
|             context["user_family"] = user_family_membership.family
 | |
|         except FamilyMembership.DoesNotExist:
 | |
|             context["user_family"] = None
 | |
| 
 | |
|         phone_numbers = [
 | |
|             u.profile.phone_number for u in User.objects.filter(
 | |
|                 memberships__roles__id=35,
 | |
|                 memberships__date_end__gte=date.today(),
 | |
|                 profile__phone_number__isnull=False
 | |
|             ).distinct()
 | |
|         ]
 | |
|         formatted_phone_numbers = [phonenumbers.format_number(num, phonenumbers.PhoneNumberFormat.INTERNATIONAL) for num in phone_numbers if num]
 | |
|         context["phone_numbers"] = formatted_phone_numbers
 | |
| 
 | |
|         return context
 | |
| 
 | |
|     def get_table(self, **kwargs):
 | |
|         table = super().get_table(**kwargs)
 | |
|         table.exclude = ('delete', 'validate',)
 | |
|         table.orderable = False
 | |
|         return table
 | |
| 
 | |
|     def get_table_data(self, **kwargs):
 | |
|         qs = super().get_queryset(**kwargs)
 | |
| 
 | |
|         qs = qs.filter(PermissionBackend.filter_queryset(self.request, Achievement, "view"))
 | |
| 
 | |
|         return qs
 | |
| 
 | |
| 
 | |
| class AchievementListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, ListView):
 | |
|     """
 | |
|     List all achievements
 | |
|     """
 | |
|     model = Achievement
 | |
|     tables = [AchievementTable, AchievementTable, ]
 | |
|     extra_context = {'title': _('Achievement list')}
 | |
| 
 | |
|     def dispatch(self, request, *args, **kwargs):
 | |
|         if not request.user.is_authenticated:
 | |
|             return self.handle_no_permission()
 | |
| 
 | |
|         if not PermissionBackend.has_model_perm(self.request, Achievement(), "change"):
 | |
|             raise PermissionDenied(_("You are not able to see the achievement validation interface."))
 | |
|         return super().dispatch(request, *args, **kwargs)
 | |
| 
 | |
|     def get_tables(self, **kwargs):
 | |
|         tables = super().get_tables(**kwargs)
 | |
| 
 | |
|         tables[0].prefix = 'invalid-'
 | |
|         tables[1].prefix = 'valid-'
 | |
|         tables[1].exclude = ('validate', 'delete',)
 | |
| 
 | |
|         return tables
 | |
| 
 | |
|     def get_tables_data(self):
 | |
|         table_valid = self.get_queryset().filter(valid=True)
 | |
|         table_invalid = self.get_queryset().filter(valid=False)
 | |
|         return [table_invalid, table_valid, ]
 | |
| 
 | |
|     def get_context_data(self, **kwargs):
 | |
|         context = super().get_context_data(**kwargs)
 | |
| 
 | |
|         tables = context['tables']
 | |
| 
 | |
|         context['invalid'] = tables[0]
 | |
|         context['valid'] = tables[1]
 | |
|         return context
 | |
| 
 | |
| 
 | |
| class AchievementValidateView(ProtectQuerysetMixin, LoginRequiredMixin, TemplateView):
 | |
|     """
 | |
|     Validate an achievement obtained by a family
 | |
|     """
 | |
|     template_name = 'family/achievement_confirm_validate.html'
 | |
| 
 | |
|     def dispatch(self, request, *args, **kwargs):
 | |
|         if not request.user.is_authenticated:
 | |
|             return self.handle_no_permission()
 | |
| 
 | |
|         fake_achievement = Achievement(
 | |
|             family=Family.objects.first(),
 | |
|             challenge=Challenge.objects.first(),
 | |
|             valid=False,
 | |
|         )
 | |
|         if not PermissionBackend.check_perm(self.request, "family.change_achievement_valid", fake_achievement):
 | |
|             raise PermissionDenied()
 | |
|         return super().dispatch(request, *args, **kwargs)
 | |
| 
 | |
|     def post(self, request, pk):
 | |
|         achievement = Achievement.objects.get(pk=pk)
 | |
| 
 | |
|         achievement.valid = True
 | |
|         achievement.save()
 | |
| 
 | |
|         return redirect(reverse_lazy('family:achievement_list'))
 | |
| 
 | |
| 
 | |
| class AchievementDeleteView(ProtectQuerysetMixin, LoginRequiredMixin, DeleteView):
 | |
|     """
 | |
|     Delete an Achievement
 | |
|     """
 | |
|     model = Achievement
 | |
| 
 | |
|     def dispatch(self, request, *args, **kwargs):
 | |
|         if not request.user.is_authenticated:
 | |
|             return self.handle_no_permission()
 | |
| 
 | |
|         fake_achievement = Achievement(
 | |
|             family=Family.objects.first(),
 | |
|             challenge=Challenge.objects.first(),
 | |
|             valid=False,
 | |
|         )
 | |
|         if not PermissionBackend.check_perm(self.request, "family.change_achievement_valid", fake_achievement):
 | |
|             raise PermissionDenied()
 | |
|         return super().dispatch(request, *args, **kwargs)
 | |
| 
 | |
|     def get_success_url(self):
 | |
|         return reverse_lazy('family:achievement_list')
 |