mirror of
				https://gitlab.crans.org/bde/nk20
				synced 2025-11-04 01:12:08 +01:00 
			
		
		
		
	Merge branch 'documents' into beta
This commit is contained in:
		@@ -54,6 +54,15 @@ class ActivityForm(forms.ModelForm):
 | 
			
		||||
 | 
			
		||||
class GuestForm(forms.ModelForm):
 | 
			
		||||
    def clean(self):
 | 
			
		||||
        """
 | 
			
		||||
        Someone can be invited as a Guest to an Activity if:
 | 
			
		||||
        - the activity has not already started.
 | 
			
		||||
        - the activity is validated.
 | 
			
		||||
        - the Guest has not already been invited more than 5 times.
 | 
			
		||||
        - the Guest is already invited.
 | 
			
		||||
        - the inviter already invited 3 peoples.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        cleaned_data = super().clean()
 | 
			
		||||
 | 
			
		||||
        if timezone.now() > timezone.localtime(self.activity.date_start):
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ from django.utils import timezone
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
from django.views.generic import DetailView, TemplateView, UpdateView
 | 
			
		||||
from django_tables2.views import SingleTableView
 | 
			
		||||
 | 
			
		||||
from note.models import Alias, NoteSpecial, NoteUser
 | 
			
		||||
from permission.backends import PermissionBackend
 | 
			
		||||
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
 | 
			
		||||
@@ -21,6 +22,9 @@ from .tables import ActivityTable, EntryTable, GuestTable
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ActivityCreateView(ProtectedCreateView):
 | 
			
		||||
    """
 | 
			
		||||
    View to create a new Activity
 | 
			
		||||
    """
 | 
			
		||||
    model = Activity
 | 
			
		||||
    form_class = ActivityForm
 | 
			
		||||
    extra_context = {"title": _("Create new activity")}
 | 
			
		||||
@@ -47,6 +51,9 @@ class ActivityCreateView(ProtectedCreateView):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
 | 
			
		||||
    """
 | 
			
		||||
    Displays all Activities, and classify if they are on-going or upcoming ones.
 | 
			
		||||
    """
 | 
			
		||||
    model = Activity
 | 
			
		||||
    table_class = ActivityTable
 | 
			
		||||
    ordering = ('-date_start',)
 | 
			
		||||
@@ -73,6 +80,9 @@ class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ActivityDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
 | 
			
		||||
    """
 | 
			
		||||
    Shows details about one activity. Add guest to context
 | 
			
		||||
    """
 | 
			
		||||
    model = Activity
 | 
			
		||||
    context_object_name = "activity"
 | 
			
		||||
    extra_context = {"title": _("Activity detail")}
 | 
			
		||||
@@ -90,6 +100,9 @@ class ActivityDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ActivityUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
 | 
			
		||||
    """
 | 
			
		||||
    Updates one Activity
 | 
			
		||||
    """
 | 
			
		||||
    model = Activity
 | 
			
		||||
    form_class = ActivityForm
 | 
			
		||||
    extra_context = {"title": _("Update activity")}
 | 
			
		||||
@@ -99,11 +112,15 @@ class ActivityUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ActivityInviteView(ProtectQuerysetMixin, ProtectedCreateView):
 | 
			
		||||
    """
 | 
			
		||||
    Invite a Guest, The rules to invites someone are defined in `forms:activity.GuestForm`
 | 
			
		||||
    """
 | 
			
		||||
    model = Guest
 | 
			
		||||
    form_class = GuestForm
 | 
			
		||||
    template_name = "activity/activity_invite.html"
 | 
			
		||||
 | 
			
		||||
    def get_sample_object(self):
 | 
			
		||||
        """ Creates a standart Guest binds to the Activity"""
 | 
			
		||||
        activity = Activity.objects.get(pk=self.kwargs["pk"])
 | 
			
		||||
        return Guest(
 | 
			
		||||
            activity=activity,
 | 
			
		||||
@@ -134,6 +151,9 @@ class ActivityInviteView(ProtectQuerysetMixin, ProtectedCreateView):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ActivityEntryView(LoginRequiredMixin, TemplateView):
 | 
			
		||||
    """
 | 
			
		||||
    Manages entry to an activity
 | 
			
		||||
    """
 | 
			
		||||
    template_name = "activity/activity_entry.html"
 | 
			
		||||
 | 
			
		||||
    def dispatch(self, request, *args, **kwargs):
 | 
			
		||||
@@ -154,14 +174,10 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
 | 
			
		||||
            raise PermissionDenied(_("This activity is closed."))
 | 
			
		||||
        return super().dispatch(request, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        context = super().get_context_data(**kwargs)
 | 
			
		||||
 | 
			
		||||
        activity = Activity.objects.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view"))\
 | 
			
		||||
            .distinct().get(pk=self.kwargs["pk"])
 | 
			
		||||
        context["activity"] = activity
 | 
			
		||||
 | 
			
		||||
        matched = []
 | 
			
		||||
    def get_invited_guest(self,activity):
 | 
			
		||||
        """
 | 
			
		||||
        Retrieves all Guests to the activity
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        guest_qs = Guest.objects\
 | 
			
		||||
            .annotate(balance=F("inviter__balance"), note_name=F("inviter__user__username"))\
 | 
			
		||||
