mirror of
				https://github.com/ae-utbm/sith.git
				synced 2025-11-04 11:03:04 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			305 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			305 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
#
 | 
						|
# Copyright 2016,2017
 | 
						|
# - Skia <skia@libskia.so>
 | 
						|
# - Sli <antoine@bartuccio.fr>
 | 
						|
#
 | 
						|
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
 | 
						|
# http://ae.utbm.fr.
 | 
						|
#
 | 
						|
# This program is free software; you can redistribute it and/or modify it under
 | 
						|
# the terms of the GNU General Public License a published by the Free Software
 | 
						|
# Foundation; either version 3 of the License, or (at your option) any later
 | 
						|
# version.
 | 
						|
#
 | 
						|
# This program is distributed in the hope that it will be useful, but WITHOUT
 | 
						|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 | 
						|
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 | 
						|
# details.
 | 
						|
#
 | 
						|
# You should have received a copy of the GNU General Public License along with
 | 
						|
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
 | 
						|
# Place - Suite 330, Boston, MA 02111-1307, USA.
 | 
						|
#
 | 
						|
#
 | 
						|
 | 
						|
from django import forms
 | 
						|
from django.conf import settings
 | 
						|
from django.db.models import Exists, OuterRef, Q
 | 
						|
from django.db.models.functions import Lower
 | 
						|
from django.utils.functional import cached_property
 | 
						|
from django.utils.translation import gettext_lazy as _
 | 
						|
 | 
						|
from club.models import Club, Mailing, MailingSubscription, Membership
 | 
						|
from core.models import User
 | 
						|
from core.views.forms import SelectDateTime
 | 
						|
from core.views.widgets.ajax_select import (
 | 
						|
    AutoCompleteSelectMultipleUser,
 | 
						|
    AutoCompleteSelectUser,
 | 
						|
)
 | 
						|
from counter.models import Counter, Selling
 | 
						|
 | 
						|
 | 
						|
class ClubEditForm(forms.ModelForm):
 | 
						|
    error_css_class = "error"
 | 
						|
    required_css_class = "required"
 | 
						|
 | 
						|
    class Meta:
 | 
						|
        model = Club
 | 
						|
        fields = ["address", "logo", "short_description"]
 | 
						|
        widgets = {"short_description": forms.Textarea()}
 | 
						|
 | 
						|
 | 
						|
class ClubAdminEditForm(ClubEditForm):
 | 
						|
    admin_fields = ["name", "parent", "is_active"]
 | 
						|
 | 
						|
    class Meta(ClubEditForm.Meta):
 | 
						|
        fields = ["name", "parent", "is_active", *ClubEditForm.Meta.fields]
 | 
						|
 | 
						|
 | 
						|
