diff options
author | Étienne Loks <etienne.loks@iggdrasil.net> | 2022-02-24 23:30:23 +0100 |
---|---|---|
committer | Étienne Loks <etienne.loks@iggdrasil.net> | 2022-12-12 12:21:00 +0100 |
commit | 4286395e83bc27f853f0919c988a716dc2b6a345 (patch) | |
tree | 26f0daf8d47183130713be5dfc52b8e788e21dc4 | |
parent | a14d0067a933ff3898773119fb85146545530dc3 (diff) | |
download | Ishtar-4286395e83bc27f853f0919c988a716dc2b6a345.tar.bz2 Ishtar-4286395e83bc27f853f0919c988a716dc2b6a345.zip |
Geodata redesign: context record, base find migration
-rw-r--r-- | archaeological_context_records/models.py | 58 | ||||
-rw-r--r-- | archaeological_finds/models_finds.py | 9 | ||||
-rw-r--r-- | ishtar_common/management/commands/migrate_to_geo_v4.py | 152 | ||||
-rw-r--r-- | ishtar_common/utils.py | 18 |
4 files changed, 210 insertions, 27 deletions
diff --git a/archaeological_context_records/models.py b/archaeological_context_records/models.py index 29e8793dc..e9bd70aa8 100644 --- a/archaeological_context_records/models.py +++ b/archaeological_context_records/models.py @@ -397,12 +397,61 @@ class CRBulkView(object): """ +class GeographicSubTownItem(GeoItem): + class Meta: + abstract = True + + def _get_geo_town(self): + raise NotImplementedError() + + def post_save_geo(self, save=True): + # manage geodata towns + if getattr(self, "_post_save_geo_ok", False): + # prevent infinite loop - should not happen, but... + return + self._post_save_geo_ok = True + + q_geodata_current_town = self.geodata.filter( + source_content_type__model="town", + source_content_type__app_label="ishtar_common", + ) + town = self._get_geo_town() + has_geo_town = ( + town and town.main_geodata and town.main_geodata.multi_polygon + ) + if has_geo_town: + bad_towns = q_geodata_current_town + else: + bad_towns = q_geodata_current_town.exclude(source_id=town.id) + + modified = False + for bad_town in bad_towns.all(): + self.geodata.remove(bad_town) + if self.main_geodata == bad_town: + self.main_geodata = None + modified = True + + if not has_geo_town: + if modified and save: + self.skip_history_when_saving = True + self._no_move = True + self.save() + return + + if not q_geodata_current_town.filter(source_id=town.id).count(): + self.geodata.add(town.main_geodata) + if save: + self.skip_history_when_saving = True + self._no_move = True + self.save() + + class ContextRecord( BulkUpdatedItem, DocumentItem, BaseHistorizedItem, CompleteIdentifierItem, - GeoItem, + GeographicSubTownItem, OwnPerms, ValueGetter, MainItem, @@ -510,7 +559,7 @@ class ContextRecord( ), "operation__common_name": SearchAltName( pgettext_lazy("key for text search", "operation-name"), - "operation__common_name__iexact" + "operation__common_name__iexact", ), "operation__code_patriarche": SearchAltName( pgettext_lazy("key for text search", "patriarche"), @@ -810,6 +859,11 @@ class ContextRecord( if self.surface: return self.surface / 10000.0 + def _get_geo_town(self): + if self.parcel: + return self.parcel.town + return self.town + def public_representation(self): dct = super(ContextRecord, self).public_representation() dct.update( diff --git a/archaeological_finds/models_finds.py b/archaeological_finds/models_finds.py index bc1d64ee6..ce9a5e4af 100644 --- a/archaeological_finds/models_finds.py +++ b/archaeological_finds/models_finds.py @@ -55,7 +55,6 @@ from ishtar_common.models import ( ValueGetter, get_current_profile, IshtarSiteProfile, - GeoItem, BulkUpdatedItem, QuickAction, MainItem, @@ -76,7 +75,8 @@ from archaeological_operations.models import ( Operation, CulturalAttributionType, ) -from archaeological_context_records.models import ContextRecord, Dating +from archaeological_context_records.models import ContextRecord, Dating, \ + GeographicSubTownItem from archaeological_warehouse.models import Warehouse @@ -386,7 +386,7 @@ class BFBulkView(object): class BaseFind( BulkUpdatedItem, BaseHistorizedItem, - GeoItem, + GeographicSubTownItem, CompleteIdentifierItem, OwnPerms, ValueGetter, @@ -486,6 +486,9 @@ class BaseFind( def __str__(self): return self.label + def _get_geo_town(self): + return self.context_record._get_geo_town() + def natural_key(self): return (self.uuid,) diff --git a/ishtar_common/management/commands/migrate_to_geo_v4.py b/ishtar_common/management/commands/migrate_to_geo_v4.py index ed1a877f3..2c27eebe9 100644 --- a/ishtar_common/management/commands/migrate_to_geo_v4.py +++ b/ishtar_common/management/commands/migrate_to_geo_v4.py @@ -10,10 +10,11 @@ from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.core.management.base import BaseCommand -from ishtar_common.utils import ugettext_lazy as _ +from ishtar_common.utils import ugettext_lazy as _, get_log_time, get_percent, get_eta from ishtar_common import models_common, models from archaeological_operations.models import Operation, ArchaeologicalSite +from archaeological_context_records.models import ContextRecord log_path = os.sep.join([settings.ROOT_PATH, "logs"]) @@ -21,6 +22,14 @@ if not os.path.exists(log_path): os.mkdir(log_path, mode=0o770) +def write_output(model_name, idx, nb, ref_time=None): + lbl = f"\r[{get_percent(idx, nb)}] Migrate {model_name}s {idx + 1}/{nb}" + if ref_time: + lbl += f" ({get_eta(idx, nb, ref_time, datetime.datetime.now())} left)" + sys.stdout.write(lbl) + sys.stdout.flush() + + def migrate(quiet=False, log=True): changed = [] # create towns @@ -35,10 +44,10 @@ def migrate(quiet=False, log=True): provider, __ = models_common.GeoProviderType.objects.get_or_create( txt_idx="france-ign", defaults={"label": "IGN"} ) + ref_time = datetime.datetime.now() for idx, town in enumerate(q.all()): if not quiet: - sys.stdout.write(f"\r[{percent(idx, nb)}] Migrate towns {idx + 1}/{nb}") - sys.stdout.flush() + write_output("town", idx, nb, ref_time) attrs = { "name": town._generate_cached_label(), "source_content_type": town_content_type, @@ -56,7 +65,7 @@ def migrate(quiet=False, log=True): town.main_geodata = data town.save() if not quiet and nb: - sys.stdout.write(f"\r[{get_time()}] Towns migrated\n") + sys.stdout.write(f"\r[{get_log_time()}] Towns migrated \n") sys.stdout.flush() model_list = [ @@ -75,14 +84,13 @@ def migrate(quiet=False, log=True): defaults={"label": f"Emprise {model_full_name}"}, ) data_type_center, __ = models_common.GeoDataType.objects.get_or_create( - txt_idx="operation-center", defaults={"label": f"Centre {model_full_name}"} + txt_idx=f"{model_slug}-center", + defaults={"label": f"Centre {model_full_name}"}, ) + ref_time = datetime.datetime.now() for idx, obj in enumerate(q.all()): if not quiet: - sys.stdout.write( - f"\r[{percent(idx, nb)}] Migrate {model_name}s {idx + 1}/{nb}" - ) - sys.stdout.flush() + write_output(model_name, idx, nb, ref_time) obj._no_move = True obj.skip_history_when_saving = True @@ -155,11 +163,119 @@ def migrate(quiet=False, log=True): ["geovectordata", data.name, data.pk, f"Point {model_name}"] ) if not quiet and nb: - sys.stdout.write(f"\r[{get_time()}] {model_name.capitalize()} migrated\n") + sys.stdout.write( + f"\r[{get_log_time()}] {model_name.capitalize()} migrated" + + " " * 20 + + "\n" + ) sys.stdout.flush() + model_list = [ + ( + "archaeological_context_records", + "contextrecord", + "unité d'enregistrement", + "de l'unité d'enregistrement", + ContextRecord, + ), + ( + "archaeological_finds", + "basefind", + "mobilier d'origine", + "du mobilier d'origine", + ContextRecord, + ), + ] + for app, model_slug, model_name, model_full_name, model in model_list: + model_content_type = ContentType.objects.get(app_label=app, model=model_slug) + q = model.objects.exclude(main_geodata__isnull=False) + nb = q.count() + data_type_outline, __ = models_common.GeoDataType.objects.get_or_create( + txt_idx=f"{model_slug}-outline", + defaults={"label": f"Contour d'{model_name}"}, + ) + data_type_center, __ = models_common.GeoDataType.objects.get_or_create( + txt_idx=f"{model_slug}-center", + defaults={"label": f"Centre {model_full_name}"}, + ) + ref_time = datetime.datetime.now() + for idx, obj in enumerate(q.all()[:100]): + if not quiet: + write_output(model_name, idx, nb, ref_time) + obj._no_move = True + obj.skip_history_when_saving = True + obj.save() # auto manage geo town association + + if obj.main_geodata: + changed.append( + [model_slug, str(obj), obj.pk, "Association géo de zone communale"] + ) + if obj.multi_polygon_source == "P" and obj.multi_polygon: + attrs = { + "name": f"{_(model_name.capitalize())}{_(':')} {str(obj)}", + "source_content_type": model_content_type, + "source_id": obj.pk, + "multi_polygon": obj.multi_polygon, + "data_type": data_type_outline, + } + data = models_common.GeoVectorData.objects.create(**attrs) + obj.main_geodata = data + obj.save() + changed.append( + [ + "geovectordata", + data.name, + data.pk, + f"Multi-polygone {model_name}", + ] + ) + if obj.point_source == "P" and obj.point_2d: + if obj.x and obj.y: + attrs = { + "name": f"{_(model_name.capitalize())}{_(':')} {str(obj)}", + "source_content_type": model_content_type, + "source_id": obj.pk, + "data_type": data_type_center, + "x": obj.x, + "y": obj.y, + "z": obj.z, + } + data = models_common.GeoVectorData.objects.create(**attrs) + obj.main_geodata = data + obj.save() + changed.append( + [ + "geovectordata", + data.name, + data.pk, + f"Coordonnées {model_name}", + ] + ) + elif obj.point_2d: + attrs = { + "name": f"{_(model_name.capitalize())}{_(':')} {str(obj)}", + "source_content_type": model_content_type, + "source_id": obj.pk, + "data_type": data_type_center, + } + if obj.point: + attrs["point_3d"] = obj.point + else: + attrs["point_2d"] = obj.point_2d + data = models_common.GeoVectorData.objects.create(**attrs) + obj.main_geodata = data + obj.save() + changed.append( + ["geovectordata", data.name, data.pk, f"Point {model_name}"] + ) + if not quiet and nb: + sys.stdout.write( + f"\r[{get_log_time()}] {model_name.capitalize()} migrated" + " " * 20 + "\n" + ) + sys.stdout.flush() + if log and changed: - filename = f"geo_migration-created-{get_time().replace(':', '')}.csv" + filename = f"geo_migration-created-{get_log_time().replace(':', '')}.csv" path = os.sep.join([log_path, filename]) with open(path, "w+") as fle: writer = csv.writer(fle) @@ -167,15 +283,7 @@ def migrate(quiet=False, log=True): for change in changed: writer.writerow(change) if not quiet: - sys.stdout.write(f"[{get_time()}] Log: {path} written\n") - - -def percent(current, total): - return f"{(current + 1) / total * 100:.1f}".rjust(4, "0") + "%" - - -def get_time(): - return datetime.datetime.now().isoformat().split(".")[0] + sys.stdout.write(f"[{get_log_time()}] Log: {path} written\n") class Command(BaseCommand): @@ -193,11 +301,11 @@ class Command(BaseCommand): log = options["log"] quiet = options["quiet"] if not quiet: - sys.stdout.write(f"[{get_time()}] Processing migration\n") + sys.stdout.write(f"[{get_log_time()}] Processing migration\n") errors = migrate(quiet=quiet, log=log) if not errors: if not quiet: - sys.stdout.write(f"[{get_time()}] Migration finished\n") + sys.stdout.write(f"[{get_log_time()}] Migration finished\n") sys.exit() if not quiet: sys.stdout.write("\n".join(errors)) diff --git a/ishtar_common/utils.py b/ishtar_common/utils.py index fc302166b..a7e07ebde 100644 --- a/ishtar_common/utils.py +++ b/ishtar_common/utils.py @@ -2335,3 +2335,21 @@ def create_osm_town(rel_id, name, numero_insee=None): town.center = town.limit.centroid town.save() return town + + +def get_percent(current, total): + return f"{(current + 1) / total * 100:.1f}".rjust(4, "0") + "%" + + +def get_log_time(): + return datetime.datetime.now().isoformat().split(".")[0] + + +def get_eta(current, total, base_time, current_time): + if current < 5: + return "-" + elapsed_time = current_time - base_time + eta = elapsed_time.seconds / current * (total - current) + if eta < 1: + return "-" + return f"{int(eta // 3600):02d}:{int(eta % 3600 // 60):02d}:{int(eta % 60):02d}" |