diff options
author | Étienne Loks <etienne.loks@iggdrasil.net> | 2019-09-11 14:34:25 +0200 |
---|---|---|
committer | Étienne Loks <etienne.loks@iggdrasil.net> | 2019-09-11 14:35:02 +0200 |
commit | b103ce9f92012db9d0bc9164b76705633c3c453a (patch) | |
tree | a22f1e8ce124fa48d218d040b7eaafb33915d84b | |
parent | 81dc5e04cd5c71c1fc0f8cd1d4be73620da8e8f0 (diff) | |
download | Ishtar-b103ce9f92012db9d0bc9164b76705633c3c453a.tar.bz2 Ishtar-b103ce9f92012db9d0bc9164b76705633c3c453a.zip |
Serialization - Import/Export: manage put an release of locks
-rw-r--r-- | archaeological_context_records/serializers.py | 12 | ||||
-rw-r--r-- | archaeological_finds/serializers.py | 12 | ||||
-rw-r--r-- | archaeological_operations/serializers.py | 13 | ||||
-rw-r--r-- | archaeological_operations/tests.py | 74 | ||||
-rw-r--r-- | archaeological_warehouse/serializers.py | 12 | ||||
-rw-r--r-- | ishtar_common/admin.py | 8 | ||||
-rw-r--r-- | ishtar_common/migrations/0109_auto_20190911_1256.py | 33 | ||||
-rw-r--r-- | ishtar_common/models.py | 11 | ||||
-rw-r--r-- | ishtar_common/serializers.py | 62 | ||||
-rw-r--r-- | ishtar_common/serializers_utils.py | 3 | ||||
-rw-r--r-- | ishtar_common/tasks.py | 7 | ||||
-rw-r--r-- | ishtar_common/tests.py | 6 | ||||
-rw-r--r-- | ishtar_common/views.py | 5 | ||||
-rw-r--r-- | ishtar_common/views_item.py | 3 |
14 files changed, 220 insertions, 41 deletions
diff --git a/archaeological_context_records/serializers.py b/archaeological_context_records/serializers.py index 9f454eb35..a41e2bd40 100644 --- a/archaeological_context_records/serializers.py +++ b/archaeological_context_records/serializers.py @@ -49,7 +49,8 @@ def generate_warehouse_queryset(ids): def cr_serialization(archive=False, return_empty_types=False, archive_name=None, operation_queryset=None, site_queryset=None, cr_queryset=None, find_queryset=None, - warehouse_queryset=None, get_queryset=False, no_geo=True): + warehouse_queryset=None, get_queryset=False, no_geo=True, + put_locks=False, lock_user=None): result_queryset = {} find_ids, cr_ids = None, None if operation_queryset: @@ -134,6 +135,15 @@ def cr_serialization(archive=False, return_empty_types=False, result = generic_get_results(CR_MODEL_LIST, "context_records", result_queryset=result_queryset, no_geo=no_geo) + if put_locks: + for model in CR_MODEL_LIST: + if not hasattr(model, "locked"): + continue + q = model.objects + if result_queryset and model.__name__ in result_queryset: + q = result_queryset[model.__name__] + q.update(locked=True, lock_user=lock_user) + full_archive = archive_serialization( result, archive_dir="context_records", archive=archive, return_empty_types=return_empty_types, archive_name=archive_name, diff --git a/archaeological_finds/serializers.py b/archaeological_finds/serializers.py index 7e79123e6..e130a0ef8 100644 --- a/archaeological_finds/serializers.py +++ b/archaeological_finds/serializers.py @@ -43,7 +43,8 @@ def find_serialization(archive=False, return_empty_types=False, archive_name=None, operation_queryset=None, site_queryset=None, cr_queryset=None, find_queryset=None, warehouse_queryset=None, - get_queryset=False, no_geo=True): + get_queryset=False, no_geo=True, + put_locks=False, lock_user=None): result_queryset = {} if operation_queryset: operation_ids = operation_queryset.values_list("id", flat=True) @@ -94,6 +95,15 @@ def find_serialization(archive=False, return_empty_types=False, result = generic_get_results(FIND_MODEL_LIST, "finds", result_queryset=result_queryset, no_geo=no_geo) + if put_locks: + for model in FIND_MODEL_LIST: + if not hasattr(model, "locked"): + continue + q = model.objects + if result_queryset and model.__name__ in result_queryset: + q = result_queryset[model.__name__] + q.update(locked=True, lock_user=lock_user) + full_archive = archive_serialization( result, archive_dir="operations", archive=archive, return_empty_types=return_empty_types, archive_name=archive_name, diff --git a/archaeological_operations/serializers.py b/archaeological_operations/serializers.py index 6fd0f10d6..91b0b1b0f 100644 --- a/archaeological_operations/serializers.py +++ b/archaeological_operations/serializers.py @@ -57,7 +57,8 @@ def operation_serialization(archive=False, return_empty_types=False, archive_name=None, operation_queryset=None, site_queryset=None, cr_queryset=None, find_queryset=None, warehouse_queryset=None, - get_queryset=False, no_geo=True): + get_queryset=False, no_geo=True, + put_locks=False, lock_user=None): result_queryset = {} if operation_queryset: operation_ids = operation_queryset.values_list("id", flat=True) @@ -124,6 +125,16 @@ def operation_serialization(archive=False, return_empty_types=False, result = generic_get_results(OPERATION_MODEL_LIST, "operations", result_queryset=result_queryset, no_geo=no_geo) + + if put_locks: + for model in OPERATION_MODEL_LIST: + if not hasattr(model, "locked"): + continue + q = model.objects + if result_queryset and model.__name__ in result_queryset: + q = result_queryset[model.__name__] + q.update(locked=True, lock_user=lock_user) + full_archive = archive_serialization( result, archive_dir="operations", archive=archive, return_empty_types=return_empty_types, archive_name=archive_name, diff --git a/archaeological_operations/tests.py b/archaeological_operations/tests.py index 72cecee95..6c20df85a 100644 --- a/archaeological_operations/tests.py +++ b/archaeological_operations/tests.py @@ -753,6 +753,7 @@ class SerializationTest(GenericSerializationTest, TestCase): operation = create_operation(self.user, values={"code_patriarche": "66666"}) ope2 = create_operation(self.user, values={"code_patriarche": "66667"}) + self.operations = [operation, ope2] models.RecordRelations.objects.create( left_record=operation, right_record=ope2, relation_type=models.RelationType.objects.all()[0] @@ -815,6 +816,41 @@ class SerializationTest(GenericSerializationTest, TestCase): ) self.assertEqual(len(rel_json), 0) + def reinit_lock(self): + for operation in self.operations: + operation = models.Operation.objects.get(pk=operation.pk) + operation.locked = False + operation.lock_user = None + operation.save() + + def test_lock(self): + self.reinit_lock() + self.generic_serialization_test( + serializers.operation_serialization, no_test=True, + kwargs={"put_locks": False}) + for operation in self.operations: + operation = models.Operation.objects.get(pk=operation.pk) + self.assertFalse(operation.locked) + self.assertIsNone(operation.lock_user) + + self.reinit_lock() + self.generic_serialization_test( + serializers.operation_serialization, no_test=True, + kwargs={"put_locks": True}) + for operation in self.operations: + operation = models.Operation.objects.get(pk=operation.pk) + self.assertTrue(operation.locked) + self.assertIsNone(operation.lock_user) + + self.reinit_lock() + self.generic_serialization_test( + serializers.operation_serialization, no_test=True, + kwargs={"put_locks": True, "lock_user": self.user}) + for operation in self.operations: + operation = models.Operation.objects.get(pk=operation.pk) + self.assertTrue(operation.locked) + self.assertEqual(operation.lock_user, self.user) + def test_restore(self): current_number, zip_filename = self.generic_restore_test_genzip( serializers.OPERATION_MODEL_LIST, @@ -822,6 +858,31 @@ class SerializationTest(GenericSerializationTest, TestCase): self.generic_restore_test(zip_filename, current_number, serializers.OPERATION_MODEL_LIST) + def test_unlock_on_restore(self): + current_number, zip_filename = self.generic_restore_test_genzip( + serializers.OPERATION_MODEL_LIST, + serializers.operation_serialization, + kwargs={"put_locks": True, "lock_user": self.user}) + + self.generic_restore_test(zip_filename, current_number, + serializers.OPERATION_MODEL_LIST, + delete_existing=False) + for operation in self.operations: + operation = models.Operation.objects.get( + code_patriarche=operation.code_patriarche) + self.assertTrue(operation.locked) + self.assertEqual(operation.lock_user, self.user) + + self.generic_restore_test(zip_filename, current_number, + serializers.OPERATION_MODEL_LIST, + delete_existing=False, + release_locks=True) + for operation in self.operations: + operation = models.Operation.objects.get( + code_patriarche=operation.code_patriarche) + self.assertFalse(operation.locked) + self.assertIsNone(operation.lock_user) + def test_historization_on_restore(self): current_number, zip_filename = self.generic_restore_test_genzip( serializers.OPERATION_MODEL_LIST, @@ -1620,18 +1681,23 @@ class LockTest(TestCase, OperationInitTest): step = 'selec-operation_modification' response = cls_wiz.wizard_post( self.client, url, step, {'pk': self.operation.pk}) - self.assertIn("locked for edition", response.content.decode(), + msg = str(_("This operation is locked for edition.") + ).replace("'", "'") + self.assertIn(msg, response.content.decode(), msg="wizard lock for edition not effective") def test_qa_lock(self): url = reverse('operation-qa-bulk-update', args=[self.operation.pk]) response = self.client.get(url) - self.assertRedirects(response, reverse('qa-not-available')) + self.assertRedirects(response, reverse('qa-not-available', + args=["locked"])) def test_sheet_lock(self): url = reverse('show-operation', kwargs={'pk': self.operation.pk}) response = self.client.get(url) - self.assertIn('This item has been locked', response.content.decode(), + msg = str(_("This item has been locked. Edition is disabled.") + ).replace("'", "\'") + self.assertIn(msg, response.content.decode(), msg="lock not displayed on sheet") @@ -3279,7 +3345,7 @@ class OperationQATest(OperationInitTest, TestCase): reverse('operation-qa-bulk-update-confirm', args=[pks]), {'qa_operation_type': operation_type.pk} ) - self.assertRedirects(response, '/qa-not-available/') + self.assertRedirects(response, '/qa-not-available/locked/') class DocumentQATest(OperationInitTest, TestCase): diff --git a/archaeological_warehouse/serializers.py b/archaeological_warehouse/serializers.py index ff7c23778..b0cf4d96f 100644 --- a/archaeological_warehouse/serializers.py +++ b/archaeological_warehouse/serializers.py @@ -60,7 +60,8 @@ def warehouse_serialization(archive=False, return_empty_types=False, archive_name=None, operation_queryset=None, site_queryset=None, cr_queryset=None, find_queryset=None, warehouse_queryset=None, - get_queryset=False, no_geo=True): + get_queryset=False, no_geo=True, + put_locks=False, lock_user=None): result_queryset = {} if operation_queryset: operation_ids = operation_queryset.values_list("id", flat=True) @@ -100,6 +101,15 @@ def warehouse_serialization(archive=False, return_empty_types=False, return result_queryset result = generic_get_results(WAREHOUSE_MODEL_LIST, "warehouse", result_queryset=result_queryset, no_geo=no_geo) + if put_locks: + for model in WAREHOUSE_MODEL_LIST: + if not hasattr(model, "locked"): + continue + q = model.objects + if result_queryset and model.__name__ in result_queryset: + q = result_queryset[model.__name__] + q.update(locked=True, lock_user=lock_user) + full_archive = archive_serialization( result, archive_dir="warehouse", archive=archive, return_empty_types=return_empty_types, archive_name=archive_name, diff --git a/ishtar_common/admin.py b/ishtar_common/admin.py index 691e68b26..bd068890e 100644 --- a/ishtar_common/admin.py +++ b/ishtar_common/admin.py @@ -1442,18 +1442,12 @@ class ExportTaskAdmin(admin.ModelAdmin): 'launch_date', 'finished_date'] list_filter = ['state'] actions = [launch_export_action] + form = make_ajax_form(models.ExportTask, {'lock_user': 'user'}) admin_site.register(models.ExportTask, ExportTaskAdmin) -""" - - - -""" - - def launch_import_action(modeladmin, request, queryset): model = modeladmin.model back_url = reverse( diff --git a/ishtar_common/migrations/0109_auto_20190911_1256.py b/ishtar_common/migrations/0109_auto_20190911_1256.py new file mode 100644 index 000000000..09d6d36f7 --- /dev/null +++ b/ishtar_common/migrations/0109_auto_20190911_1256.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.18 on 2019-09-11 12:56 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('ishtar_common', '0108_auto_20190910_1323'), + ] + + operations = [ + migrations.AddField( + model_name='exporttask', + name='lock_user', + field=models.ForeignKey(blank=True, help_text='Owner of the lock if item are locked. Warning: if no user is provided the locks can be remove by any user with the permission to edit.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Lock user'), + ), + migrations.AddField( + model_name='exporttask', + name='put_locks', + field=models.BooleanField(default=False, verbose_name='Put locks on associated items'), + ), + migrations.AddField( + model_name='importtask', + name='releasing_locks', + field=models.BooleanField(default=False, verbose_name='Releasing locks on associated items'), + ), + ] diff --git a/ishtar_common/models.py b/ishtar_common/models.py index 1c17a4421..f2ebb3c29 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -5710,6 +5710,15 @@ class ExportTask(models.Model): "export")) state = models.CharField(_("State"), max_length=2, choices=EXPORT_STATE, default='C') + put_locks = models.BooleanField(_("Put locks on associated items"), + default=False) + lock_user = models.ForeignKey( + User, related_name='+', on_delete=models.SET_NULL, + verbose_name=_("Lock user"), blank=True, null=True, + help_text=_("Owner of the lock if item are locked. Warning: if no " + "user is provided the locks can be remove by any user " + "with the permission to edit.") + ) export_types = models.BooleanField(_("Export types"), default=True) export_conf = models.BooleanField(_("Export configuration"), default=True) export_importers = models.BooleanField(_("Export importers"), default=True) @@ -5769,6 +5778,8 @@ class ImportTask(models.Model): delete_before = models.BooleanField( _("Delete before adding"), default=False, help_text=_("Delete existing items before adding")) + releasing_locks = models.BooleanField( + _("Releasing locks on associated items"), default=False) source = models.FileField(_("Source"), upload_to="imports/%Y/%m/") class Meta: diff --git a/ishtar_common/serializers.py b/ishtar_common/serializers.py index 4b06a9be3..0914a297a 100644 --- a/ishtar_common/serializers.py +++ b/ishtar_common/serializers.py @@ -122,7 +122,8 @@ def directory_serialization(archive=False, return_empty_types=False, def document_serialization(archive=False, return_empty_types=False, archive_name=None, operation_queryset=None, site_queryset=None, cr_queryset=None, - find_queryset=None, warehouse_queryset=None): + find_queryset=None, warehouse_queryset=None, + put_locks=False, lock_user=None): result_queryset = {} get_queryset_attr = None if operation_queryset: @@ -164,6 +165,12 @@ def document_serialization(archive=False, return_empty_types=False, result = generic_get_results([models.Document], "documents", result_queryset=result_queryset) + if put_locks: + q = models.Document.objects + if result_queryset: + q = result_queryset["Document"] + q.update(locked=True, lock_user=lock_user) + media_archive = None if archive: media_archive = generic_archive_files([models.Document], @@ -215,7 +222,8 @@ def full_serialization(operation_queryset=None, site_queryset=None, warehouse_queryset=None, archive=True, no_geo=True, info=None, export_types=True, export_conf=True, export_importers=True, export_geo=True, export_dir=True, - export_docs=True, export_items=True): + export_docs=True, export_items=True, put_locks=False, + lock_user=None): archive_name = None if export_types: # print("type") @@ -242,7 +250,9 @@ def full_serialization(operation_queryset=None, site_queryset=None, archive=archive, archive_name=archive_name, operation_queryset=operation_queryset, site_queryset=site_queryset, cr_queryset=cr_queryset, find_queryset=find_queryset, - warehouse_queryset=warehouse_queryset) + warehouse_queryset=warehouse_queryset, + put_locks=put_locks, lock_user=lock_user + ) if export_items: # print("operation") archive_name = operation_serialization( @@ -250,32 +260,33 @@ def full_serialization(operation_queryset=None, site_queryset=None, archive_name=archive_name, operation_queryset=operation_queryset, site_queryset=site_queryset, cr_queryset=cr_queryset, find_queryset=find_queryset, warehouse_queryset=warehouse_queryset, - no_geo=no_geo) + no_geo=no_geo, put_locks=put_locks, lock_user=lock_user) # print("cr") cr_serialization( archive=archive, archive_name=archive_name, operation_queryset=operation_queryset, site_queryset=site_queryset, cr_queryset=cr_queryset, find_queryset=find_queryset, warehouse_queryset=warehouse_queryset, - no_geo=no_geo) + no_geo=no_geo, put_locks=put_locks, lock_user=lock_user) # print("find") find_serialization( archive=archive, archive_name=archive_name, operation_queryset=operation_queryset, site_queryset=site_queryset, cr_queryset=cr_queryset, find_queryset=find_queryset, warehouse_queryset=warehouse_queryset, - no_geo=no_geo) + no_geo=no_geo, put_locks=put_locks, lock_user=lock_user) # print("warehouse") warehouse_serialization( archive=archive, archive_name=archive_name, operation_queryset=operation_queryset, site_queryset=site_queryset, cr_queryset=cr_queryset, find_queryset=find_queryset, warehouse_queryset=warehouse_queryset, - no_geo=no_geo) + no_geo=no_geo, put_locks=put_locks, lock_user=lock_user) return archive_name -def restore_serialized(archive_name, user=None, delete_existing=False): +def restore_serialized(archive_name, user=None, delete_existing=False, + release_locks=False): with zipfile.ZipFile(archive_name, "r") as zip_file: # check version info = json.loads(zip_file.read("info.json").decode("utf-8")) @@ -323,13 +334,15 @@ def restore_serialized(archive_name, user=None, delete_existing=False): # regenerate labels, add a new version, etc. historized = hasattr(model, "history_modifier") and ( hasattr(model, "history_creator")) + releasing_locks = hasattr(model, "locked") and ( + release_locks) need_resave = hasattr(model, "CACHED_LABELS") or \ hasattr(model, "cached_label") or \ - (user and historized) + releasing_locks or (user and historized) idx = -1 for idx, obj in enumerate(deserialize("json", data)): extra_attrs = {} - if historized: + if historized or hasattr(model, "locked"): keys = obj.object.natural_key() old_obj = None try: @@ -337,14 +350,20 @@ def restore_serialized(archive_name, user=None, delete_existing=False): *keys) except model.DoesNotExist: pass - if old_obj and (old_obj.history_creator or - old_obj.history_modifier): - extra_attrs = { - "history_modifier_id": - old_obj.history_modifier_id, - "history_creator_id": - old_obj.history_creator_id - } + if old_obj: + if historized and (old_obj.history_creator or + old_obj.history_modifier): + extra_attrs = { + "history_modifier_id": + old_obj.history_modifier_id, + "history_creator_id": + old_obj.history_creator_id + } + if hasattr(model, "locked") and old_obj.locked: + extra_attrs.update({ + "locked": old_obj.locked, + "lock_user": old_obj.lock_user, + }) obj.save() if need_resave or extra_attrs: obj = model.objects.get(id=obj.object.id) @@ -356,10 +375,17 @@ def restore_serialized(archive_name, user=None, delete_existing=False): "history_creator_id"] else: obj.history_creator = user + if extra_attrs and \ + "locked" in extra_attrs: + obj.locked = extra_attrs["locked"] + obj.lock_user = extra_attrs["lock_user"] elif extra_attrs: for k in extra_attrs: setattr(obj, k, extra_attrs[k]) obj.skip_history_when_saving = True + if releasing_locks: + obj.locked = False + obj.lock_user = None obj._no_move = True obj.save() if idx >= 0: diff --git a/ishtar_common/serializers_utils.py b/ishtar_common/serializers_utils.py index ef36e2695..2242c349e 100644 --- a/ishtar_common/serializers_utils.py +++ b/ishtar_common/serializers_utils.py @@ -161,7 +161,8 @@ def generic_get_results(model_list, dirname, no_geo=True, new_result += result_to_add result[key] = json.dumps(new_result, indent=2) - excluded_fields = ["history_modifier", "history_creator", "imports"] + excluded_fields = ["history_modifier", "history_creator", "imports", + "locked", "lock_user"] if hasattr(model, "SERIALIZATION_EXCLUDE"): excluded_fields = list(model.SERIALIZATION_EXCLUDE) if no_geo: diff --git a/ishtar_common/tasks.py b/ishtar_common/tasks.py index 1f95df392..07286ad76 100644 --- a/ishtar_common/tasks.py +++ b/ishtar_common/tasks.py @@ -52,7 +52,8 @@ def launch_import(import_task): restore_serialized(import_task.source.path, import_task.import_user, - delete_existing=import_task.delete_before) + delete_existing=import_task.delete_before, + release_locks=import_task.releasing_locks) import_task.finished_date = datetime.datetime.now() import_task.state = 'F' import_task.save() @@ -66,7 +67,9 @@ def launch_export(export_task): export_task.state = 'P' export_task.save() - kwargs = {"info": {}} + kwargs = {"info": {}, + "put_locks": export_task.put_locks, + "lock_user": export_task.lock_user} for fltr_key in ("export_types", "export_conf", "export_importers", "export_geo", "export_dir", "export_docs", "export_items"): diff --git a/ishtar_common/tests.py b/ishtar_common/tests.py index 22876784c..939a29d2b 100644 --- a/ishtar_common/tests.py +++ b/ishtar_common/tests.py @@ -664,8 +664,10 @@ class GenericSerializationTest: zip_filename = serialization(**kwargs) return current_number, zip_filename - def generic_restore_test(self, zip_filename, current_number, model_list): - restore_serialized(zip_filename, delete_existing=True) + def generic_restore_test(self, zip_filename, current_number, model_list, + release_locks=False, delete_existing=True): + restore_serialized(zip_filename, delete_existing=delete_existing, + release_locks=release_locks) for model in model_list: previous_nb = current_number[(model.__module__, model.__name__)] current_nb = model.objects.count() diff --git a/ishtar_common/views.py b/ishtar_common/views.py index db0d3631b..acf7eb70c 100644 --- a/ishtar_common/views.py +++ b/ishtar_common/views.py @@ -2114,7 +2114,8 @@ class QANotAvailable(IshtarMixin, LoginRequiredMixin, TemplateView): template_name = 'ishtar/forms/qa_message.html' modal_size = "small" contexts = {"locked-by-others": _("Some items have been locked by other " - "user")} + "users."), + "locked": _("Some items are locked.")} def get_context_data(self, **kwargs): data = super(QANotAvailable, self).get_context_data(**kwargs) @@ -2196,7 +2197,7 @@ class QAItemEditForm(QAItemForm): for item in self.items: if item.locked: redirected = HttpResponseRedirect( - reverse("qa-not-available")) + reverse("qa-not-available", args=["locked"])) return redirected def get_form_class(self): diff --git a/ishtar_common/views_item.py b/ishtar_common/views_item.py index 2c4cc1a55..6c10285db 100644 --- a/ishtar_common/views_item.py +++ b/ishtar_common/views_item.py @@ -282,7 +282,8 @@ def show_item(model, name, extra_dct=None, model_for_perms=None): pandoc_args = ["pandoc", "-f", "html", "-t", "odt", "-o", odt.name, html_source.name] try: - subprocess.check_call(pandoc_args) + subprocess.check_call(pandoc_args, stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL) except subprocess.CalledProcessError: return HttpResponse(content, content_type="application/xhtml") |