fix: django connection settings (#1529)

This commit is contained in:
Markos Gogoulos
2026-05-19 21:34:18 +03:00
committed by GitHub
parent 7a02d25d0b
commit e89c4a3c85
8 changed files with 64 additions and 10 deletions
+12
View File
@@ -3,7 +3,9 @@ from __future__ import absolute_import
import os import os
from celery import Celery from celery import Celery
from celery.signals import worker_process_init
from django.conf import settings from django.conf import settings
from django.db import connections
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cms.settings") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cms.settings")
app = Celery("cms") app = Celery("cms")
@@ -20,3 +22,13 @@ app.conf.task_always_eager = settings.CELERY_TASK_ALWAYS_EAGER
app.conf.worker_prefetch_multiplier = 1 app.conf.worker_prefetch_multiplier = 1
@worker_process_init.connect
def close_db_pool_on_fork(**_):
# psycopg3's ConnectionPool is not fork-safe: children inherit dead sockets
# from the parent's pool and block on getconn() until PoolTimeout. Dispose
# the inherited pool here so each prefork child opens its own on first use.
# NB: plain conn.close() would only putconn() back into the broken pool.
for conn in connections.all():
conn.close_pool()
+19 -1
View File
@@ -407,7 +407,25 @@ LOGGING = {
}, },
} }
DATABASES = {"default": {"ENGINE": "django.db.backends.postgresql", "NAME": "mediacms", "HOST": "127.0.0.1", "PORT": "5432", "USER": "mediacms", "PASSWORD": "mediacms", "OPTIONS": {'pool': True}}} DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "mediacms",
"HOST": "127.0.0.1",
"PORT": "5432",
"USER": "mediacms",
"PASSWORD": "mediacms",
"OPTIONS": {
"pool": {
"min_size": 2,
"max_size": 8,
"timeout": 10,
"max_lifetime": 30 * 60,
"max_idle": 10 * 60,
}
},
}
}
REDIS_LOCATION = "redis://127.0.0.1:6379/1" REDIS_LOCATION = "redis://127.0.0.1:6379/1"
+9 -1
View File
@@ -12,7 +12,15 @@ DATABASES = {
"PORT": os.getenv('POSTGRES_PORT', '5432'), "PORT": os.getenv('POSTGRES_PORT', '5432'),
"USER": os.getenv('POSTGRES_USER', 'mediacms'), "USER": os.getenv('POSTGRES_USER', 'mediacms'),
"PASSWORD": os.getenv('POSTGRES_PASSWORD', 'mediacms'), "PASSWORD": os.getenv('POSTGRES_PASSWORD', 'mediacms'),
"OPTIONS": {'pool': True}, "OPTIONS": {
"pool": {
"min_size": 2,
"max_size": 8,
"timeout": 10,
"max_lifetime": 30 * 60,
"max_idle": 10 * 60,
}
},
} }
} }
+4 -2
View File
@@ -10,7 +10,7 @@ from django.conf import settings
from django.contrib.postgres.indexes import GinIndex from django.contrib.postgres.indexes import GinIndex
from django.contrib.postgres.search import SearchVectorField from django.contrib.postgres.search import SearchVectorField
from django.core.files import File from django.core.files import File
from django.db import models from django.db import models, transaction
from django.db.models import Func, Value from django.db.models import Func, Value
from django.db.models.signals import m2m_changed, post_delete, post_save, pre_delete from django.db.models.signals import m2m_changed, post_delete, post_save, pre_delete
from django.dispatch import receiver from django.dispatch import receiver
@@ -536,7 +536,9 @@ class Media(models.Model):
from .. import tasks from .. import tasks
tasks.produce_sprite_from_video.delay(self.friendly_token) # Defer until the surrounding transaction commits so the worker can
# actually find the Media row. Runs immediately if not in a tx.
transaction.on_commit(lambda token=self.friendly_token: tasks.produce_sprite_from_video.delay(token))
return True return True
def encode(self, profiles=[], force=True, chunkize=True): def encode(self, profiles=[], force=True, chunkize=True):
+7 -3
View File
@@ -660,7 +660,9 @@ class MediaBulkUserActions(APIView):
# Prioritize category_uids # Prioritize category_uids
if category_uids: if category_uids:
categories = Category.objects.filter(uid__in=category_uids) requested = Category.objects.filter(uid__in=category_uids)
allowed_ids = [cat.id for cat in requested if not cat.is_rbac_category or request.user.has_contributor_access_to_category(cat)]
categories = Category.objects.filter(id__in=allowed_ids)
elif lti_context_id: elif lti_context_id:
# Filter categories by lti_context_id and ensure they ARE RBAC categories # Filter categories by lti_context_id and ensure they ARE RBAC categories
potential_categories = Category.objects.filter(lti_context_id=lti_context_id, is_rbac_category=True) potential_categories = Category.objects.filter(lti_context_id=lti_context_id, is_rbac_category=True)
@@ -691,9 +693,11 @@ class MediaBulkUserActions(APIView):
if not category_uids: if not category_uids:
return Response({"detail": "category_uids is required for remove_from_category action"}, status=status.HTTP_400_BAD_REQUEST) return Response({"detail": "category_uids is required for remove_from_category action"}, status=status.HTTP_400_BAD_REQUEST)
categories = Category.objects.filter(uid__in=category_uids) requested = Category.objects.filter(uid__in=category_uids)
allowed_ids = [cat.id for cat in requested if not cat.is_rbac_category or request.user.has_contributor_access_to_category(cat)]
categories = Category.objects.filter(id__in=allowed_ids)
if not categories: if not categories:
return Response({"detail": "No matching categories found"}, status=status.HTTP_400_BAD_REQUEST) return Response({"detail": "No matching categories found or access denied"}, status=status.HTTP_400_BAD_REQUEST)
removed_count = 0 removed_count = 0
for category in categories: for category in categories:
+5 -1
View File
@@ -1,5 +1,6 @@
import base64 import base64
import logging import logging
import re
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from allauth.socialaccount.models import SocialApp from allauth.socialaccount.models import SocialApp
@@ -22,7 +23,10 @@ class SAMLAccountAdapter(DefaultSocialAccountAdapter):
def populate_user(self, request, sociallogin, data): def populate_user(self, request, sociallogin, data):
user = sociallogin.user user = sociallogin.user
user.username = sociallogin.account.uid raw_uid = sociallogin.account.uid or ""
# Match the user URL pattern in users/urls.py: only [\w@._-] is reverse-able.
sanitized = re.sub(r"[^\w.@-]", "_", raw_uid, flags=re.ASCII)
user.username = sanitized[:150] if sanitized else raw_uid
for item in ["name", "first_name", "last_name"]: for item in ["name", "first_name", "last_name"]:
if data.get(item): if data.get(item):
setattr(user, item, data[item]) setattr(user, item, data[item])
+2 -2
View File
@@ -7,8 +7,8 @@ from django.utils.translation import gettext_lazy as _
@deconstructible @deconstructible
class ASCIIUsernameValidator(validators.RegexValidator): class ASCIIUsernameValidator(validators.RegexValidator):
regex = r"^[\w.@]+$" regex = r"^[\w.@-]+$"
message = _("Enter a valid username. This value may contain only " "English letters and numbers") message = _("Enter a valid username. This value may contain only English letters, numbers, and '_', '.', '@', '-'.")
flags = re.ASCII flags = re.ASCII
+6
View File
@@ -1,3 +1,4 @@
from allauth.account.adapter import get_adapter
from django.conf import settings from django.conf import settings
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.password_validation import validate_password from django.contrib.auth.password_validation import validate_password
@@ -277,6 +278,11 @@ class UserList(APIView):
if not all([username, password, email, name]): if not all([username, password, email, name]):
return Response({"detail": "username, password, email, and name are required."}, status=status.HTTP_400_BAD_REQUEST) return Response({"detail": "username, password, email, and name are required."}, status=status.HTTP_400_BAD_REQUEST)
try:
username = get_adapter().clean_username(username, shallow=True)
except DjangoValidationError as e:
return Response({"detail": e.messages[0]}, status=status.HTTP_400_BAD_REQUEST)
if User.objects.filter(username=username).exists(): if User.objects.filter(username=username).exists():
return Response({"detail": "A user with that username already exists."}, status=status.HTTP_400_BAD_REQUEST) return Response({"detail": "A user with that username already exists."}, status=status.HTTP_400_BAD_REQUEST)