Include global and object-level permissions in export / import

adds test for transaction
This commit is contained in:
shamoon 2023-06-23 08:56:18 -07:00
parent 0880420ef6
commit bbd4659fbf
3 changed files with 95 additions and 5 deletions

View File

@ -11,13 +11,17 @@ from typing import Set
import tqdm import tqdm
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.contrib.auth.models import Permission
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.core import serializers from django.core import serializers
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.core.management.base import CommandError from django.core.management.base import CommandError
from django.db import transaction from django.db import transaction
from django.utils import timezone from django.utils import timezone
from filelock import FileLock from filelock import FileLock
from guardian.models import GroupObjectPermission
from guardian.models import UserObjectPermission
from documents.file_handling import delete_empty_directories from documents.file_handling import delete_empty_directories
from documents.file_handling import generate_filename from documents.file_handling import generate_filename
@ -261,6 +265,22 @@ class Command(BaseCommand):
serializers.serialize("json", UiSettings.objects.all()), serializers.serialize("json", UiSettings.objects.all()),
) )
manifest += json.loads(
serializers.serialize("json", ContentType.objects.all()),
)
manifest += json.loads(
serializers.serialize("json", Permission.objects.all()),
)
manifest += json.loads(
serializers.serialize("json", UserObjectPermission.objects.all()),
)
manifest += json.loads(
serializers.serialize("json", GroupObjectPermission.objects.all()),
)
# 3. Export files from each document # 3. Export files from each document
for index, document_dict in tqdm.tqdm( for index, document_dict in tqdm.tqdm(
enumerate(document_manifest), enumerate(document_manifest),

View File

@ -7,11 +7,15 @@ from pathlib import Path
import tqdm import tqdm
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import FieldDoesNotExist from django.core.exceptions import FieldDoesNotExist
from django.core.management import call_command from django.core.management import call_command
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.core.management.base import CommandError from django.core.management.base import CommandError
from django.core.serializers.base import DeserializationError from django.core.serializers.base import DeserializationError
from django.db import IntegrityError
from django.db import transaction
from django.db.models.signals import m2m_changed from django.db.models.signals import m2m_changed
from django.db.models.signals import post_save from django.db.models.signals import post_save
from filelock import FileLock from filelock import FileLock
@ -116,9 +120,13 @@ class Command(BaseCommand):
): ):
# Fill up the database with whatever is in the manifest # Fill up the database with whatever is in the manifest
try: try:
for manifest_path in manifest_paths: with transaction.atomic():
call_command("loaddata", manifest_path) for manifest_path in manifest_paths:
except (FieldDoesNotExist, DeserializationError) as e: # delete these since pk can change, re-created from import
ContentType.objects.all().delete()
Permission.objects.all().delete()
call_command("loaddata", manifest_path)
except (FieldDoesNotExist, DeserializationError, IntegrityError) as e:
self.stdout.write(self.style.ERROR("Database import failed")) self.stdout.write(self.style.ERROR("Database import failed"))
if ( if (
self.version is not None self.version is not None

View File

@ -7,11 +7,18 @@ from pathlib import Path
from unittest import mock from unittest import mock
from zipfile import ZipFile from zipfile import ZipFile
from django.contrib.auth.models import Group
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.core.management import call_command from django.core.management import call_command
from django.core.management.base import CommandError from django.core.management.base import CommandError
from django.db import IntegrityError
from django.test import TestCase from django.test import TestCase
from django.test import override_settings from django.test import override_settings
from django.utils import timezone from django.utils import timezone
from guardian.models import GroupObjectPermission
from guardian.models import UserObjectPermission
from guardian.shortcuts import assign_perm
from documents.management.commands import document_exporter from documents.management.commands import document_exporter
from documents.models import Correspondent from documents.models import Correspondent
@ -34,6 +41,8 @@ class TestExportImport(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
self.addCleanup(shutil.rmtree, self.target) self.addCleanup(shutil.rmtree, self.target)
self.user = User.objects.create(username="temp_admin") self.user = User.objects.create(username="temp_admin")
self.user2 = User.objects.create(username="user2")
self.group1 = Group.objects.create(name="group1")
self.d1 = Document.objects.create( self.d1 = Document.objects.create(
content="Content", content="Content",
@ -73,6 +82,9 @@ class TestExportImport(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
user=self.user, user=self.user,
) )
assign_perm("view_document", self.user2, self.d2)
assign_perm("view_document", self.group1, self.d3)
self.t1 = Tag.objects.create(name="t") self.t1 = Tag.objects.create(name="t")
self.dt1 = DocumentType.objects.create(name="dt") self.dt1 = DocumentType.objects.create(name="dt")
self.c1 = Correspondent.objects.create(name="c") self.c1 = Correspondent.objects.create(name="c")
@ -141,12 +153,12 @@ class TestExportImport(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
manifest = self._do_export(use_filename_format=use_filename_format) manifest = self._do_export(use_filename_format=use_filename_format)
self.assertEqual(len(manifest), 10) self.assertEqual(len(manifest), 149)
# dont include consumer or AnonymousUser users # dont include consumer or AnonymousUser users
self.assertEqual( self.assertEqual(
len(list(filter(lambda e: e["model"] == "auth.user", manifest))), len(list(filter(lambda e: e["model"] == "auth.user", manifest))),
1, 2,
) )
self.assertEqual( self.assertEqual(
@ -218,6 +230,9 @@ class TestExportImport(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
Correspondent.objects.all().delete() Correspondent.objects.all().delete()
DocumentType.objects.all().delete() DocumentType.objects.all().delete()
Tag.objects.all().delete() Tag.objects.all().delete()
Permission.objects.all().delete()
UserObjectPermission.objects.all().delete()
GroupObjectPermission.objects.all().delete()
self.assertEqual(Document.objects.count(), 0) self.assertEqual(Document.objects.count(), 0)
call_command("document_importer", "--no-progress-bar", self.target) call_command("document_importer", "--no-progress-bar", self.target)
@ -230,6 +245,9 @@ class TestExportImport(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
self.assertEqual(Document.objects.get(id=self.d2.id).title, "wow2") self.assertEqual(Document.objects.get(id=self.d2.id).title, "wow2")
self.assertEqual(Document.objects.get(id=self.d3.id).title, "wow2") self.assertEqual(Document.objects.get(id=self.d3.id).title, "wow2")
self.assertEqual(Document.objects.get(id=self.d4.id).title, "wow_dec") self.assertEqual(Document.objects.get(id=self.d4.id).title, "wow_dec")
self.assertEqual(GroupObjectPermission.objects.count(), 1)
self.assertEqual(UserObjectPermission.objects.count(), 1)
self.assertEqual(Permission.objects.count(), 108)
messages = check_sanity() messages = check_sanity()
# everything is alright after the test # everything is alright after the test
self.assertEqual(len(messages), 0) self.assertEqual(len(messages), 0)
@ -641,3 +659,47 @@ class TestExportImport(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
self.assertEqual(Document.objects.count(), 0) self.assertEqual(Document.objects.count(), 0)
call_command("document_importer", "--no-progress-bar", self.target) call_command("document_importer", "--no-progress-bar", self.target)
self.assertEqual(Document.objects.count(), 4) self.assertEqual(Document.objects.count(), 4)
def test_import_db_transaction_failed(self):
"""
GIVEN:
- Import from manifest started
WHEN:
- Import of database fails
THEN:
- ContentType & Permission objects are not deleted, db transaction rolled back
"""
shutil.rmtree(os.path.join(self.dirs.media_dir, "documents"))
shutil.copytree(
os.path.join(os.path.dirname(__file__), "samples", "documents"),
os.path.join(self.dirs.media_dir, "documents"),
)
self.assertEqual(ContentType.objects.count(), 27)
self.assertEqual(Permission.objects.count(), 108)
manifest = self._do_export()
with paperless_environment():
self.assertEqual(
len(list(filter(lambda e: e["model"] == "auth.permission", manifest))),
108,
)
# add 1 more to db to show objects are not re-created by import
Permission.objects.create(
name="test",
codename="test_perm",
content_type_id=1,
)
self.assertEqual(Permission.objects.count(), 109)
# will cause an import error
self.user.delete()
self.user = User.objects.create(username="temp_admin")
with self.assertRaises(IntegrityError):
call_command("document_importer", "--no-progress-bar", self.target)
self.assertEqual(ContentType.objects.count(), 27)
self.assertEqual(Permission.objects.count(), 109)