Python 3.11, relevant upgrades
authorRadek Czajka <radekczajka@wolnelektury.pl>
Tue, 10 Nov 2026 12:00:14 +0000 (13:00 +0100)
committerRadek Czajka <radekczajka@wolnelektury.pl>
Tue, 10 Nov 2026 12:00:14 +0000 (13:00 +0100)
Dockerfile
docker-compose.yml
requirements/requirements.txt
src/catalogue/fields.py
src/wolnelektury/settings/auth.py
src/wolnelektury/settings/static.py
src/wolnelektury/utils.py

index f6debff..434dade 100644 (file)
@@ -12,7 +12,7 @@
 # Global build arguments. Stages using UID/GID must redeclare them
 # (bare `ARG UID`) to bring them into scope; the defaults are set here.
 
 # Global build arguments. Stages using UID/GID must redeclare them
 # (bare `ARG UID`) to bring them into scope; the defaults are set here.
 
-ARG PYTHON_IMAGE=python:3.10-trixie
+ARG PYTHON_IMAGE=python:3.11-trixie
 ARG UID=8100
 ARG GID=8100
 
 ARG UID=8100
 ARG GID=8100
 
index 81df203..afa1786 100644 (file)
@@ -17,6 +17,7 @@ x-app-base: &app-base
    - ./var/counters/:/app/var/counters
    - ${LIBRARIAN:-/nonexistent}:/${LIBRARIAN:+edita}ble/librarian
    - ipython:/app/.ipython
    - ./var/counters/:/app/var/counters
    - ${LIBRARIAN:-/nonexistent}:/${LIBRARIAN:+edita}ble/librarian
    - ipython:/app/.ipython
+   - ./var/.bash_history:/app/.bash_history
   depends_on:
    - db
    - redis
   depends_on:
    - db
    - redis
index 9c3aa79..0743b99 100644 (file)
@@ -11,13 +11,13 @@ django-pipeline==4.1.0
 jsmin==3.0.1
 
 fnp-django-pagination==2.2.12
 jsmin==3.0.1
 
 fnp-django-pagination==2.2.12
-django-modeltranslation==0.18.12
-django-allauth[socialaccount,openid]==0.63.6
-django-extensions==3.2.3
-djangorestframework==3.15.1
-django-filter==23.5
+django-modeltranslation==0.19.17
+django-allauth[socialaccount,openid]==65.21.1
+django-extensions==4.1
+djangorestframework==3.17.2
+django-filter==25.1
 djangorestframework-xml==2.0.0
 djangorestframework-xml==2.0.0
-django-admin-ordering==0.18
+django-admin-ordering==0.22
 django-countries==7.6.1
 
 django-forms-builder @ git+https://github.com/rczajka/django-forms-builder@6533f9442f004d44ed362202b6bdab82b73dd5e9
 django-countries==7.6.1
 
 django-forms-builder @ git+https://github.com/rczajka/django-forms-builder@6533f9442f004d44ed362202b6bdab82b73dd5e9
@@ -33,9 +33,9 @@ django-honeypot==1.2.1
 
 Feedparser==6.0.14
 
 
 Feedparser==6.0.14
 
-Pillow==12.3.0
+Pillow==13.0.0
 mutagen==1.48.1
 mutagen==1.48.1
-sorl-thumbnail==12.10.0
+sorl-thumbnail==13.0.0
 
 zipstream-ng==1.9.3
 
 
 zipstream-ng==1.9.3
 
@@ -46,11 +46,12 @@ librarian==26.11
 # celery tasks
 celery[redis]==5.6.2
 
 # celery tasks
 celery[redis]==5.6.2
 
-oaipmh==3.2.1
+oaipmh==4.0.0
 
 sentry-sdk==2.74.0
 
 requests==2.34.2
 
 sentry-sdk==2.74.0
 
 requests==2.34.2
+requests_oauthlib==2.0.0
 
 paypalrestsdk
 
 
 paypalrestsdk
 
index 2d35357..ee64ee4 100644 (file)
@@ -1,9 +1,9 @@
 # This file is part of Wolne Lektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Wolne Lektury. See NOTICE for more information.
 #
 # This file is part of Wolne Lektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Wolne Lektury. See NOTICE for more information.
 #
+from importlib.metadata import version
 import io
 import os
 import io
 import os
-import pkg_resources
 import random
 import time
 from urllib.request import urlopen
 import random
 import time
 from urllib.request import urlopen
@@ -151,7 +151,7 @@ class EbookField(models.FileField):
 
     def get_current_etag(self):
         MediaInsertSet = apps.get_model('annoy', 'MediaInsertSet')
 
     def get_current_etag(self):
         MediaInsertSet = apps.get_model('annoy', 'MediaInsertSet')
-        librarian_version = pkg_resources.get_distribution("librarian").version
+        librarian_version = version("librarian")
         etag = librarian_version
         mis = MediaInsertSet.get_for_format(self.ext)
         if mis is not None:
         etag = librarian_version
         mis = MediaInsertSet.get_for_format(self.ext)
         if mis is not None:
