From e9f18c3ed9956bec07d6b71e391f7e275534663b Mon Sep 17 00:00:00 2001
From: bleizi <bleizi@crans.org>
Date: Wed, 24 Jan 2024 19:18:02 +0100
Subject: [PATCH 01/11] migrate to django 4.2 (LTS), change requirement and
 tests. remove depreciated ifnotequal

---
 .gitlab-ci.yml                              | 34 +++++++++---------
 apps/note/templates/note/amount_input.html  |  2 +-
 note_kfet/templates/autocomplete_model.html |  2 +-
 requirements.txt                            | 38 ++++++++++-----------
 tox.ini                                     |  6 ++--
 5 files changed, 42 insertions(+), 40 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 97110ecd..98fbac88 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -7,21 +7,6 @@ stages:
 variables:
   GIT_SUBMODULE_STRATEGY: recursive
 
-# Debian Buster
-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
@@ -54,9 +39,26 @@ py39-django22:
         python3-bs4 python3-setuptools tox texlive-xetex
   script: tox -e py39-django22
 
+# Debian Bookworm
+py311-django42:
+  stage: test
+  image: debian:bookworm
+  before_script:
+    - >
+        apt-get update &&
+        apt-get install --no-install-recommends -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 py311-django42
+
+
+
 linters:
   stage: quality-assurance
-  image: debian:buster-backports
+  image: debian:bookworm
   before_script:
     - apt-get update && apt-get install -y tox
   script: tox -e linters
