Try to automatically migrate user's DB settings values

This commit is contained in:
Trenton Holmes
2026-04-05 14:51:23 -07:00
parent 7f01b3a6f9
commit 7454ce5a20
3 changed files with 154 additions and 15 deletions
+4
View File
@@ -146,6 +146,10 @@ The new settings are independent:
- [`PAPERLESS_OCR_MODE`](configuration.md#PAPERLESS_OCR_MODE) controls OCR: `auto` (default), `force`, `redo`, `off`.
- [`PAPERLESS_ARCHIVE_FILE_GENERATION`](configuration.md#PAPERLESS_ARCHIVE_FILE_GENERATION) controls archive production: `auto` (default), `always`, `never`.
### Database configuration
If you changed OCR settings via the admin UI (ApplicationConfiguration), the database values are **migrated automatically** during the upgrade. `mode` values (`skip` / `skip_noarchive`) are mapped to their new equivalents and `skip_archive_file` values are converted to the new `archive_file_generation` field. After upgrading, review the OCR settings in the admin UI to confirm the migrated values match your intent.
### Action Required
Remove any `PAPERLESS_OCR_SKIP_ARCHIVE_FILE` variable from your environment. If you relied on `OCR_MODE=skip` or `OCR_MODE=skip_noarchive`, update accordingly:
@@ -3,6 +3,44 @@
from django.db import migrations
from django.db import models
_MODE_MAP = {
"skip": "auto",
"redo": "redo",
"force": "force",
"skip_noarchive": "auto",
}
_ARCHIVE_MAP = {
# never skip -> always generate
"never": "always",
# skip when text present -> auto
"with_text": "auto",
# always skip -> never generate
"always": "never",
}
def migrate_old_values(apps, schema_editor):
ApplicationConfiguration = apps.get_model("paperless", "ApplicationConfiguration")
for config in ApplicationConfiguration.objects.all():
old_mode = config.mode
old_skip = config.skip_archive_file
# Map the old mode value
if old_mode in _MODE_MAP:
config.mode = _MODE_MAP[old_mode]
# Map skip_archive_file -> archive_file_generation
if old_skip in _ARCHIVE_MAP:
config.archive_file_generation = _ARCHIVE_MAP[old_skip]
# skip_noarchive implied no archive file; set that if the user
# didn't already have an explicit skip_archive_file preference
if old_mode == "skip_noarchive" and old_skip is None:
config.archive_file_generation = "never"
config.save()
class Migration(migrations.Migration):
dependencies = [
@@ -10,21 +48,7 @@ class Migration(migrations.Migration):
]
operations = [
migrations.RemoveField(
model_name="applicationconfiguration",
name="skip_archive_file",
),
migrations.AddField(
model_name="applicationconfiguration",
name="archive_file_generation",
field=models.CharField(
blank=True,
choices=[("auto", "auto"), ("always", "always"), ("never", "never")],
max_length=8,
null=True,
verbose_name="Controls archive file generation",
),
),
# 1. Update mode choices in-place (old values still in the column)
migrations.AlterField(
model_name="applicationconfiguration",
name="mode",
@@ -41,4 +65,26 @@ class Migration(migrations.Migration):
verbose_name="Sets the OCR mode",
),
),
# 2. Add the new field
migrations.AddField(
model_name="applicationconfiguration",
name="archive_file_generation",
field=models.CharField(
blank=True,
choices=[("auto", "auto"), ("always", "always"), ("never", "never")],
max_length=8,
null=True,
verbose_name="Controls archive file generation",
),
),
# 3. Migrate data from old values to new
migrations.RunPython(
migrate_old_values,
migrations.RunPython.noop,
),
# 4. Drop the old field
migrations.RemoveField(
model_name="applicationconfiguration",
name="skip_archive_file",
),
]
@@ -0,0 +1,89 @@
from documents.tests.utils import TestMigrations
class TestMigrateSkipArchiveFile(TestMigrations):
migrate_from = "0007_optimize_integer_field_sizes"
migrate_to = "0008_replace_skip_archive_file"
def setUpBeforeMigration(self, apps):
ApplicationConfiguration = apps.get_model(
"paperless",
"ApplicationConfiguration",
)
ApplicationConfiguration.objects.all().delete()
ApplicationConfiguration.objects.create(
pk=1,
mode="skip",
skip_archive_file="always",
)
ApplicationConfiguration.objects.create(
pk=2,
mode="redo",
skip_archive_file="with_text",
)
ApplicationConfiguration.objects.create(
pk=3,
mode="force",
skip_archive_file="never",
)
ApplicationConfiguration.objects.create(
pk=4,
mode="skip_noarchive",
skip_archive_file=None,
)
ApplicationConfiguration.objects.create(
pk=5,
mode="skip_noarchive",
skip_archive_file="never",
)
ApplicationConfiguration.objects.create(pk=6, mode=None, skip_archive_file=None)
def _get_config(self, pk):
ApplicationConfiguration = self.apps.get_model(
"paperless",
"ApplicationConfiguration",
)
return ApplicationConfiguration.objects.get(pk=pk)
def test_skip_mapped_to_auto(self):
config = self._get_config(1)
assert config.mode == "auto"
def test_skip_archive_always_mapped_to_never(self):
config = self._get_config(1)
assert config.archive_file_generation == "never"
def test_redo_unchanged(self):
config = self._get_config(2)
assert config.mode == "redo"
def test_skip_archive_with_text_mapped_to_auto(self):
config = self._get_config(2)
assert config.archive_file_generation == "auto"
def test_force_unchanged(self):
config = self._get_config(3)
assert config.mode == "force"
def test_skip_archive_never_mapped_to_always(self):
config = self._get_config(3)
assert config.archive_file_generation == "always"
def test_skip_noarchive_mapped_to_auto(self):
config = self._get_config(4)
assert config.mode == "auto"
def test_skip_noarchive_implies_archive_never(self):
config = self._get_config(4)
assert config.archive_file_generation == "never"
def test_skip_noarchive_explicit_skip_archive_takes_precedence(self):
"""skip_archive_file=never maps to always, not overridden by skip_noarchive."""
config = self._get_config(5)
assert config.mode == "auto"
assert config.archive_file_generation == "always"
def test_null_values_remain_null(self):
config = self._get_config(6)
assert config.mode is None
assert config.archive_file_generation is None