class MailingForm(forms.Form):
 | 
						|
    """Form handling mailing lists right."""
 | 
						|
 | 
						|
    ACTION_NEW_MAILING = 1
 | 
						|
    ACTION_NEW_SUBSCRIPTION = 2
 | 
						|
    ACTION_REMOVE_SUBSCRIPTION = 3
 | 
						|
 | 
						|
    subscription_users = forms.ModelMultipleChoiceField(
 | 
						|
        label=_("Users to add"),
 | 
						|
        help_text=_("Search users to add (one or more)."),
 | 
						|
        required=False,
 | 
						|
        widget=AutoCompleteSelectMultipleUser,
 | 
						|
        queryset=User.objects.all(),
 | 
						|
    )
 | 
						|
 | 
						|
    def __init__(self, club_id, user_id, mailings, *args, **kwargs):
 | 
						|
        super().__init__(*args, **kwargs)
 | 
						|
 | 
						|
        self.fields["action"] = forms.TypedChoiceField(
 | 
						|
            choices=(
 | 
						|
                (self.ACTION_NEW_MAILING, _("New Mailing")),
 | 
						|
                (self.ACTION_NEW_SUBSCRIPTION, _("Subscribe")),
 | 
						|
                (self.ACTION_REMOVE_SUBSCRIPTION, _("Remove")),
 | 
						|
            ),
 | 
						|
            coerce=int,
 | 
						|
            label=_("Action"),
 | 
						|
            initial=1,
 | 
						|
            required=True,
 | 
						|
            widget=forms.HiddenInput(),
 | 
						|
        )
 | 
						|
 | 
						|
        # Generate bulk removal forms, they are never required
 | 
						|
        for mailing in mailings:
 | 
						|
            self.fields["removal_" + str(mailing.id)] = forms.ModelMultipleChoiceField(
 | 
						|
                mailing.subscriptions.all(),
 | 
						|
                label=_("Remove"),
 | 
						|
                required=False,
 | 
						|
                widget=forms.CheckboxSelectMultiple,
 | 
						|
            )
 | 
						|
 | 
						|
        # Include fields for handling mailing creation
 | 
						|
        mailing_fields = ("email",)
 | 
						|
        self.fields.update(forms.fields_for_model(Mailing, fields=mailing_fields))
 | 
						|
        for field in mailing_fields:
 | 
						|
            self.fields["mailing_" + field] = self.fields.pop(field)
 | 
						|
            self.fields["mailing_" + field].required = False
 | 
						|
 | 
						|
        # Include fields for handling subscription creation
 | 
						|
        subscription_fields = ("mailing", "email")
 | 
						|
        self.fields.update(
 | 
						|
            forms.fields_for_model(MailingSubscription, fields=subscription_fields)
 | 
						|
        )
 | 
						|
        for field in subscription_fields:
 | 
						|
            self.fields["subscription_" + field] = self.fields.pop(field)
 | 
						|
            self.fields["subscription_" + field].required = False
 | 
						|
 | 
						|
        self.fields["subscription_mailing"].queryset = Mailing.objects.filter(
 | 
						|
            club__id=club_id, is_moderated=True
 | 
						|
        )
 | 
						|
 | 
						|
    def check_required(self, cleaned_data, field):
 | 
						|
        """If the given field doesn't exist or has no value, add a required error on it."""
 | 
						|
        if not cleaned_data.get(field, None):
 | 
						|
            self.add_error(field, _("This field is required"))
 | 
						|
 | 
						|
    def clean_subscription_users(self):
 | 
						|
        """Convert given users into real users and check their validity."""
 | 
						|
        cleaned_data = super().clean()
 | 
						|
        users = []
 | 
						|
        for user in cleaned_data["subscription_users"]:
 | 
						|
            if not user.email:
 | 
						|
                raise forms.ValidationError(
 | 
						|
                    _("One of the selected users doesn't have an email address"),
 | 
						|
                    code="invalid",
 | 
						|
                )
 | 
						|
            users.append(user)
 | 
						|
        return users
 | 
						|
 | 
						|
    def clean(self):
 | 
						|
        cleaned_data = super().clean()
 | 
						|
 | 
						|
        if "action" not in cleaned_data:
 | 
						|
            # If there is no action provided, we can stop here
 | 
						|
            raise forms.ValidationError(_("An action is required"), code="invalid")
 | 
						|
 | 
						|
        if cleaned_data["action"] == self.ACTION_NEW_MAILING:
 | 
						|
            self.check_required(cleaned_data, "mailing_email")
 | 
						|
 | 
						|
        if cleaned_data["action"] == self.ACTION_NEW_SUBSCRIPTION:
 | 
						|
            self.check_required(cleaned_data, "subscription_mailing")
 | 
						|
            if not cleaned_data.get(
 | 
						|
                "subscription_users", None
 | 
						|
            ) and not cleaned_data.get("subscription_email", None):
 | 
						|
                raise forms.ValidationError(
 | 
						|
                    _("You must specify at least an user or an email address"),
 | 
						|
                    code="invalid",
 | 
						|
                )
 | 
						|
 | 
						|
        return cleaned_data
 | 
						|
 | 
						|
 | 
						|
class SellingsForm(forms.Form):
 | 
						|
    begin_date = forms.DateTimeField(
 | 
						|
        label=_("Begin date"), widget=SelectDateTime, required=False
 | 
						|
    )
 | 
						|
    end_date = forms.DateTimeField(
 | 
						|
        label=_("End date"), widget=SelectDateTime, required=False
 | 
						|
    )
 | 
						|
 | 
						|
    def __init__(self, club, *args, **kwargs):
 | 
						|
        super().__init__(*args, **kwargs)
 | 
						|
        # postgres struggles really hard with a single query having three WHERE conditions,
 | 
						|
        # but deals perfectly fine with UNION of multiple queryset with their own WHERE clause,
 | 
						|
        # so we do this to get the ids, which we use to build another queryset that can be used by django.
 | 
						|
        club_sales_subquery = Selling.objects.filter(counter=OuterRef("pk"), club=club)
 | 
						|
        ids = (
 | 
						|
            Counter.objects.filter(Q(club=club) | Q(products__club=club))
 | 
						|
            .union(Counter.objects.filter(Exists(club_sales_subquery)))
 | 
						|
            .values_list("id", flat=True)
 | 
						|
        )
 | 
						|
        counters_qs = Counter.objects.filter(id__in=ids).order_by(Lower("name"))
 | 
						|
        self.fields["counters"] = forms.ModelMultipleChoiceField(
 | 
						|
            counters_qs, label=_("Counter"), required=False
 | 
						|
        )
 | 
						|
        self.fields["products"] = forms.ModelMultipleChoiceField(
 | 
						|
            club.products.order_by("name").filter(archived=False).all(),
 | 
						|
            label=_("Products"),
 | 
						|
            required=False,
 | 
						|
        )
 | 
						|
        self.fields["archived_products"] = forms.ModelMultipleChoiceField(
 | 
						|
            club.products.order_by("name").filter(archived=True).all(),
 | 
						|
            label=_("Archived products"),
 | 
						|
            required=False,
 | 
						|
        )
 | 
						|
 | 
						|
 | 
						|
