mirror of
				https://gitlab.crans.org/bde/nk20
				synced 2025-11-04 01:12:08 +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')
 |