index 2fe844c..0acfa90 100644 (file)
@@ -6,7 +6,7 @@ AUTHENTICATION_BACKENDS = [
     'allauth.account.auth_backends.AuthenticationBackend',
 ]
 ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = 2
     'allauth.account.auth_backends.AuthenticationBackend',
 ]
 ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = 2
-ACCOUNT_AUTHENTICATION_METHOD = "username_email"
+ACCOUNT_LOGIN_METHODS = {'email', 'username'}
 FEATURE_CONFIRM_USER = False
 
 LOGIN_URL = '/uzytkownik/login/'
 FEATURE_CONFIRM_USER = False
 
 LOGIN_URL = '/uzytkownik/login/'
index 9c2960f..505d139 100644 (file)
@@ -144,7 +144,14 @@ PIPELINE = {
     'CSSMIN_ARGUMENTS': '--stdin --style=compressed --no-source-map',
 }
 
     'CSSMIN_ARGUMENTS': '--stdin --style=compressed --no-source-map',
 }
 
-STATICFILES_STORAGE = 'fnpdjango.pipeline_storage.GzipPipelineManifestStorage'
+STORAGES = {
+    'default': {
+        'BACKEND': 'django.core.files.storage.FileSystemStorage'
+    },
+    'staticfiles': {
+        'BACKEND': 'fnpdjango.pipeline_storage.GzipPipelineManifestStorage'
+    },
+}
 
 
 STATICFILES_FINDERS = [
 
 
 STATICFILES_FINDERS = [
index dfa8522..245c076 100644 (file)
@@ -4,9 +4,7 @@
 import codecs
 import csv
 from functools import wraps
 import codecs
 import csv
 from functools import wraps
-from inspect import getargspec
 from io import BytesIO
 from io import BytesIO
-import json
 import os
 import re
 
 import os
 import re
 
@@ -14,7 +12,6 @@ from django.conf import settings
 from django.contrib import admin
 from django.core.cache import cache
 from django.core.mail import send_mail
 from django.contrib import admin
 from django.core.cache import cache
 from django.core.mail import send_mail
-from django.http import HttpResponse
 from django.template.loader import render_to_string
 from django.utils.translation import get_language
 from django.conf import settings
 from django.template.loader import render_to_string
 from django.utils.translation import get_language
 from django.conf import settings
@@ -30,81 +27,6 @@ def makedirs(path):
         os.makedirs(path)
 
 
         os.makedirs(path)
 
 
-def stringify_keys(dictionary):
-    return dict((keyword.encode('ascii'), value)
-                for keyword, value in dictionary.items())
-
-
-def json_encode(obj, sort_keys=True, ensure_ascii=False):
-    return json.dumps(obj, sort_keys=sort_keys, ensure_ascii=ensure_ascii)
-
-
-def json_decode(obj):
-    return json.loads(obj)
-
-
-def json_decode_fallback(value):
-    try:
-        return json_decode(value)
-    except ValueError:
-        return value
-
-
-class AjaxError(Exception):
-    pass
-
-
-def ajax(login_required=False, method=None, template=None, permission_required=None):
-    def decorator(fun):
-        @wraps(fun)
-        def ajax_view(request):
-            kwargs = {}
-            request_params = None
-            if method == 'post':
-                request_params = request.POST
-            elif method == 'get':
-                request_params = request.GET
-            fun_params, xx, fun_kwargs, defaults = getargspec(fun)
-            if defaults:
-                required_params = fun_params[1:-len(defaults)]
-            else:
-                required_params = fun_params[1:]
-            missing_params = set(required_params) - set(request_params)
-            if missing_params:
-                res = {
-                    'result': 'missing params',
-                    'missing': ', '.join(missing_params),
-                }
-            else:
-                if request_params:
-                    request_params = dict(
-                        (key, json_decode_fallback(value))
-                        for key, value in request_params.items()
-                        if fun_kwargs or key in fun_params)
-                    kwargs.update(stringify_keys(request_params))
-                res = None
-                if login_required and not request.user.is_authenticated:
-                    res = {'result': 'logout'}
-                if (permission_required and
-                        not request.user.has_perm(permission_required)):
-                    res = {'result': 'access denied'}
-            if not res:
-                try:
-                    res = fun(request, **kwargs)
-                    if res and template:
-                        res = {'html': render_to_string(template, res, request=request)}
-                except AjaxError as e:
-                    res = {'result': e.args[0]}
-            if 'result' not in res:
-                res['result'] = 'ok'
-            return HttpResponse(json_encode(res), content_type='application/json; charset=utf-8',
-                                status=200 if res['result'] == 'ok' else 400)
-
-        return ajax_view
-
-    return decorator
-
-
 def send_noreply_mail(subject, message, recipient_list, **kwargs):
     send_mail(
         '[WolneLektury] ' + subject,
 def send_noreply_mail(subject, message, recipient_list, **kwargs):
     send_mail(
         '[WolneLektury] ' + subject,