mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-11-09 08:01:46 +00:00
Chorehancement: add automatic clearing of sessions
This commit is contained in:
5 files changed
+100
No files matched your search
@@ -1871,6 +1871,14 @@ processing. This only has an effect if
|
||||
|
||||
Defaults to `0 1 * * *`, once per day.
|
||||
|
||||
#### [`PAPERLESS_SESSION_CLEANUP_TASK_CRON=<cron expression>`](#PAPERLESS_SESSION_CLEANUP_TASK_CRON) {#PAPERLESS_SESSION_CLEANUP_TASK_CRON}
|
||||
|
||||
: Configures the schedule to remove expired sessions from the session store.
|
||||
Django does not prune these automatically, so without this they would
|
||||
accumulate indefinitely.
|
||||
|
||||
Defaults to `0 3 * * *`, once per day at 03:00.
|
||||
|
||||
## Share links
|
||||
|
||||
#### [`PAPERLESS_SHARE_LINK_BUNDLE_CLEANUP_CRON=<cron expression>`](#PAPERLESS_SHARE_LINK_BUNDLE_CLEANUP_CRON) {#PAPERLESS_SHARE_LINK_BUNDLE_CLEANUP_CRON}
|
||||
|
||||
@@ -4,6 +4,7 @@ import shutil
|
||||
import uuid
|
||||
import zipfile
|
||||
from collections.abc import Callable
|
||||
from importlib import import_module
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
from tempfile import mkstemp
|
||||
@@ -925,3 +926,19 @@ def cleanup_expired_share_link_bundles() -> None:
|
||||
)
|
||||
if count:
|
||||
logger.info("Deleted %s expired share link bundle(s)", count)
|
||||
|
||||
|
||||
@shared_task
|
||||
def clear_expired_sessions() -> None:
|
||||
"""
|
||||
Removes expired sessions from the session store. Django does not prune these automatically.
|
||||
"""
|
||||
engine = import_module(settings.SESSION_ENGINE)
|
||||
try:
|
||||
engine.SessionStore.clear_expired()
|
||||
logger.info("Cleared expired sessions")
|
||||
except NotImplementedError:
|
||||
logger.warning(
|
||||
"Session engine '%s' does not support clearing expired sessions",
|
||||
settings.SESSION_ENGINE,
|
||||
)
|
||||
@@ -612,3 +612,57 @@ class TestApplyAISuggestionsTask(DirectoriesMixin, TestCase):
|
||||
|
||||
apply_suggestions.assert_not_called()
|
||||
self.assertIn("no longer exists", "".join(cm.output))
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestClearExpiredSessions:
|
||||
def test_clears_only_expired_sessions(self) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- One expired and one still-valid session in the session store
|
||||
WHEN:
|
||||
- The clear_expired_sessions task runs
|
||||
THEN:
|
||||
- Only the expired session is removed
|
||||
"""
|
||||
from django.contrib.sessions.models import Session
|
||||
|
||||
now = timezone.now()
|
||||
Session.objects.create(
|
||||
session_key="expired",
|
||||
session_data="",
|
||||
expire_date=now - timedelta(days=1),
|
||||
)
|
||||
Session.objects.create(
|
||||
session_key="valid",
|
||||
session_data="",
|
||||
expire_date=now + timedelta(days=1),
|
||||
)
|
||||
|
||||
tasks.clear_expired_sessions()
|
||||
|
||||
assert set(Session.objects.values_list("session_key", flat=True)) == {"valid"}
|
||||
|
||||
def test_unsupported_engine_logs_warning(
|
||||
self,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- A session engine that does not support clearing expired sessions
|
||||
WHEN:
|
||||
- The clear_expired_sessions task runs
|
||||
THEN:
|
||||
- The error is swallowed and a warning is logged
|
||||
"""
|
||||
fake_engine = mock.Mock()
|
||||
fake_engine.SessionStore.clear_expired.side_effect = NotImplementedError
|
||||
|
||||
with (
|
||||
mock.patch("documents.tasks.import_module", return_value=fake_engine),
|
||||
override_settings(SESSION_ENGINE="some.unsupported.engine"),
|
||||
caplog.at_level("WARNING", logger="paperless.tasks"),
|
||||
):
|
||||
tasks.clear_expired_sessions()
|
||||
|
||||
assert "does not support clearing expired sessions" in caplog.text
|
||||
@@ -165,6 +165,17 @@ def parse_beat_schedule() -> dict:
|
||||
"expires": 23.0 * 60.0 * 60.0,
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "Clear expired sessions",
|
||||
"env_key": "PAPERLESS_SESSION_CLEANUP_TASK_CRON",
|
||||
# Default daily at 03:00
|
||||
"env_default": "0 3 * * *",
|
||||
"task": "documents.tasks.clear_expired_sessions",
|
||||
"options": {
|
||||
# 1 hour before default schedule sends again
|
||||
"expires": 23.0 * 60.0 * 60.0,
|
||||
},
|
||||
},
|
||||
]
|
||||
for task in tasks:
|
||||
# Either get the environment setting or use the default
|
||||
|
||||
@@ -182,6 +182,7 @@ def make_expected_schedule(
|
||||
workflow_expire = 59.0 * 60.0
|
||||
llm_index_expire = 23.0 * 60.0 * 60.0
|
||||
share_link_cleanup_expire = 23.0 * 60.0 * 60.0
|
||||
session_cleanup_expire = 23.0 * 60.0 * 60.0
|
||||
|
||||
schedule: dict[str, Any] = {
|
||||
"Check all e-mail accounts": {
|
||||
@@ -248,6 +249,14 @@ def make_expected_schedule(
|
||||
"headers": {"trigger_source": "scheduled"},
|
||||
},
|
||||
},
|
||||
"Clear expired sessions": {
|
||||
"task": "documents.tasks.clear_expired_sessions",
|
||||
"schedule": crontab(minute=0, hour="3"),
|
||||
"options": {
|
||||
"expires": session_cleanup_expire,
|
||||
"headers": {"trigger_source": "scheduled"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
overrides = overrides or {}
|
||||
@@ -298,6 +307,7 @@ class TestParseBeatSchedule:
|
||||
"PAPERLESS_WORKFLOW_SCHEDULED_TASK_CRON": "disable",
|
||||
"PAPERLESS_LLM_INDEX_TASK_CRON": "disable",
|
||||
"PAPERLESS_SHARE_LINK_BUNDLE_CLEANUP_CRON": "disable",
|
||||
"PAPERLESS_SESSION_CLEANUP_TASK_CRON": "disable",
|
||||
},
|
||||
{},
|
||||
id="all-disabled",
|
||||
|
||||
Reference in new issue
Block a user