@@ -182,11 +198,13 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
 | 
			
		||||
        else:
 | 
			
		||||
            pattern = None
 | 
			
		||||
            guest_qs = guest_qs.none()
 | 
			
		||||
        return guest_qs
 | 
			
		||||
 | 
			
		||||
        for guest in guest_qs:
 | 
			
		||||
            guest.type = "Invité"
 | 
			
		||||
            matched.append(guest)
 | 
			
		||||
 | 
			
		||||
    def get_invited_note(self,activity):
 | 
			
		||||
        """
 | 
			
		||||
        Retrieves all Note that can attend the activity,
 | 
			
		||||
        they need to have an up-to-date membership in the attendees_club.
 | 
			
		||||
        """
 | 
			
		||||
        note_qs = Alias.objects.annotate(last_name=F("note__noteuser__user__last_name"),
 | 
			
		||||
                                         first_name=F("note__noteuser__user__first_name"),
 | 
			
		||||
                                         username=F("note__noteuser__user__username"),
 | 
			
		||||
@@ -223,8 +241,25 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
 | 
			
		||||
            # have distinct aliases rather than distinct notes with a SQLite DB, but it can fill the result page.
 | 
			
		||||
            # In production mode, please use PostgreSQL.
 | 
			
		||||
            note_qs = note_qs.distinct()[:20]
 | 
			
		||||
        return note_qs
 | 
			
		||||
 | 
			
		||||
        for note in note_qs:
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Query the list of Guest and Note to the activity and add information to makes entry with JS.
 | 
			
		||||
        """
 | 
			
		||||
        context = super().get_context_data(**kwargs)
 | 
			
		||||
 | 
			
		||||
        activity = Activity.objects.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view"))\
 | 
			
		||||
            .distinct().get(pk=self.kwargs["pk"])
 | 
			
		||||
        context["activity"] = activity
 | 
			
		||||
 | 
			
		||||
        matched=[]
 | 
			
		||||
 | 
			
		||||
        for guest in get_invited_guest(self,activity):
 | 
			
		||||
            guest.type = "Invité"
 | 
			
		||||
            matched.append(guest)
 | 
			
		||||
 | 
			
		||||
        for note in get_invited_note(self,activity):
 | 
			
		||||
            note.type = "Adhérent"
 | 
			
		||||
            note.activity = activity
 | 
			
		||||
            matched.append(note)
 | 
			
		||||
 
 | 
			
		||||
@@ -65,6 +65,18 @@ class ProfileForm(forms.ModelForm):
 | 
			
		||||
        exclude = ('user', 'email_confirmed', 'registration_valid', )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ImageForm(forms.Form):
 | 
			
		||||
    """
 | 
			
		||||
    Form used for the js interface for profile picture
 | 
			
		||||
    """
 | 
			
		||||
    image = forms.ImageField(required=False,
 | 
			
		||||
                             label=_('select an image'),
 | 
			
		||||
                             help_text=_('Maximal size: 2MB'))
 | 
			
		||||
    x = forms.FloatField(widget=forms.HiddenInput())
 | 
			
		||||
    y = forms.FloatField(widget=forms.HiddenInput())
 | 
			
		||||
    width = forms.FloatField(widget=forms.HiddenInput())
 | 
			
		||||
    height = forms.FloatField(widget=forms.HiddenInput())
 | 
			
		||||
 | 
			
		||||
class ClubForm(forms.ModelForm):
 | 
			
		||||
    def clean(self):
 | 
			
		||||
        cleaned_data = super().clean()
 | 
			
		||||
 
 | 
			
		||||
@@ -3,8 +3,8 @@
 | 
			
		||||
 | 
			
		||||
import io
 | 
			
		||||
from datetime import timedelta, date
 | 
			
		||||
 | 
			
		||||
from PIL import Image
 | 
			
		||||
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.contrib.auth import logout
 | 
			
		||||
from django.contrib.auth.mixins import LoginRequiredMixin
 | 
			
		||||
@@ -18,8 +18,8 @@ from django.utils.translation import gettext_lazy as _
 | 
			
		||||
from django.views.generic import DetailView, UpdateView, TemplateView
 | 
			
		||||
from django.views.generic.edit import FormMixin
 | 
			
		||||
from django_tables2.views import SingleTableView
 | 
			
		||||
 | 
			
		||||
from rest_framework.authtoken.models import Token
 | 
			
		||||
from note.forms import ImageForm
 | 
			
		||||
from note.models import Alias, NoteUser
 | 
			
		||||
from note.models.transactions import Transaction, SpecialTransaction
 | 
			
		||||
from note.tables import HistoryTable, AliasTable
 | 
			
		||||
@@ -28,7 +28,8 @@ from permission.backends import PermissionBackend
 | 
			
		||||
from permission.models import Role
 | 
			
		||||
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
 | 
			
		||||
 | 
			
		||||
from .forms import ProfileForm, ClubForm, MembershipForm, CustomAuthenticationForm, UserForm, MembershipRolesForm
 | 
			
		||||
from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm,\
 | 
			
		||||
    CustomAuthenticationForm, MembershipRolesForm,
 | 
			
		||||
from .models import Club, Membership
 | 
			
		||||
from .tables import ClubTable, UserTable, MembershipTable, ClubManagerTable
 | 
			
		||||
 | 
			
		||||
@@ -49,6 +50,7 @@ class CustomLoginView(LoginView):
 | 
			
		||||
class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
 | 
			
		||||
    """
 | 
			
		||||
    Update the user information.
 | 
			
		||||
    On this view both `:models:member.User` and `:models:member.Profile` are updated through forms
 | 
			
		||||
    """
 | 
			
		||||
    model = User
 | 
			
		||||
    form_class = UserForm
 | 
			
		||||
@@ -77,14 +79,11 @@ class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
 | 
			
		||||
        return context
 | 
			
		||||
 | 
			
		||||
    def form_valid(self, form):
 | 
			
		||||
        new_username = form.data['username']
 | 
			
		||||
        # Si l'utilisateur cherche à modifier son pseudo, le nouveau pseudo ne doit pas être proche d'un alias existant
 | 
			
		||||
        note = NoteUser.objects.filter(
 | 
			
		||||
            alias__normalized_name=Alias.normalize(new_username))
 | 
			
		||||
        if note.exists() and note.get().user != self.object:
 | 
			
		||||
            form.add_error('username',
 | 
			
		||||
                           _("An alias with a similar name already exists."))
 | 
			
		||||
            return super().form_invalid(form)
 | 
			
		||||
        """
 | 
			
		||||
        Check if ProfileForm is correct
 | 
			
		||||
        then check if username is not already taken by someone else or by the user,
 | 
			
		||||
        then check if email has changed, and if so ask for new validation.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        profile_form = ProfileForm(
 | 
			
		||||
            data=self.request.POST,
 | 
			
		||||
@@ -93,31 +92,35 @@ class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
 | 
			
		||||
        profile_form.full_clean()
 | 
			
		||||
        if not profile_form.is_valid():
 | 
			
		||||
            return super().form_invalid(form)
 | 
			
		||||
 | 
			
		||||
        new_username = form.data['username']
 | 
			
		||||
        # Check if the new username is not already taken as an alias of someone else.
 | 
			
		||||
        note = NoteUser.objects.filter(
 | 
			
		||||
            alias__normalized_name=Alias.normalize(new_username))
 | 
			
		||||
        if note.exists() and note.get().user != self.object:
 | 
			
		||||
            form.add_error('username',
 | 
			
		||||
                           _("An alias with a similar name already exists."))
 | 
			
		||||
            return super().form_invalid(form)
 | 
			
		||||
        # Check if the username is one of user's aliases.
 | 
			
		||||
        alias = Alias.objects.filter(name=new_username)
 | 
			
		||||
        # Si le nouveau pseudo n'est pas un de nos alias,
 | 
			
		||||
        # on supprime éventuellement un alias similaire pour le remplacer
 | 
			
		||||
        if not alias.exists():
 | 
			
		||||
            similar = Alias.objects.filter(
 | 
			
		||||
                normalized_name=Alias.normalize(new_username))
 | 
			
		||||
            if similar.exists():
 | 
			
		||||
                similar.delete()
 | 
			
		||||
 | 
			
		||||
        olduser = User.objects.get(pk=form.instance.pk)
 | 
			
		||||
 | 
			
		||||
        user = form.save(commit=False)
 | 
			
		||||
        profile = profile_form.save(commit=False)
 | 
			
		||||
        profile.user = user
 | 
			
		||||
        profile.save()
 | 
			
		||||
        user.save()
 | 
			
		||||
 | 
			
		||||
        if olduser.email != user.email:
 | 
			
		||||
            # If the user changed her/his email, then it is unvalidated and a confirmation link is sent.
 | 
			
		||||
            user.profile.email_confirmed = False
 | 
			
		||||
            user.profile.save()
 | 
			
		||||
            user.profile.send_email_validation_link()
 | 
			
		||||
 | 
			
		||||
        profile = profile_form.save(commit=False)
 | 
			
		||||
        profile.user = user
 | 
			
		||||
        profile.save()
 | 
			
		||||
        user.save()
 | 
			
		||||
 | 
			
		||||
        return super().form_valid(form)
 | 
			
		||||
 | 
			
		||||
    def get_success_url(self, **kwargs):
 | 
			
		||||
@@ -127,7 +130,7 @@ class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
 | 
			
		||||
 | 
			
		||||
class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
 | 
			
		||||
    """
 | 
			
		||||
    Affiche les informations sur un utilisateur, sa note, ses clubs...
 | 
			
		||||
    Display all information about a user.
 | 
			
		||||
    """
 | 
			
		||||
    model = User
 | 
			
		||||
    context_object_name = "user_object"
 | 
			
		||||
@@ -141,6 +144,9 @@ class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
 | 
			
		||||
        return super().get_queryset().filter(profile__registration_valid=True)
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Add history of transaction and list of membership of user.
 | 
			
		||||
        """
 | 
			
		||||
        context = super().get_context_data(**kwargs)
 | 
			
		||||
        user = context['user_object']
 | 
			
		||||
        history_list = \
 | 
			
		||||
@@ -356,22 +362,26 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
 | 
			
		||||
    extra_context = {"title": _("Club detail")}
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Add list of managers (peoples with Permission/Roles in this club), history of transactions and members list
 | 
			
		||||
        """
 | 
			
		||||
        context = super().get_context_data(**kwargs)
 | 
			
		||||
 | 
			
		||||
        club = context["club"]
 | 
			
		||||
        if PermissionBackend.check_perm(self.request.user, "member.change_club_membership_start", club):
 | 
			
		||||
            club.update_membership_dates()
 | 
			
		||||
 | 
			
		||||
        # managers list
 | 
			
		||||
        managers = Membership.objects.filter(club=self.object, roles__name="Bureau de club")\
 | 
			
		||||
            .order_by('user__last_name').all()
 | 
			
		||||
        context["managers"] = ClubManagerTable(data=managers, prefix="managers-")
 | 
			
		||||
 | 
			
		||||
        # transaction history
 | 
			
		||||
        club_transactions = Transaction.objects.all().filter(Q(source=club.note) | Q(destination=club.note))\
 | 
			
		||||
            .filter(PermissionBackend.filter_queryset(self.request.user, Transaction, "view"))\
 | 
			
		||||
            .order_by('-created_at')
 | 
			
		||||
        history_table = HistoryTable(club_transactions, prefix="history-")
 | 
			
		||||
        history_table.paginate(per_page=20, page=self.request.GET.get('history-page', 1))
 | 
			
		||||
        context['history_list'] = history_table
 | 
			
		||||
        # member list
 | 
			
		||||
        club_member = Membership.objects.filter(
 | 
			
		||||
            club=club,
 | 
			
		||||
            date_end__gte=date.today(),
 | 
			
		||||
@@ -469,15 +479,19 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Membership can be created, or renewed
 | 
			
		||||
        In case of creation the url is /club/<club_pk>/add_member
 | 
			
		||||
        For a renewal it will be `club/renew_membership/<pk>`
 | 
			
		||||
        """
 | 
			
		||||
        context = super().get_context_data(**kwargs)
 | 
			
		||||
        form = context['form']
 | 
			
		||||
 | 
			
		||||
        if "club_pk" in self.kwargs:
 | 
			
		||||
            # We create a new membership.
 | 
			
		||||
        if "club_pk" in self.kwargs:  # We create a new membership.
 | 
			
		||||
            club = Club.objects.filter(PermissionBackend.filter_queryset(self.request.user, Club, "view"))\
 | 
			
		||||
                .get(pk=self.kwargs["club_pk"], weiclub=None)
 | 
			
		||||
            form.fields['credit_amount'].initial = club.membership_fee_paid
 | 
			
		||||
 | 
			
		||||
            # Ensure that the user is member of the parent club and all its the family tree.
 | 
			
		||||
            c = club
 | 
			
		||||
            clubs_renewal = []
 | 
			
		||||
            additional_fee_renewal = 0
 | 
			
		||||
@@ -498,8 +512,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
 | 
			
		||||
                kfet = Club.objects.get(name="Kfet")
 | 
			
		||||
                fee += kfet.membership_fee_paid
 | 
			
		||||
                context["total_fee"] = "{:.02f}".format(fee / 100, )
 | 
			
		||||
        else:
 | 
			
		||||
            # This is a renewal. Fields can be pre-completed.
 | 
			
		||||
        else:  # This is a renewal. Fields can be pre-completed.
 | 
			
		||||
            context["renewal"] = True
 | 
			
		||||
 | 
			
		||||
            old_membership = self.get_queryset().get(pk=self.kwargs["pk"])
 | 
			
		||||
@@ -511,6 +524,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
 | 
			
		||||
            additional_fee_renewal = 0
 | 
			
		||||
            while c.parent_club is not None:
 | 
			
		||||
                c = c.parent_club
 | 
			
		||||
                # check if a valid membership exists for the parent club
 | 
			
		||||
                if c.membership_start and not Membership.objects.filter(
 | 
			
		||||
                        club=c,
 | 
			
		||||
                        user=user,
 | 
			
		||||
@@ -529,7 +543,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
 | 
			
		||||
            form.fields['last_name'].initial = user.last_name
 | 
			
		||||
            form.fields['first_name'].initial = user.first_name
 | 
			
		||||
 | 
			
		||||
            # If this is a renewal of a BDE membership, Société générale can pays, if it is not yet done
 | 
			
		||||
            # If this is a renewal of a BDE membership, Société générale can pays, if it has not been already done.
 | 
			
		||||
            if (club.name != "BDE" and club.name != "Kfet") or user.profile.soge:
 | 
			
		||||
                del form.fields['soge']
 | 
			
		||||
            else:
 | 
			
		||||
@@ -559,11 +573,11 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
 | 
			
		||||
        Create membership, check that all is good, make transactions
 | 
			
		||||
        """
 | 
			
		||||
        # Get the club that is concerned by the membership
 | 
			
		||||
        if "club_pk" in self.kwargs:
 | 
			
		||||
        if "club_pk" in self.kwargs: # get from url of new membership
 | 
			
		||||
            club = Club.objects.filter(PermissionBackend.filter_queryset(self.request.user, Club, "view")) \
 | 
			
		||||
                .get(pk=self.kwargs["club_pk"])
 | 
			
		||||
            user = form.instance.user
 | 
			
		||||
        else:
 | 
			
		||||
        else: # get from url for renewal
 | 
			
		||||
            old_membership = self.get_queryset().get(pk=self.kwargs["pk"])
 | 
			
		||||
            club = old_membership.club
 | 
			
		||||
            user = old_membership.user
 | 
			
		||||
@@ -572,6 +586,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
 | 
			
		||||
 | 
			
		||||
        # Get form data
 | 
			
		||||
        credit_type = form.cleaned_data["credit_type"]
 | 
			
		||||
            # but with this way users can customize their section as they want.
 | 
			
		||||
        credit_amount = form.cleaned_data["credit_amount"]
 | 
			
		||||
        last_name = form.cleaned_data["last_name"]
 | 
			
		||||
        first_name = form.cleaned_data["first_name"]
 | 
			
		||||
@@ -589,6 +604,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
 | 
			
		||||
 | 
			
		||||
        fee = 0
 | 
			
		||||
        c = club
 | 
			
		||||
        # collect the fees required to be paid
 | 
			
		||||
        while c is not None and c.membership_start:
 | 
			
		||||
            if not Membership.objects.filter(
 | 
			
		||||
                    club=c,
 | 
			
		||||
@@ -632,9 +648,8 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
 | 
			
		||||
        # Now, all is fine, the membership can be created.
 | 
			
		||||
 | 
			
		||||
        if club.name == "BDE" or club.name == "Kfet":
 | 
			
		||||
            # When we renew the BDE membership, we update the profile section.
 | 
			
		||||
            # We could automate that and remove the section field from the Profile model,
 | 
			
		||||
            # but with this way users can customize their section as they want.
 | 
			
		||||
            # When we renew the BDE membership, we update the profile section
 | 
			
		||||
            # that should happens at least once a year.
 | 
			
		||||
            user.profile.section = user.profile.section_generated
 | 
			
		||||
            user.profile.save()
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,15 +10,6 @@ from note_kfet.inputs import Autocomplete, AmountInput, DateTimePickerInput
 | 
			
		||||
from .models import TransactionTemplate, NoteClub, Alias
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ImageForm(forms.Form):
 | 
			
		||||
    image = forms.ImageField(required=False,
 | 
			
		||||
                             label=_('select an image'),
 | 
			
		||||
                             help_text=_('Maximal size: 2MB'))
 | 
			
		||||
    x = forms.FloatField(widget=forms.HiddenInput())
 | 
			
		||||
    y = forms.FloatField(widget=forms.HiddenInput())
 | 
			
		||||
    width = forms.FloatField(widget=forms.HiddenInput())
 | 
			
		||||
    height = forms.FloatField(widget=forms.HiddenInput())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TransactionTemplateForm(forms.ModelForm):
 | 
			
		||||
    class Meta:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,14 @@
 | 
			
		||||
{# Select amount to transfert in € #}
 | 
			
		||||
<div class="input-group">
 | 
			
		||||
    <input class="form-control mx-auto d-block" type="number" {% if not widget.attrs.negative %}min="0"{% endif %} step="0.01"
 | 
			
		||||
           {% if widget.value != None and widget.value != "" %}value="{{ widget.value }}"{% endif %}
 | 
			
		||||
           name="{{ widget.name }}"
 | 
			
		||||
            {% for name, value in widget.attrs.items %}
 | 
			
		||||
           {# Other attributes are loaded  #}
 | 
			
		||||
           {% for name, value in widget.attrs.items %}
 | 
			
		||||
                {% ifnotequal value False %}{{ name }}{% ifnotequal value True %}="{{ value|stringformat:'s' }}"{% endifnotequal %}{% endifnotequal %}
 | 
			
		||||
            {% endfor %}>
 | 
			
		||||
    <div class="input-group-append">
 | 
			
		||||
        <span class="input-group-text">€</span>
 | 
			
		||||
    </div>
 | 
			
		||||
    <p id="amount-required" class="invalid-feedback"></p>
 | 
			
		||||
</div>
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -45,7 +45,7 @@
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                {# Summary of consumption and consume button #}
 | 
			
		||||
                <div class="col-xl-5 d-none" id="consos_list_div">
 | 
			
		||||
                    <div class="card border-info shadow mb-4">
 | 
			
		||||
                        <div class="card-header">
 | 
			
		||||
@@ -91,7 +91,6 @@
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            {# Regroup buttons under categories #}
 | 
			
		||||
	    {# {% regroup transaction_templates by category as categories %} #}
 | 
			
		||||
 | 
			
		||||
            <div class="card border-primary text-center shadow mb-4">
 | 
			
		||||
                {# Tabs for button categories #}
 | 
			
		||||
@@ -148,7 +147,7 @@
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    {# history of transaction #}
 | 
			
		||||
    <div class="card shadow mb-4" id="history">
 | 
			
		||||
        <div class="card-header">
 | 
			
		||||
            <p class="card-text font-weight-bold">
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 | 
			
		||||
{% load i18n static django_tables2 perms %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
 | 
			
		||||
{# bandeau transfert/crédit/débit/activité #}
 | 
			
		||||
    <div class="row">
 | 
			
		||||
        <div class="col-xl-12">
 | 
			
		||||
            <div class="btn-group btn-group-toggle" style="width: 100%; padding: 0 0 2em 0" data-toggle="buttons">
 | 
			
		||||
@@ -34,8 +34,8 @@ SPDX-License-Identifier: GPL-2.0-or-later
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="row">
 | 
			
		||||
    {#  Preview note profile (picture, username and balance) #}
 | 
			
		||||
        <div class="col-md-3" id="note_infos_div">
 | 
			
		||||
            <div class="card border-success shadow mb-4">
 | 
			
		||||
                <a id="profile_pic_link" href="#"><img src="/media/pic/default.png"
 | 
			
		||||
@@ -45,7 +45,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
    {# list of emitters #}
 | 
			
		||||
        <div class="col-md-3" id="emitters_div">
 | 
			
		||||
            <div class="card border-success shadow mb-4">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
@@ -66,7 +66,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
{# list of receiver #}
 | 
			
		||||
        <div class="col-md-3" id="dests_div">
 | 
			
		||||
            <div class="card border-info shadow mb-4">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
@@ -83,7 +83,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
{# Information on transaction (amount, reason, name,...) #}
 | 
			
		||||
        <div class="col-md-3" id="external_div">
 | 
			
		||||
            <div class="card border-warning shadow mb-4">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
@@ -108,7 +108,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 | 
			
		||||
                            <p id="reason-required" class="invalid-feedback"></p>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
 | 
			
		||||
                    {# in case of special transaction add identity information #}
 | 
			
		||||
                    <div class="d-none" id="special_transaction_div">
 | 
			
		||||
                        <div class="form-row">
 | 
			
		||||
                            <div class="col-md-12">
 | 
			
		||||
@@ -149,7 +149,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
{# transaction history #}
 | 
			
		||||
    <div class="card shadow mb-4" id="history">
 | 
			
		||||
        <div class="card-header">
 | 
			
		||||
            <p class="card-text font-weight-bold">
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="row justify-content-center mb-4">
 | 
			
		||||
    <div class="col-md-10 text-center">
 | 
			
		||||
        {# Search field , see js #}
 | 
			
		||||
        <input class="form-control mx-auto w-25" type="text" id="search_field" placeholder="{% trans "Name of the button..." %}" value="{{ request.GET.search }}">
 | 
			
		||||
        <hr>
 | 
			
		||||
        <a class="btn btn-primary text-center my-1" href="{% url 'note:template_create' %}" data-turbolinks="false">{% trans "New button" %}</a>
 | 
			
		||||
 
 | 
			
		||||
@@ -29,21 +29,19 @@ class TransactionCreateView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTabl
 | 
			
		||||
    e.g. for donation/transfer between people and clubs or for credit/debit with :models:`note.NoteSpecial`
 | 
			
		||||
    """
 | 
			
		||||
    template_name = "note/transaction_form.html"
 | 
			
		||||
 | 
			
		||||
    # SingleTableView creates `context["table"]` we will  load it with transaction history
 | 
			
		||||
    model = Transaction
 | 
			
		||||
    # Transaction history table
 | 
			
		||||
    table_class = HistoryTable
 | 
			
		||||
    extra_context = {"title": _("Transfer money")}
 | 
			
		||||
 | 
			
		||||
    def get_queryset(self, **kwargs):
 | 
			
		||||
        # retrieves only Transaction that user has the right to see.
 | 
			
		||||
        return Transaction.objects.filter(
 | 
			
		||||
            PermissionBackend.filter_queryset(self.request.user, Transaction, "view")
 | 
			
		||||
        ).order_by("-created_at").all()[:20]
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Add some context variables in template such as page title
 | 
			
		||||
        """
 | 
			
		||||
        context = super().get_context_data(**kwargs)
 | 
			
		||||
        context['amount_widget'] = AmountInput(attrs={"id": "amount"})
 | 
			
		||||
        context['polymorphic_ctype'] = ContentType.objects.get_for_model(Transaction).pk
 | 
			
		||||
@@ -146,7 +144,7 @@ class TransactionTemplateUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, Up
 | 
			
		||||
class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
 | 
			
		||||
    """
 | 
			
		||||
    The Magic View that make people pay their beer and burgers.
 | 
			
		||||
    (Most of the magic happens in the dark world of Javascript see consos.js)
 | 
			
		||||
    (Most of the magic happens in the dark world of Javascript see `note_kfet/static/js/consos.js`)
 | 
			
		||||
    """
 | 
			
		||||
    model = Transaction
 | 
			
		||||
    template_name = "note/conso_form.html"
 | 
			
		||||
@@ -168,29 +166,30 @@ class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
 | 
			
		||||
        return super().dispatch(request, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def get_queryset(self, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        restrict to the transaction history the user can see.
 | 
			
		||||
        """
 | 
			
		||||
        return Transaction.objects.filter(
 | 
			
		||||
            PermissionBackend.filter_queryset(self.request.user, Transaction, "view")
 | 
			
		||||
        ).order_by("-created_at").all()[:20]
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Add some context variables in template such as page title
 | 
			
		||||
        """
 | 
			
		||||
        context = super().get_context_data(**kwargs)
 | 
			
		||||
 | 
			
		||||
        categories = TemplateCategory.objects.order_by('name').all()
 | 
			
		||||
        # for each category, find which transaction templates the user can see.
 | 
			
		||||
        for category in categories:
 | 
			
		||||
            category.templates_filtered = category.templates.filter(
 | 
			
		||||
                PermissionBackend().filter_queryset(self.request.user, TransactionTemplate, "view")
 | 
			
		||||
            ).filter(display=True).order_by('name').all()
 | 
			
		||||
 | 
			
		||||
        context['categories'] = [cat for cat in categories if cat.templates_filtered]
 | 
			
		||||
        # some transactiontemplate are put forward to find them easily
 | 
			
		||||
        context['highlighted'] = TransactionTemplate.objects.filter(highlighted=True).filter(
 | 
			
		||||
            PermissionBackend().filter_queryset(self.request.user, TransactionTemplate, "view")
 | 
			
		||||
        ).order_by('name').all()
 | 
			
		||||
        context['polymorphic_ctype'] = ContentType.objects.get_for_model(RecurrentTransaction).pk
 | 
			
		||||
 | 
			
		||||
        # select2 compatibility
 | 
			
		||||
        context['no_cache'] = True
 | 
			
		||||
 | 
			
		||||
        return context
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,7 @@ from .tokens import email_validation_token
 | 
			
		||||
 | 
			
		||||
class UserCreateView(CreateView):
 | 
			
		||||
    """
 | 
			
		||||
    Une vue pour inscrire un utilisateur et lui créer un profil
 | 
			
		||||
    A view to create a User and add a Profile
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    form_class = SignUpForm
 | 
			
		||||
 
 | 
			
		||||
@@ -57,7 +57,6 @@ class InvoiceCreateView(ProtectQuerysetMixin, ProtectedCreateView):
 | 
			
		||||
        form_set = ProductFormSet(instance=form.instance)
 | 
			
		||||
        context['formset'] = form_set
 | 
			
		||||
        context['helper'] = ProductFormSetHelper()
 | 
			
		||||
        context['no_cache'] = True
 | 
			
		||||
 | 
			
		||||
        return context
 | 
			
		||||
 | 
			
		||||
@@ -125,7 +124,6 @@ class InvoiceUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
 | 
			
		||||
        form_set = ProductFormSet(instance=self.object)
 | 
			
		||||
        context['formset'] = form_set
 | 
			
		||||
        context['helper'] = ProductFormSetHelper()
 | 
			
		||||
        context['no_cache'] = True
 | 
			
		||||
 | 
			
		||||
        if self.object.locked:
 | 
			
		||||
            for field_name in form.fields:
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user