1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-06-23 02:48:22 +02:00

Compare commits

..

19 Commits

Author SHA1 Message Date
4234383621 Merge branch 'sortable_tables' into 'beta'
Sortable tables

See merge request bde/nk20!257
2024-08-07 21:30:04 +02:00
a3920fcae3 Merge branch 'Fix_time_zone_calendar.ics' into 'beta'
Update views.py - Fix calendar.ics

See merge request bde/nk20!237
2024-08-07 21:26:32 +02:00
ae4213d087 Merge branch 'colored_linters' into 'beta'
Colored linters

See merge request bde/nk20!255
2024-08-07 21:25:22 +02:00
35ffbfcf55 Colored linters 2024-08-01 17:29:24 +02:00
581715d804 Fix #95 (calendar) 2024-07-31 23:18:41 +02:00
c7c6f0350f Looks unused 2024-07-31 22:19:16 +02:00
9d1024024b Each table can be sorted (with a few exceptions) 2024-07-30 21:42:45 +02:00
b8f81048a5 Merge branch 'fix_ActivityList' into 'main'
Allow to order the 2 tables and to fix the bug of several activities

See merge request bde/nk20!252
2024-07-18 18:17:06 +02:00
af819f45a1 Merge branch 'remove_picture' into 'main'
Allow you to delete the profile picture

See merge request bde/nk20!250
2024-07-18 18:02:43 +02:00
076d065ffa Merge branch 'main' into 'remove_picture'
# Conflicts:
#   locale/fr/LC_MESSAGES/django.po
2024-07-18 17:52:22 +02:00
2da77d9c17 Merge branch 'fix_join_bda' into 'main'
Fix #126 (join_bda)

Closes #126

See merge request bde/nk20!251
2024-07-18 17:14:23 +02:00
01584d6330 Merge branch 'modif_perm' into 'main'
Modif perm

See merge request bde/nk20!249
2024-07-18 16:54:23 +02:00
4c0a5922c4 Allow to order the 2 tables and to fix the bug of several activities 2024-07-15 22:06:11 +02:00
f90b28fc7c Fix #126 (join_bda) 2024-07-15 14:30:46 +02:00
925e0f26f5 Allow you to delete the profile picture 2024-07-13 17:37:19 +02:00
c912383f86 oups la virgule oublié 2024-06-24 22:36:22 +02:00
32830e43fd Modify permission for negative 2024-06-24 21:21:22 +02:00
11c6a6fa7a modifications permissions consommation pc kfet (Alcool) 2024-06-24 16:57:39 +02:00
6c61daf1c5 Update views.py
Passage à la time zone Europe/Paris
2024-03-11 10:25:48 +01:00
36 changed files with 409 additions and 333 deletions

View File

@ -7,10 +7,25 @@ stages:
variables: variables:
GIT_SUBMODULE_STRATEGY: recursive GIT_SUBMODULE_STRATEGY: recursive
# Ubuntu 22.04 # Debian Buster
py310-django50: # py37-django22:
# stage: test
# image: debian:buster-backports
# before_script:
# - >
# apt-get update &&
# apt-get install --no-install-recommends -t buster-backports -y
# python3-django python3-django-crispy-forms
# python3-django-extensions python3-django-filters python3-django-polymorphic
# python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
# python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
# python3-bs4 python3-setuptools tox texlive-xetex
# script: tox -e py37-django22
# Ubuntu 20.04
py38-django22:
stage: test stage: test
image: ubuntu:22.04 image: ubuntu:20.04
before_script: before_script:
# Fix tzdata prompt # Fix tzdata prompt
- ln -sf /usr/share/zoneinfo/Europe/Paris /etc/localtime && echo Europe/Paris > /etc/timezone - ln -sf /usr/share/zoneinfo/Europe/Paris /etc/localtime && echo Europe/Paris > /etc/timezone
@ -22,12 +37,12 @@ py310-django50:
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
python3-bs4 python3-setuptools tox texlive-xetex python3-bs4 python3-setuptools tox texlive-xetex
script: tox -e py310-django50 script: tox -e py38-django22
# Debian Bookworm # Debian Bullseye
py311-django50: py39-django22:
stage: test stage: test
image: debian:bookworm image: debian:bullseye
before_script: before_script:
- > - >
apt-get update && apt-get update &&
@ -37,7 +52,7 @@ py311-django50:
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
python3-bs4 python3-setuptools tox texlive-xetex python3-bs4 python3-setuptools tox texlive-xetex
script: tox -e py311-django50 script: tox -e py39-django22
linters: linters:
stage: quality-assurance stage: quality-assurance

View File

@ -1,8 +1,6 @@
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from bootstrap_datepicker_plus.widgets import DateTimePickerInput
from datetime import timedelta from datetime import timedelta
from random import shuffle from random import shuffle
@ -12,7 +10,7 @@ from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from member.models import Club from member.models import Club
from note.models import Note, NoteUser from note.models import Note, NoteUser
from note_kfet.inputs import Autocomplete from note_kfet.inputs import Autocomplete, DateTimePickerInput
from note_kfet.middlewares import get_current_request from note_kfet.middlewares import get_current_request
from permission.backends import PermissionBackend from permission.backends import PermissionBackend

View File

