mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-06-21 01:48:21 +02:00
Merge branch 'beta-soon' into 'master'
Pre-beta fixes Closes #51 See merge request bde/nk20!86
This commit is contained in:
@ -4,9 +4,12 @@
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from note.templatetags.pretty_money import pretty_money
|
||||
from note_kfet.admin import admin_site
|
||||
|
||||
from .forms import ProfileForm
|
||||
from .models import Club, Membership, Profile, Role
|
||||
from .models import Club, Membership, Profile
|
||||
|
||||
|
||||
class ProfileInline(admin.StackedInline):
|
||||
@ -17,6 +20,7 @@ class ProfileInline(admin.StackedInline):
|
||||
can_delete = False
|
||||
|
||||
|
||||
@admin.register(User, site=admin_site)
|
||||
class CustomUserAdmin(UserAdmin):
|
||||
inlines = (ProfileInline,)
|
||||
list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff')
|
||||
@ -32,11 +36,33 @@ class CustomUserAdmin(UserAdmin):
|
||||
return super().get_inline_instances(request, obj)
|
||||
|
||||
|
||||
# Update Django User with profile
|
||||
admin.site.unregister(User)
|
||||
admin.site.register(User, CustomUserAdmin)
|
||||
@admin.register(Club, site=admin_site)
|
||||
class ClubAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'parent_club', 'email', 'require_memberships', 'pretty_fee_paid',
|
||||
'pretty_fee_unpaid', 'membership_start', 'membership_end',)
|
||||
ordering = ('name',)
|
||||
search_fields = ('name', 'email',)
|
||||
|
||||
# Add other models
|
||||
admin.site.register(Club)
|
||||
admin.site.register(Membership)
|
||||
admin.site.register(Role)
|
||||
def pretty_fee_paid(self, obj):
|
||||
return pretty_money(obj.membership_fee_paid)
|
||||
|
||||
def pretty_fee_unpaid(self, obj):
|
||||
return pretty_money(obj.membership_fee_unpaid)
|
||||
|
||||
pretty_fee_paid.short_description = _("membership fee (paid students)")
|
||||
pretty_fee_unpaid.short_description = _("membership fee (unpaid students)")
|
||||
|
||||
|
||||
@admin.register(Membership, site=admin_site)
|
||||
class MembershipAdmin(admin.ModelAdmin):
|
||||
list_display = ('user', 'club', 'date_start', 'date_end', 'view_roles', 'pretty_fee',)
|
||||
ordering = ('-date_start', 'club')
|
||||
|
||||
def view_roles(self, obj):
|
||||
return ", ".join(role.name for role in obj.roles.all())
|
||||
|
||||
def pretty_fee(self, obj):
|
||||
return pretty_money(obj.fee)
|
||||
|
||||
view_roles.short_description = _("roles")
|
||||
pretty_fee.short_description = _("fee")
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from ..models import Profile, Club, Role, Membership
|
||||
from ..models import Profile, Club, Membership
|
||||
|
||||
|
||||
class ProfileSerializer(serializers.ModelSerializer):
|
||||
@ -29,17 +29,6 @@ class ClubSerializer(serializers.ModelSerializer):
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class RoleSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
REST API Serializer for Roles.
|
||||
The djangorestframework plugin will analyse the model `Role` and parse all fields in the API.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = Role
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class MembershipSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
REST API Serializer for Memberships.
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from .views import ProfileViewSet, ClubViewSet, RoleViewSet, MembershipViewSet
|
||||
from .views import ProfileViewSet, ClubViewSet, MembershipViewSet
|
||||
|
||||
|
||||
def register_members_urls(router, path):
|
||||
@ -10,5 +10,4 @@ def register_members_urls(router, path):
|
||||
"""
|
||||
router.register(path + '/profile', ProfileViewSet)
|
||||
router.register(path + '/club', ClubViewSet)
|
||||
router.register(path + '/role', RoleViewSet)
|
||||
router.register(path + '/membership', MembershipViewSet)
|
||||
|
@ -4,8 +4,8 @@
|
||||
from rest_framework.filters import SearchFilter
|
||||
from api.viewsets import ReadProtectedModelViewSet
|
||||
|
||||
from .serializers import ProfileSerializer, ClubSerializer, RoleSerializer, MembershipSerializer
|
||||
from ..models import Profile, Club, Role, Membership
|
||||
from .serializers import ProfileSerializer, ClubSerializer, MembershipSerializer
|
||||
from ..models import Profile, Club, Membership
|
||||
|
||||
|
||||
class ProfileViewSet(ReadProtectedModelViewSet):
|
||||
@ -30,18 +30,6 @@ class ClubViewSet(ReadProtectedModelViewSet):
|
||||
search_fields = ['$name', ]
|
||||
|
||||
|
||||
class RoleViewSet(ReadProtectedModelViewSet):
|
||||
"""
|
||||
REST API View set.
|
||||
The djangorestframework plugin will get all `Role` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/members/role/
|
||||
"""
|
||||
queryset = Role.objects.all()
|
||||
serializer_class = RoleSerializer
|
||||
filter_backends = [SearchFilter]
|
||||
search_fields = ['$name', ]
|
||||
|
||||
|
||||
class MembershipViewSet(ReadProtectedModelViewSet):
|
||||
"""
|
||||
REST API View set.
|
||||
|
@ -5,11 +5,11 @@ from django import forms
|
||||
from django.contrib.auth.forms import AuthenticationForm
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from note.models import NoteSpecial
|
||||
from note.models import NoteSpecial, Alias
|
||||
from note_kfet.inputs import Autocomplete, AmountInput, DatePickerInput
|
||||
from permission.models import PermissionMask
|
||||
from permission.models import PermissionMask, Role
|
||||
|
||||
from .models import Profile, Club, Membership, Role
|
||||
from .models import Profile, Club, Membership
|
||||
|
||||
|
||||
class CustomAuthenticationForm(AuthenticationForm):
|
||||
@ -20,6 +20,18 @@ class CustomAuthenticationForm(AuthenticationForm):
|
||||
)
|
||||
|
||||
|
||||
class UserForm(forms.ModelForm):
|
||||
def _get_validation_exclusions(self):
|
||||
# Django usernames can only contain letters, numbers, @, ., +, - and _.
|
||||
# We want to allow users to have uncommon and unpractical usernames:
|
||||
# That is their problem, and we have normalized aliases for us.
|
||||
return super()._get_validation_exclusions() + ["username"]
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('first_name', 'last_name', 'username', 'email',)
|
||||
|
||||
|
||||
class ProfileForm(forms.ModelForm):
|
||||
"""
|
||||
A form for the extras field provided by the :model:`member.Profile` model.
|
||||
@ -38,6 +50,15 @@ class ProfileForm(forms.ModelForm):
|
||||
|
||||
|
||||
class ClubForm(forms.ModelForm):
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
|
||||
if not self.instance.pk: # Creating a club
|
||||
if Alias.objects.filter(normalized_name=Alias.normalize(self.cleaned_data["name"])).exists():
|
||||
self.add_error('name', _("An alias with a similar name already exists."))
|
||||
|
||||
return cleaned_data
|
||||
|
||||
class Meta:
|
||||
model = Club
|
||||
fields = '__all__'
|
||||
@ -56,8 +77,6 @@ class ClubForm(forms.ModelForm):
|
||||
|
||||
|
||||
class MembershipForm(forms.ModelForm):
|
||||
roles = forms.ModelMultipleChoiceField(queryset=Role.objects.filter(weirole=None).all())
|
||||
|
||||
soge = forms.BooleanField(
|
||||
label=_("Inscription paid by Société Générale"),
|
||||
required=False,
|
||||
@ -96,7 +115,7 @@ class MembershipForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Membership
|
||||
fields = ('user', 'roles', 'date_start')
|
||||
fields = ('user', 'date_start')
|
||||
# Le champ d'utilisateur est remplacé par un champ d'auto-complétion.
|
||||
# Quand des lettres sont tapées, une requête est envoyée sur l'API d'auto-complétion
|
||||
# et récupère les noms d'utilisateur valides
|
||||
@ -112,3 +131,28 @@ class MembershipForm(forms.ModelForm):
|
||||
),
|
||||
'date_start': DatePickerInput(),
|
||||
}
|
||||
|
||||
|
||||
class MembershipRolesForm(forms.ModelForm):
|
||||
user = forms.ModelChoiceField(
|
||||
queryset=User.objects,
|
||||
label=_("User"),
|
||||
disabled=True,
|
||||
widget=Autocomplete(
|
||||
User,
|
||||
attrs={
|
||||
'api_url': '/api/user/',
|
||||
'name_field': 'username',
|
||||
'placeholder': 'Nom ...',
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
roles = forms.ModelMultipleChoiceField(
|
||||
queryset=Role.objects.filter(weirole=None).all(),
|
||||
label=_("Roles"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Membership
|
||||
fields = ('user', 'roles')
|
||||
|
@ -3,8 +3,10 @@
|
||||
|
||||
import hashlib
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.hashers import PBKDF2PasswordHasher
|
||||
from django.utils.crypto import constant_time_compare
|
||||
from note_kfet.middlewares import get_current_authenticated_user, get_current_session
|
||||
|
||||
|
||||
class CustomNK15Hasher(PBKDF2PasswordHasher):
|
||||
@ -20,8 +22,37 @@ class CustomNK15Hasher(PBKDF2PasswordHasher):
|
||||
"""
|
||||
algorithm = "custom_nk15"
|
||||
|
||||
def must_update(self, encoded):
|
||||
if settings.DEBUG:
|
||||
current_user = get_current_authenticated_user()
|
||||
if current_user is not None and current_user.is_superuser:
|
||||
return False
|
||||
return True
|
||||
|
||||
def verify(self, password, encoded):
|
||||
if settings.DEBUG:
|
||||
current_user = get_current_authenticated_user()
|
||||
if current_user is not None and current_user.is_superuser\
|
||||
and get_current_session().get("permission_mask", -1) >= 42:
|
||||
return True
|
||||
|
||||
if '|' in encoded:
|
||||
salt, db_hashed_pass = encoded.split('$')[2].split('|')
|
||||
return constant_time_compare(hashlib.sha256((salt + password).encode("utf-8")).hexdigest(), db_hashed_pass)
|
||||
return super().verify(password, encoded)
|
||||
|
||||
|
||||
class DebugSuperuserBackdoor(PBKDF2PasswordHasher):
|
||||
"""
|
||||
In debug mode and during the beta, superusers can login into other accounts for tests.
|
||||
"""
|
||||
def must_update(self, encoded):
|
||||
return False
|
||||
|
||||
def verify(self, password, encoded):
|
||||
if settings.DEBUG:
|
||||
current_user = get_current_authenticated_user()
|
||||
if current_user is not None and current_user.is_superuser\
|
||||
and get_current_session().get("permission_mask", -1) >= 42:
|
||||
return True
|
||||
return super().verify(password, encoded)
|
||||
|
@ -131,7 +131,7 @@ class Profile(models.Model):
|
||||
return reverse('user_detail', args=(self.pk,))
|
||||
|
||||
def send_email_validation_link(self):
|
||||
subject = "Activate your Note Kfet account"
|
||||
subject = _("Activate your Note Kfet account")
|
||||
message = loader.render_to_string('registration/mails/email_validation_email.html',
|
||||
{
|
||||
'user': self.user,
|
||||
@ -247,24 +247,6 @@ class Club(models.Model):
|
||||
return reverse_lazy('member:club_detail', args=(self.pk,))
|
||||
|
||||
|
||||
class Role(models.Model):
|
||||
"""
|
||||
Role that an :model:`auth.User` can have in a :model:`member.Club`
|
||||
"""
|
||||
name = models.CharField(
|
||||
verbose_name=_('name'),
|
||||
max_length=255,
|
||||
unique=True,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('role')
|
||||
verbose_name_plural = _('roles')
|
||||
|
||||
def __str__(self):
|
||||
return str(self.name)
|
||||
|
||||
|
||||
class Membership(models.Model):
|
||||
"""
|
||||
Register the membership of a user to a club, including roles and membership duration.
|
||||
@ -284,7 +266,7 @@ class Membership(models.Model):
|
||||
)
|
||||
|
||||
roles = models.ManyToManyField(
|
||||
Role,
|
||||
"permission.Role",
|
||||
verbose_name=_("roles"),
|
||||
)
|
||||
|
||||
@ -302,6 +284,7 @@ class Membership(models.Model):
|
||||
verbose_name=_('fee'),
|
||||
)
|
||||
|
||||
@property
|
||||
def valid(self):
|
||||
"""
|
||||
A membership is valid if today is between the start and the end date.
|
||||
@ -319,6 +302,14 @@ class Membership(models.Model):
|
||||
if not Membership.objects.filter(user=self.user, club=self.club.parent_club).exists():
|
||||
raise ValidationError(_('User is not a member of the parent club') + ' ' + self.club.parent_club.name)
|
||||
|
||||
if self.pk:
|
||||
for role in self.roles.all():
|
||||
club = role.for_club
|
||||
if club is not None:
|
||||
if club.pk != self.club_id:
|
||||
raise ValidationError(_('The role {role} does not apply to the club {club}.')
|
||||
.format(role=role.name, club=club.name))
|
||||
|
||||
created = not self.pk
|
||||
if created:
|
||||
if Membership.objects.filter(
|
||||
|
@ -131,3 +131,31 @@ class MembershipTable(tables.Table):
|
||||
template_name = 'django_tables2/bootstrap4.html'
|
||||
fields = ('user', 'club', 'date_start', 'date_end', 'roles', 'fee', )
|
||||
model = Membership
|
||||
|
||||
|
||||
class ClubManagerTable(tables.Table):
|
||||
"""
|
||||
List managers of a club.
|
||||
"""
|
||||
|
||||
def render_user(self, value):
|
||||
# If the user has the right, link the displayed user with the page of its detail.
|
||||
s = value.username
|
||||
if PermissionBackend.check_perm(get_current_authenticated_user(), "auth.view_user", value):
|
||||
s = format_html("<a href={url}>{name}</a>",
|
||||
url=reverse_lazy('member:user_detail', kwargs={"pk": value.pk}), name=s)
|
||||
|
||||
return s
|
||||
|
||||
def render_roles(self, record):
|
||||
roles = record.roles.all()
|
||||
return ", ".join(str(role) for role in roles)
|
||||
|
||||
class Meta:
|
||||
attrs = {
|
||||
'class': 'table table-condensed table-striped table-hover',
|
||||
'style': 'table-layout: fixed;'
|
||||
}
|
||||
template_name = 'django_tables2/bootstrap4.html'
|
||||
fields = ('user', 'user.first_name', 'user.last_name', 'roles', )
|
||||
model = Membership
|
||||
|
@ -16,6 +16,7 @@ urlpatterns = [
|
||||
path('club/<int:pk>/update/', views.ClubUpdateView.as_view(), name="club_update"),
|
||||
path('club/<int:pk>/update_pic/', views.ClubPictureUpdateView.as_view(), name="club_update_pic"),
|
||||
path('club/<int:pk>/aliases/', views.ClubAliasView.as_view(), name="club_alias"),
|
||||
path('club/<int:pk>/members/', views.ClubMembersListView.as_view(), name="club_members"),
|
||||
|
||||
path('user/', views.UserListView.as_view(), name="user_list"),
|
||||
path('user/<int:pk>/', views.UserDetailView.as_view(), name="user_detail"),
|
||||
|
@ -6,12 +6,14 @@ from datetime import datetime, timedelta
|
||||
|
||||
from PIL import Image
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import logout
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.views import LoginView
|
||||
from django.db.models import Q
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic import CreateView, DetailView, UpdateView, TemplateView
|
||||
from django.views.generic.edit import FormMixin
|
||||
@ -21,12 +23,14 @@ from note.forms import ImageForm
|
||||
from note.models import Alias, NoteUser
|
||||
from note.models.transactions import Transaction, SpecialTransaction
|
||||
from note.tables import HistoryTable, AliasTable
|
||||
from note_kfet.middlewares import _set_current_user_and_ip
|
||||
from permission.backends import PermissionBackend
|
||||
from permission.models import Role
|
||||
from permission.views import ProtectQuerysetMixin
|
||||
|
||||
from .forms import ProfileForm, ClubForm, MembershipForm, CustomAuthenticationForm
|
||||
from .models import Club, Membership, Role
|
||||
from .tables import ClubTable, UserTable, MembershipTable
|
||||
from .forms import ProfileForm, ClubForm, MembershipForm, CustomAuthenticationForm, UserForm, MembershipRolesForm
|
||||
from .models import Club, Membership
|
||||
from .tables import ClubTable, UserTable, MembershipTable, ClubManagerTable
|
||||
|
||||
|
||||
class CustomLoginView(LoginView):
|
||||
@ -36,6 +40,8 @@ class CustomLoginView(LoginView):
|
||||
form_class = CustomAuthenticationForm
|
||||
|
||||
def form_valid(self, form):
|
||||
logout(self.request)
|
||||
_set_current_user_and_ip(form.get_user(), self.request.session, None)
|
||||
self.request.session['permission_mask'] = form.cleaned_data['permission_mask'].rank
|
||||
return super().form_valid(form)
|
||||
|
||||
@ -45,9 +51,11 @@ class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
||||
Update the user information.
|
||||
"""
|
||||
model = User
|
||||
fields = ['first_name', 'last_name', 'username', 'email']
|
||||
form_class = UserForm
|
||||
template_name = 'member/profile_update.html'
|
||||
context_object_name = 'user_object'
|
||||
extra_context = {"title": _("Update Profile")}
|
||||
|
||||
profile_form = ProfileForm
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
@ -62,7 +70,6 @@ class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
||||
form.fields['email'].help_text = _("This address must be valid.")
|
||||
|
||||
context['profile_form'] = self.profile_form(instance=context['user_object'].profile)
|
||||
context['title'] = _("Update Profile")
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
@ -101,6 +108,7 @@ class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
||||
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()
|
||||
|
||||
return super().form_valid(form)
|
||||
@ -117,6 +125,7 @@ class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
model = User
|
||||
context_object_name = "user_object"
|
||||
template_name = "member/profile_detail.html"
|
||||
extra_context = {"title": _("Profile detail")}
|
||||
|
||||
def get_queryset(self, **kwargs):
|
||||
"""
|
||||
@ -129,7 +138,7 @@ class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
user = context['user_object']
|
||||
history_list = \
|
||||
Transaction.objects.all().filter(Q(source=user.note) | Q(destination=user.note))\
|
||||
.order_by("-created_at", "-id")\
|
||||
.order_by("-created_at")\
|
||||
.filter(PermissionBackend.filter_queryset(self.request.user, Transaction, "view"))
|
||||
history_table = HistoryTable(history_list, prefix='transaction-')
|
||||
history_table.paginate(per_page=20, page=self.request.GET.get("transaction-page", 1))
|
||||
@ -150,12 +159,13 @@ class UserListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
||||
model = User
|
||||
table_class = UserTable
|
||||
template_name = 'member/user_list.html'
|
||||
extra_context = {"title": _("Search user")}
|
||||
|
||||
def get_queryset(self, **kwargs):
|
||||
"""
|
||||
Filter the user list with the given pattern.
|
||||
"""
|
||||
qs = super().get_queryset().filter(profile__registration_valid=True)
|
||||
qs = super().get_queryset().distinct().filter(profile__registration_valid=True)
|
||||
if "search" in self.request.GET:
|
||||
pattern = self.request.GET["search"]
|
||||
|
||||
@ -175,13 +185,6 @@ class UserListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
||||
|
||||
return qs[:20]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
context["title"] = _("Search user")
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
"""
|
||||
@ -190,6 +193,7 @@ class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
model = User
|
||||
template_name = 'member/profile_alias.html'
|
||||
context_object_name = 'user_object'
|
||||
extra_context = {"title": _("Note aliases")}
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
@ -203,6 +207,7 @@ class PictureUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, Det
|
||||
Update profile picture of the user note.
|
||||
"""
|
||||
form_class = ImageForm
|
||||
extra_context = {"title": _("Update note picture")}
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
@ -260,6 +265,7 @@ class ManageAuthTokens(LoginRequiredMixin, TemplateView):
|
||||
"""
|
||||
model = Token
|
||||
template_name = "member/manage_auth_tokens.html"
|
||||
extra_context = {"title": _("Manage auth token")}
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if 'regenerate' in request.GET and Token.objects.filter(user=request.user).exists():
|
||||
@ -287,6 +293,7 @@ class ClubCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
|
||||
model = Club
|
||||
form_class = ClubForm
|
||||
success_url = reverse_lazy('member:club_list')
|
||||
extra_context = {"title": _("Create new club")}
|
||||
|
||||
def form_valid(self, form):
|
||||
return super().form_valid(form)
|
||||
@ -298,12 +305,13 @@ class ClubListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
||||
"""
|
||||
model = Club
|
||||
table_class = ClubTable
|
||||
extra_context = {"title": _("Search club")}
|
||||
|
||||
def get_queryset(self, **kwargs):
|
||||
"""
|
||||
Filter the user list with the given pattern.
|
||||
"""
|
||||
qs = super().get_queryset().filter()
|
||||
qs = super().get_queryset().distinct()
|
||||
if "search" in self.request.GET:
|
||||
pattern = self.request.GET["search"]
|
||||
|
||||
@ -322,6 +330,7 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
"""
|
||||
model = Club
|
||||
context_object_name = "club"
|
||||
extra_context = {"title": _("Club detail")}
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
@ -330,9 +339,13 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
if PermissionBackend.check_perm(self.request.user, "member.change_club_membership_start", club):
|
||||
club.update_membership_dates()
|
||||
|
||||
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-")
|
||||
|
||||
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', '-id')
|
||||
.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
|
||||
@ -342,7 +355,7 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
).filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view"))
|
||||
|
||||
membership_table = MembershipTable(data=club_member, prefix="membership-")
|
||||
membership_table.paginate(per_page=20, page=self.request.GET.get('membership-page', 1))
|
||||
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.
|
||||
@ -366,6 +379,7 @@ class ClubAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
model = Club
|
||||
template_name = 'member/club_alias.html'
|
||||
context_object_name = 'club'
|
||||
extra_context = {"title": _("Note aliases")}
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
@ -382,6 +396,7 @@ class ClubUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
||||
context_object_name = "club"
|
||||
form_class = ClubForm
|
||||
template_name = "member/club_form.html"
|
||||
extra_context = {"title": _("Update club")}
|
||||
|
||||
def get_queryset(self, **kwargs):
|
||||
qs = super().get_queryset(**kwargs)
|
||||
@ -415,6 +430,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
|
||||
model = Membership
|
||||
form_class = MembershipForm
|
||||
template_name = 'member/add_members.html'
|
||||
extra_context = {"title": _("Add new member to the club")}
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
@ -425,7 +441,6 @@ class ClubAddMemberView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
|
||||
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
|
||||
form.fields['roles'].initial = Role.objects.filter(name="Membre de club").all()
|
||||
|
||||
# If the concerned club is the BDE, then we add the option that Société générale pays the membership.
|
||||
if club.name != "BDE":
|
||||
@ -444,7 +459,6 @@ class ClubAddMemberView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
|
||||
user = old_membership.user
|
||||
form.fields['user'].initial = user
|
||||
form.fields['user'].disabled = True
|
||||
form.fields['roles'].initial = old_membership.roles.all()
|
||||
form.fields['date_start'].initial = old_membership.date_end + timedelta(days=1)
|
||||
form.fields['credit_amount'].initial = club.membership_fee_paid if user.profile.paid \
|
||||
else club.membership_fee_unpaid
|
||||
@ -560,7 +574,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
|
||||
form.add_error('bank', _("This field is required."))
|
||||
return self.form_invalid(form)
|
||||
|
||||
SpecialTransaction.objects.create(
|
||||
transaction = SpecialTransaction(
|
||||
source=credit_type,
|
||||
destination=user.note,
|
||||
quantity=1,
|
||||
@ -571,9 +585,16 @@ class ClubAddMemberView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
|
||||
bank=bank,
|
||||
valid=True,
|
||||
)
|
||||
transaction._force_save = True
|
||||
transaction.save()
|
||||
|
||||
ret = super().form_valid(form)
|
||||
|
||||
member_role = Role.objects.filter(name="Membre de club").all()
|
||||
form.instance.roles.set(member_role)
|
||||
form.instance._force_save = True
|
||||
form.instance.save()
|
||||
|
||||
# If Société générale pays, then we assume that this is the BDE membership, and we auto-renew the
|
||||
# Kfet membership.
|
||||
if soge:
|
||||
@ -595,6 +616,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
|
||||
date_start=old_membership.get().date_end + timedelta(days=1)
|
||||
if old_membership.exists() else form.instance.date_start,
|
||||
)
|
||||
membership._force_save = True
|
||||
membership._soge = True
|
||||
membership.save()
|
||||
membership.refresh_from_db()
|
||||
@ -615,8 +637,9 @@ class ClubManageRolesView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
||||
Manage the roles of a user in a club
|
||||
"""
|
||||
model = Membership
|
||||
form_class = MembershipForm
|
||||
form_class = MembershipRolesForm
|
||||
template_name = 'member/add_members.html'
|
||||
extra_context = {"title": _("Manage roles of an user in the club")}
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
@ -626,15 +649,61 @@ class ClubManageRolesView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
form = super().get_form(form_class)
|
||||
# We don't create a full membership, we only update one field
|
||||
form.fields['user'].disabled = True
|
||||
del form.fields['date_start']
|
||||
del form.fields['credit_type']
|
||||
del form.fields['credit_amount']
|
||||
del form.fields['last_name']
|
||||
del form.fields['first_name']
|
||||
del form.fields['bank']
|
||||
|
||||
club = self.object.club
|
||||
form.fields['roles'].queryset = Role.objects.filter(Q(weirole__isnull=not hasattr(club, 'weiclub'))
|
||||
& (Q(for_club__isnull=True) | Q(for_club=club))).all()
|
||||
|
||||
return form
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('member:club_detail', kwargs={'pk': self.object.club.id})
|
||||
return reverse_lazy('member:user_detail', kwargs={'pk': self.object.user.id})
|
||||
|
||||
|
||||
class ClubMembersListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
||||
model = Membership
|
||||
table_class = MembershipTable
|
||||
template_name = "member/club_members.html"
|
||||
extra_context = {"title": _("Members of the club")}
|
||||
|
||||
def get_queryset(self, **kwargs):
|
||||
qs = super().get_queryset().filter(club_id=self.kwargs["pk"])
|
||||
|
||||
if 'search' in self.request.GET:
|
||||
pattern = self.request.GET['search']
|
||||
qs = qs.filter(
|
||||
Q(user__first_name__iregex='^' + pattern)
|
||||
| Q(user__last_name__iregex='^' + pattern)
|
||||
| Q(user__note__alias__normalized_name__iregex='^' + Alias.normalize(pattern))
|
||||
)
|
||||
|
||||
only_active = "only_active" not in self.request.GET or self.request.GET["only_active"] != '0'
|
||||
|
||||
if only_active:
|
||||
qs = qs.filter(date_start__lte=timezone.now().today(), date_end__gte=timezone.now().today())
|
||||
|
||||
if "roles" in self.request.GET:
|
||||
if not self.request.GET["roles"]:
|
||||
return qs.none()
|
||||
roles_str = self.request.GET["roles"].replace(' ', '').split(',')
|
||||
roles_int = map(int, roles_str)
|
||||
qs = qs.filter(roles__in=roles_int)
|
||||
|
||||
qs = qs.order_by('-date_start', 'user__username')
|
||||
|
||||
return qs.distinct()
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
club = Club.objects.filter(
|
||||
PermissionBackend.filter_queryset(self.request.user, Club, "view")
|
||||
).get(pk=self.kwargs["pk"])
|
||||
context["club"] = club
|
||||
|
||||
applicable_roles = Role.objects.filter(Q(weirole__isnull=not hasattr(club, 'weiclub'))
|
||||
& (Q(for_club__isnull=True) | Q(for_club=club))).all()
|
||||
context["applicable_roles"] = applicable_roles
|
||||
|
||||
context["only_active"] = "only_active" not in self.request.GET or self.request.GET["only_active"] != '0'
|
||||
|
||||
return context
|
||||
|
Reference in New Issue
Block a user