diff --git a/apps/note/templates/note/amount_input.html b/apps/note/templates/note/amount_input.html
index d4873115..cbe9d160 100644
--- a/apps/note/templates/note/amount_input.html
+++ b/apps/note/templates/note/amount_input.html
@@ -9,7 +9,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
            name="{{ widget.name }}"
            {# Other attributes are loaded  #}
            {% for name, value in widget.attrs.items %}
-                {% ifnotequal value False %}{{ name }}{% ifnotequal value True %}="{{ value|stringformat:'s' }}"{% endifnotequal %}{% endifnotequal %}
+                {% if value is not False %}{{ name }}{% if value is not True %}="{{ value|stringformat:'s' }}"{% endif %}{% endif %}
             {% endfor %}>
     <div class="input-group-append">
         <span class="input-group-text">€</span>
diff --git a/note_kfet/templates/autocomplete_model.html b/note_kfet/templates/autocomplete_model.html
index fa24213f..5ffe971d 100644
--- a/note_kfet/templates/autocomplete_model.html
+++ b/note_kfet/templates/autocomplete_model.html
@@ -8,7 +8,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
    {% if widget.value != None and widget.value != "" %}value="{{ widget.value }}"{% endif %}
    name="{{ widget.name }}_name" autocomplete="off"
     {% for name, value in widget.attrs.items %}
-        {% ifnotequal value False %}{{ name }}{% ifnotequal value True %}="{{ value|stringformat:'s' }}"{% endifnotequal %}{% endifnotequal %}
+        {% if value is not False %}{{ name }}{% if value is not True %}="{{ value|stringformat:'s' }}"{% endif %}{% endif %}
     {% endfor %}
     aria-describedby="{{widget.attrs.id}}_tooltip">
     {% if widget.resetable %}
diff --git a/requirements.txt b/requirements.txt
index f4ece220..5548fd4b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,19 +1,19 @@
-beautifulsoup4~=4.7.1
-Django~=2.2.15
-django-bootstrap-datepicker-plus~=3.0.5
-django-cas-server~=1.2.0
-django-colorfield~=0.3.2
-django-crispy-forms~=1.7.2
-django-extensions>=2.1.4
-django-filter~=2.1
-django-htcpcp-tea~=0.3.1
-django-mailer~=2.0.1
-django-oauth-toolkit~=1.3.3
-django-phonenumber-field~=5.0.0
-django-polymorphic>=2.0.3,<3.0.0
-djangorestframework>=3.9.0,<3.13.0
-django-rest-polymorphic~=0.1.9
-django-tables2~=2.3.1
-python-memcached~=1.59
-phonenumbers~=8.9.10
-Pillow>=5.4.1
+beautifulsoup4~=4.12.3
+Django~=4.2.9
+django-bootstrap-datepicker-plus~=5.0.5
+django-cas-server~=2.0.0
+django-colorfield~=0.11.0
+django-crispy-forms~=2.1.0
+django-extensions>=3.2.3
+django-filter~=23.5
+django-htcpcp-tea~=0.8.1
+django-mailer~=2.3.1
+django-oauth-toolkit~=2.3.0
+django-phonenumber-field~=7.3.0
+django-polymorphic~=3.1.0
+djangorestframework~=3.14.0
+django-rest-polymorphic~=0.1.10
+django-tables2~=2.7.0
+python-memcached~=1.62
+phonenumbers~=8.13.28
+Pillow>=10.2.0
diff --git a/tox.ini b/tox.ini
index ad3c6798..0526236f 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,14 +1,14 @@
 [tox]
 envlist =
-    # Debian Buster Python
-    py37-django22
-
     # Ubuntu 20.04 Python
     py38-django22
 
     # Debian Bullseye Python
     py39-django22
 
+    # Debian Bookworm Python
+    py311-django42
+
     linters
 skipsdist = True
 

From 2f8c9b54e7c42bbd62dca80d658832c77f36f992 Mon Sep 17 00:00:00 2001
From: bleizi <bleizi@crans.org>
Date: Wed, 24 Jan 2024 19:58:55 +0100
Subject: [PATCH 02/11] Remove importation of django-cas-server which is not
 compatible with django 4.2

---
 requirements.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/requirements.txt b/requirements.txt
index 5548fd4b..7af89d9f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,7 +1,7 @@
 beautifulsoup4~=4.12.3
 Django~=4.2.9
 django-bootstrap-datepicker-plus~=5.0.5
-django-cas-server~=2.0.0
+#django-cas-server~=2.0.0
 django-colorfield~=0.11.0
 django-crispy-forms~=2.1.0
 django-extensions>=3.2.3

From 516a7f4be57360e917143c35abd7b21996999e6d Mon Sep 17 00:00:00 2001
From: bleizi <bleizi@crans.org>
Date: Wed, 24 Jan 2024 20:14:32 +0100
Subject: [PATCH 03/11] Remove importation of django-htcpcp-tea which is not
 compatible with django 4.2

---
 note_kfet/settings/base.py | 2 +-
 note_kfet/urls.py          | 8 +++++---
 requirements.txt           | 2 +-
 3 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/note_kfet/settings/base.py b/note_kfet/settings/base.py
index f3c23f4d..1e9e0752 100644
--- a/note_kfet/settings/base.py
+++ b/note_kfet/settings/base.py
@@ -41,7 +41,7 @@ INSTALLED_APPS = [
     'bootstrap_datepicker_plus',
     'colorfield',
     'crispy_forms',
-    'django_htcpcp_tea',
+#    'django_htcpcp_tea',
     'django_tables2',
     'mailer',
     'phonenumber_field',
diff --git a/note_kfet/urls.py b/note_kfet/urls.py
index 7fc37fa7..72be36ea 100644
--- a/note_kfet/urls.py
+++ b/note_kfet/urls.py
@@ -30,9 +30,6 @@ urlpatterns = [
     path('accounts/', include('django.contrib.auth.urls')),
     path('api/', include('api.urls')),
     path('permission/', include('permission.urls')),
-
-    # Make coffee
-    path('coffee/', include('django_htcpcp_tea.urls')),
 ]
 
 # During development, serve static and media files
@@ -57,6 +54,11 @@ if "debug_toolbar" in settings.INSTALLED_APPS:
         path('__debug__/', include(debug_toolbar.urls)),
     ] + urlpatterns
 
+if "django_htcpcp_tea" in settings.INSTALLED_APPS:
+    # Make coffee
+    urlpatterns.append(
+        path('coffee/', include('django_htcpcp_tea.urls'))
+    )
 
 handler400 = bad_request
 handler403 = permission_denied
diff --git a/requirements.txt b/requirements.txt
index 7af89d9f..1750cd66 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,7 +6,7 @@ django-colorfield~=0.11.0
 django-crispy-forms~=2.1.0
 django-extensions>=3.2.3
 django-filter~=23.5
-django-htcpcp-tea~=0.8.1
+#django-htcpcp-tea~=0.8.1
 django-mailer~=2.3.1
 django-oauth-toolkit~=2.3.0
 django-phonenumber-field~=7.3.0

From d228dbf225767303e98f08660abfd66ef332b1e1 Mon Sep 17 00:00:00 2001
From: bleizi <bleizi@crans.org>
Date: Wed, 7 Feb 2024 18:02:56 +0100
Subject: [PATCH 04/11] fix some breaking changes and linters

---
 apps/member/views.py                 | 2 +-
 apps/note/api/views.py               | 2 +-
 apps/note/tests/test_transactions.py | 2 +-
 apps/permission/scopes.py            | 2 ++
 apps/treasury/api/views.py           | 2 +-
 apps/treasury/urls.py                | 4 ++--
 note_kfet/admin.py                   | 4 ++--
 note_kfet/settings/base.py           | 3 +++
 8 files changed, 13 insertions(+), 8 deletions(-)

diff --git a/apps/member/views.py b/apps/member/views.py
index 066a7ef3..e56ed7b2 100644
--- a/apps/member/views.py
+++ b/apps/member/views.py
@@ -26,7 +26,7 @@ from permission.backends import PermissionBackend
 from permission.models import Role
 from permission.views import ProtectQuerysetMixin, ProtectedCreateView
 
-from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm,\
+from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm, \
     CustomAuthenticationForm, MembershipRolesForm
 from .models import Club, Membership
 from .tables import ClubTable, UserTable, MembershipTable, ClubManagerTable
diff --git a/apps/note/api/views.py b/apps/note/api/views.py
index 34ffaf2d..bc4f99ef 100644
--- a/apps/note/api/views.py
+++ b/apps/note/api/views.py
@@ -13,7 +13,7 @@ from rest_framework import status
 from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet
 from permission.backends import PermissionBackend
 
-from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer,\
+from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer, \
     TemplateCategorySerializer, TransactionTemplateSerializer, TransactionPolymorphicSerializer, \
     TrustSerializer
 from ..models.notes import Note, Alias, NoteUser, NoteClub, NoteSpecial, Trust
diff --git a/apps/note/tests/test_transactions.py b/apps/note/tests/test_transactions.py
index 4f5dd6c5..1f0920ec 100644
--- a/apps/note/tests/test_transactions.py
+++ b/apps/note/tests/test_transactions.py
@@ -10,7 +10,7 @@ from django.urls import reverse
 from django.utils import timezone
 from permission.models import Role
 
-from ..api.views import AliasViewSet, ConsumerViewSet, NotePolymorphicViewSet, TemplateCategoryViewSet,\
+from ..api.views import AliasViewSet, ConsumerViewSet, NotePolymorphicViewSet, TemplateCategoryViewSet, \
     TransactionTemplateViewSet, TransactionViewSet
 from ..models import NoteUser, Transaction, TemplateCategory, TransactionTemplate, RecurrentTransaction, \
     MembershipTransaction, SpecialTransaction, NoteSpecial, Alias, Note
diff --git a/apps/permission/scopes.py b/apps/permission/scopes.py
index 65242804..f8fd7687 100644
--- a/apps/permission/scopes.py
+++ b/apps/permission/scopes.py
@@ -44,6 +44,8 @@ class PermissionOAuth2Validator(OAuth2Validator):
         subset of permissions.
         """
 
+        oidc_claim_scope = None  # fix breaking change of django-oauth-toolkit 2.0.0
+
         valid_scopes = set()
 
         for t in Permission.PERMISSION_TYPES:
diff --git a/apps/treasury/api/views.py b/apps/treasury/api/views.py
index e6ba9ced..36388279 100644
--- a/apps/treasury/api/views.py
+++ b/apps/treasury/api/views.py
@@ -5,7 +5,7 @@ from django_filters.rest_framework import DjangoFilterBackend
 from rest_framework.filters import SearchFilter
 from api.viewsets import ReadProtectedModelViewSet
 
-from .serializers import InvoiceSerializer, ProductSerializer, RemittanceTypeSerializer, RemittanceSerializer,\
+from .serializers import InvoiceSerializer, ProductSerializer, RemittanceTypeSerializer, RemittanceSerializer, \
     SogeCreditSerializer
 from ..models import Invoice, Product, RemittanceType, Remittance, SogeCredit
 
diff --git a/apps/treasury/urls.py b/apps/treasury/urls.py
index 4fe87924..144ae296 100644
--- a/apps/treasury/urls.py
+++ b/apps/treasury/urls.py
@@ -3,8 +3,8 @@
 
 from django.urls import path
 
-from .views import InvoiceCreateView, InvoiceListView, InvoiceUpdateView, InvoiceDeleteView, InvoiceRenderView,\
-    RemittanceListView, RemittanceCreateView, RemittanceUpdateView, LinkTransactionToRemittanceView,\
+from .views import InvoiceCreateView, InvoiceListView, InvoiceUpdateView, InvoiceDeleteView, InvoiceRenderView, \
+    RemittanceListView, RemittanceCreateView, RemittanceUpdateView, LinkTransactionToRemittanceView, \
     UnlinkTransactionToRemittanceView, SogeCreditListView, SogeCreditManageView
 
 app_name = 'treasury'
diff --git a/note_kfet/admin.py b/note_kfet/admin.py
index dc209c67..1f26f559 100644
--- a/note_kfet/admin.py
+++ b/note_kfet/admin.py
@@ -25,8 +25,8 @@ admin_site.register(Site, SiteAdmin)
 
 # Add external apps model
 if "oauth2_provider" in settings.INSTALLED_APPS:
-    from oauth2_provider.admin import Application, ApplicationAdmin, Grant, \
-        GrantAdmin, AccessToken, AccessTokenAdmin, RefreshToken, RefreshTokenAdmin
+    from oauth2_provider.admin import ApplicationAdmin, GrantAdmin, AccessTokenAdmin, RefreshTokenAdmin
+    from oauth2_provider.model import Application, Grant, AccessToken, RefreshToken
     admin_site.register(Application, ApplicationAdmin)
     admin_site.register(Grant, GrantAdmin)
     admin_site.register(AccessToken, AccessTokenAdmin)
diff --git a/note_kfet/settings/base.py b/note_kfet/settings/base.py
index 1e9e0752..fe466b1e 100644
--- a/note_kfet/settings/base.py
+++ b/note_kfet/settings/base.py
@@ -263,6 +263,9 @@ OAUTH2_PROVIDER = {
     'REFRESH_TOKEN_EXPIRE_SECONDS': timedelta(days=14),
 }
 
+# PKCE (fix a breaking change of django-oauth-toolkit 2.0.0)
+PKCE_REQUIRED = False
+
 # Take control on how widget templates are sourced
 # See https://docs.djangoproject.com/en/2.2/ref/forms/renderers/#templatessetting
 FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'

From b6b81a8b8f1d1b9d3d388abb0f698550b19f96fb Mon Sep 17 00:00:00 2001
From: bleizi <bleizi@crans.org>
Date: Wed, 7 Feb 2024 18:05:32 +0100
Subject: [PATCH 05/11] typo

---
 note_kfet/admin.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/note_kfet/admin.py b/note_kfet/admin.py
index 1f26f559..62d2c688 100644
--- a/note_kfet/admin.py
+++ b/note_kfet/admin.py
@@ -26,7 +26,7 @@ admin_site.register(Site, SiteAdmin)
 # Add external apps model
 if "oauth2_provider" in settings.INSTALLED_APPS:
     from oauth2_provider.admin import ApplicationAdmin, GrantAdmin, AccessTokenAdmin, RefreshTokenAdmin
-    from oauth2_provider.model import Application, Grant, AccessToken, RefreshToken
+    from oauth2_provider.models import Application, Grant, AccessToken, RefreshToken
     admin_site.register(Application, ApplicationAdmin)
     admin_site.register(Grant, GrantAdmin)
     admin_site.register(AccessToken, AccessTokenAdmin)

From abc88d0118c7f3bbc060058cc5a50a45da3e9f6e Mon Sep 17 00:00:00 2001
From: bleizi <bleizi@crans.org>
Date: Wed, 7 Feb 2024 18:21:08 +0100
Subject: [PATCH 06/11] replace url from django.conf.urls by re_path from
 django.urls

---
 apps/api/urls.py | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

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

From 82fea65b5edbf58b6bd469cdfef63dab037adef1 Mon Sep 17 00:00:00 2001
From: bleizi <bleizi@crans.org>
Date: Wed, 7 Feb 2024 20:00:58 +0100
Subject: [PATCH 07/11] django_htcpcp_tea in middleware only if in apps

---
 apps/api/urls.py           | 2 +-
 note_kfet/settings/base.py | 4 +++-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/apps/api/urls.py b/apps/api/urls.py
index cb342b1c..4cb8085e 100644
--- a/apps/api/urls.py
+++ b/apps/api/urls.py
@@ -2,7 +2,7 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
 
 from django.conf import settings
-from django.conf.urls import  include
+from django.conf.urls import include
 from django.urls import re_path
 from rest_framework import routers
 
diff --git a/note_kfet/settings/base.py b/note_kfet/settings/base.py
index fe466b1e..d653dfc6 100644
--- a/note_kfet/settings/base.py
+++ b/note_kfet/settings/base.py
@@ -90,12 +90,14 @@ MIDDLEWARE = [
     'django.middleware.clickjacking.XFrameOptionsMiddleware',
     'django.middleware.locale.LocaleMiddleware',
     'django.contrib.sites.middleware.CurrentSiteMiddleware',
-    'django_htcpcp_tea.middleware.HTCPCPTeaMiddleware',
     'note_kfet.middlewares.SessionMiddleware',
     'note_kfet.middlewares.LoginByIPMiddleware',
     'note_kfet.middlewares.TurbolinksMiddleware',
     'note_kfet.middlewares.ClacksMiddleware',
 ]
+if "django_htcpcp_tea" in INSTALLED_APPS:
+    MIDDLEWARE.append('django_htcpcp_tea.middleware.HTCPCPTeaMiddleware')
+
 
 ROOT_URLCONF = 'note_kfet.urls'
 

From 399a32bece44af5b322d3089eb09a9dc31928393 Mon Sep 17 00:00:00 2001
From: bleizi <bleizi@crans.org>
Date: Sun, 11 Feb 2024 16:51:48 +0100
Subject: [PATCH 08/11] default auto field

---
 note_kfet/settings/base.py | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/note_kfet/settings/base.py b/note_kfet/settings/base.py
index d653dfc6..9ba5bb34 100644
--- a/note_kfet/settings/base.py
+++ b/note_kfet/settings/base.py
@@ -300,3 +300,6 @@ PHONENUMBER_DEFAULT_REGION = 'FR'
 
 # We add custom information to CAS, in order to give a normalized name to other services
 CAS_AUTH_CLASS = 'member.auth.CustomAuthUser'
+
+# Default field for primary key
+DEFAULT_AUTO_FIELD = "django.db.models.AutoField"

From fb3337966e029a1d69f5e25f1c5d5e588ec3af11 Mon Sep 17 00:00:00 2001
From: bleizi <bleizi@crans.org>
Date: Sun, 11 Feb 2024 22:24:37 +0100
Subject: [PATCH 09/11] bootstrap4 is now a standalone package from
 crispy-forms

---
 note_kfet/settings/base.py | 2 ++
 requirements.txt           | 1 +
 2 files changed, 3 insertions(+)

diff --git a/note_kfet/settings/base.py b/note_kfet/settings/base.py
index 9ba5bb34..44c34020 100644
--- a/note_kfet/settings/base.py
+++ b/note_kfet/settings/base.py
@@ -40,6 +40,7 @@ INSTALLED_APPS = [
     # External apps
     'bootstrap_datepicker_plus',
     'colorfield',
+    'crispy_bootstrap4',
     'crispy_forms',
 #    'django_htcpcp_tea',
     'django_tables2',
@@ -279,6 +280,7 @@ LOGIN_REDIRECT_URL = '/'
 SESSION_COOKIE_AGE = 60 * 60 * 3
 
 # Use Crispy Bootstrap4 theme
+CRISPY_ALLOWED_TEMPLATE_PACKS = 'bootstrap4'
 CRISPY_TEMPLATE_PACK = 'bootstrap4'
 
 # Use Django Table2 Bootstrap4 theme
diff --git a/requirements.txt b/requirements.txt
index 1750cd66..f4a32c97 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,5 @@
 beautifulsoup4~=4.12.3
+crispy-bootstrap4~=2023.1
 Django~=4.2.9
 django-bootstrap-datepicker-plus~=5.0.5
 #django-cas-server~=2.0.0

From 2ee7f41dfebdc53de4a4b3341bc05977c2050ebb Mon Sep 17 00:00:00 2001
From: bleizi <bleizi@crans.org>
Date: Mon, 12 Feb 2024 21:25:07 +0100
Subject: [PATCH 10/11] tests with ubuntu 22.04,
 django-bootstrap-datepicker-plus is a standalone package and fix encoding in
 tests

---
 .gitlab-ci.yml                          |  38 ++--
 apps/activity/forms.py                  |   3 +-
 apps/member/forms.py                    |   5 +-
 apps/note/forms.py                      |   3 +-
 apps/wei/forms/registration.py          |   3 +-
 apps/wei/tests/test_wei_registration.py |   4 +-
 note_kfet/inputs.py                     | 261 ------------------------
 tox.ini                                 |   8 +-
 8 files changed, 34 insertions(+), 291 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 98fbac88..23ba25aa 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -7,25 +7,8 @@ stages:
 variables:
   GIT_SUBMODULE_STRATEGY: recursive
 
-# Ubuntu 20.04
-py38-django22:
-  stage: test
-  image: ubuntu:20.04
-  before_script:
-    # Fix tzdata prompt
-    - ln -sf /usr/share/zoneinfo/Europe/Paris /etc/localtime && echo Europe/Paris > /etc/timezone
-    - >
-        apt-get update &&
-        apt-get install --no-install-recommends -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 py38-django22
-
 # Debian Bullseye
-py39-django22:
+py39-django42:
   stage: test
   image: debian:bullseye
   before_script:
@@ -37,7 +20,24 @@ py39-django22:
         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 py39-django22
+  script: tox -e py39-django42
+
+# Ubuntu 22.04
+py310-django42:
+  stage: test
+  image: ubuntu:22.04
+  before_script:
+    # Fix tzdata prompt
+    - ln -sf /usr/share/zoneinfo/Europe/Paris /etc/localtime && echo Europe/Paris > /etc/timezone
+    - >
+        apt-get update &&
+        apt-get install --no-install-recommends -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 py38-django42
 
 # Debian Bookworm
 py311-django42:
diff --git a/apps/activity/forms.py b/apps/activity/forms.py
index 1ca98cef..f8744f14 100644
--- a/apps/activity/forms.py
+++ b/apps/activity/forms.py
@@ -4,13 +4,14 @@
 from datetime import timedelta
 from random import shuffle
 
+from bootstrap_datepicker_plus.widgets import DateTimePickerInput
 from django import forms
 from django.contrib.contenttypes.models import ContentType
 from django.utils import timezone
 from django.utils.translation import gettext_lazy as _
 from member.models import Club
 from note.models import Note, NoteUser
-from note_kfet.inputs import Autocomplete, DateTimePickerInput
+from note_kfet.inputs import Autocomplete
 from note_kfet.middlewares import get_current_request
 from permission.backends import PermissionBackend
 
diff --git a/apps/member/forms.py b/apps/member/forms.py
index 527816cb..420b35a4 100644
--- a/apps/member/forms.py
+++ b/apps/member/forms.py
@@ -3,7 +3,7 @@
 
 import io
 
-from PIL import Image, ImageSequence
+from bootstrap_datepicker_plus.widgets import DatePickerInput
 from django import forms
 from django.conf import settings
 from django.contrib.auth.forms import AuthenticationForm
@@ -13,8 +13,9 @@ from django.forms import CheckboxSelectMultiple
 from django.utils import timezone
 from django.utils.translation import gettext_lazy as _
 from note.models import NoteSpecial, Alias
-from note_kfet.inputs import Autocomplete, AmountInput, DatePickerInput
+from note_kfet.inputs import Autocomplete, AmountInput
 from permission.models import PermissionMask, Role
+from PIL import Image, ImageSequence
 
 from .models import Profile, Club, Membership
 
diff --git a/apps/note/forms.py b/apps/note/forms.py
index 791abb51..f496843a 100644
--- a/apps/note/forms.py
+++ b/apps/note/forms.py
@@ -2,12 +2,13 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
 from datetime import datetime
 
+from bootstrap_datepicker_plus.widgets import DateTimePickerInput
 from django import forms
 from django.contrib.contenttypes.models import ContentType
 from django.forms import CheckboxSelectMultiple
 from django.utils.timezone import make_aware
 from django.utils.translation import gettext_lazy as _
-from note_kfet.inputs import Autocomplete, AmountInput, DateTimePickerInput
+from note_kfet.inputs import Autocomplete, AmountInput
 
 from .models import TransactionTemplate, NoteClub, Alias
 
diff --git a/apps/wei/forms/registration.py b/apps/wei/forms/registration.py
index 408071f4..65a98bfe 100644
--- a/apps/wei/forms/registration.py
+++ b/apps/wei/forms/registration.py
@@ -1,13 +1,14 @@
 # Copyright (C) 2018-2023 by BDE ENS Paris-Saclay
 # SPDX-License-Identifier: GPL-3.0-or-later
 
+from bootstrap_datepicker_plus.widgets import DatePickerInput
 from django import forms
 from django.contrib.auth.models import User
 from django.db.models import Q
 from django.forms import CheckboxSelectMultiple
 from django.utils.translation import gettext_lazy as _
 from note.models import NoteSpecial, NoteUser
-from note_kfet.inputs import AmountInput, DatePickerInput, Autocomplete, ColorWidget
+from note_kfet.inputs import AmountInput, Autocomplete, ColorWidget
 
 from ..models import WEIClub, WEIRegistration, Bus, BusTeam, WEIMembership, WEIRole
 
diff --git a/apps/wei/tests/test_wei_registration.py b/apps/wei/tests/test_wei_registration.py
index 86dd4cfd..2ca28e43 100644
--- a/apps/wei/tests/test_wei_registration.py
+++ b/apps/wei/tests/test_wei_registration.py
@@ -439,7 +439,7 @@ class TestWEIRegistration(TestCase):
             emergency_contact_phone='+33123456789',
         ))
         self.assertEqual(response.status_code, 200)
-        self.assertTrue("This user can&#39;t be in her/his first year since he/she has already participated to a WEI."
+        self.assertTrue("This user can&#x27;t be in her/his first year since he/she has already participated to a WEI."
                         in str(response.context["form"].errors))
 
         # 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.assertFalse(response.context["form"].is_valid())
-        self.assertTrue("This team doesn&#39;t belong to the given bus." in str(response.context["form"].errors))
+        self.assertTrue("This team doesn&#x27;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(
             roles=[WEIRole.objects.get(name="GC WEI").id],
diff --git a/note_kfet/inputs.py b/note_kfet/inputs.py
index 6e729c47..e6e7f8f9 100644
--- a/note_kfet/inputs.py
+++ b/note_kfet/inputs.py
@@ -68,264 +68,3 @@ class ColorWidget(Widget):
     def value_from_datadict(self, data, files, name):
         val = super().value_from_datadict(data, files, name)
         return int(val[1:], 16)
-
-
-"""
-The remaining of this file comes from the project `django-bootstrap-datepicker-plus` available on Github:
-https://github.com/monim67/django-bootstrap-datepicker-plus
-This is distributed under Apache License 2.0.
-
-This adds datetime pickers with bootstrap.
-"""
-
-"""Contains Base Date-Picker input class for widgets of this package."""
-
-
-class DatePickerDictionary:
-    """Keeps track of all date-picker input classes."""
-
-    _i = 0
-    items = dict()
-
-    @classmethod
-    def generate_id(cls):
-        """Return a unique ID for each date-picker input class."""
-        cls._i += 1
-        return 'dp_%s' % cls._i
-
-
-class BasePickerInput(DateTimeBaseInput):
-    """Base Date-Picker input class for widgets of this package."""
-
-    template_name = 'bootstrap_datepicker_plus/date-picker.html'
-    picker_type = 'DATE'
-    format = '%Y-%m-%d'
-    config = {}
-    _default_config = {
-        'id': None,
-        'picker_type': None,
-        'linked_to': None,
-        'options': {}  # final merged options
-    }
-    options = {}  # options extended by user
-    options_param = {}  # options passed as parameter
-    _default_options = {
-        'showClose': True,
-        'showClear': True,
-        'showTodayButton': True,
-        "locale": "fr",
-    }
-
-    # source: https://github.com/tutorcruncher/django-bootstrap3-datetimepicker
-    # file: /blob/31fbb09/bootstrap3_datetime/widgets.py#L33
-    format_map = (
-        ('DDD', r'%j'),
-        ('DD', r'%d'),
-        ('MMMM', r'%B'),
-        ('MMM', r'%b'),
-        ('MM', r'%m'),
-        ('YYYY', r'%Y'),
-        ('YY', r'%y'),
-        ('HH', r'%H'),
-        ('hh', r'%I'),
-        ('mm', r'%M'),
-        ('ss', r'%S'),
-        ('a', r'%p'),
-        ('ZZ', r'%z'),
-    )
-
-    class Media:
-        """JS/CSS resources needed to render the date-picker calendar."""
-
-        js = (
-            'https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.9.0/'
-            'moment-with-locales.min.js',
-            'https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/'
-            '4.17.47/js/bootstrap-datetimepicker.min.js',
-            'bootstrap_datepicker_plus/js/datepicker-widget.js'
-        )
-        css = {'all': (
-            'https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/'
-            '4.17.47/css/bootstrap-datetimepicker.css',
-            'bootstrap_datepicker_plus/css/datepicker-widget.css'
-        ), }
-
-    @classmethod
-    def format_py2js(cls, datetime_format):
-        """Convert python datetime format to moment datetime format."""
-        for js_format, py_format in cls.format_map:
-            datetime_format = datetime_format.replace(py_format, js_format)
-        return datetime_format
-
-    @classmethod
-    def format_js2py(cls, datetime_format):
-        """Convert moment datetime format to python datetime format."""
-        for js_format, py_format in cls.format_map:
-            datetime_format = datetime_format.replace(js_format, py_format)
-        return datetime_format
-
-    def __init__(self, attrs=None, format=None, options=None):
-        """Initialize the Date-picker widget."""
-        self.format_param = format
-        self.options_param = options if options else {}
-        self.config = self._default_config.copy()
-        self.config['id'] = DatePickerDictionary.generate_id()
-        self.config['picker_type'] = self.picker_type
-        self.config['options'] = self._calculate_options()
-        attrs = attrs if attrs else {}
-        if 'class' not in attrs:
-            attrs['class'] = 'form-control'
-        super().__init__(attrs, self._calculate_format())
-
-    def _calculate_options(self):
-        """Calculate and Return the options."""
-        _options = self._default_options.copy()
-        _options.update(self.options)
-        if self.options_param:
-            _options.update(self.options_param)
-        return _options
-
-    def _calculate_format(self):
-        """Calculate and Return the datetime format."""
-        _format = self.format_param if self.format_param else self.format
-        if self.config['options'].get('format'):
-            _format = self.format_js2py(self.config['options'].get('format'))
-        else:
-            self.config['options']['format'] = self.format_py2js(_format)
-        return _format
-
-    def get_context(self, name, value, attrs):
-        """Return widget context dictionary."""
-        context = super().get_context(
-            name, value, attrs)
-        context['widget']['attrs']['dp_config'] = json_dumps(self.config)
-        return context
-
-    def start_of(self, event_id):
-        """
-        Set Date-Picker as the start-date of a date-range.
-
-        Args:
-            - event_id (string): User-defined unique id for linking two fields
-        """
-        DatePickerDictionary.items[str(event_id)] = self
-        return self
-
-    def end_of(self, event_id, import_options=True):
-        """
-        Set Date-Picker as the end-date of a date-range.
-
-        Args:
-            - event_id (string): User-defined unique id for linking two fields
-            - import_options (bool): inherit options from start-date input,
-              default: TRUE
-        """
-        event_id = str(event_id)
-        if event_id in DatePickerDictionary.items:
-            linked_picker = DatePickerDictionary.items[event_id]
-            self.config['linked_to'] = linked_picker.config['id']
-            if import_options:
-                backup_moment_format = self.config['options']['format']
-                self.config['options'].update(linked_picker.config['options'])
-                self.config['options'].update(self.options_param)
-                if self.format_param or 'format' in self.options_param:
-                    self.config['options']['format'] = backup_moment_format
-                else:
-                    self.format = linked_picker.format
-            # Setting useCurrent is necessary, see following issue
-            # https://github.com/Eonasdan/bootstrap-datetimepicker/issues/1075
-            self.config['options']['useCurrent'] = False
-            self._link_to(linked_picker)
-        else:
-            raise KeyError(
-                'start-date not specified for event_id "%s"' % event_id)
-        return self
-
-    def _link_to(self, linked_picker):
-        """
-        Executed when two date-inputs are linked together.
-
-        This method for sub-classes to override to customize the linking.
-        """
-        pass
-
-
-class DatePickerInput(BasePickerInput):
-    """
-    Widget to display a Date-Picker Calendar on a DateField property.
-
-    Args:
-        - attrs (dict): HTML attributes of rendered HTML input
-        - format (string): Python DateTime format eg. "%Y-%m-%d"
-        - options (dict): Options to customize the widget, see README
-    """
-
-    picker_type = 'DATE'
-    format = '%Y-%m-%d'
-    format_key = 'DATE_INPUT_FORMATS'
-
-
-class TimePickerInput(BasePickerInput):
-    """
-    Widget to display a Time-Picker Calendar on a TimeField property.
-
-    Args:
-        - attrs (dict): HTML attributes of rendered HTML input
-        - format (string): Python DateTime format eg. "%Y-%m-%d"
-        - options (dict): Options to customize the widget, see README
-    """
-
-    picker_type = 'TIME'
-    format = '%H:%M'
-    format_key = 'TIME_INPUT_FORMATS'
-    template_name = 'bootstrap_datepicker_plus/time_picker.html'
-
-
-class DateTimePickerInput(BasePickerInput):
-    """
-    Widget to display a DateTime-Picker Calendar on a DateTimeField property.
-
-    Args:
-        - attrs (dict): HTML attributes of rendered HTML input
-        - format (string): Python DateTime format eg. "%Y-%m-%d"
-        - options (dict): Options to customize the widget, see README
-    """
-
-    picker_type = 'DATETIME'
-    format = '%Y-%m-%d %H:%M'
-    format_key = 'DATETIME_INPUT_FORMATS'
-
-
-class MonthPickerInput(BasePickerInput):
-    """
-    Widget to display a Month-Picker Calendar on a DateField property.
-
-    Args:
-        - attrs (dict): HTML attributes of rendered HTML input
-        - format (string): Python DateTime format eg. "%Y-%m-%d"
-        - options (dict): Options to customize the widget, see README
-    """
-
-    picker_type = 'MONTH'
-    format = '01/%m/%Y'
-    format_key = 'DATE_INPUT_FORMATS'
-
-
-class YearPickerInput(BasePickerInput):
-    """
-    Widget to display a Year-Picker Calendar on a DateField property.
-
-    Args:
-        - attrs (dict): HTML attributes of rendered HTML input
-        - format (string): Python DateTime format eg. "%Y-%m-%d"
-        - options (dict): Options to customize the widget, see README
-    """
-
-    picker_type = 'YEAR'
-    format = '01/01/%Y'
-    format_key = 'DATE_INPUT_FORMATS'
-
-    def _link_to(self, linked_picker):
-        """Customize the options when linked with other date-time input"""
-        yformat = self.config['options']['format'].replace('-01-01', '-12-31')
-        self.config['options']['format'] = yformat
diff --git a/tox.ini b/tox.ini
index 0526236f..a18af893 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,10 +1,10 @@
 [tox]
 envlist =
-    # Ubuntu 20.04 Python
-    py38-django22
-
     # Debian Bullseye Python
-    py39-django22
+    py39-django42
+
+    # Ubuntu 22.04 Python
+    py310-django42
 
     # Debian Bookworm Python
     py311-django42

From b7a71d911d22fe2adfd322ecdeb0dfc4b2b3490f Mon Sep 17 00:00:00 2001
From: bleizi <bleizi@crans.org>
Date: Mon, 12 Feb 2024 22:56:43 +0100
Subject: [PATCH 11/11] _get_validtion_exclusions() now return a set,
 PIL.Image.ANTIALIAS was renamed LANCZOS and typo in .gitlab-ci.yml

---
 .gitlab-ci.yml       | 2 +-
 apps/member/forms.py | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 23ba25aa..4f041867 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -37,7 +37,7 @@ py310-django42:
         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 py38-django42
+  script: tox -e py310-django42
 
 # Debian Bookworm
 py311-django42:
diff --git a/apps/member/forms.py b/apps/member/forms.py
index 420b35a4..0d78c726 100644
--- a/apps/member/forms.py
+++ b/apps/member/forms.py
@@ -33,7 +33,7 @@ class UserForm(forms.ModelForm):
         # 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"]
+        return super()._get_validation_exclusions() | {"username"}
 
     class Meta:
         model = User
@@ -122,7 +122,7 @@ class ImageForm(forms.Form):
                 frame = frame.crop((x, y, x + w, y + h))
                 frame = frame.resize(
                     (settings.PIC_WIDTH, settings.PIC_RATIO * settings.PIC_WIDTH),
-                    Image.ANTIALIAS,
+                    Image.LANCZOS,
                 )
                 frames.append(frame)