#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright (C) 2015-2018 Étienne Loks # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # See the file COPYING for details. import csv import json from io import StringIO import locale from django.conf import settings from django.contrib.auth.models import Permission from django.core.exceptions import ValidationError, ImproperlyConfigured from django.core.urlresolvers import reverse from django.test.client import Client from django.utils.translation import pgettext_lazy from ishtar_common.models import ( IshtarSiteProfile, ImporterModel, UserProfile, ProfileType, Town, Area, get_current_profile, ) from archaeological_operations.tests import OperationInitTest, ImportTest from archaeological_operations import models as models_ope from archaeological_context_records import models from ishtar_common.utils import generate_relation_graph from ishtar_common.tests import ( WizardTest, WizardTestFormData as FormData, create_superuser, create_user, TestCase, AutocompleteTestBase, AcItem, CONTEXT_RECORD_FIXTURES, CONTEXT_RECORD_TOWNS_FIXTURES, OPERATION_TOWNS_FIXTURES, GenericSerializationTest, COMMON_FIXTURES, WAREHOUSE_FIXTURES, SearchText, ) from archaeological_operations.models import Operation from archaeological_operations.serializers import operation_serialization from archaeological_context_records import views, serializers class ImportContextRecordTest(ImportTest, TestCase): fixtures = CONTEXT_RECORD_TOWNS_FIXTURES def test_mcc_import_contextrecords(self): old_nb = models.ContextRecord.objects.count() mcc, form = self.init_context_record_import() self.assertTrue(form.is_valid()) impt = form.save(self.ishtar_user) impt.initialize() self.init_cr_targetkey(impt) impt.importation() # new context records has now been imported current_nb = models.ContextRecord.objects.count() self.assertEqual(current_nb, old_nb + 4) self.assertEqual( models.ContextRecord.objects.filter(unit__txt_idx="not_in_context").count(), 3, ) self.assertEqual( models.ContextRecord.objects.filter(unit__txt_idx="negative").count(), 1 ) def test_model_limitation(self): locale.setlocale(locale.LC_ALL, "fr_FR.UTF-8") old_nb = models.ContextRecord.objects.count() mcc, form = self.init_context_record_import() mcc.created_models.clear() self.assertTrue(form.is_valid()) impt = form.save(self.ishtar_user) impt.initialize() self.init_cr_targetkey(impt) impt.importation() # no model defined in created_models: normal import current_nb = models.ContextRecord.objects.count() self.assertEqual(current_nb, old_nb + 4) # add an inadequate model to make created_models non empty for cr in models.ContextRecord.objects.all(): cr.delete() mcc, form = self.init_context_record_import() mcc.created_models.clear() mcc.created_models.add( ImporterModel.objects.get(klass="ishtar_common.models.Organization") ) impt = form.save(self.ishtar_user) impt.initialize() self.init_cr_targetkey(impt) # Dating is not in models that can be created but force new is # set for a column that references Dating impt.importation() self.assertEqual(len(impt.errors), 5) self.assertTrue( "doesn't exist in the database." in impt.errors[0]["error"] or "n'existe pas dans la base" in impt.errors[0]["error"] ) # retry with only Dating (no context record) for cr in models.ContextRecord.objects.all(): cr.delete() mcc, form = self.init_context_record_import() mcc.created_models.clear() dat_model, c = ImporterModel.objects.get_or_create( klass="archaeological_context_records.models.Dating", defaults={"name": "Dating"}, ) mcc.created_models.add(dat_model) impt = form.save(self.ishtar_user) impt.initialize() self.init_cr_targetkey(impt) impt.importation() current_nb = models.ContextRecord.objects.count() self.assertEqual(current_nb, 0) # add a context record model for cr in models.ContextRecord.objects.all(): cr.delete() mcc, form = self.init_context_record_import() mcc.created_models.clear() mcc.created_models.add( ImporterModel.objects.get( klass="archaeological_context_records.models.ContextRecord" ) ) mcc.created_models.add(dat_model) impt = form.save(self.ishtar_user) impt.initialize() self.init_cr_targetkey(impt) impt.importation() current_nb = models.ContextRecord.objects.count() self.assertEqual(current_nb, 4) """ # add a context record model for cr in models.ContextRecord.objects.all(): cr.delete() mcc, form = self.init_context_record_import() mcc.created_models.clear() mcc.created_models.add(ImporterModel.objects.get( klass='archaeological_context_records.models.ContextRecord' )) impt = form.save(self.ishtar_user) impt.initialize() self.init_cr_targetkey(impt) impt.importation() current_nb = models.ContextRecord.objects.count() self.assertEqual(current_nb, 4) """ class ContextRecordInit(OperationInitTest): def create_context_record(self, data=None, user=None, force=False): if not data: data = {} if not getattr(self, "context_records", None): self.context_records = [] default = {"label": "Context record"} if ( force or not data.get("operation") or not models.Operation.objects.filter(pk=data["operation"].pk).count() ): data["operation"] = self.get_default_operation(force=force, user=user) if ( not data.get("parcel") or not data["parcel"].pk or not models.Parcel.objects.filter(pk=data["parcel"].pk).count() ): data["parcel"] = self.get_default_parcel(force=force) if user: data["history_modifier"] = user elif not data.get("history_modifier"): data["history_modifier"] = self.get_default_user() default.update(data) data["operation"] = models.Operation.objects.get(pk=data["operation"].pk) data["parcel"] = models.Parcel.objects.get(pk=data["parcel"].pk) self.context_records.append(models.ContextRecord.objects.create(**default)) return self.context_records def get_default_context_record(self, force=False, user=None): if force: return self.create_context_record(force=force, user=user)[-1] return self.create_context_record(force=force, user=user)[0] def tearDown(self): if hasattr(self, "context_records"): for cr in self.context_records: try: cr.delete() except: pass self.context_records = [] super(ContextRecordInit, self).tearDown() class SerializationTest(GenericSerializationTest, ContextRecordInit, TestCase): fixtures = COMMON_FIXTURES + WAREHOUSE_FIXTURES def setUp(self): ope1 = self.create_operation()[0] ope2 = self.create_operation()[1] cr = self.create_context_record(data={"label": "CR 1", "operation": ope1})[0] cr2 = self.create_context_record(data={"label": "CR 2", "operation": ope2})[1] dating = models.Dating.objects.create( period=models.Period.objects.all()[0], ) cr.datings.add(dating) rlt = models.RelationType.objects.create( label="Test", txt_idx="test", symmetrical=False ) models.RecordRelations.objects.create( left_record=cr, right_record=cr2, relation_type=rlt ) def test_serialization(self): res = self.generic_serialization_test(serializers.cr_serialization) cr_json = json.loads( res[("context_records", "archaeological_context_records__ContextRecord")] ) self.assertEqual(len(cr_json), 2) result_queryset = Operation.objects.filter(uuid=self.operations[0].uuid) res = self.generic_serialization_test( serializers.cr_serialization, no_test=True, kwargs={"operation_queryset": result_queryset}, ) cr_json = json.loads( res[("context_records", "archaeological_context_records__ContextRecord")] ) self.assertEqual(len(cr_json), 1) result_queryset = models.ContextRecord.objects.filter( uuid=self.context_records[0].uuid ) res = self.generic_serialization_test( serializers.cr_serialization, no_test=True, kwargs={"cr_queryset": result_queryset}, ) cr_json = json.loads( res[("context_records", "archaeological_context_records__ContextRecord")] ) self.assertEqual(len(cr_json), 1) def test_ope_serialization_with_cr_filter(self): res = self.generic_serialization_test( operation_serialization, no_test=True, ) ope_json = json.loads( res[("operations", "archaeological_operations__Operation")] ) self.assertEqual(len(ope_json), 2) result_queryset = models.ContextRecord.objects.filter( uuid=self.context_records[0].uuid ) res = self.generic_serialization_test( operation_serialization, no_test=True, kwargs={"cr_queryset": result_queryset}, ) ope_json = json.loads( res[("operations", "archaeological_operations__Operation")] ) self.assertEqual(len(ope_json), 1) def test_restore(self): current_number, zip_filename = self.generic_restore_test_genzip( serializers.CR_MODEL_LIST, serializers.cr_serialization ) self.generic_restore_test( zip_filename, current_number, serializers.CR_MODEL_LIST ) class ExportTest(ContextRecordInit, TestCase): fixtures = CONTEXT_RECORD_TOWNS_FIXTURES def setUp(self): self.username, self.password, self.user = create_superuser() def test_ishtar_export_ue(self): ope = self.create_operation()[0] ope.code_patriarche = "45000" ope.save() cr = self.create_context_record(data={"label": "CR 1"})[0] c = Client() url = reverse( "get-by-importer", kwargs={"slug": "ishtar-context-record", "type": "csv"} ) response = c.get(url) # no result when no authentication self.assertTrue(not response.content) c.login(username=self.username, password=self.password) response = c.get(url) ENCODING = settings.ENCODING or "utf-8" rows = list(csv.reader(StringIO(response.content.decode(ENCODING)))) # one header + one context record self.assertEqual(len(rows), 2) row_cr = rows[1] self.assertEqual(row_cr[0], "45000") self.assertEqual(row_cr[1], "12345") self.assertEqual(row_cr[2], "A1") class ContextRecordTest(ContextRecordInit, TestCase): fixtures = CONTEXT_RECORD_TOWNS_FIXTURES def setUp(self): IshtarSiteProfile.objects.create() self.username, self.password, self.user = create_superuser() self.create_context_record(data={"label": "CR 1"}) self.create_context_record(data={"label": "CR 2"}) cr_1 = self.context_records[0] cr_2 = self.context_records[1] sym_rel_type = models.RelationType.objects.filter(symmetrical=True).all()[0] self.cr_rel_type = sym_rel_type models.RecordRelations.objects.create( left_record=cr_1, right_record=cr_2, relation_type=sym_rel_type ) def test_external_id(self): cr = self.context_records[0] self.assertEqual( cr.external_id, "{}-{}".format(cr.parcel.external_id, cr.label) ) def test_lost_parcel_dont_delete_context_record(self): cr = self.create_context_record(force=True)[0] parcel = models.Parcel.objects.get(pk=cr.parcel.pk) parcel.operation = None parcel.save() # associated context record is not removed self.assertEqual(models.ContextRecord.objects.filter(pk=cr.pk).count(), 1) # associated operation is restored self.assertEqual( models.Parcel.objects.get(pk=parcel.pk).operation, cr.operation ) def test_search_vector_update(self): cr = self.create_context_record(force=True)[0] cr = models.ContextRecord.objects.get(pk=cr.pk) cr.label = "Label label" cr.location = "I am heeere" cr.save() cr = models.ContextRecord.objects.get(pk=cr.pk) self.assertIsNotNone(cr.search_vector) for key in ("label", "heeer"): self.assertIn(key, cr.search_vector) cr.operation.code_patriarche = "PATRIARCHE" cr.operation.save() cr = models.ContextRecord.objects.get(pk=cr.pk) profile = get_current_profile() self.assertIsNotNone(cr.cached_label) self.assertIn(profile.operation_prefix.lower() + "patriarch", cr.search_vector) def test_upstream_cache_update(self): cr = self.create_context_record()[0] cr_pk = cr.pk self.assertIsNotNone(cr.cached_label) # OA1 | A | 1 | CR 1 ope_id, parcel_sec, parcel_nb, cr_label = cr.cached_label.split(" | ") self.assertEqual(ope_id, "OA1") self.assertEqual(parcel_sec, cr.parcel.section) self.assertEqual(parcel_nb, cr.parcel.parcel_number) self.assertEqual(cr_label, cr.label) new_lbl = "UE 2" cr.label = new_lbl cr.save() cr = models.ContextRecord.objects.get(pk=cr_pk) self.assertIsNotNone(cr.cached_label) ope_id, parcel_sec, parcel_nb, cr_label = cr.cached_label.split(" | ") self.assertEqual(cr_label, new_lbl) new_sec, new_nb = "B", "42" parcel = cr.parcel parcel.section = new_sec parcel.parcel_number = new_nb parcel.save() cr = models.ContextRecord.objects.get(pk=cr_pk) self.assertIsNotNone(cr.cached_label) ope_id, parcel_sec, parcel_nb, cr_label = cr.cached_label.split(" | ") self.assertEqual(parcel_sec, new_sec) self.assertEqual(parcel_nb, new_nb) cr.operation.year = 2017 cr.operation.save() cr = models.ContextRecord.objects.get(pk=cr_pk) self.assertIsNotNone(cr.cached_label) ope_id, parcel_sec, parcel_nb, cr_label = cr.cached_label.split(" | ") self.assertEqual(ope_id, "OA1") def test_downstream_cache_update(self): cr = self.create_context_record()[0] from archaeological_finds.models import Find, BaseFind, MaterialType data = { "label": "Find me a reason", "context_record": cr, "history_modifier": self.get_default_user(), } bf = BaseFind.objects.create(**data) find = Find.objects.create( history_modifier=self.get_default_user(), label="Find me too" ) find.base_finds.add(bf) mat = MaterialType.objects.create( label="Adamentium", txt_idx="admentium", code="ADA" ) find.material_types.add(mat) class TestObj(object): def __init__(self): self.find_reached = [] def reached(self, sender, **kwargs): instance = kwargs.get("instance") if sender in (Find, BaseFind): self.find_reached.append(instance) test_obj = TestObj() cr = models.ContextRecord.objects.get(pk=cr.pk) cr.test_obj = test_obj cr.label = "New label!" cr.save() # verify the relevance of the update bf = BaseFind.objects.get(pk=bf.pk) self.assertIn("New label!", bf.cache_complete_id) # bulk update of find cached label gen don't have to be # reached self.assertEqual(len(test_obj.find_reached), 0) def test_show(self): obj = self.context_records[0] c = Client() response = c.get(reverse("show-contextrecord", kwargs={"pk": obj.pk})) self.assertEqual(response.status_code, 200) # empty content when not allowed self.assertEqual(response.content, b"") c.login(username=self.username, password=self.password) response = c.get(reverse("show-contextrecord", kwargs={"pk": obj.pk})) self.assertEqual(response.status_code, 200) self.assertIn(b'class="card sheet"', response.content) def test_redundant_dating_clean(self): obj = self.context_records[0] values = {"period": models.Period.objects.all()[0]} values_2 = { "period": models.Period.objects.all()[0], "quality": models.DatingQuality.objects.all()[0], } obj.datings.add(models.Dating.objects.create(**values)) obj.datings.add(models.Dating.objects.create(**values)) obj.datings.add(models.Dating.objects.create(**values_2)) obj.datings.add(models.Dating.objects.create(**values_2)) self.assertEqual(obj.datings.count(), 4) obj.fix() self.assertEqual(obj.datings.count(), 2) def test_custom_index(self): profile, created = IshtarSiteProfile.objects.get_or_create( slug="default", active=True ) # key: operation profile.contextrecord_custom_index = "operation_id" profile.save() cr1 = self.context_records[0] cr1 = models.ContextRecord.objects.get(pk=cr1.pk) cr1.save() cr1 = models.ContextRecord.objects.get(pk=cr1.pk) self.assertEqual(cr1.custom_index, 1) cr2 = self.context_records[1] cr2 = models.ContextRecord.objects.get(pk=cr2.pk) cr2.operation = cr1.operation cr2.save() cr2 = models.ContextRecord.objects.get(pk=cr2.pk) self.assertEqual(cr2.custom_index, 2) ope = self.create_operation()[-1] cr3 = self.create_context_record(data={"operation": ope})[-1] cr3 = models.ContextRecord.objects.get(pk=cr3.pk) self.assertEqual(cr3.custom_index, 1) # key: operation, unit profile.contextrecord_custom_index = "unit_id;operation_id" profile.save() su = models.Unit.objects.get(txt_idx="stratigraphic-unit") dest = models.Unit.objects.get(txt_idx="sector") cr1.unit, cr2.unit = su, dest cr1.save() cr2.save() cr2 = models.ContextRecord.objects.get(pk=cr2.pk) # no change if custom_index not reinit self.assertEqual(cr2.custom_index, 2) cr1 = models.ContextRecord.objects.get(pk=cr1.pk) self.assertEqual(cr1.custom_index, 1) cr2.custom_index = None cr2.save() cr2 = models.ContextRecord.objects.get(pk=cr2.pk) # different unit -> 1 self.assertEqual(cr2.custom_index, 1) cr2.unit = cr1.unit cr2.custom_index = None cr2.save() cr2 = models.ContextRecord.objects.get(pk=cr2.pk) # same unit and operation -> 2 self.assertEqual(cr2.custom_index, 2) cr3.unit = cr1.unit cr3.custom_index = None cr3.save() cr3 = models.ContextRecord.objects.get(pk=cr3.pk) # same unit and other operation -> 1 self.assertEqual(cr3.custom_index, 1) class ContextRecordSearchTest(ContextRecordInit, TestCase, SearchText): fixtures = CONTEXT_RECORD_TOWNS_FIXTURES SEARCH_URL = "get-contextrecord" def setUp(self): IshtarSiteProfile.objects.create() self.username, self.password, self.user = create_superuser() self.create_context_record(data={"label": "CR 1"}) self.create_context_record(data={"label": "CR 2"}) cr_1 = self.context_records[0] cr_2 = self.context_records[1] sym_rel_type = models.RelationType.objects.filter(symmetrical=True).all()[0] self.cr_rel_type = sym_rel_type models.RecordRelations.objects.create( left_record=cr_1, right_record=cr_2, relation_type=sym_rel_type ) def test_town_search(self): c = Client() c.login(username=self.username, password=self.password) data = {"numero_insee": "98989", "name": "base_town"} base_town = self.create_towns(datas=data)[-1] parcel = self.create_parcel( data={"town": base_town, "section": "A", "parcel_number": "1"} )[-1] self.context_records[0].parcel = parcel self.context_records[0].save() data = {"numero_insee": "56789", "name": "parent_town"} parent_town = self.create_towns(datas=data)[-1] parent_town.children.add(base_town) data = {"numero_insee": "01234", "name": "child_town"} child_town = self.create_towns(datas=data)[-1] base_town.children.add(child_town) town_key = pgettext_lazy("key for text search", "town") # simple search search = {town_key: base_town.cached_label} response = c.get(reverse("get-contextrecord"), search) self.assertEqual(response.status_code, 200) self.assertEqual(json.loads(response.content.decode())["recordsTotal"], 1) # parent search search = {town_key: parent_town.cached_label} response = c.get(reverse("get-contextrecord"), search) self.assertEqual(response.status_code, 200) self.assertEqual(json.loads(response.content.decode())["recordsTotal"], 1) # child search search = {town_key: child_town.cached_label} response = c.get(reverse("get-contextrecord"), search) self.assertEqual(response.status_code, 200) self.assertEqual(json.loads(response.content.decode())["recordsTotal"], 1) def test_search_export(self): c = Client() response = c.get(reverse("get-contextrecord")) # no result when no authentification self.assertTrue(not json.loads(response.content.decode())) c.login(username=self.username, password=self.password) response = c.get(reverse("get-contextrecord")) self.assertEqual(json.loads(response.content.decode())["recordsTotal"], 2) # test search label response = c.get(reverse("get-contextrecord"), {"label": "cr 1"}) self.assertEqual(json.loads(response.content.decode())["recordsTotal"], 1) # test search between relations rel_rec_key = pgettext_lazy("key for text search", "record-relation-type") response = c.get( reverse("get-contextrecord"), { "search_vector": 'label="cr 1" {}="{}"'.format( rel_rec_key, self.cr_rel_type.label ) }, ) self.assertEqual(json.loads(response.content.decode())["recordsTotal"], 2) # test search vector response = c.get(reverse("get-contextrecord"), {"search_vector": "CR"}) self.assertEqual(json.loads(response.content.decode())["recordsTotal"], 2) # the 2 context records have the same operation response = c.get(reverse("get-contextrecord"), {"search_vector": "oa1"}) self.assertEqual(json.loads(response.content.decode())["recordsTotal"], 2) # test search between related operations first_ope = self.operations[0] first_ope.year = 2010 first_ope.save() cr_1 = self.context_records[0] cr_1.operation = first_ope cr_1.save() other_ope = self.operations[1] other_ope.year = 2016 other_ope.save() cr_2 = self.context_records[1] cr_2.operation = other_ope cr_2.save() rel_ope = models_ope.RelationType.objects.create( symmetrical=True, label="Linked", txt_idx="link" ) models_ope.RecordRelations.objects.create( left_record=other_ope, right_record=first_ope, relation_type=rel_ope ) rel_key = pgettext_lazy("key for text search", "operation-relation-type") response = c.get( reverse("get-contextrecord"), {"operation__year": 2010, rel_key: rel_ope.label}, ) self.assertEqual(json.loads(response.content.decode())["recordsTotal"], 2) # export response = c.get( reverse("get-contextrecord-full", kwargs={"type": "csv"}), {"submited": "1", "operation__year": 2010, rel_key: rel_ope.label}, ) ENCODING = settings.ENCODING or "utf-8" content = response.content.decode(ENCODING) # 2 lines + header lines = [line for line in content.split("\n") if line] self.assertEqual(len(lines), 3) def test_unit_hierarchic_search(self): cr = self.context_records[0] c = Client() su = models.Unit.objects.get(txt_idx="stratigraphic-unit") neg = models.Unit.objects.get(txt_idx="negative") dest = models.Unit.objects.get(txt_idx="sector") dest.parent = su dest.save() cr.unit = neg cr.save() search = {"unit": neg.pk} # no result when no authentication response = c.get(reverse("get-contextrecord"), search) self.assertEqual(response.status_code, 200) content = response.content.decode() self.assertTrue(not json.loads(content)) # one result for exact search c.login(username=self.username, password=self.password) response = c.get(reverse("get-contextrecord"), search) self.assertEqual(response.status_code, 200) res = json.loads(response.content.decode()) self.assertEqual(res["recordsTotal"], 1) self.assertEqual(res["rows"][0]["unit__label"], str(neg)) # no result for the brother search = {"unit": dest.pk} response = c.get(reverse("get-contextrecord"), search) self.assertEqual(response.status_code, 200) content = response.content.decode() self.assertEqual(json.loads(content)["recordsTotal"], 0) # one result for the father search = {"unit": su.pk} response = c.get(reverse("get-contextrecord"), search) self.assertEqual(response.status_code, 200) content = response.content.decode() self.assertEqual(json.loads(content)["recordsTotal"], 1) # test on text search material_key = str(pgettext_lazy("key for text search", "unit-type")) result = [ ('{}="{}"'.format(material_key, str(neg)), 1), ('{}="{}"'.format(material_key, str(dest)), 0), ('{}="{}"'.format(material_key, str(su)), 1), ] self._test_search(c, result, context="Text unit type search") def test_period_hierarchic_search(self): cr = self.context_records[0] c = Client() neo = models.Period.objects.get(txt_idx="neolithic") final_neo = models.Period.objects.get(txt_idx="final-neolithic") recent_neo = models.Period.objects.get(txt_idx="recent-neolithic") dating = models.Dating.objects.create(period=final_neo) cr.datings.add(dating) search = {"datings__period": final_neo.pk} # no result when no authentication response = c.get(reverse("get-contextrecord"), search) self.assertEqual(response.status_code, 200) content = response.content.decode() self.assertTrue(not json.loads(content)) # one result for exact search c.login(username=self.username, password=self.password) response = c.get(reverse("get-contextrecord"), search) self.assertEqual(response.status_code, 200) res = json.loads(response.content.decode()) self.assertTrue(res["recordsTotal"] == 1) # no result for the brother search = {"datings__period": recent_neo.pk} response = c.get(reverse("get-contextrecord"), search) self.assertEqual(response.status_code, 200) content = response.content.decode() self.assertEqual(json.loads(content)["recordsTotal"], 0) # one result for the father search = {"datings__period": neo.pk} response = c.get(reverse("get-contextrecord"), search) self.assertEqual(response.status_code, 200) content = response.content.decode() self.assertEqual(json.loads(content)["recordsTotal"], 1) # test on text search period_key = str(pgettext_lazy("key for text search", "datings-period")) result = [ ('{}="{}"'.format(period_key, str(final_neo)), 1), ('{}="{}"'.format(period_key, str(recent_neo)), 0), ('{}="{}"'.format(period_key, str(neo)), 1), ] self._test_search(c, result, context="Text period search") class ContextRecordPermissionTest(ContextRecordInit, TestCase): fixtures = CONTEXT_RECORD_TOWNS_FIXTURES def setUp(self): IshtarSiteProfile.objects.create() self.username, self.password, self.user = create_superuser() self.alt_username, self.alt_password, self.alt_user = create_user() self.alt_user.user_permissions.add( Permission.objects.get(codename="view_own_contextrecord") ) self.alt_user.user_permissions.add( Permission.objects.get(codename="change_own_contextrecord") ) self.alt_username2, self.alt_password2, self.alt_user2 = create_user( username="luke", password="iamyourfather" ) profile = UserProfile.objects.create( profile_type=ProfileType.objects.get(txt_idx="collaborator"), person=self.alt_user2.ishtaruser.person, current=True, ) town = Town.objects.create(name="Tatouine", numero_insee="66000") area = Area.objects.create(label="Galaxie", txt_idx="galaxie") area.towns.add(town) profile.areas.add(area) self.orgas = self.create_orgas(self.user) self.operations = self.create_operation(self.user, self.orgas[0]) self.operations += self.create_operation(self.alt_user, self.orgas[0]) self.operations[1].towns.add(town) self.create_context_record( user=self.user, data={"label": "CR 1", "operation": self.operations[0]} ) self.create_context_record( user=self.alt_user, data={"label": "CR 2", "operation": self.operations[1]} ) self.cr_1 = self.context_records[0] self.cr_2 = self.context_records[1] def test_own_search(self): # no result when no authentification c = Client() response = c.get(reverse("get-contextrecord")) self.assertTrue(not json.loads(response.content.decode())) # possession c = Client() c.login(username=self.alt_username, password=self.alt_password) response = c.get(reverse("get-contextrecord")) # only one "own" context record available content = response.content.decode() self.assertTrue(json.loads(content)) self.assertEqual(json.loads(content)["recordsTotal"], 1) # area filter c = Client() c.login(username=self.alt_username2, password=self.alt_password2) response = c.get(reverse("get-contextrecord")) # only one "own" operation available content = response.content.decode() self.assertTrue(json.loads(content)) self.assertEqual(json.loads(content)["recordsTotal"], 1) def test_own_modify(self): # no result when no authentification c = Client() response = c.get(reverse("record_modify", args=[self.cr_2.pk])) self.assertRedirects(response, "/") modif_url = "/record_modification/operation-record_modification" # possession c = Client() c.login(username=self.alt_username, password=self.alt_password) response = c.get(reverse("record_modify", args=[self.cr_2.pk]), follow=True) self.assertRedirects(response, modif_url) response = c.get(modif_url) self.assertEqual(response.status_code, 200) response = c.get(reverse("record_modify", args=[self.cr_1.pk]), follow=True) self.assertRedirects(response, "/") # area filter c = Client() c.login(username=self.alt_username2, password=self.alt_password2) response = c.get(reverse("record_modify", args=[self.cr_2.pk]), follow=True) self.assertRedirects(response, modif_url) response = c.get(modif_url) self.assertEqual(response.status_code, 200) response = c.get(reverse("record_modify", args=[self.cr_1.pk]), follow=True) self.assertRedirects(response, "/") class RecordRelationsTest(ContextRecordInit, TestCase): fixtures = OPERATION_TOWNS_FIXTURES model = models.ContextRecord def setUp(self): for idx in range(1, 15): self.create_context_record({"label": f"CR {idx}"}) def test_relations(self): sym_rel_type = models.RelationType.objects.create( symmetrical=True, txt_idx="sym" ) rel_type_1 = models.RelationType.objects.create( symmetrical=False, txt_idx="rel_1" ) # cannot be symmetrical and have an inverse_relation with self.assertRaises(ValidationError): rel_test = models.RelationType.objects.create( symmetrical=True, inverse_relation=rel_type_1, txt_idx="rel_3" ) rel_test.full_clean() # auto fill inverse relations rel_type_2 = models.RelationType.objects.create( symmetrical=False, inverse_relation=rel_type_1, txt_idx="rel_2" ) self.assertEqual(rel_type_1.inverse_relation, rel_type_2) cr_1 = self.context_records[0] cr_2 = self.context_records[1] # inserting a new symmetrical relation automatically creates the same # relation for the second context record rel = models.RecordRelations.objects.create( left_record=cr_1, right_record=cr_2, relation_type=sym_rel_type ) self.assertEqual( models.RecordRelations.objects.filter( left_record=cr_2, right_record=cr_1, relation_type=sym_rel_type ).count(), 1, ) # removing one symmetrical relation removes the other rel.delete() self.assertEqual( models.RecordRelations.objects.filter( left_record=cr_2, right_record=cr_1, relation_type=sym_rel_type ).count(), 0, ) # for non-symmetrical relation, adding one relation automatically # adds the inverse models.RecordRelations.objects.create( left_record=cr_1, right_record=cr_2, relation_type=rel_type_1 ) self.assertEqual( models.RecordRelations.objects.filter( left_record=cr_2, right_record=cr_1, relation_type=rel_type_2 ).count(), 1, ) def test_relation_view(self): profile = get_current_profile() profile.parent_relations_engine = "V" profile.save() profile = get_current_profile(force=True) models.ContextRecordTree.check_engine() crs = self.context_records rel_type_1 = models.RelationType.objects.create( symmetrical=False, txt_idx="rel_1", logical_relation='included' ) """ 6 7 8 9 10 = 11 = 12 | | | | | ------- ----- | | 4 5 = 13 | | --------- | 3 = 14 | --------- | | 1 2 """ relations = ( (1, 3), (2, 3), (3, 4), (3, 5), (4, 6), (4, 7), (4, 8), (5, 9), (5, 10) ) models.RecordRelations._no_post_treatments = True for child_idx, parent_idx in relations: models.RecordRelations.objects.create( left_record=crs[child_idx - 1], right_record=crs[parent_idx - 1], relation_type=rel_type_1 ) rel_type_2 = models.RelationType.objects.create( symmetrical=True, txt_idx="rel_2", logical_relation='equal' ) equal_relations = ( (10, 11), (11, 12), (5, 13), (3, 14) ) for child_idx, parent_idx in equal_relations: models.RecordRelations.objects.create( left_record=crs[child_idx - 1], right_record=crs[parent_idx - 1], relation_type=rel_type_2 ) q = models.ContextRecordTree.objects.filter( cr_parent_id=crs[2].pk, cr_id=crs[0].pk) self.assertGreaterEqual(q.count(), 1) ## use tables self.assertIsNone(models.ContextRecordTree.check_engine()) # no change profile.parent_relations_engine = "T" profile.save() get_current_profile(force=True) self.assertTrue(models.ContextRecordTree.check_engine()) # change to table q = models.ContextRecordTree.objects.filter(cr=crs[0], cr_parent=crs[1]) self.assertEqual(q.count(), 0) # empty table # verify tree generation full_trees = [ [10, 5, 3, 2], [11, 5, 3, 2], [12, 5, 3, 2], [12, 13, 14, 2], [10, 5, 14, 2], [10, 14], [10, 5, 3, 1], [9, 5, 3, 2], [9, 5, 3, 1], [8, 4, 3, 2], [8, 4, 3, 1], [7, 4, 3, 2], [7, 4, 3, 1], [6, 4, 3, 2], [6, 4, 3, 1], ] self._test_tree_generation(0, full_trees) trees = [ [7, 4, 3, 2], [7, 4, 3, 1], ] self._test_tree_generation(6, trees) trees = [ [8, 4, 3, 2], [8, 4, 3, 1], ] self._test_tree_generation(7, trees) trees = [ [9, 5, 3, 2], [9, 5, 3, 1], ] self._test_tree_generation(8, trees) trees = [ [10, 5, 3, 2], [10, 5, 3, 1], ] self._test_tree_generation(9, trees) models.ContextRecordTree.objects.filter(pk__isnull=False).delete() # test regenerate all models.ContextRecordTree.regenerate_all() self._test_tree_(full_trees, "'FULL GENERATION'") # test remove a node nb = models.ContextRecordTree.objects.filter( cr_parent=crs[6 - 1], cr=crs[3 - 1]).count() self.assertEqual(nb, 1) models.RecordRelations.objects.filter( left_record=crs[3 - 1], right_record=crs[4 - 1] ).delete() models.ContextRecordTree.update(crs[3 - 1].pk) models.ContextRecordTree.update(crs[4 - 1].pk) nb = models.ContextRecordTree.objects.filter( cr_parent=crs[6 - 1], cr=crs[3 - 1]).count() self.assertEqual(nb, 0) # test remove a node (update equal links) nb = models.ContextRecordTree.objects.filter( cr_parent=crs[10 - 1], cr=crs[14 - 1]).count() self.assertEqual(nb, 1) models.RecordRelations.objects.filter( left_record=crs[3 - 1], right_record=crs[5 - 1] ).delete() models.ContextRecordTree.update(crs[3 - 1].pk) models.ContextRecordTree.update(crs[5 - 1].pk) nb = models.ContextRecordTree.objects.filter( cr_parent=crs[10 - 1], cr=crs[14 - 1]).count() self.assertEqual(nb, 0) # auto update models.RecordRelations._no_post_treatments = False models.RecordRelations.objects.create( left_record=crs[3 - 1], right_record=crs[4 - 1], relation_type=rel_type_1 ) nb = models.ContextRecordTree.objects.filter( cr_parent=crs[6 - 1], cr=crs[3 - 1]).count() self.assertEqual(nb, 1) models.RecordRelations.objects.create( left_record=crs[3 - 1], right_record=crs[5 - 1], relation_type=rel_type_1 ) nb = models.ContextRecordTree.objects.filter( cr_parent=crs[10 - 1], cr=crs[14 - 1]).count() self.assertEqual(nb, 1) # delete nb = models.ContextRecordTree.objects.filter( cr_parent=crs[13 - 1], cr=crs[1 - 1]).count() self.assertEqual(nb, 1) crs[3 - 1].delete() nb = models.ContextRecordTree.objects.filter( cr_parent=crs[13 - 1], cr=crs[1 - 1]).count() self.assertEqual(nb, 0) # delete on views profile = get_current_profile() profile.parent_relations_engine = "V" profile.save() get_current_profile(force=True) models.ContextRecordTree.objects.filter(pk__isnull=False).delete() crs[4 - 1].delete() q = models.ContextRecordTree.objects.filter( cr_parent_id=crs[1 - 1].pk, cr_id=crs[6 - 1].pk) self.assertGreaterEqual(q.count(), 0) def _test_tree_(self, test_trees, context_record): crs = self.context_records for tree in test_trees: for tree_idx in range(len(tree) - 1): q = models.ContextRecordTree.objects.filter( cr_parent=crs[tree[tree_idx] - 1], cr=crs[tree[tree_idx + 1] - 1]) self.assertEqual( q.count(), 1, msg="Tree relation ({}, {}) is missing for context " "record {}".format(tree[tree_idx + 1], tree[tree_idx], context_record) ) def _test_tree_generation(self, cr_idx, test_trees): crs = self.context_records models.ContextRecordTree.objects.filter(pk__isnull=False).delete() models.ContextRecordTree.update(crs[cr_idx].id) self._test_tree_(test_trees, cr_idx + 1) def tearDown(self): models.ContextRecordTree.objects.filter(pk__isnull=False).delete() profile = get_current_profile() profile.parent_relations_engine = "V" profile.save() class ContextRecordWizardCreationTest(WizardTest, ContextRecordInit, TestCase): fixtures = OPERATION_TOWNS_FIXTURES url_name = "record_creation" wizard_name = "record_wizard" steps = views.record_creation_steps redirect_url = ( "/record_modification/selec-record_modification" "?open_item={last_id}" ) model = models.ContextRecord form_datas = [ FormData( "Create a simple context record", form_datas={ "selec": {}, "general": {"label": "First"}, "relations": [], }, ignored=( "datings", "interpretation", ), ), FormData( "Create a context record with a relation and datings", form_datas={ "selec": {}, "general": {"label": "Second"}, "relations": [], "datings": [], }, ignored=("interpretation",), ), ] def pre_wizard(self): profile, created = IshtarSiteProfile.objects.get_or_create( slug="default", active=True ) profile.context_record = True profile.save() ope = self.get_default_operation() parcel = self.get_default_parcel() for form_data in self.form_datas: form_data.set("selec", "operation_id", ope.pk) form_data.set("general", "parcel", parcel.pk) self.related_cr = self.create_context_record(data={"operation": ope})[0] self.form_datas[1].append( "relations", { "right_record": self.related_cr.pk, "relation_type": models.RelationType.objects.create( label="Test", symmetrical=False ).pk, }, ) period = models.Period.objects.all()[0].pk self.form_datas[1].append("datings", {"period": period}) self.form_datas[1].append("datings", {"period": period}) self.cr_nb = models.ContextRecord.objects.count() super(ContextRecordWizardCreationTest, self).pre_wizard() def post_wizard(self): self.assertEqual(models.ContextRecord.objects.count(), self.cr_nb + 2) self.assertEqual(self.related_cr.left_relations.count(), 1) # identical datings, only one should be finaly save cr = models.ContextRecord.objects.order_by("-pk")[0] self.assertEqual(cr.datings.count(), 1) class AutocompleteTest(AutocompleteTestBase, TestCase): fixtures = CONTEXT_RECORD_FIXTURES models = [ AcItem( models.ContextRecord, "autocomplete-contextrecord", prepare_func="create_cr" ), ] def create_cr(self, base_name): ope, __ = models_ope.Operation.objects.get_or_create( common_name="Test", operation_type=models_ope.OperationType.objects.all()[0] ) item, __ = models.ContextRecord.objects.get_or_create( operation=ope, label=base_name ) return item, None class GraphGenerationTest(ContextRecordInit, TestCase): fixtures = CONTEXT_RECORD_TOWNS_FIXTURES def setUp(self): self.sym_rel_type, __ = models.RelationType.objects.get_or_create( symmetrical=True, txt_idx="sym", logical_relation="equal" ) self.rel_type_below, __ = models.RelationType.objects.get_or_create( symmetrical=False, txt_idx="below", logical_relation="below" ) self.rel_type_above, __ = models.RelationType.objects.get_or_create( symmetrical=False, txt_idx="above", logical_relation="above" ) self.create_context_record({"label": "CR 1"}) self.create_context_record({"label": "CR 2"}) self.create_context_record({"label": "CR 3"}) self.create_context_record({"label": "CR 4"}) self.create_context_record({"label": "CR 1B"}) self.create_context_record({"label": "CR 2B"}) self.create_context_record({"label": "CR 3B"}) self.create_context_record({"label": "CR 2C"}) self.create_context_record({"label": "CR 2D"}) cr_1 = self.context_records[0] self.cr_2 = cr_2 = self.context_records[1] cr_3 = self.context_records[2] cr_4 = self.context_records[3] cr_1B = self.context_records[4] cr_2B = self.context_records[5] cr_3B = self.context_records[6] cr_2C = self.context_records[7] cr_2D = self.context_records[8] models.RecordRelations.objects.create( left_record=cr_2, right_record=cr_2B, relation_type=self.sym_rel_type ) models.RecordRelations.objects.create( left_record=cr_1, right_record=cr_2, relation_type=self.rel_type_below ) models.RecordRelations.objects.create( left_record=cr_3, right_record=cr_2, relation_type=self.rel_type_above ) models.RecordRelations.objects.create( left_record=cr_3, right_record=cr_4, relation_type=self.rel_type_below ) models.RecordRelations.objects.create( left_record=cr_1B, right_record=cr_2B, relation_type=self.rel_type_below ) models.RecordRelations.objects.create( left_record=cr_3B, right_record=cr_2B, relation_type=self.rel_type_above ) models.RecordRelations.objects.create( left_record=cr_3B, right_record=cr_2C, relation_type=self.rel_type_above ) models.RecordRelations.objects.create( left_record=cr_2D, right_record=cr_3B, relation_type=self.rel_type_below ) def test_gen_relation(self): generate_relation_graph(self.cr_2) cr_2 = models.ContextRecord.objects.get(pk=self.cr_2.pk) self.assertIsNotNone(cr_2.relation_image) self.assertIsNotNone(cr_2.relation_bitmap_image) self.assertIsNotNone(cr_2.relation_dot) content = open(cr_2.relation_dot.path).read() self.assertIn('"CR 1"', content) self.assertIn('"CR 1B"', content) self.assertIn('"CR 2B"', content) self.assertIn('"CR 2",style=filled,fillcolor="#C6C0C0"', content) self.assertIn('"CR 3"', content) self.assertIn('"CR 3B"', content) self.assertIn('"CR 4"', content) self.assertNotIn('"CR 2C"', content) self.assertNotIn('"CR 2D"', content) def test_gen_relation_full(self): generate_relation_graph(self.cr_2, full=True) cr_2 = models.ContextRecord.objects.get(pk=self.cr_2.pk) operation = cr_2.operation self.assertIsNotNone(operation.relation_image) self.assertIsNotNone(operation.relation_bitmap_image) self.assertIsNotNone(operation.relation_dot) content = open(operation.relation_dot.path).read() self.assertIn('"CR 1"', content) self.assertIn('"CR 1B"', content) self.assertIn('"CR 2B"', content) self.assertIn('"CR 2",style=filled,fillcolor="#C6C0C0"', content) self.assertIn('"CR 3"', content) self.assertIn('"CR 3B"', content) self.assertIn('"CR 4"', content) self.assertIn('"CR 2C"', content) self.assertIn('"CR 2D"', content) def test_gen_relation_above(self): generate_relation_graph(self.cr_2, render_below=False) cr_2 = models.ContextRecord.objects.get(pk=self.cr_2.pk) self.assertIsNotNone(cr_2.relation_image_above) self.assertIsNotNone(cr_2.relation_bitmap_image_above) self.assertIsNotNone(cr_2.relation_dot_above) content = open(cr_2.relation_dot_above.path).read() self.assertNotIn('"CR 1"', content) self.assertNotIn('"CR 1B"', content) self.assertIn('"CR 2B"', content) self.assertIn('"CR 2",style=filled,fillcolor="#C6C0C0"', content) self.assertIn('"CR 3"', content) self.assertIn('"CR 3B"', content) self.assertIn('"CR 4"', content) self.assertNotIn('"CR 2C"', content) self.assertNotIn('"CR 2D"', content) def test_gen_relation_below(self): generate_relation_graph(self.cr_2, render_above=False) cr_2 = models.ContextRecord.objects.get(pk=self.cr_2.pk) self.assertIsNotNone(cr_2.relation_image_below) self.assertIsNotNone(cr_2.relation_bitmap_image_below) self.assertIsNotNone(cr_2.relation_dot_below) content = open(cr_2.relation_dot_below.path).read() self.assertIn('"CR 1"', content) self.assertIn('"CR 1B"', content) self.assertIn('"CR 2B"', content) self.assertIn('"CR 2",style=filled,fillcolor="#C6C0C0"', content) self.assertNotIn('"CR 3B"', content) self.assertNotIn('"CR 3"', content) self.assertNotIn('"CR 4"', content) self.assertNotIn('"CR 2C"', content) self.assertNotIn('"CR 2D"', content)