@ -17,7 +17,8 @@ from django.utils.translation import gettext_lazy as _
from django.views import View from django.views import View
from django.views.decorators.cache import cache_page from django.views.decorators.cache import cache_page
from django.views.generic import DetailView, TemplateView, UpdateView from django.views.generic import DetailView, TemplateView, UpdateView
from django_tables2.views import SingleTableView from django.views.generic.list import ListView
from django_tables2.views import MultiTableMixin, SingleTableMixin
from note.models import Alias, NoteSpecial, NoteUser from note.models import Alias, NoteSpecial, NoteUser
from permission.backends import PermissionBackend from permission.backends import PermissionBackend
from permission.views import ProtectQuerysetMixin, ProtectedCreateView from permission.views import ProtectQuerysetMixin, ProtectedCreateView
@ -57,27 +58,36 @@ class ActivityCreateView(ProtectQuerysetMixin, ProtectedCreateView):
return reverse_lazy('activity:activity_detail', kwargs={"pk": self.object.pk}) return reverse_lazy('activity:activity_detail', kwargs={"pk": self.object.pk})
class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, ListView):
""" """
Displays all Activities, and classify if they are on-going or upcoming ones. Displays all Activities, and classify if they are on-going or upcoming ones.
""" """
model = Activity model = Activity
table_class = ActivityTable tables = [
ordering = ('-date_start',) lambda data: ActivityTable(data, prefix="all-"),
lambda data: ActivityTable(data, prefix="upcoming-"),
]
extra_context = {"title": _("Activities")} extra_context = {"title": _("Activities")}
def get_queryset(self, **kwargs): def get_queryset(self, **kwargs):
return super().get_queryset(**kwargs).distinct() return super().get_queryset(**kwargs).distinct()
def get_tables_data(self):
# first table = all activities, second table = upcoming
return [
self.get_queryset().order_by("-date_start"),
Activity.objects.filter(date_end__gt=timezone.now())
.filter(PermissionBackend.filter_queryset(self.request, Activity, "view"))
.distinct()
.order_by("date_start")
]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
upcoming_activities = Activity.objects.filter(date_end__gt=timezone.now()) tables = context["tables"]
context['upcoming'] = ActivityTable( for name, table in zip(["table", "upcoming"], tables):
data=upcoming_activities.filter(PermissionBackend.filter_queryset(self.request, Activity, "view")), context[name] = table
prefix='upcoming-',
order_by='date_start',
)
started_activities = self.get_queryset().filter(open=True, valid=True).distinct().all() started_activities = self.get_queryset().filter(open=True, valid=True).distinct().all()
context["started_activities"] = started_activities context["started_activities"] = started_activities
@ -85,7 +95,7 @@ class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView
return context return context
class ActivityDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): class ActivityDetailView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableMixin, DetailView):
""" """
Shows details about one activity. Add guest to context Shows details about one activity. Add guest to context
""" """
@ -93,13 +103,16 @@ class ActivityDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
context_object_name = "activity" context_object_name = "activity"
extra_context = {"title": _("Activity detail")} extra_context = {"title": _("Activity detail")}
table_class = GuestTable
context_table_name = "guests"
def get_table_data(self):
return Guest.objects.filter(activity=self.object) \
.filter(PermissionBackend.filter_queryset(self.request, Guest, "view"))
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data() context = super().get_context_data()
table = GuestTable(data=Guest.objects.filter(activity=self.object)
.filter(PermissionBackend.filter_queryset(self.request, Guest, "view")))
context["guests"] = table
context["activity_started"] = timezone.now() > timezone.localtime(self.object.date_start) context["activity_started"] = timezone.now() > timezone.localtime(self.object.date_start)
return context return context
@ -158,12 +171,14 @@ class ActivityInviteView(ProtectQuerysetMixin, ProtectedCreateView):
return reverse_lazy('activity:activity_detail', kwargs={"pk": self.kwargs["pk"]}) return reverse_lazy('activity:activity_detail', kwargs={"pk": self.kwargs["pk"]})
class ActivityEntryView(LoginRequiredMixin, TemplateView): class ActivityEntryView(LoginRequiredMixin, SingleTableMixin, TemplateView):
""" """
Manages entry to an activity Manages entry to an activity
""" """
template_name = "activity/activity_entry.html" template_name = "activity/activity_entry.html"
table_class = EntryTable
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
""" """
Don't display the entry interface if the user has no right to see it (no right to add an entry for itself), Don't display the entry interface if the user has no right to see it (no right to add an entry for itself),
@ -252,15 +267,9 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
if settings.DATABASES[note_qs.db]["ENGINE"] == 'django.db.backends.postgresql' else note_qs.distinct()[:20] if settings.DATABASES[note_qs.db]["ENGINE"] == 'django.db.backends.postgresql' else note_qs.distinct()[:20]
return note_qs return note_qs
def get_context_data(self, **kwargs): def get_table_data(self):
"""
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, Activity, "view"))\ activity = Activity.objects.filter(PermissionBackend.filter_queryset(self.request, Activity, "view"))\
.distinct().get(pk=self.kwargs["pk"]) .distinct().get(pk=self.kwargs["pk"])
context["activity"] = activity
matched = [] matched = []
@ -273,8 +282,17 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
note.activity = activity note.activity = activity
matched.append(note) matched.append(note)
table = EntryTable(data=matched) return matched
context["table"] = table
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, Activity, "view"))\
.distinct().get(pk=self.kwargs["pk"])
context["activity"] = activity
context["entries"] = Entry.objects.filter(activity=activity) context["entries"] = Entry.objects.filter(activity=activity)
@ -316,8 +334,8 @@ X-WR-CALNAME:Kfet Calendar
NAME:Kfet Calendar NAME:Kfet Calendar
CALSCALE:GREGORIAN CALSCALE:GREGORIAN
BEGIN:VTIMEZONE BEGIN:VTIMEZONE
TZID:Europe/Berlin TZID:Europe/Paris
X-LIC-LOCATION:Europe/Berlin X-LIC-LOCATION:Europe/Paris
BEGIN:DAYLIGHT BEGIN:DAYLIGHT
TZOFFSETFROM:+0100 TZOFFSETFROM:+0100
TZOFFSETTO:+0200 TZOFFSETTO:+0200
@ -339,10 +357,10 @@ END:VTIMEZONE
DTSTAMP:{"{:%Y%m%dT%H%M%S}".format(activity.date_start)}Z DTSTAMP:{"{:%Y%m%dT%H%M%S}".format(activity.date_start)}Z
UID:{md5((activity.name + "$" + str(activity.id) + str(activity.date_start)).encode("UTF-8")).hexdigest()} UID:{md5((activity.name + "$" + str(activity.id) + str(activity.date_start)).encode("UTF-8")).hexdigest()}
SUMMARY;CHARSET=UTF-8:{self.multilines(activity.name, 75, 22)} SUMMARY;CHARSET=UTF-8:{self.multilines(activity.name, 75, 22)}
DTSTART;TZID=Europe/Berlin:{"{:%Y%m%dT%H%M%S}".format(activity.date_start)} DTSTART:{"{:%Y%m%dT%H%M%S}Z".format(activity.date_start)}
DTEND;TZID=Europe/Berlin:{"{:%Y%m%dT%H%M%S}".format(activity.date_end)} DTEND:{"{:%Y%m%dT%H%M%S}Z".format(activity.date_end)}
LOCATION:{self.multilines(activity.location, 75, 9) if activity.location else "Kfet"} LOCATION:{self.multilines(activity.location, 75, 9) if activity.location else "Kfet"}
DESCRIPTION;CHARSET=UTF-8:""" + self.multilines(activity.description.replace("\n", "\\n"), 75, 26) + """ DESCRIPTION;CHARSET=UTF-8:""" + self.multilines(activity.description.replace("\n", "\\n"), 75, 26) + f"""
-- {activity.organizer.name} -- {activity.organizer.name}
END:VEVENT END:VEVENT
""" """

View File

@ -2,7 +2,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.conf import settings from django.conf import settings
from django.urls import include, re_path from django.conf.urls import url, include
from rest_framework import routers from rest_framework import routers
from .views import UserInformationView from .views import UserInformationView
@ -47,7 +47,7 @@ app_name = 'api'
# Wire up our API using automatic URL routing. # Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API. # Additionally, we include login URLs for the browsable API.
urlpatterns = [ urlpatterns = [
re_path('^', include(router.urls)), url('^', include(router.urls)),
re_path('^me/', UserInformationView.as_view()), url('^me/', UserInformationView.as_view()),
re_path('^api-auth/', include('rest_framework.urls', namespace='rest_framework')), url('^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
] ]

View File

@ -3,7 +3,6 @@
import io import io
from bootstrap_datepicker_plus.widgets import DatePickerInput
from PIL import Image, ImageSequence from PIL import Image, ImageSequence
from django import forms from django import forms
from django.conf import settings from django.conf import settings
@ -14,7 +13,7 @@ from django.forms import CheckboxSelectMultiple
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from note.models import NoteSpecial, Alias from note.models import NoteSpecial, Alias
from note_kfet.inputs import Autocomplete, AmountInput from note_kfet.inputs import Autocomplete, AmountInput, DatePickerInput
from permission.models import PermissionMask, Role from permission.models import PermissionMask, Role
from .models import Profile, Club, Membership from .models import Profile, Club, Membership
@ -33,7 +32,7 @@ class UserForm(forms.ModelForm):
# Django usernames can only contain letters, numbers, @, ., +, - and _. # Django usernames can only contain letters, numbers, @, ., +, - and _.
# We want to allow users to have uncommon and unpractical usernames: # We want to allow users to have uncommon and unpractical usernames:
# That is their problem, and we have normalized aliases for us. # That is their problem, and we have normalized aliases for us.
return super()._get_validation_exclusions() | {"username"} return super()._get_validation_exclusions() + ["username"]
class Meta: class Meta:
model = User model = User
@ -139,6 +138,9 @@ class ImageForm(forms.Form):
return cleaned_data return cleaned_data
def is_valid(self):
return super().is_valid() or super().clean().get('image') is None
class ClubForm(forms.ModelForm): class ClubForm(forms.ModelForm):
def clean(self): def clean(self):
@ -152,7 +154,7 @@ class ClubForm(forms.ModelForm):
class Meta: class Meta:
model = Club model = Club
fields = '__all__' exclude = ("add_registration_form",)
widgets = { widgets = {
"membership_fee_paid": AmountInput(), "membership_fee_paid": AmountInput(),
"membership_fee_unpaid": AmountInput(), "membership_fee_unpaid": AmountInput(),

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.28 on 2024-07-15 09:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('member', '0011_profile_vss_charter_read'),
]
operations = [
migrations.AddField(
model_name='club',
name='add_registration_form',
field=models.BooleanField(default=False, verbose_name='add to registration form'),
),
]

View File

@ -259,6 +259,11 @@ class Club(models.Model):
help_text=_('Maximal date of a membership, after which members must renew it.'), help_text=_('Maximal date of a membership, after which members must renew it.'),
) )
add_registration_form = models.BooleanField(
verbose_name=_("add to registration form"),
default=False,
)
class Meta: class Meta:
verbose_name = _("club") verbose_name = _("club")
verbose_name_plural = _("clubs") verbose_name_plural = _("clubs")

View File

@ -14,6 +14,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
<form method="post" enctype="multipart/form-data" id="formUpload"> <form method="post" enctype="multipart/form-data" id="formUpload">
{% csrf_token %} {% csrf_token %}
{{ form |crispy }} {{ form |crispy }}
{% if user.note.display_image != "pic/default.png" %}
<input type="submit" class="btn btn-primary" value="{% trans "Remove" %}">
{% endif %}
</form> </form>
</div> </div>
<!-- MODAL TO CROP THE IMAGE --> <!-- MODAL TO CROP THE IMAGE -->

View File

@ -44,7 +44,7 @@ class TemplateLoggedInTests(TestCase):
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL, 302, 302) self.assertRedirects(response, settings.LOGIN_REDIRECT_URL, 302, 302)
def test_logout(self): def test_logout(self):
response = self.client.post(reverse("logout")) response = self.client.get(reverse("logout"))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_admin_index(self): def test_admin_index(self):

View File

@ -16,7 +16,7 @@ from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, UpdateView, TemplateView from django.views.generic import DetailView, UpdateView, TemplateView
from django.views.generic.edit import FormMixin from django.views.generic.edit import FormMixin
from django_tables2.views import SingleTableView from django_tables2.views import MultiTableMixin, SingleTableMixin, SingleTableView
from rest_framework.authtoken.models import Token from rest_framework.authtoken.models import Token
from note.models import Alias, NoteClub, NoteUser, Trust from note.models import Alias, NoteClub, NoteUser, Trust
from note.models.transactions import Transaction, SpecialTransaction from note.models.transactions import Transaction, SpecialTransaction
@ -165,7 +165,8 @@ class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
# Display only the most recent membership # Display only the most recent membership
club_list = club_list.distinct("club__name")\ club_list = club_list.distinct("club__name")\
if settings.DATABASES["default"]["ENGINE"] == 'django.db.backends.postgresql' else club_list if settings.DATABASES["default"]["ENGINE"] == 'django.db.backends.postgresql' else club_list
membership_table = MembershipTable(data=club_list, prefix='membership-') club_list_order_by = self.request.GET.getlist("membership-sort", ("club__name", "-date_start"))
membership_table = MembershipTable(data=club_list, prefix='membership-', order_by=club_list_order_by)
membership_table.paginate(per_page=10, page=self.request.GET.get("membership-page", 1)) membership_table.paginate(per_page=10, page=self.request.GET.get("membership-page", 1))
context['club_list'] = membership_table context['club_list'] = membership_table
@ -243,7 +244,7 @@ class UserListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
return context return context
class ProfileTrustView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): class ProfileTrustView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, DetailView):
""" """
View and manage user trust relationships View and manage user trust relationships
""" """
@ -252,13 +253,25 @@ class ProfileTrustView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
context_object_name = 'user_object' context_object_name = 'user_object'
extra_context = {"title": _("Note friendships")} extra_context = {"title": _("Note friendships")}
tables = [
lambda data: TrustTable(data, prefix="trust-"),
lambda data: TrustedTable(data, prefix="trusted-"),
]
def get_tables_data(self):
note = self.object.note
return [
note.trusting.filter(PermissionBackend.filter_queryset(self.request, Trust, "view")).distinct(),
note.trusted.filter(PermissionBackend.filter_queryset(self.request, Trust, "view")).distinct(),
]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
note = context['object'].note
context["trusting"] = TrustTable( tables = context["tables"]
note.trusting.filter(PermissionBackend.filter_queryset(self.request, Trust, "view")).distinct().all()) for name, table in zip(["trusting", "trusted_by"], tables):
context["trusted_by"] = TrustedTable( context[name] = table
note.trusted.filter(PermissionBackend.filter_queryset(self.request, Trust, "view")).distinct().all())
context["can_create"] = PermissionBackend.check_perm(self.request, "note.add_trust", Trust( context["can_create"] = PermissionBackend.check_perm(self.request, "note.add_trust", Trust(
trusting=context["object"].note, trusting=context["object"].note,
trusted=context["object"].note trusted=context["object"].note
@ -277,7 +290,7 @@ class ProfileTrustView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
return context return context
class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableMixin, DetailView):
""" """
View and manage user aliases. View and manage user aliases.
""" """
@ -286,12 +299,15 @@ class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
context_object_name = 'user_object' context_object_name = 'user_object'
extra_context = {"title": _("Note aliases")} extra_context = {"title": _("Note aliases")}
table_class = AliasTable
context_table_name = "aliases"
def get_table_data(self):
return self.object.note.alias.filter(PermissionBackend.filter_queryset(self.request, Alias, "view")).distinct() \
.order_by('normalized_name')
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
note = context['object'].note
context["aliases"] = AliasTable(
note.alias.filter(PermissionBackend.filter_queryset(self.request, Alias, "view")).distinct()
.order_by('normalized_name').all())
context["can_create"] = PermissionBackend.check_perm(self.request, "note.add_alias", Alias( context["can_create"] = PermissionBackend.check_perm(self.request, "note.add_alias", Alias(
note=context["object"].note, note=context["object"].note,
name="", name="",
@ -326,12 +342,15 @@ class PictureUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, Det
"""Save image to note""" """Save image to note"""
image = form.cleaned_data['image'] image = form.cleaned_data['image']
# Rename as a PNG or GIF if image is None:
extension = image.name.split(".")[-1] image = "pic/default.png"
if extension == "gif":
image.name = "{}_pic.gif".format(self.object.note.pk)
else: else:
image.name = "{}_pic.png".format(self.object.note.pk) # Rename as a PNG or GIF
extension = image.name.split(".")[-1]
if extension == "gif":
image.name = "{}_pic.gif".format(self.object.note.pk)
else:
image.name = "{}_pic.png".format(self.object.note.pk)
# Save # Save
self.object.note.display_image = image self.object.note.display_image = image
@ -448,7 +467,8 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
managers = Membership.objects.filter(club=self.object, roles__name="Bureau de club", managers = Membership.objects.filter(club=self.object, roles__name="Bureau de club",
date_start__lte=date.today(), date_end__gte=date.today())\ date_start__lte=date.today(), date_end__gte=date.today())\
.order_by('user__last_name').all() .order_by('user__last_name').all()
context["managers"] = ClubManagerTable(data=managers, prefix="managers-") managers_order_by = self.request.GET.getlist("managers-sort", ('user__last_name'))
context["managers"] = ClubManagerTable(data=managers, prefix="managers-", order_by=managers_order_by)
# transaction history # transaction history
club_transactions = Transaction.objects.all().filter(Q(source=club.note) | Q(destination=club.note))\ club_transactions = Transaction.objects.all().filter(Q(source=club.note) | Q(destination=club.note))\
.filter(PermissionBackend.filter_queryset(self.request, Transaction, "view"))\ .filter(PermissionBackend.filter_queryset(self.request, Transaction, "view"))\
@ -466,7 +486,8 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
club_member = club_member.distinct("user__username")\ club_member = club_member.distinct("user__username")\
if settings.DATABASES["default"]["ENGINE"] == 'django.db.backends.postgresql' else club_member if settings.DATABASES["default"]["ENGINE"] == 'django.db.backends.postgresql' else club_member
membership_table = MembershipTable(data=club_member, prefix="membership-") membership_order_by = self.request.GET.getlist("membership-sort", ("user__username", "-date_start"))
membership_table = MembershipTable(data=club_member, prefix="membership-", order_by=membership_order_by)
membership_table.paginate(per_page=5, 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 context['member_list'] = membership_table
@ -507,7 +528,7 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
return context return context
class ClubAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): class ClubAliasView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableMixin, DetailView):
""" """
Manage aliases of a club. Manage aliases of a club.
""" """
@ -516,11 +537,16 @@ class ClubAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
context_object_name = 'club' context_object_name = 'club'
extra_context = {"title": _("Note aliases")} extra_context = {"title": _("Note aliases")}
table_class = AliasTable
context_table_name = "aliases"
def get_table_data(self):
return self.object.note.alias.filter(
PermissionBackend.filter_queryset(self.request, Alias, "view")).distinct()
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
note = context['object'].note
context["aliases"] = AliasTable(note.alias.filter(
PermissionBackend.filter_queryset(self.request, Alias, "view")).distinct().all())
context["can_create"] = PermissionBackend.check_perm(self.request, "note.add_alias", Alias( context["can_create"] = PermissionBackend.check_perm(self.request, "note.add_alias", Alias(
note=context["object"].note, note=context["object"].note,
name="", name="",

View File

@ -13,7 +13,7 @@ def register_note_urls(router, path):
router.register(path + '/note', NotePolymorphicViewSet) router.register(path + '/note', NotePolymorphicViewSet)
router.register(path + '/alias', AliasViewSet) router.register(path + '/alias', AliasViewSet)
router.register(path + '/trust', TrustViewSet) router.register(path + '/trust', TrustViewSet)
router.register(path + '/consumer', ConsumerViewSet, basename="consumer") router.register(path + '/consumer', ConsumerViewSet)
router.register(path + '/transaction/category', TemplateCategoryViewSet) router.register(path + '/transaction/category', TemplateCategoryViewSet)
router.register(path + '/transaction/transaction', TransactionViewSet) router.register(path + '/transaction/transaction', TransactionViewSet)

View File

@ -179,10 +179,19 @@ class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
# We match first an alias if it is matched without normalization, # We match first an alias if it is matched without normalization,
# then if the normalized pattern matches a normalized alias. # then if the normalized pattern matches a normalized alias.
queryset = queryset.filter( queryset = queryset.filter(
Q(**{f'name{suffix}': alias_prefix + alias}) **{f'name{suffix}': alias_prefix + alias}
| Q(**{f'normalized_name{suffix}': alias_prefix + Alias.normalize(alias)}) ).union(
| Q(**{f'normalized_name{suffix}': alias_prefix + alias.lower()}) queryset.filter(
) Q(**{f'normalized_name{suffix}': alias_prefix + Alias.normalize(alias)})
& ~Q(**{f'name{suffix}': alias_prefix + alias})
),
all=True).union(
queryset.filter(
Q(**{f'normalized_name{suffix}': alias_prefix + alias.lower()})
& ~Q(**{f'normalized_name{suffix}': alias_prefix + Alias.normalize(alias)})
& ~Q(**{f'name{suffix}': alias_prefix + alias})
),
all=True)
queryset = queryset if settings.DATABASES[queryset.db]["ENGINE"] == 'django.db.backends.postgresql' \ queryset = queryset if settings.DATABASES[queryset.db]["ENGINE"] == 'django.db.backends.postgresql' \
else queryset.order_by("name") else queryset.order_by("name")

View File

@ -1,15 +1,13 @@
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from datetime import datetime from datetime import datetime
from bootstrap_datepicker_plus.widgets import DateTimePickerInput
from django import forms from django import forms
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.forms import CheckboxSelectMultiple from django.forms import CheckboxSelectMultiple
from django.utils.timezone import make_aware from django.utils.timezone import make_aware
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from note_kfet.inputs import Autocomplete, AmountInput from note_kfet.inputs import Autocomplete, AmountInput, DateTimePickerInput
from .models import TransactionTemplate, NoteClub, Alias from .models import TransactionTemplate, NoteClub, Alias

View File

@ -18,7 +18,6 @@ def create_special_notes(apps, schema_editor):
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('note', '0001_initial'), ('note', '0001_initial'),
('logs', '0001_initial'),
] ]
operations = [ operations = [

View File

@ -1,25 +0,0 @@
# Generated by Django 5.0.7 on 2024-07-11 09:24
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('note', '0006_trust'),
]
operations = [
migrations.AlterField(
model_name='note',
name='polymorphic_ctype',
field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype'),
),
migrations.AlterField(
model_name='transaction',
name='polymorphic_ctype',
field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype'),
),
]

View File

@ -9,7 +9,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
name="{{ widget.name }}" name="{{ widget.name }}"
{# Other attributes are loaded #} {# Other attributes are loaded #}
{% for name, value in widget.attrs.items %} {% for name, value in widget.attrs.items %}
{% if value != False %}{{ name }}{% if value != True %}="{{ value|stringformat:'s' }}"{% endif %}{% endif %} {% ifnotequal value False %}{{ name }}{% ifnotequal value True %}="{{ value|stringformat:'s' }}"{% endifnotequal %}{% endifnotequal %}
{% endfor %}> {% endfor %}>
<div class="input-group-append"> <div class="input-group-append">
<span class="input-group-text"></span> <span class="input-group-text"></span>

View File

@ -23,7 +23,7 @@
<p> <p>
Par ailleurs, le BDE ne sert pas d'alcool aux adhérents dont le solde Par ailleurs, le BDE ne sert pas d'alcool aux adhérents dont le solde
est inférieur à 0 € depuis plus de 24h. est inférieur à 0 €.
</p> </p>
<p> <p>

View File

@ -2591,12 +2591,12 @@
"note", "note",
"transaction" "transaction"
], ],
"query": "[\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}}, {\"valid\": false}]", "query": "[\"OR\", {\"source__balance__gte\": 0}, [\"AND\", [\"NOT\", {\"recurrenttransaction__template__category__name\": \"Alcool\"}], {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}}], {\"valid\": false}]",
"type": "add", "type": "add",
"mask": 2, "mask": 2,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Créer une transaction quelconque tant que la source reste au-dessus de -20 €" "description": "Créer une transaction quelconque tant que la source reste positive s'il s'agit d'alcool, sinon au-dessus de -20€"
} }
}, },
{ {

View File

@ -12,6 +12,7 @@ from django.forms import HiddenInput
from django.http import Http404 from django.http import Http404
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic import UpdateView, TemplateView, CreateView from django.views.generic import UpdateView, TemplateView, CreateView
from django_tables2 import MultiTableMixin
from member.models import Membership from member.models import Membership
from .backends import PermissionBackend from .backends import PermissionBackend
@ -107,10 +108,31 @@ class ProtectedCreateView(LoginRequiredMixin, CreateView):
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
class RightsView(TemplateView): class RightsView(MultiTableMixin, TemplateView):
template_name = "permission/all_rights.html" template_name = "permission/all_rights.html"
extra_context = {"title": _("Rights")} extra_context = {"title": _("Rights")}
tables = [
lambda data: RightsTable(data, prefix="clubs-"),
lambda data: SuperuserTable(data, prefix="superusers-"),
]
def get_tables_data(self):
special_memberships = Membership.objects.filter(
date_start__lte=date.today(),
date_end__gte=date.today(),
).filter(roles__in=Role.objects.filter((~(Q(name="Adhérent BDE")
| Q(name="Adhérent Kfet")
| Q(name="Membre de club")
| Q(name="Bureau de club"))
& Q(weirole__isnull=True))))\
.order_by("club__name", "user__last_name")\
.distinct().all()
return [
special_memberships,
User.objects.filter(is_superuser=True).order_by("last_name"),
]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
@ -128,19 +150,9 @@ class RightsView(TemplateView):
role.clubs = [membership.club for membership in active_memberships if role in membership.roles.all()] role.clubs = [membership.club for membership in active_memberships if role in membership.roles.all()]
if self.request.user.is_authenticated: if self.request.user.is_authenticated:
special_memberships = Membership.objects.filter( tables = context["tables"]
date_start__lte=date.today(), for name, table in zip(["special_memberships_table", "superusers"], tables):
date_end__gte=date.today(), context[name] = table
).filter(roles__in=Role.objects.filter((~(Q(name="Adhérent BDE")
| Q(name="Adhérent Kfet")
| Q(name="Membre de club")
| Q(name="Bureau de club"))
& Q(weirole__isnull=True))))\
.order_by("club__name", "user__last_name")\
.distinct().all()
context["special_memberships_table"] = RightsTable(special_memberships, prefix="clubs-")
context["superusers"] = SuperuserTable(User.objects.filter(is_superuser=True).order_by("last_name").all(),
prefix="superusers-")
return context return context

View File

@ -5,7 +5,6 @@ from django import forms
from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
# from member.models import Club
from note.models import NoteSpecial, Alias from note.models import NoteSpecial, Alias
from note_kfet.inputs import AmountInput from note_kfet.inputs import AmountInput
@ -115,12 +114,3 @@ class ValidationForm(forms.Form):
required=False, required=False,
initial=True, initial=True,
) )
# If the bda exists
# if Club.objects.filter(name__iexact="bda").exists():
# The user can join the bda club at the inscription
# join_bda = forms.BooleanField(
# label=_("Join BDA Club"),
# required=False,
# initial=True,
# )

View File

@ -1,6 +1,7 @@
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django import forms
from django.conf import settings from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User from django.contrib.auth.models import User
@ -238,9 +239,8 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
fee += bde.membership_fee_paid if user.profile.paid else bde.membership_fee_unpaid fee += bde.membership_fee_paid if user.profile.paid else bde.membership_fee_unpaid
kfet = Club.objects.get(name="Kfet") kfet = Club.objects.get(name="Kfet")
fee += kfet.membership_fee_paid if user.profile.paid else kfet.membership_fee_unpaid fee += kfet.membership_fee_paid if user.profile.paid else kfet.membership_fee_unpaid
if Club.objects.filter(name__iexact="BDA").exists(): for club in Club.objects.filter(add_registration_form=True):
bda = Club.objects.get(name__iexact="BDA") fee += club.membership_fee_paid if user.profile.paid else club.membership_fee_unpaid
fee += bda.membership_fee_paid if user.profile.paid else bda.membership_fee_unpaid
ctx["total_fee"] = "{:.02f}".format(fee / 100, ) ctx["total_fee"] = "{:.02f}".format(fee / 100, )
# ctx["declare_soge_account"] = SogeCredit.objects.filter(user=user).exists() # ctx["declare_soge_account"] = SogeCredit.objects.filter(user=user).exists()
@ -249,6 +249,16 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
def get_form(self, form_class=None): def get_form(self, form_class=None):
form = super().get_form(form_class) form = super().get_form(form_class)
# add clubs that are in registration form
for club in Club.objects.filter(add_registration_form=True).order_by("name"):
form_join_club = forms.BooleanField(
label=_("Join %(club)s Club") % {'club': club.name},
required=False,
initial=False,
)
form.fields.update({f"join_{club.id}": form_join_club})
user = self.get_object() user = self.get_object()
form.fields["last_name"].initial = user.last_name form.fields["last_name"].initial = user.last_name
form.fields["first_name"].initial = user.first_name form.fields["first_name"].initial = user.first_name
@ -266,11 +276,6 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
form.add_error(None, _("An alias with a similar name already exists.")) form.add_error(None, _("An alias with a similar name already exists."))
return self.form_invalid(form) return self.form_invalid(form)
# Check if BDA exist to propose membership at regisration
bda_exists = False
if Club.objects.filter(name__iexact="BDA").exists():
bda_exists = True
# Get form data # Get form data
# soge = form.cleaned_data["soge"] # soge = form.cleaned_data["soge"]
credit_type = form.cleaned_data["credit_type"] credit_type = form.cleaned_data["credit_type"]
@ -280,8 +285,9 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
bank = form.cleaned_data["bank"] bank = form.cleaned_data["bank"]
join_bde = form.cleaned_data["join_bde"] join_bde = form.cleaned_data["join_bde"]
join_kfet = form.cleaned_data["join_kfet"] join_kfet = form.cleaned_data["join_kfet"]
if bda_exists:
join_bda = form.cleaned_data["join_bda"] clubs_registration = Club.objects.filter(add_registration_form=True).order_by("name")
join_clubs = [(club, form.cleaned_data[f"join_{club.id}"]) for club in clubs_registration]
# if soge: # if soge:
# # If Société Générale pays the inscription, the user automatically joins the two clubs. # # If Société Générale pays the inscription, the user automatically joins the two clubs.
@ -303,11 +309,12 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
kfet_fee = kfet.membership_fee_paid if user.profile.paid else kfet.membership_fee_unpaid kfet_fee = kfet.membership_fee_paid if user.profile.paid else kfet.membership_fee_unpaid
# Add extra fee for the full membership # Add extra fee for the full membership
fee += kfet_fee if join_kfet else 0 fee += kfet_fee if join_kfet else 0
if bda_exists: clubs_fee = dict()
bda = Club.objects.get(name__iexact="BDA") for club, join_club in join_clubs:
bda_fee = bda.membership_fee_paid if user.profile.paid else bda.membership_fee_unpaid club_fee = club.membership_fee_paid if user.profile.paid else club.membership_fee_unpaid
# Add extra fee for the bda membership # Add extra fee for the club membership
fee += bda_fee if join_bda else 0 clubs_fee[club] = club_fee
fee += club_fee if join_club else 0
# # If the bank pays, then we don't credit now. Treasurers will validate the transaction # # If the bank pays, then we don't credit now. Treasurers will validate the transaction
# # and credit the note later. # # and credit the note later.
@ -387,17 +394,18 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
membership.roles.add(Role.objects.get(name="Adhérent Kfet")) membership.roles.add(Role.objects.get(name="Adhérent Kfet"))
membership.save() membership.save()
if bda_exists and join_bda: for club, join_club in join_clubs:
# Create membership for the user to the BDA starting today if join_club:
membership = Membership( # Create membership for the user to the BDA starting today
club=bda, membership = Membership(
user=user, club=club,
fee=bda_fee, user=user,
) fee=clubs_fee[club],
membership.save() )
membership.refresh_from_db() membership.save()
membership.roles.add(Role.objects.get(name="Membre de club")) membership.refresh_from_db()
membership.save() membership.roles.add(Role.objects.get(name="Membre de club"))
membership.save()
# if soge: # if soge:
# soge_credit = SogeCredit.objects.get(user=user) # soge_credit = SogeCredit.objects.get(user=user)

View File

@ -1,19 +0,0 @@
# Generated by Django 5.0.7 on 2024-07-11 09:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('note', '0007_alter_note_polymorphic_ctype_and_more'),
('treasury', '0008_auto_20240322_0045'),
]
operations = [
migrations.AlterField(
model_name='sogecredit',
name='transactions',
field=models.ManyToManyField(blank=True, related_name='+', to='note.membershiptransaction', verbose_name='membership transactions'),
),
]

View File

@ -19,7 +19,7 @@ from django.utils.translation import gettext_lazy as _
from django.views.generic import UpdateView, DetailView from django.views.generic import UpdateView, DetailView
from django.views.generic.base import View, TemplateView from django.views.generic.base import View, TemplateView
from django.views.generic.edit import BaseFormView, DeleteView from django.views.generic.edit import BaseFormView, DeleteView
from django_tables2 import SingleTableView from django_tables2 import MultiTableMixin, SingleTableMixin, SingleTableView
from note.models import SpecialTransaction, NoteSpecial, Alias from note.models import SpecialTransaction, NoteSpecial, Alias
from note_kfet.settings.base import BASE_DIR from note_kfet.settings.base import BASE_DIR
from permission.backends import PermissionBackend from permission.backends import PermissionBackend
@ -251,21 +251,26 @@ class RemittanceCreateView(ProtectQuerysetMixin, ProtectedCreateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["table"] = RemittanceTable(
data=Remittance.objects.filter(
PermissionBackend.filter_queryset(self.request, Remittance, "view")).all())
context["special_transactions"] = SpecialTransactionTable(data=SpecialTransaction.objects.none()) context["special_transactions"] = SpecialTransactionTable(data=SpecialTransaction.objects.none())
return context return context
class RemittanceListView(LoginRequiredMixin, TemplateView): class RemittanceListView(LoginRequiredMixin, MultiTableMixin, TemplateView):
""" """
List existing Remittances List existing Remittances
""" """
template_name = "treasury/remittance_list.html" template_name = "treasury/remittance_list.html"
extra_context = {"title": _("Remittances list")} extra_context = {"title": _("Remittances list")}
tables = [
lambda data: RemittanceTable(data, prefix="opened-remittances-"),
lambda data: RemittanceTable(data, prefix="closed-remittances-"),
lambda data: SpecialTransactionTable(data, prefix="no-remittance-", exclude=('remittance_remove', )),
lambda data: SpecialTransactionTable(data, prefix="with-remittance-", exclude=('remittance_add', )),
]
paginate_by = 10 # number of rows in tables
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
# Check that the user is authenticated # Check that the user is authenticated
if not request.user.is_authenticated: if not request.user.is_authenticated:
@ -275,49 +280,37 @@ class RemittanceListView(LoginRequiredMixin, TemplateView):
raise PermissionDenied(_("You are not able to see the treasury interface.")) raise PermissionDenied(_("You are not able to see the treasury interface."))
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
def get_tables_data(self):
return [
Remittance.objects.filter(closed=False).filter(
PermissionBackend.filter_queryset(self.request, Remittance, "view")),
Remittance.objects.filter(closed=True).filter(
PermissionBackend.filter_queryset(self.request, Remittance, "view")),
SpecialTransaction.objects.filter(source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)),
specialtransactionproxy__remittance=None).filter(
PermissionBackend.filter_queryset(self.request, Remittance, "view")),
SpecialTransaction.objects.filter(source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)),
specialtransactionproxy__remittance__closed=False).filter(
PermissionBackend.filter_queryset(self.request, Remittance, "view")),
]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
opened_remittances = RemittanceTable( tables = context["tables"]
data=Remittance.objects.filter(closed=False).filter( names = [
PermissionBackend.filter_queryset(self.request, Remittance, "view")).all(), "opened_remittances",
prefix="opened-remittances-", "closed_remittances",
) "special_transactions_no_remittance",
opened_remittances.paginate(page=self.request.GET.get("opened-remittances-page", 1), per_page=10) "special_transactions_with_remittance",
context["opened_remittances"] = opened_remittances ]
for name, table in zip(names, tables):
closed_remittances = RemittanceTable( context[name] = table
data=Remittance.objects.filter(closed=True).filter(
PermissionBackend.filter_queryset(self.request, Remittance, "view")).all(),
prefix="closed-remittances-",
)
closed_remittances.paginate(page=self.request.GET.get("closed-remittances-page", 1), per_page=10)
context["closed_remittances"] = closed_remittances
no_remittance_tr = SpecialTransactionTable(
data=SpecialTransaction.objects.filter(source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)),
specialtransactionproxy__remittance=None).filter(
PermissionBackend.filter_queryset(self.request, Remittance, "view")).all(),
exclude=('remittance_remove', ),
prefix="no-remittance-",
)
no_remittance_tr.paginate(page=self.request.GET.get("no-remittance-page", 1), per_page=10)
context["special_transactions_no_remittance"] = no_remittance_tr
with_remittance_tr = SpecialTransactionTable(
data=SpecialTransaction.objects.filter(source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)),
specialtransactionproxy__remittance__closed=False).filter(
PermissionBackend.filter_queryset(self.request, Remittance, "view")).all(),
exclude=('remittance_add', ),
prefix="with-remittance-",
)
with_remittance_tr.paginate(page=self.request.GET.get("with-remittance-page", 1), per_page=10)
context["special_transactions_with_remittance"] = with_remittance_tr
return context return context
class RemittanceUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): class RemittanceUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableMixin, UpdateView):
""" """
Update Remittance Update Remittance
""" """
@ -325,19 +318,18 @@ class RemittanceUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView)
form_class = RemittanceForm form_class = RemittanceForm
extra_context = {"title": _("Update a remittance")} extra_context = {"title": _("Update a remittance")}
table_class = SpecialTransactionTable
context_table_name = "special_transactions"
def get_success_url(self): def get_success_url(self):
return reverse_lazy('treasury:remittance_list') return reverse_lazy('treasury:remittance_list')
def get_context_data(self, **kwargs): def get_table_data(self):
context = super().get_context_data(**kwargs) return SpecialTransaction.objects.filter(specialtransactionproxy__remittance=self.object).filter(
PermissionBackend.filter_queryset(self.request, Remittance, "view"))
data = SpecialTransaction.objects.filter(specialtransactionproxy__remittance=self.object).filter( def get_table_kwargs(self):
PermissionBackend.filter_queryset(self.request, Remittance, "view")).all() return {"exclude": ('remittance_add', 'remittance_remove', ) if self.object.closed else ('remittance_add', )}
context["special_transactions"] = SpecialTransactionTable(
data=data,
exclude=('remittance_add', 'remittance_remove', ) if self.object.closed else ('remittance_add', ))
return context
class LinkTransactionToRemittanceView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): class LinkTransactionToRemittanceView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):

View File

@ -1,14 +1,13 @@
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from bootstrap_datepicker_plus.widgets import DatePickerInput
from django import forms from django import forms
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db.models import Q from django.db.models import Q
from django.forms import CheckboxSelectMultiple from django.forms import CheckboxSelectMultiple
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from note.models import NoteSpecial, NoteUser from note.models import NoteSpecial, NoteUser
from note_kfet.inputs import AmountInput, Autocomplete, ColorWidget from note_kfet.inputs import AmountInput, DatePickerInput, Autocomplete, ColorWidget
from ..models import WEIClub, WEIRegistration, Bus, BusTeam, WEIMembership, WEIRole from ..models import WEIClub, WEIRegistration, Bus, BusTeam, WEIMembership, WEIRole

View File

@ -439,7 +439,7 @@ class TestWEIRegistration(TestCase):
emergency_contact_phone='+33123456789', emergency_contact_phone='+33123456789',
)) ))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertTrue("This user can&#x27;t be in her/his first year since he/she has already participated to a WEI." self.assertTrue("This user can&#39;t be in her/his first year since he/she has already participated to a WEI."
in str(response.context["form"].errors)) in str(response.context["form"].errors))
# Check that if the WEI is started, we can't register anyone # Check that if the WEI is started, we can't register anyone
@ -635,7 +635,7 @@ class TestWEIRegistration(TestCase):
)) ))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertFalse(response.context["form"].is_valid()) self.assertFalse(response.context["form"].is_valid())
self.assertTrue("This team doesn&#x27;t belong to the given bus." in str(response.context["form"].errors)) self.assertTrue("This team doesn&#39;t belong to the given bus." in str(response.context["form"].errors))
response = self.client.post(reverse("wei:validate_registration", kwargs=dict(pk=self.registration.pk)), dict( response = self.client.post(reverse("wei:validate_registration", kwargs=dict(pk=self.registration.pk)), dict(
roles=[WEIRole.objects.get(name="GC WEI").id], roles=[WEIRole.objects.get(name="GC WEI").id],

View File

@ -22,7 +22,7 @@ from django.views import View
from django.views.generic import DetailView, UpdateView, RedirectView, TemplateView from django.views.generic import DetailView, UpdateView, RedirectView, TemplateView
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic.edit import BaseFormView, DeleteView from django.views.generic.edit import BaseFormView, DeleteView
from django_tables2 import SingleTableView from django_tables2 import SingleTableView, MultiTableMixin
from member.models import Membership, Club from member.models import Membership, Club
from note.models import Transaction, NoteClub, Alias, SpecialTransaction, NoteSpecial from note.models import Transaction, NoteClub, Alias, SpecialTransaction, NoteSpecial
from note.tables import HistoryTable from note.tables import HistoryTable
@ -100,7 +100,7 @@ class WEICreateView(ProtectQuerysetMixin, ProtectedCreateView):
return reverse_lazy("wei:wei_detail", kwargs={"pk": self.object.pk}) return reverse_lazy("wei:wei_detail", kwargs={"pk": self.object.pk})
class WEIDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): class WEIDetailView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, DetailView):
""" """
View WEI information View WEI information
""" """
@ -108,34 +108,40 @@ class WEIDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
context_object_name = "club" context_object_name = "club"
extra_context = {"title": _("WEI Detail")} extra_context = {"title": _("WEI Detail")}
def get_context_data(self, **kwargs): tables = [
context = super().get_context_data(**kwargs) lambda data: HistoryTable(data, prefix="history-"),
lambda data: WEIMembershipTable(data, prefix="membership-"),
club = context["club"] lambda data: WEIRegistrationTable(data, prefix="pre-registration-"),
lambda data: BusTable(data, prefix="bus-"),
]
paginate_by = 20 # number of rows in tables
def get_tables_data(self):
club = self.object
club_transactions = Transaction.objects.all().filter(Q(source=club.note) | Q(destination=club.note)) \ club_transactions = Transaction.objects.all().filter(Q(source=club.note) | Q(destination=club.note)) \
.filter(PermissionBackend.filter_queryset(self.request, Transaction, "view")) \ .filter(PermissionBackend.filter_queryset(self.request, Transaction, "view")) \
.order_by('-created_at', '-id') .order_by('-created_at', '-id')
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
club_member = WEIMembership.objects.filter( club_member = WEIMembership.objects.filter(
club=club, club=club,
date_end__gte=date.today(), date_end__gte=date.today(),
).filter(PermissionBackend.filter_queryset(self.request, WEIMembership, "view")) ).filter(PermissionBackend.filter_queryset(self.request, WEIMembership, "view"))
membership_table = WEIMembershipTable(data=club_member, prefix="membership-")
membership_table.paginate(per_page=20, page=self.request.GET.get('membership-page', 1))
context['member_list'] = membership_table
pre_registrations = WEIRegistration.objects.filter( pre_registrations = WEIRegistration.objects.filter(
PermissionBackend.filter_queryset(self.request, WEIRegistration, "view")).filter( PermissionBackend.filter_queryset(self.request, WEIRegistration, "view")).filter(
membership=None, membership=None,
wei=club wei=club
) )
pre_registrations_table = WEIRegistrationTable(data=pre_registrations, prefix="pre-registration-") buses = Bus.objects.filter(PermissionBackend.filter_queryset(self.request, Bus, "view")) \
pre_registrations_table.paginate(per_page=20, page=self.request.GET.get('pre-registration-page', 1)) .filter(wei=self.object).annotate(count=Count("memberships")).order_by("name")
context['pre_registrations'] = pre_registrations_table return [club_transactions, club_member, pre_registrations, buses, ]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
club = context["club"]
tables = context["tables"]
for name, table in zip(["history_list", "member_list", "pre_registrations", "buses"], tables):
context[name] = table
my_registration = WEIRegistration.objects.filter(wei=club, user=self.request.user) my_registration = WEIRegistration.objects.filter(wei=club, user=self.request.user)
if my_registration.exists(): if my_registration.exists():
@ -144,11 +150,6 @@ class WEIDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
my_registration = None my_registration = None
context["my_registration"] = my_registration context["my_registration"] = my_registration
buses = Bus.objects.filter(PermissionBackend.filter_queryset(self.request, Bus, "view")) \
.filter(wei=self.object).annotate(count=Count("memberships")).order_by("name")
bus_table = BusTable(data=buses, prefix="bus-")
context['buses'] = bus_table
random_user = User.objects.filter(~Q(wei__wei__in=[club])).first() random_user = User.objects.filter(~Q(wei__wei__in=[club])).first()
if random_user is None: if random_user is None:

View File

@ -114,7 +114,7 @@ msgstr "Lieu où l'activité est organisée, par exemple la Kfet."
msgid "type" msgid "type"
msgstr "type" msgstr "type"
#: apps/activity/models.py:89 apps/logs/models.py:22 apps/member/models.py:313 #: apps/activity/models.py:89 apps/logs/models.py:22 apps/member/models.py:318
#: apps/note/models/notes.py:148 apps/treasury/models.py:293 #: apps/note/models/notes.py:148 apps/treasury/models.py:293
#: apps/wei/models.py:171 apps/wei/templates/wei/attribute_bus_1A.html:13 #: apps/wei/models.py:171 apps/wei/templates/wei/attribute_bus_1A.html:13
#: apps/wei/templates/wei/survey.html:15 #: apps/wei/templates/wei/survey.html:15
@ -247,6 +247,7 @@ msgid "The validation of the activity is pending."
msgstr "La validation de cette activité est en attente." msgstr "La validation de cette activité est en attente."
#: apps/activity/tables.py:43 apps/treasury/tables.py:107 #: apps/activity/tables.py:43 apps/treasury/tables.py:107
#: apps/member/templates/member/picture_update.html:18
msgid "Remove" msgid "Remove"
msgstr "Supprimer" msgstr "Supprimer"
@ -262,13 +263,13 @@ msgstr "supprimer"
msgid "Type" msgid "Type"
msgstr "Type" msgstr "Type"
#: apps/activity/tables.py:84 apps/member/forms.py:193 #: apps/activity/tables.py:84 apps/member/forms.py:196
#: apps/registration/forms.py:92 apps/treasury/forms.py:131 #: apps/registration/forms.py:92 apps/treasury/forms.py:131
#: apps/wei/forms/registration.py:104 #: apps/wei/forms/registration.py:104
msgid "Last name" msgid "Last name"
msgstr "Nom de famille" msgstr "Nom de famille"
#: apps/activity/tables.py:86 apps/member/forms.py:198 #: apps/activity/tables.py:86 apps/member/forms.py:201
#: apps/note/templates/note/transaction_form.html:138 #: apps/note/templates/note/transaction_form.html:138
#: apps/registration/forms.py:97 apps/treasury/forms.py:133 #: apps/registration/forms.py:97 apps/treasury/forms.py:133
#: apps/wei/forms/registration.py:109 #: apps/wei/forms/registration.py:109
@ -400,37 +401,37 @@ msgstr "Inviter"
msgid "Create new activity" msgid "Create new activity"
msgstr "Créer une nouvelle activité" msgstr "Créer une nouvelle activité"
#: apps/activity/views.py:67 note_kfet/templates/base.html:90 #: apps/activity/views.py:68 note_kfet/templates/base.html:90
msgid "Activities" msgid "Activities"
msgstr "Activités" msgstr "Activités"
#: apps/activity/views.py:93 #: apps/activity/views.py:108
msgid "Activity detail" msgid "Activity detail"
msgstr "Détails de l'activité" msgstr "Détails de l'activité"
#: apps/activity/views.py:113 #: apps/activity/views.py:128
msgid "Update activity" msgid "Update activity"
msgstr "Modifier l'activité" msgstr "Modifier l'activité"
#: apps/activity/views.py:140 #: apps/activity/views.py:155
msgid "Invite guest to the activity \"{}\"" msgid "Invite guest to the activity \"{}\""
msgstr "Invitation pour l'activité « {} »" msgstr "Invitation pour l'activité « {} »"
#: apps/activity/views.py:178 #: apps/activity/views.py:193
msgid "You are not allowed to display the entry interface for this activity." msgid "You are not allowed to display the entry interface for this activity."
msgstr "" msgstr ""
"Vous n'êtes pas autorisé·e à afficher l'interface des entrées pour cette " "Vous n'êtes pas autorisé·e à afficher l'interface des entrées pour cette "
"activité." "activité."
#: apps/activity/views.py:181 #: apps/activity/views.py:196
msgid "This activity does not support activity entries." msgid "This activity does not support activity entries."
msgstr "Cette activité ne requiert pas d'entrées." msgstr "Cette activité ne requiert pas d'entrées."
#: apps/activity/views.py:184 #: apps/activity/views.py:199
msgid "This activity is closed." msgid "This activity is closed."
msgstr "Cette activité est fermée." msgstr "Cette activité est fermée."
#: apps/activity/views.py:280 #: apps/activity/views.py:295
msgid "Entry for activity \"{}\"" msgid "Entry for activity \"{}\""
msgstr "Entrées pour l'activité « {} »" msgstr "Entrées pour l'activité « {} »"
@ -507,11 +508,11 @@ msgstr "cotisation pour adhérer (normalien·ne élève)"
msgid "membership fee (unpaid students)" msgid "membership fee (unpaid students)"
msgstr "cotisation pour adhérer (normalien·ne étudiant·e)" msgstr "cotisation pour adhérer (normalien·ne étudiant·e)"
#: apps/member/admin.py:65 apps/member/models.py:325 #: apps/member/admin.py:65 apps/member/models.py:330
msgid "roles" msgid "roles"
msgstr "rôles" msgstr "rôles"
#: apps/member/admin.py:66 apps/member/models.py:339 #: apps/member/admin.py:66 apps/member/models.py:344
msgid "fee" msgid "fee"
msgstr "cotisation" msgstr "cotisation"
@ -563,29 +564,29 @@ msgid "This image cannot be loaded."
msgstr "Cette image ne peut pas être chargée." msgstr "Cette image ne peut pas être chargée."
#: apps/member/forms.py:148 apps/member/views.py:102 #: apps/member/forms.py:148 apps/member/views.py:102
#: apps/registration/forms.py:34 apps/registration/views.py:266 #: apps/registration/forms.py:34 apps/registration/views.py:276
msgid "An alias with a similar name already exists." msgid "An alias with a similar name already exists."
msgstr "Un alias avec un nom similaire existe déjà." msgstr "Un alias avec un nom similaire existe déjà."
#: apps/member/forms.py:172 #: apps/member/forms.py:175
msgid "Inscription paid by Société Générale" msgid "Inscription paid by Société Générale"
msgstr "Inscription payée par la Société générale" msgstr "Inscription payée par la Société générale"
#: apps/member/forms.py:174 #: apps/member/forms.py:177
msgid "Check this case if the Société Générale paid the inscription." msgid "Check this case if the Société Générale paid the inscription."
msgstr "Cochez cette case si la Société Générale a payé l'inscription." msgstr "Cochez cette case si la Société Générale a payé l'inscription."
#: apps/member/forms.py:179 apps/registration/forms.py:79 #: apps/member/forms.py:182 apps/registration/forms.py:79
#: apps/wei/forms/registration.py:91 #: apps/wei/forms/registration.py:91
msgid "Credit type" msgid "Credit type"
msgstr "Type de rechargement" msgstr "Type de rechargement"
#: apps/member/forms.py:180 apps/registration/forms.py:80 #: apps/member/forms.py:183 apps/registration/forms.py:80
#: apps/wei/forms/registration.py:92 #: apps/wei/forms/registration.py:92
msgid "No credit" msgid "No credit"
msgstr "Pas de rechargement" msgstr "Pas de rechargement"
#: apps/member/forms.py:182 #: apps/member/forms.py:185
msgid "You can credit the note of the user." msgid "You can credit the note of the user."
msgstr "Vous pouvez créditer la note de l'utilisateur·ice avant l'adhésion." msgstr "Vous pouvez créditer la note de l'utilisateur·ice avant l'adhésion."
@ -594,17 +595,17 @@ msgstr "Vous pouvez créditer la note de l'utilisateur·ice avant l'adhésion."
msgid "Credit amount" msgid "Credit amount"
msgstr "Montant à créditer" msgstr "Montant à créditer"
#: apps/member/forms.py:203 apps/note/templates/note/transaction_form.html:144 #: apps/member/forms.py:206 apps/note/templates/note/transaction_form.html:144
#: apps/registration/forms.py:102 apps/treasury/forms.py:135 #: apps/registration/forms.py:102 apps/treasury/forms.py:135
#: apps/wei/forms/registration.py:114 #: apps/wei/forms/registration.py:114
msgid "Bank" msgid "Bank"
msgstr "Banque" msgstr "Banque"
#: apps/member/forms.py:230 #: apps/member/forms.py:233
msgid "User" msgid "User"
msgstr "Utilisateur·ice" msgstr "Utilisateur·ice"
#: apps/member/forms.py:244 #: apps/member/forms.py:247
msgid "Roles" msgid "Roles"
msgstr "Rôles" msgstr "Rôles"
@ -852,46 +853,50 @@ msgstr ""
"Date maximale d'une fin d'adhésion, après laquelle les adhérent·e·s doivent la " "Date maximale d'une fin d'adhésion, après laquelle les adhérent·e·s doivent la "
"renouveler." "renouveler."
#: apps/member/models.py:263 apps/member/models.py:319 #: apps/member/models.py:263
msgid "add to registration form"
msgstr "ajouter au formulaire d'inscription"
#: apps/member/models.py:268 apps/member/models.py:324
#: apps/note/models/notes.py:176 #: apps/note/models/notes.py:176
msgid "club" msgid "club"
msgstr "club" msgstr "club"
#: apps/member/models.py:264 #: apps/member/models.py:269
msgid "clubs" msgid "clubs"
msgstr "clubs" msgstr "clubs"
#: apps/member/models.py:330 #: apps/member/models.py:335
msgid "membership starts on" msgid "membership starts on"
msgstr "l'adhésion commence le" msgstr "l'adhésion commence le"
#: apps/member/models.py:334 #: apps/member/models.py:339
msgid "membership ends on" msgid "membership ends on"
msgstr "l'adhésion finit le" msgstr "l'adhésion finit le"
#: apps/member/models.py:343 apps/note/models/transactions.py:385 #: apps/member/models.py:348 apps/note/models/transactions.py:385
msgid "membership" msgid "membership"
msgstr "adhésion" msgstr "adhésion"
#: apps/member/models.py:344 #: apps/member/models.py:349
msgid "memberships" msgid "memberships"
msgstr "adhésions" msgstr "adhésions"
#: apps/member/models.py:348 #: apps/member/models.py:353
#, python-brace-format #, python-brace-format
msgid "Membership of {user} for the club {club}" msgid "Membership of {user} for the club {club}"
msgstr "Adhésion de {user} pour le club {club}" msgstr "Adhésion de {user} pour le club {club}"
#: apps/member/models.py:367 #: apps/member/models.py:372
#, python-brace-format #, python-brace-format
msgid "The role {role} does not apply to the club {club}." msgid "The role {role} does not apply to the club {club}."
msgstr "Le rôle {role} ne s'applique pas au club {club}." msgstr "Le rôle {role} ne s'applique pas au club {club}."
#: apps/member/models.py:376 apps/member/views.py:712 #: apps/member/models.py:381 apps/member/views.py:712
msgid "User is already a member of the club" msgid "User is already a member of the club"
msgstr "L'utilisateur·ice est déjà membre du club" msgstr "L'utilisateur·ice est déjà membre du club"
#: apps/member/models.py:388 apps/member/views.py:721 #: apps/member/models.py:393 apps/member/views.py:721
msgid "User is not a member of the parent club" msgid "User is not a member of the parent club"
msgstr "L'utilisateur·ice n'est pas membre du club parent" msgstr "L'utilisateur·ice n'est pas membre du club parent"
@ -1153,11 +1158,11 @@ msgstr "Introspection :"
msgid "Show my applications" msgid "Show my applications"
msgstr "Voir mes applications" msgstr "Voir mes applications"
#: apps/member/templates/member/picture_update.html:35 #: apps/member/templates/member/picture_update.html:38
msgid "Nevermind" msgid "Nevermind"
msgstr "Annuler" msgstr "Annuler"
#: apps/member/templates/member/picture_update.html:36 #: apps/member/templates/member/picture_update.html:39
msgid "Crop and upload" msgid "Crop and upload"
msgstr "Recadrer et envoyer" msgstr "Recadrer et envoyer"
@ -2183,18 +2188,23 @@ msgstr "Utilisateur·ice·s en attente d'inscription"
msgid "Registration detail" msgid "Registration detail"
msgstr "Détails de l'inscription" msgstr "Détails de l'inscription"
#: apps/registration/views.py:293 #: apps/registration/views.py:256
#, python-format
msgid "Join %(club)s Club"
msgstr "Adhérer au club %(club)s"
#: apps/registration/views.py:299
msgid "You must join the BDE." msgid "You must join the BDE."
msgstr "Vous devez adhérer au BDE." msgstr "Vous devez adhérer au BDE."
#: apps/registration/views.py:323 #: apps/registration/views.py:330
msgid "" msgid ""
"The entered amount is not enough for the memberships, should be at least {}" "The entered amount is not enough for the memberships, should be at least {}"
msgstr "" msgstr ""
"Le montant crédité est trop faible pour adhérer, il doit être au minimum de " "Le montant crédité est trop faible pour adhérer, il doit être au minimum de "
"{}" "{}"
#: apps/registration/views.py:417 #: apps/registration/views.py:425
msgid "Invalidate pre-registration" msgid "Invalidate pre-registration"
msgstr "Invalider l'inscription" msgstr "Invalider l'inscription"
@ -3620,9 +3630,6 @@ msgstr ""
"d'adhésion. Vous devez également valider votre adresse email en suivant le " "d'adhésion. Vous devez également valider votre adresse email en suivant le "
"lien que vous avez reçu." "lien que vous avez reçu."
#~ msgid "Join BDA Club"
#~ msgstr "Adhérer au club BDA"
#, fuzzy #, fuzzy
#~| msgid "People having you as a friend" #~| msgid "People having you as a friend"
#~ msgid "You already have that person as a friend" #~ msgid "You already have that person as a friend"

View File

@ -25,13 +25,19 @@ admin_site.register(Site, SiteAdmin)
# Add external apps model # Add external apps model
if "oauth2_provider" in settings.INSTALLED_APPS: if "oauth2_provider" in settings.INSTALLED_APPS:
from oauth2_provider.admin import ApplicationAdmin, GrantAdmin, AccessTokenAdmin, RefreshTokenAdmin from oauth2_provider.admin import Application, ApplicationAdmin, Grant, \
from oauth2_provider.models import Application, Grant, AccessToken, RefreshToken GrantAdmin, AccessToken, AccessTokenAdmin, RefreshToken, RefreshTokenAdmin
admin_site.register(Application, ApplicationAdmin) admin_site.register(Application, ApplicationAdmin)
admin_site.register(Grant, GrantAdmin) admin_site.register(Grant, GrantAdmin)
admin_site.register(AccessToken, AccessTokenAdmin) admin_site.register(AccessToken, AccessTokenAdmin)
admin_site.register(RefreshToken, RefreshTokenAdmin) admin_site.register(RefreshToken, RefreshTokenAdmin)
if "django_htcpcp_tea" in settings.INSTALLED_APPS:
from django_htcpcp_tea.admin import *
from django_htcpcp_tea.models import *
admin_site.register(Pot, PotAdmin)
admin_site.register(TeaType, TeaTypeAdmin)
admin_site.register(Addition, AdditionAdmin)
if "mailer" in settings.INSTALLED_APPS: if "mailer" in settings.INSTALLED_APPS:
from mailer.admin import * from mailer.admin import *
@ -44,3 +50,9 @@ if "rest_framework" in settings.INSTALLED_APPS:
from rest_framework.authtoken.admin import * from rest_framework.authtoken.admin import *
from rest_framework.authtoken.models import * from rest_framework.authtoken.models import *
admin_site.register(Token, TokenAdmin) admin_site.register(Token, TokenAdmin)
if "cas_server" in settings.INSTALLED_APPS:
from cas_server.admin import *
from cas_server.models import *
admin_site.register(ServicePattern, ServicePatternAdmin)
admin_site.register(FederatedIendityProvider, FederatedIendityProviderAdmin)

View File

@ -41,7 +41,7 @@ INSTALLED_APPS = [
'bootstrap_datepicker_plus', 'bootstrap_datepicker_plus',
'colorfield', 'colorfield',
'crispy_forms', 'crispy_forms',
'crispy_bootstrap4', 'django_htcpcp_tea',
'django_tables2', 'django_tables2',
'mailer', 'mailer',
'phonenumber_field', 'phonenumber_field',
@ -90,6 +90,7 @@ MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.locale.LocaleMiddleware', 'django.middleware.locale.LocaleMiddleware',
'django.contrib.sites.middleware.CurrentSiteMiddleware', 'django.contrib.sites.middleware.CurrentSiteMiddleware',
'django_htcpcp_tea.middleware.HTCPCPTeaMiddleware',
'note_kfet.middlewares.SessionMiddleware', 'note_kfet.middlewares.SessionMiddleware',
'note_kfet.middlewares.LoginByIPMiddleware', 'note_kfet.middlewares.LoginByIPMiddleware',
'note_kfet.middlewares.TurbolinksMiddleware', 'note_kfet.middlewares.TurbolinksMiddleware',
@ -294,6 +295,3 @@ PHONENUMBER_DEFAULT_REGION = 'FR'
# We add custom information to CAS, in order to give a normalized name to other services # We add custom information to CAS, in order to give a normalized name to other services
CAS_AUTH_CLASS = 'member.auth.CustomAuthUser' CAS_AUTH_CLASS = 'member.auth.CustomAuthUser'
# Default field for primary key
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"

View File

@ -8,7 +8,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% if widget.value != None and widget.value != "" %}value="{{ widget.value }}"{% endif %} {% if widget.value != None and widget.value != "" %}value="{{ widget.value }}"{% endif %}
name="{{ widget.name }}_name" autocomplete="off" name="{{ widget.name }}_name" autocomplete="off"
{% for name, value in widget.attrs.items %} {% for name, value in widget.attrs.items %}
{% if value != False %}{{ name }}{% if value != True %}="{{ value|stringformat:'s' }}"{% endif %}{% endif %} {% ifnotequal value False %}{{ name }}{% ifnotequal value True %}="{{ value|stringformat:'s' }}"{% endifnotequal %}{% endifnotequal %}
{% endfor %} {% endfor %}
aria-describedby="{{widget.attrs.id}}_tooltip"> aria-describedby="{{widget.attrs.id}}_tooltip">
{% if widget.resetable %} {% if widget.resetable %}

View File

@ -126,12 +126,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
<a class="dropdown-item" href="{% url 'member:user_detail' pk=request.user.pk %}"> <a class="dropdown-item" href="{% url 'member:user_detail' pk=request.user.pk %}">
<i class="fa fa-user"></i> {% trans "My account" %} <i class="fa fa-user"></i> {% trans "My account" %}
</a> </a>
<form method="post" action="{% url 'logout' %}"> <a class="dropdown-item" href="{% url 'logout' %}">
{% csrf_token %}
<button class="dropdown-item" type="submit">
<i class="fa fa-sign-out"></i> {% trans "Log out" %} <i class="fa fa-sign-out"></i> {% trans "Log out" %}
</button> </a>
</form>
</div> </div>
</li> </li>
{% else %} {% else %}

View File

@ -30,6 +30,9 @@ urlpatterns = [
path('accounts/', include('django.contrib.auth.urls')), path('accounts/', include('django.contrib.auth.urls')),
path('api/', include('api.urls')), path('api/', include('api.urls')),
path('permission/', include('permission.urls')), path('permission/', include('permission.urls')),
# Make coffee
path('coffee/', include('django_htcpcp_tea.urls')),
] ]
# During development, serve static and media files # During development, serve static and media files
@ -43,6 +46,11 @@ if "oauth2_provider" in settings.INSTALLED_APPS:
path('o/', include('oauth2_provider.urls', namespace='oauth2_provider')) path('o/', include('oauth2_provider.urls', namespace='oauth2_provider'))
) )
if "cas_server" in settings.INSTALLED_APPS:
urlpatterns.append(
path('cas/', include('cas_server.urls', namespace='cas_server'))
)
if "debug_toolbar" in settings.INSTALLED_APPS: if "debug_toolbar" in settings.INSTALLED_APPS:
import debug_toolbar import debug_toolbar
urlpatterns = [ urlpatterns = [

View File

@ -1,17 +1,19 @@
beautifulsoup4~=4.12.3 beautifulsoup4~=4.7.1
crispy-bootstrap4~=2024.1 Django~=2.2.15
Django~=5.0.7 django-bootstrap-datepicker-plus~=3.0.5
django-bootstrap-datepicker-plus~=5.0.5 django-cas-server~=1.2.0
django-colorfield~=0.11.0 django-colorfield~=0.3.2
django-crispy-forms~=2.2 django-crispy-forms~=1.7.2
django-extensions~=3.2.3 django-extensions>=2.1.4
django-filter~=24.2 django-filter~=2.1
django-mailer~=2.3.2 django-htcpcp-tea~=0.3.1
django-oauth-toolkit~=2.4.0 django-mailer~=2.0.1
django-phonenumber-field~=8.0.0 django-oauth-toolkit~=1.3.3
django-polymorphic~=3.1.0 django-phonenumber-field~=5.0.0
django-rest-polymorphic~=0.1.10 django-polymorphic>=2.0.3,<3.0.0
django-tables2~=2.7.0 djangorestframework>=3.9.0,<3.13.0
djangorestframework~=3.15.2 django-rest-polymorphic~=0.1.9
phonenumbers~=8.13.40 django-tables2~=2.3.1
python-memcached~=1.59
phonenumbers~=8.9.10
Pillow>=5.4.1 Pillow>=5.4.1

View File

@ -1,10 +1,13 @@
[tox] [tox]
envlist = envlist =
# Debian Buster Python
py37-django22
# Ubuntu 20.04 Python # Ubuntu 20.04 Python
py310-django50 py38-django22
# Debian Bullseye Python # Debian Bullseye Python
py311-django50 py39-django22
linters linters
skipsdist = True skipsdist = True
@ -48,4 +51,4 @@ max-complexity = 15
max-line-length = 160 max-line-length = 160
import-order-style = google import-order-style = google
application-import-names = flake8 application-import-names = flake8
format = ${cyan}%(path)s${reset}:${yellow_bold}%(row)d${reset}:${green_bold}%(col)d${reset}: ${red_bold}%(code)s${reset} %(text)s format = %(cyan)s%(path)s%(reset)s:%(yellow)s%(bold)s%(row)d%(reset)s:%(green)s%(bold)s%(col)d%(reset)s: %(red)s%(bold)s%(code)s%(reset)s %(text)s