mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-11-10 22:56:19 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7d4014bbd | ||
|
|
a139bdff46 |
No files matched your search
@@ -38,7 +38,7 @@ repos:
|
|||||||
- json
|
- json
|
||||||
# See https://github.com/prettier/prettier/issues/15742 for the fork reason
|
# See https://github.com/prettier/prettier/issues/15742 for the fork reason
|
||||||
- repo: https://github.com/rbubley/mirrors-prettier
|
- repo: https://github.com/rbubley/mirrors-prettier
|
||||||
rev: 'v3.9.9'
|
rev: 'v3.9.10'
|
||||||
hooks:
|
hooks:
|
||||||
- id: prettier
|
- id: prettier
|
||||||
types_or:
|
types_or:
|
||||||
@@ -46,16 +46,16 @@ repos:
|
|||||||
- ts
|
- ts
|
||||||
- markdown
|
- markdown
|
||||||
additional_dependencies:
|
additional_dependencies:
|
||||||
- prettier@3.9.9
|
- prettier@3.9.10
|
||||||
- 'prettier-plugin-organize-imports@4.3.0'
|
- 'prettier-plugin-organize-imports@4.3.0'
|
||||||
# Python hooks
|
# Python hooks
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.16.9
|
rev: v0.17.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff-check
|
- id: ruff-check
|
||||||
- id: ruff-format
|
- id: ruff-format
|
||||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||||
rev: "v2.29.4"
|
rev: "v2.30.3"
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyproject-fmt
|
- id: pyproject-fmt
|
||||||
additional_dependencies: [tomli]
|
additional_dependencies: [tomli]
|
||||||
|
|||||||
@@ -873,6 +873,14 @@ If both the [PAPERLESS_ACCOUNT_DEFAULT_GROUPS](#PAPERLESS_ACCOUNT_DEFAULT_GROUPS
|
|||||||
|
|
||||||
Defaults to 1209600 (2 weeks)
|
Defaults to 1209600 (2 weeks)
|
||||||
|
|
||||||
|
#### [`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.
|
||||||
|
|
||||||
#### [`PAPERLESS_TOKEN_THROTTLE_RATE=<rate>`](#PAPERLESS_TOKEN_THROTTLE_RATE) {#PAPERLESS_TOKEN_THROTTLE_RATE}
|
#### [`PAPERLESS_TOKEN_THROTTLE_RATE=<rate>`](#PAPERLESS_TOKEN_THROTTLE_RATE) {#PAPERLESS_TOKEN_THROTTLE_RATE}
|
||||||
|
|
||||||
: Rate limit for the API token authentication endpoint (`/api/token/`), used to mitigate brute-force login attempts.
|
: Rate limit for the API token authentication endpoint (`/api/token/`), used to mitigate brute-force login attempts.
|
||||||
|
|||||||
+1
-6
@@ -212,12 +212,7 @@ extend-select = [
|
|||||||
"UP", # https://docs.astral.sh/ruff/rules/#pyupgrade-up
|
"UP", # https://docs.astral.sh/ruff/rules/#pyupgrade-up
|
||||||
"W", # https://docs.astral.sh/ruff/rules/#pycodestyle-e-w
|
"W", # https://docs.astral.sh/ruff/rules/#pycodestyle-e-w
|
||||||
]
|
]
|
||||||
ignore = [
|
ignore = [ "DJ001", "ICN001", "PLC0415", "RUF012", "SIM105" ]
|
||||||
"DJ001",
|
|
||||||
"PLC0415",
|
|
||||||
"RUF012",
|
|
||||||
"SIM105",
|
|
||||||
]
|
|
||||||
# Migrations
|
# Migrations
|
||||||
per-file-ignores."*/migrations/*.py" = [
|
per-file-ignores."*/migrations/*.py" = [
|
||||||
"E501",
|
"E501",
|
||||||
|
|||||||
@@ -631,9 +631,7 @@ class ConsumerPlugin(
|
|||||||
pk=self.metadata.actor_id,
|
pk=self.metadata.actor_id,
|
||||||
).first()
|
).first()
|
||||||
if actor is not None:
|
if actor is not None:
|
||||||
from auditlog.context import ( # type: ignore[import-untyped]
|
from auditlog.context import set_actor # type: ignore[import-untyped]
|
||||||
set_actor,
|
|
||||||
)
|
|
||||||
|
|
||||||
with set_actor(actor):
|
with set_actor(actor):
|
||||||
original_document.save()
|
original_document.save()
|
||||||
@@ -651,9 +649,7 @@ class ConsumerPlugin(
|
|||||||
|
|
||||||
# Create a log entry for the version addition, if enabled
|
# Create a log entry for the version addition, if enabled
|
||||||
if settings.AUDIT_LOG_ENABLED:
|
if settings.AUDIT_LOG_ENABLED:
|
||||||
from auditlog.models import ( # type: ignore[import-untyped]
|
from auditlog.models import LogEntry # type: ignore[import-untyped]
|
||||||
LogEntry,
|
|
||||||
)
|
|
||||||
|
|
||||||
LogEntry.objects.log_create(
|
LogEntry.objects.log_create(
|
||||||
instance=root_doc,
|
instance=root_doc,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from celery import Task
|
|||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.contrib.sessions.models import Session
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models.signals import post_save
|
from django.db.models.signals import post_save
|
||||||
@@ -925,3 +926,17 @@ def cleanup_expired_share_link_bundles() -> None:
|
|||||||
)
|
)
|
||||||
if count:
|
if count:
|
||||||
logger.info("Deleted %s expired share link bundle(s)", count)
|
logger.info("Deleted %s expired share link bundle(s)", count)
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def clear_expired_sessions() -> None:
|
||||||
|
"""
|
||||||
|
Remove expired database sessions in batches to limit the size of each delete.
|
||||||
|
"""
|
||||||
|
expired = Session.objects.filter(expire_date__lt=timezone.now())
|
||||||
|
while session_keys := list(
|
||||||
|
expired.order_by("expire_date").values_list("pk", flat=True)[:500],
|
||||||
|
):
|
||||||
|
expired.filter(pk__in=session_keys).delete()
|
||||||
|
|
||||||
|
logger.info("Cleared expired sessions")
|
||||||
@@ -5,8 +5,11 @@ from unittest import mock
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib.sessions.models import Session
|
||||||
|
from django.db import connection
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test import override_settings
|
from django.test import override_settings
|
||||||
|
from django.test.utils import CaptureQueriesContext
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from documents import tasks
|
from documents import tasks
|
||||||
@@ -612,3 +615,36 @@ class TestApplyAISuggestionsTask(DirectoriesMixin, TestCase):
|
|||||||
|
|
||||||
apply_suggestions.assert_not_called()
|
apply_suggestions.assert_not_called()
|
||||||
self.assertIn("no longer exists", "".join(cm.output))
|
self.assertIn("no longer exists", "".join(cm.output))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
class TestClearExpiredSessions:
|
||||||
|
def test_clears_only_expired_sessions_in_batches(self) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- More than one batch of expired sessions and one still-valid session
|
||||||
|
WHEN:
|
||||||
|
- The clear_expired_sessions task runs
|
||||||
|
THEN:
|
||||||
|
- Only expired sessions are removed, using multiple deletes
|
||||||
|
"""
|
||||||
|
now = timezone.now()
|
||||||
|
Session.objects.bulk_create(
|
||||||
|
Session(
|
||||||
|
session_key=f"expired-{i}",
|
||||||
|
session_data="",
|
||||||
|
expire_date=now - timedelta(days=1),
|
||||||
|
)
|
||||||
|
for i in range(501)
|
||||||
|
)
|
||||||
|
Session.objects.create(
|
||||||
|
session_key="valid",
|
||||||
|
session_data="",
|
||||||
|
expire_date=now + timedelta(days=1),
|
||||||
|
)
|
||||||
|
|
||||||
|
with CaptureQueriesContext(connection) as queries:
|
||||||
|
tasks.clear_expired_sessions()
|
||||||
|
|
||||||
|
assert set(Session.objects.values_list("session_key", flat=True)) == {"valid"}
|
||||||
|
assert sum(query["sql"].startswith("DELETE FROM") for query in queries) == 2
|
||||||
@@ -165,6 +165,17 @@ def parse_beat_schedule() -> dict:
|
|||||||
"expires": 23.0 * 60.0 * 60.0,
|
"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:
|
for task in tasks:
|
||||||
# Either get the environment setting or use the default
|
# Either get the environment setting or use the default
|
||||||
|
|||||||
@@ -182,6 +182,7 @@ def make_expected_schedule(
|
|||||||
workflow_expire = 59.0 * 60.0
|
workflow_expire = 59.0 * 60.0
|
||||||
llm_index_expire = 23.0 * 60.0 * 60.0
|
llm_index_expire = 23.0 * 60.0 * 60.0
|
||||||
share_link_cleanup_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] = {
|
schedule: dict[str, Any] = {
|
||||||
"Check all e-mail accounts": {
|
"Check all e-mail accounts": {
|
||||||
@@ -248,6 +249,14 @@ def make_expected_schedule(
|
|||||||
"headers": {"trigger_source": "scheduled"},
|
"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 {}
|
overrides = overrides or {}
|
||||||
@@ -298,6 +307,7 @@ class TestParseBeatSchedule:
|
|||||||
"PAPERLESS_WORKFLOW_SCHEDULED_TASK_CRON": "disable",
|
"PAPERLESS_WORKFLOW_SCHEDULED_TASK_CRON": "disable",
|
||||||
"PAPERLESS_LLM_INDEX_TASK_CRON": "disable",
|
"PAPERLESS_LLM_INDEX_TASK_CRON": "disable",
|
||||||
"PAPERLESS_SHARE_LINK_BUNDLE_CLEANUP_CRON": "disable",
|
"PAPERLESS_SHARE_LINK_BUNDLE_CLEANUP_CRON": "disable",
|
||||||
|
"PAPERLESS_SESSION_CLEANUP_TASK_CRON": "disable",
|
||||||
},
|
},
|
||||||
{},
|
{},
|
||||||
id="all-disabled",
|
id="all-disabled",
|
||||||
|
|||||||
Reference in new issue
Block a user