class ClubOldMemberForm(forms.Form):
 | 
						|
    members_old = forms.ModelMultipleChoiceField(
 | 
						|
        Membership.objects.none(),
 | 
						|
        label=_("Mark as old"),
 | 
						|
        widget=forms.CheckboxSelectMultiple,
 | 
						|
        required=False,
 | 
						|
    )
 | 
						|
 | 
						|
    def __init__(self, *args, user: User, club: Club, **kwargs):
 | 
						|
        super().__init__(*args, **kwargs)
 | 
						|
        self.fields["members_old"].queryset = (
 | 
						|
            Membership.objects.ongoing().filter(club=club).editable_by(user)
 | 
						|
        )
 | 
						|
 | 
						|
 | 
						|
class ClubMemberForm(forms.ModelForm):
 | 
						|
    """Form to add a member to the club, as a board member."""
 | 
						|
 | 
						|
    error_css_class = "error"
 | 
						|
    required_css_class = "required"
 | 
						|
 | 
						|
    class Meta:
 | 
						|
        model = Membership
 | 
						|
        fields = ["role", "description"]
 | 
						|
 | 
						|
    def __init__(self, *args, club: Club, request_user: User, **kwargs):
 | 
						|
        self.club = club
 | 
						|
        self.request_user = request_user
 | 
						|
        self.request_user_membership = self.club.get_membership_for(self.request_user)
 | 
						|
        super().__init__(*args, **kwargs)
 | 
						|
        self.fields["role"].required = True
 | 
						|
        self.fields["role"].choices = [
 | 
						|
            (value, name)
 | 
						|
            for value, name in settings.SITH_CLUB_ROLES.items()
 | 
						|
            if value <= self.max_available_role
 | 
						|
        ]
 | 
						|
        self.instance.club = club
 | 
						|
 | 
						|
    @property
 | 
						|
    def max_available_role(self):
 | 
						|
        """The greatest role that will be obtainable with this form."""
 | 
						|
        # this is unreachable, because it will be overridden by subclasses
 | 
						|
        return -1  # pragma: no cover
 | 
						|
 | 
						|
 | 
						|
class ClubAddMemberForm(ClubMemberForm):
 | 
						|
    """Form to add a member to the club, as a board member."""
 | 
						|
 | 
						|
    class Meta(ClubMemberForm.Meta):
 | 
						|
        fields = ["user", *ClubMemberForm.Meta.fields]
 | 
						|
        widgets = {"user": AutoCompleteSelectUser}
 | 
						|
 | 
						|
    @cached_property
 | 
						|
    def max_available_role(self):
 | 
						|
        """The greatest role that will be obtainable with this form.
 | 
						|
 | 
						|
        Admins and the club president can attribute any role.
 | 
						|
        Board members can attribute roles lower than their own.
 | 
						|
        Other users cannot attribute roles with this form
 | 
						|
        """
 | 
						|
        if self.request_user.has_perm("club.add_membership"):
 | 
						|
            return settings.SITH_CLUB_ROLES_ID["President"]
 | 
						|
        membership = self.request_user_membership
 | 
						|
        if membership is None or membership.role <= settings.SITH_MAXIMUM_FREE_ROLE:
 | 
						|
            return -1
 | 
						|
        if membership.role == settings.SITH_CLUB_ROLES_ID["President"]:
 | 
						|
            return membership.role
 | 
						|
        return membership.role - 1
 | 
						|
 | 
						|
    def clean_user(self):
 | 
						|
        """Check that the user is not trying to add a user already in the club.
 | 
						|
 | 
						|
        Also check that the user is valid and has a valid subscription.
 | 
						|
        """
 | 
						|
        user = self.cleaned_data["user"]
 | 
						|
        if not user.is_subscribed:
 | 
						|
            raise forms.ValidationError(
 | 
						|
                _("User must be subscriber to take part to a club"), code="invalid"
 | 
						|
            )
 | 
						|
        if self.club.get_membership_for(user):
 | 
						|
            raise forms.ValidationError(
 | 
						|
                _("You can not add the same user twice"), code="invalid"
 | 
						|
            )
 | 
						|
        return user
 | 
						|
 | 
						|
 | 
						|
class JoinClubForm(ClubMemberForm):
 | 
						|
    """Form to join a club."""
 | 
						|
 | 
						|
    def __init__(self, *args, club: Club, request_user: User, **kwargs):
 | 
						|
        super().__init__(*args, club=club, request_user=request_user, **kwargs)
 | 
						|
        # this form doesn't manage the user who will join the club,
 | 
						|
        # so we must set this here to avoid errors
 | 
						|
        self.instance.user = self.request_user
 | 
						|
 | 
						|
    @cached_property
 | 
						|
    def max_available_role(self):
 | 
						|
        return settings.SITH_MAXIMUM_FREE_ROLE
 | 
						|
 | 
						|
    def clean(self):
 | 
						|
        """Check that the user is subscribed and isn't already in the club."""
 | 
						|
        if not self.request_user.is_subscribed:
 | 
						|
            raise forms.ValidationError(
 | 
						|
                _("You must be subscribed to join a club"), code="invalid"
 | 
						|
            )
 | 
						|
        if self.club.get_membership_for(self.request_user):
 | 
						|
            raise forms.ValidationError(
 | 
						|
                _("You are already a member of this club"), code="invalid"
 | 
						|
            )
 | 
						|
        return super().clean()
 |