summaryrefslogtreecommitdiff
path: root/ishtar_common
diff options
context:
space:
mode:
Diffstat (limited to 'ishtar_common')
-rw-r--r--ishtar_common/forms.py3
-rw-r--r--ishtar_common/migrations/0088_auto_20190218_1808.py165
-rw-r--r--ishtar_common/models.py528
-rw-r--r--ishtar_common/templates/ishtar/blocks/sheet_address_section.html14
-rw-r--r--ishtar_common/templates/ishtar/blocks/sheet_coordinates.html6
-rw-r--r--ishtar_common/tests.py1
-rw-r--r--ishtar_common/utils.py104
7 files changed, 559 insertions, 262 deletions
diff --git a/ishtar_common/forms.py b/ishtar_common/forms.py
index 7f9c8a400..311f6fee4 100644
--- a/ishtar_common/forms.py
+++ b/ishtar_common/forms.py
@@ -664,8 +664,7 @@ def get_form_selection(
class_name, label, key, model, base_form, get_url,
not_selected_error=_(u"You should select an item."), new=False,
new_message=_(u"Add a new item"), get_full_url=None,
- gallery=False, map=False
- ):
+ gallery=False, map=False):
"""
Generate a class selection form
class_name -- name of the class
diff --git a/ishtar_common/migrations/0088_auto_20190218_1808.py b/ishtar_common/migrations/0088_auto_20190218_1808.py
new file mode 100644
index 000000000..fc78d7883
--- /dev/null
+++ b/ishtar_common/migrations/0088_auto_20190218_1808.py
@@ -0,0 +1,165 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.10 on 2019-02-18 18:08
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+import ishtar_common.models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('ishtar_common', '0087_auto_20190204_1149'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='document',
+ options={'ordering': ('title',), 'permissions': (('view_document', 'Peut voir tous les Documents'), ('view_own_document', 'Peut voir ses propres Documents'), ('add_own_document', 'Peut ajouter son propre Document'), ('change_own_document', 'Peut modifier ses propres Documents'), ('delete_own_document', 'Peut supprimer ses propres Documents')), 'verbose_name': 'Document', 'verbose_name_plural': 'Documents'},
+ ),
+ migrations.AlterModelOptions(
+ name='profiletypesummary',
+ options={'verbose_name': 'R\xe9sum\xe9 du type de profil', 'verbose_name_plural': 'R\xe9sum\xe9s des types de profil'},
+ ),
+ migrations.AddField(
+ model_name='historicalorganization',
+ name='precise_town',
+ field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='ishtar_common.Town'),
+ ),
+ migrations.AddField(
+ model_name='historicalperson',
+ name='precise_town',
+ field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='ishtar_common.Town'),
+ ),
+ migrations.AddField(
+ model_name='organization',
+ name='precise_town',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='ishtar_common.Town', verbose_name='Town (precise)'),
+ ),
+ migrations.AddField(
+ model_name='person',
+ name='precise_town',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='ishtar_common.Town', verbose_name='Town (precise)'),
+ ),
+ migrations.AlterField(
+ model_name='document',
+ name='associated_file',
+ field=models.FileField(blank=True, help_text='La taille maximale support\xe9e pour le fichier est de 100 Mo.', max_length=255, null=True, upload_to=ishtar_common.models.get_image_path),
+ ),
+ migrations.AlterField(
+ model_name='document',
+ name='image',
+ field=models.ImageField(blank=True, help_text='La taille maximale support\xe9e pour le fichier est de 100 Mo.', max_length=255, null=True, upload_to=ishtar_common.models.get_image_path),
+ ),
+ migrations.AlterField(
+ model_name='document',
+ name='thumbnail',
+ field=models.ImageField(blank=True, help_text='La taille maximale support\xe9e pour le fichier est de 100 Mo.', max_length=255, null=True, upload_to=ishtar_common.models.get_image_path),
+ ),
+ migrations.AlterField(
+ model_name='documenttemplate',
+ name='template',
+ field=models.FileField(help_text='La taille maximale support\xe9e pour le fichier est de 100 Mo.', upload_to=b'templates/%Y/', verbose_name='Patron'),
+ ),
+ migrations.AlterField(
+ model_name='historicalorganization',
+ name='town',
+ field=models.CharField(blank=True, max_length=150, null=True, verbose_name='Town (freeform)'),
+ ),
+ migrations.AlterField(
+ model_name='historicalperson',
+ name='town',
+ field=models.CharField(blank=True, max_length=150, null=True, verbose_name='Town (freeform)'),
+ ),
+ migrations.AlterField(
+ model_name='import',
+ name='error_file',
+ field=models.FileField(blank=True, help_text='La taille maximale support\xe9e pour le fichier est de 100 Mo.', max_length=255, null=True, upload_to=b'upload/imports/%Y/%m/', verbose_name='Fichier erreur'),
+ ),
+ migrations.AlterField(
+ model_name='import',
+ name='imported_file',
+ field=models.FileField(help_text='La taille maximale support\xe9e pour le fichier est de 100 Mo.', max_length=220, upload_to=b'upload/imports/%Y/%m/', verbose_name='Fichier import\xe9'),
+ ),
+ migrations.AlterField(
+ model_name='import',
+ name='imported_images',
+ field=models.FileField(blank=True, help_text='La taille maximale support\xe9e pour le fichier est de 100 Mo.', max_length=220, null=True, upload_to=b'upload/imports/%Y/%m/', verbose_name='Images associ\xe9es (fichier zip)'),
+ ),
+ migrations.AlterField(
+ model_name='import',
+ name='match_file',
+ field=models.FileField(blank=True, help_text='La taille maximale support\xe9e pour le fichier est de 100 Mo.', max_length=255, null=True, upload_to=b'upload/imports/%Y/%m/', verbose_name='Fichier de correspondance'),
+ ),
+ migrations.AlterField(
+ model_name='import',
+ name='result_file',
+ field=models.FileField(blank=True, help_text='La taille maximale support\xe9e pour le fichier est de 100 Mo.', max_length=255, null=True, upload_to=b'upload/imports/%Y/%m/', verbose_name='Fichier r\xe9sultant'),
+ ),
+ migrations.AlterField(
+ model_name='importertype',
+ name='is_template',
+ field=models.BooleanField(default=False, verbose_name='Peut \xeatre export\xe9'),
+ ),
+ migrations.AlterField(
+ model_name='ishtarsiteprofile',
+ name='config',
+ field=models.CharField(blank=True, choices=[(b'DRASSM', 'DRASSM')], help_text='Choisir une configuration alternative pour les libell\xe9s, gestion des index', max_length=200, null=True, verbose_name='Configuration alternative'),
+ ),
+ migrations.AlterField(
+ model_name='ishtarsiteprofile',
+ name='default_operation_prefix',
+ field=models.CharField(blank=True, default='OP', max_length=20, null=True, verbose_name='Pr\xe9fixe par d\xe9faut pour le code op\xe9ration'),
+ ),
+ migrations.AlterField(
+ model_name='ishtarsiteprofile',
+ name='document_external_id',
+ field=models.TextField(default='{index}', help_text="Formule pour g\xe9rer les identifiants de document. \xc0 manipuler avec pr\xe9caution. Une formule incorrecte peut rendre l'application inutilisable et l'import de donn\xe9es externes peut alors \xeatre destructif.", verbose_name='ID externe document'),
+ ),
+ migrations.AlterField(
+ model_name='ishtarsiteprofile',
+ name='find_use_index',
+ field=models.BooleanField(default=True, verbose_name='Utiliser les index automatiques pour le mobilier'),
+ ),
+ migrations.AlterField(
+ model_name='ishtarsiteprofile',
+ name='operation_prefix',
+ field=models.CharField(blank=True, default='OA', max_length=20, null=True, verbose_name='Pr\xe9fixe principal pour le code op\xe9ration'),
+ ),
+ migrations.AlterField(
+ model_name='ishtarsiteprofile',
+ name='operation_region_code',
+ field=models.CharField(blank=True, max_length=5, null=True, verbose_name='Code r\xe9gion des op\xe9rations'),
+ ),
+ migrations.AlterField(
+ model_name='jsondatafield',
+ name='value_type',
+ field=models.CharField(choices=[(b'T', 'Texte'), (b'LT', 'Texte long'), (b'I', 'Entier'), (b'B', 'Bool\xe9en'), (b'F', 'Nombre \xe0 virgule'), (b'D', 'Date'), (b'C', 'Choix')], default=b'T', max_length=10, verbose_name='Type'),
+ ),
+ migrations.AlterField(
+ model_name='organization',
+ name='town',
+ field=models.CharField(blank=True, max_length=150, null=True, verbose_name='Town (freeform)'),
+ ),
+ migrations.AlterField(
+ model_name='person',
+ name='town',
+ field=models.CharField(blank=True, max_length=150, null=True, verbose_name='Town (freeform)'),
+ ),
+ migrations.AlterField(
+ model_name='userprofile',
+ name='auto_pin',
+ field=models.BooleanField(default=False, verbose_name='\xc9pingler automatiquement'),
+ ),
+ migrations.AlterField(
+ model_name='userprofile',
+ name='display_pin_menu',
+ field=models.BooleanField(default=False, verbose_name="Montrer le menu d'\xe9pinglage"),
+ ),
+ migrations.AlterField(
+ model_name='userprofile',
+ name='show_field_number',
+ field=models.BooleanField(default=False, verbose_name='Afficher les num\xe9ros des champs'),
+ ),
+ ]
diff --git a/ishtar_common/models.py b/ishtar_common/models.py
index 778461562..28e469e31 100644
--- a/ishtar_common/models.py
+++ b/ishtar_common/models.py
@@ -1612,23 +1612,96 @@ class DocumentItem(object):
return actions
-class GeoItem(object):
+class SpatialReferenceSystem(GeneralType):
+ order = models.IntegerField(_(u"Order"), default=10)
+ auth_name = models.CharField(
+ _(u"Authority name"), default=u'EPSG', max_length=256)
+ srid = models.IntegerField(_(u"Authority SRID"))
+
+ class Meta:
+ verbose_name = _(u"Spatial reference system")
+ verbose_name_plural = _(u"Spatial reference systems")
+ ordering = ('label',)
+
+
+post_save.connect(post_save_cache, sender=SpatialReferenceSystem)
+post_delete.connect(post_save_cache, sender=SpatialReferenceSystem)
+
+
+class GeoItem(models.Model):
GEO_SOURCE = ('T', _(u"Town")), ('P', _(u"Precise"))
+ # gis
+ x = models.FloatField(_(u'X'), blank=True, null=True)
+ y = models.FloatField(_(u'Y'), blank=True, null=True)
+ z = models.FloatField(_(u'Z'), blank=True, null=True)
+ estimated_error_x = models.FloatField(_(u'Estimated error for X'),
+ blank=True, null=True)
+ estimated_error_y = models.FloatField(_(u'Estimated error for Y'),
+ blank=True, null=True)
+ estimated_error_z = models.FloatField(_(u'Estimated error for Z'),
+ blank=True, null=True)
+ spatial_reference_system = models.ForeignKey(
+ SpatialReferenceSystem, verbose_name=_(u"Spatial Reference System"),
+ blank=True, null=True)
+ point = models.PointField(_(u"Point"), blank=True, null=True, dim=3)
+ point_2d = models.PointField(_(u"Point (2D)"), blank=True, null=True)
+ point_source = models.CharField(
+ _(u"Point source"), choices=GEO_SOURCE, max_length=1, blank=True,
+ null=True)
+ point_source_item = models.CharField(
+ _(u"Point source item"), max_length=100, blank=True, null=True)
+ multi_polygon = models.MultiPolygonField(_(u"Multi polygon"), blank=True,
+ null=True)
+ multi_polygon_source = models.CharField(
+ _(u"Multi-polygon source"), choices=GEO_SOURCE, max_length=1,
+ blank=True, null=True)
+ multi_polygon_source_item = models.CharField(
+ _(u"Multi polygon source item"), max_length=100, blank=True, null=True)
+
+ GEO_LABEL = ""
+
+ class Meta:
+ abstract = True
+
+ def get_town_centroid(self):
+ raise NotImplementedError
+
+ def get_town_polygons(self):
+ raise NotImplementedError
+
+ def get_precise_points(self):
+ if self.point_source == 'P' and self.point_2d:
+ return self.point_2d, self.point, self.point_source_item
+
+ def get_precise_polygons(self):
+ if self.multi_polygon_source == 'P' and self.multi_polygon:
+ return self.multi_polygon, self.multi_polygon_source_item
+
def geo_point_source(self):
if not self.point_source:
return ""
- return dict(self.GEO_SOURCE)[self.point_source]
+ src = u"{} - {}".format(
+ dict(self.GEO_SOURCE)[self.point_source],
+ self.point_source_item
+ )
+ return src
def geo_polygon_source(self):
if not self.multi_polygon_source:
return ""
- return dict(self.GEO_SOURCE)[self.multi_polygon_source]
+ src = u"{} - {}".format(
+ dict(self.GEO_SOURCE)[self.multi_polygon_source],
+ self.multi_polygon_source_item
+ )
+ return src
def _geojson_serialize(self, geom_attr):
if not hasattr(self, geom_attr):
return ""
cached_label_key = 'cached_label'
+ if self.GEO_LABEL:
+ cached_label_key = self.GEO_LABEL
if getattr(self, "CACHED_LABELS", None):
cached_label_key = self.CACHED_LABELS[-1]
geojson = serialize(
@@ -1658,7 +1731,7 @@ class GeoItem(object):
return self._geojson_serialize('multi_polygon')
-class BaseHistorizedItem(DocumentItem, FullSearch, Imported, JsonData, GeoItem,
+class BaseHistorizedItem(DocumentItem, FullSearch, Imported, JsonData,
FixAssociated):
"""
Historized item with external ID management.
@@ -2883,13 +2956,225 @@ class Department(models.Model):
return res
+class Arrondissement(models.Model):
+ name = models.CharField(u"Nom", max_length=30)
+ department = models.ForeignKey(Department, verbose_name=u"Département")
+
+ def __unicode__(self):
+ return settings.JOINT.join((self.name, unicode(self.department)))
+
+
+class Canton(models.Model):
+ name = models.CharField(u"Nom", max_length=30)
+ arrondissement = models.ForeignKey(Arrondissement,
+ verbose_name=u"Arrondissement")
+
+ def __unicode__(self):
+ return settings.JOINT.join(
+ (self.name, unicode(self.arrondissement)))
+
+
+class TownManager(models.GeoManager):
+ def get_by_natural_key(self, numero_insee, year):
+ return self.get(numero_insee=numero_insee, year=year)
+
+
+class Town(Imported, models.Model):
+ name = models.CharField(_(u"Name"), max_length=100)
+ surface = models.IntegerField(_(u"Surface (m2)"), blank=True, null=True)
+ center = models.PointField(_(u"Localisation"), srid=settings.SRID,
+ blank=True, null=True)
+ limit = models.MultiPolygonField(_(u"Limit"), blank=True, null=True)
+ numero_insee = models.CharField(u"Code commune (numéro INSEE)",
+ max_length=120)
+ departement = models.ForeignKey(
+ Department, verbose_name=_(u"Department"),
+ on_delete=models.SET_NULL, null=True, blank=True)
+ year = models.IntegerField(
+ _("Year of creation"), null=True, blank=True,
+ help_text=_(u"Filling this field is relevant to distinguish old towns "
+ u"from new towns."))
+ children = models.ManyToManyField(
+ 'Town', verbose_name=_(u"Town children"), blank=True,
+ related_name='parents')
+ cached_label = models.CharField(_(u"Cached name"), max_length=500,
+ null=True, blank=True, db_index=True)
+ objects = TownManager()
+
+ class Meta:
+ verbose_name = _(u"Town")
+ verbose_name_plural = _(u"Towns")
+ if settings.COUNTRY == 'fr':
+ ordering = ['numero_insee']
+ unique_together = (('numero_insee', 'year'),)
+
+ def natural_key(self):
+ return (self.numero_insee, self.year)
+
+ def history_compress(self):
+ values = {'numero_insee': self.numero_insee,
+ 'year': self.year or ""}
+ return values
+
+ @classmethod
+ def history_decompress(cls, full_value, create=False):
+ if not full_value:
+ return []
+ res = []
+ for value in full_value:
+ try:
+ res.append(
+ cls.objects.get(numero_insee=value['numero_insee'],
+ year=value['year'] or None))
+ except cls.DoesNotExist:
+ continue
+ return res
+
+ def __unicode__(self):
+ if self.cached_label:
+ return self.cached_label
+ self.save()
+ return self.cached_label
+
+ @property
+ def label_with_areas(self):
+ label = [self.name]
+ if self.numero_insee:
+ label.append(u"({})".format(self.numero_insee))
+ for area in self.areas.all():
+ label.append(u" - ")
+ label.append(area.full_label)
+ return u" ".join(label)
+
+ def generate_geo(self, force=False):
+ force = self.generate_limit(force=force)
+ self.generate_center(force=force)
+ self.generate_area(force=force)
+
+ def generate_limit(self, force=False):
+ if not force and self.limit:
+ return
+ parents = None
+ if not self.parents.count():
+ return
+ for parent in self.parents.all():
+ if not parent.limit:
+ return
+ if not parents:
+ parents = parent.limit
+ else:
+ parents = parents.union(parent.limit)
+ # if union is a simple polygon make it a multi
+ if 'MULTI' not in parents.wkt:
+ parents = parents.wkt.replace('POLYGON', 'MULTIPOLYGON(') + ")"
+ if not parents:
+ return
+ self.limit = parents
+ self.save()
+ return True
+
+ def generate_center(self, force=False):
+ if not force and (self.center or not self.limit):
+ return
+ self.center = self.limit.centroid
+ if not self.center:
+ return False
+ self.save()
+ return True
+
+ def generate_area(self, force=False):
+ if not force and (self.surface or not self.limit):
+ return
+ self.surface = self.limit.transform(settings.SURFACE_SRID,
+ clone=True).area
+ if not self.surface:
+ return False
+ self.save()
+ return True
+
+ def update_town_code(self):
+ if not self.numero_insee or not self.children.count() or not self.year:
+ return
+ old_num = self.numero_insee[:]
+ numero = old_num.split('-')[0]
+ self.numero_insee = u"{}-{}".format(numero, self.year)
+ if self.numero_insee != old_num:
+ return True
+
+ def _generate_cached_label(self):
+ cached_label = self.name
+ if settings.COUNTRY == "fr" and self.numero_insee:
+ dpt_len = 2
+ if self.numero_insee.startswith('97') or \
+ self.numero_insee.startswith('98') or \
+ self.numero_insee[0] not in ('0', '1', '2', '3', '4', '5',
+ '6', '7', '8', '9'):
+ dpt_len = 3
+ cached_label = u"%s - %s" % (self.name, self.numero_insee[:dpt_len])
+ if self.year and self.children.count():
+ cached_label += u" ({})".format(self.year)
+ return cached_label
+
+
+def post_save_town(sender, **kwargs):
+ cached_label_changed(sender, **kwargs)
+ town = kwargs['instance']
+ town.generate_geo()
+ if town.update_town_code():
+ town.save()
+
+
+post_save.connect(post_save_town, sender=Town)
+
+
+def town_child_changed(sender, **kwargs):
+ town = kwargs['instance']
+ if town.update_town_code():
+ town.save()
+
+
+m2m_changed.connect(town_child_changed, sender=Town.children.through)
+
+
+class Area(HierarchicalType):
+ towns = models.ManyToManyField(Town, verbose_name=_(u"Towns"), blank=True,
+ related_name='areas')
+ reference = models.CharField(_(u"Reference"), max_length=200, blank=True,
+ null=True)
+ parent = models.ForeignKey(
+ 'self', blank=True, null=True, verbose_name=_(u"Parent"),
+ help_text=_(u"Only four level of parent are managed."),
+ related_name='children', on_delete=models.SET_NULL
+ )
+
+ class Meta:
+ verbose_name = _(u"Area")
+ verbose_name_plural = _(u"Areas")
+ ordering = ('label',)
+
+ def __unicode__(self):
+ if not self.reference:
+ return self.label
+ return u"{} ({})".format(self.label, self.reference)
+
+ @property
+ def full_label(self):
+ label = [unicode(self)]
+ if self.parent:
+ label.append(self.parent.full_label)
+ return u" / ".join(label)
+
+
class Address(BaseHistorizedItem):
address = models.TextField(_(u"Address"), null=True, blank=True)
address_complement = models.TextField(_(u"Address complement"), null=True,
blank=True)
postal_code = models.CharField(_(u"Postal code"), max_length=10, null=True,
blank=True)
- town = models.CharField(_(u"Town"), max_length=70, null=True, blank=True)
+ town = models.CharField(_(u"Town (freeform)"), max_length=150, null=True,
+ blank=True)
+ precise_town = models.ForeignKey(
+ Town, verbose_name=_(u"Town (precise)"), null=True, blank=True)
country = models.CharField(_(u"Country"), max_length=30, null=True,
blank=True)
alt_address = models.TextField(_(u"Other address: address"), null=True,
@@ -2925,6 +3210,14 @@ class Address(BaseHistorizedItem):
class Meta:
abstract = True
+ def get_town_centroid(self):
+ if self.precise_town:
+ return self.precise_town.center, self._meta.verbose_name
+
+ def get_town_polygons(self):
+ if self.precise_town:
+ return self.precise_town.limit, self._meta.verbose_name
+
def simple_lbl(self):
return unicode(self)
@@ -4400,215 +4693,6 @@ def document_attached_changed(sender, **kwargs):
post_save.connect(cached_label_changed, sender=Document)
-class Arrondissement(models.Model):
- name = models.CharField(u"Nom", max_length=30)
- department = models.ForeignKey(Department, verbose_name=u"Département")
-
- def __unicode__(self):
- return settings.JOINT.join((self.name, unicode(self.department)))
-
-
-class Canton(models.Model):
- name = models.CharField(u"Nom", max_length=30)
- arrondissement = models.ForeignKey(Arrondissement,
- verbose_name=u"Arrondissement")
-
- def __unicode__(self):
- return settings.JOINT.join(
- (self.name, unicode(self.arrondissement)))
-
-
-class TownManager(models.GeoManager):
- def get_by_natural_key(self, numero_insee, year):
- return self.get(numero_insee=numero_insee, year=year)
-
-
-class Town(Imported, models.Model):
- name = models.CharField(_(u"Name"), max_length=100)
- surface = models.IntegerField(_(u"Surface (m2)"), blank=True, null=True)
- center = models.PointField(_(u"Localisation"), srid=settings.SRID,
- blank=True, null=True)
- limit = models.MultiPolygonField(_(u"Limit"), blank=True, null=True)
- numero_insee = models.CharField(u"Code commune (numéro INSEE)",
- max_length=120)
- departement = models.ForeignKey(
- Department, verbose_name=_(u"Department"),
- on_delete=models.SET_NULL, null=True, blank=True)
- year = models.IntegerField(
- _("Year of creation"), null=True, blank=True,
- help_text=_(u"Filling this field is relevant to distinguish old towns "
- u"from new towns."))
- children = models.ManyToManyField(
- 'Town', verbose_name=_(u"Town children"), blank=True,
- related_name='parents')
- cached_label = models.CharField(_(u"Cached name"), max_length=500,
- null=True, blank=True, db_index=True)
- objects = TownManager()
-
- class Meta:
- verbose_name = _(u"Town")
- verbose_name_plural = _(u"Towns")
- if settings.COUNTRY == 'fr':
- ordering = ['numero_insee']
- unique_together = (('numero_insee', 'year'),)
-
- def natural_key(self):
- return (self.numero_insee, self.year)
-
- def history_compress(self):
- values = {'numero_insee': self.numero_insee,
- 'year': self.year or ""}
- return values
-
- @classmethod
- def history_decompress(cls, full_value, create=False):
- if not full_value:
- return []
- res = []
- for value in full_value:
- try:
- res.append(
- cls.objects.get(numero_insee=value['numero_insee'],
- year=value['year'] or None))
- except cls.DoesNotExist:
- continue
- return res
-
- def __unicode__(self):
- if self.cached_label:
- return self.cached_label
- self.save()
- return self.cached_label
-
- @property
- def label_with_areas(self):
- label = [self.name]
- if self.numero_insee:
- label.append(u"({})".format(self.numero_insee))
- for area in self.areas.all():
- label.append(u" - ")
- label.append(area.full_label)
- return u" ".join(label)
-
- def generate_geo(self, force=False):
- force = self.generate_limit(force=force)
- self.generate_center(force=force)
- self.generate_area(force=force)
-
- def generate_limit(self, force=False):
- if not force and self.limit:
- return
- parents = None
- if not self.parents.count():
- return
- for parent in self.parents.all():
- if not parent.limit:
- return
- if not parents:
- parents = parent.limit
- else:
- parents = parents.union(parent.limit)
- # if union is a simple polygon make it a multi
- if 'MULTI' not in parents.wkt:
- parents = parents.wkt.replace('POLYGON', 'MULTIPOLYGON(') + ")"
- if not parents:
- return
- self.limit = parents
- self.save()
- return True
-
- def generate_center(self, force=False):
- if not force and (self.center or not self.limit):
- return
- self.center = self.limit.centroid
- if not self.center:
- return False
- self.save()
- return True
-
- def generate_area(self, force=False):
- if not force and (self.surface or not self.limit):
- return
- self.surface = self.limit.transform(settings.SURFACE_SRID,
- clone=True).area
- if not self.surface:
- return False
- self.save()
- return True
-
- def update_town_code(self):
- if not self.numero_insee or not self.children.count() or not self.year:
- return
- old_num = self.numero_insee[:]
- numero = old_num.split('-')[0]
- self.numero_insee = u"{}-{}".format(numero, self.year)
- if self.numero_insee != old_num:
- return True
-
- def _generate_cached_label(self):
- cached_label = self.name
- if settings.COUNTRY == "fr" and self.numero_insee:
- dpt_len = 2
- if self.numero_insee.startswith('97') or \
- self.numero_insee.startswith('98') or \
- self.numero_insee[0] not in ('0', '1', '2', '3', '4', '5',
- '6', '7', '8', '9'):
- dpt_len = 3
- cached_label = u"%s - %s" % (self.name, self.numero_insee[:dpt_len])
- if self.year and self.children.count():
- cached_label += u" ({})".format(self.year)
- return cached_label
-
-
-def post_save_town(sender, **kwargs):
- cached_label_changed(sender, **kwargs)
- town = kwargs['instance']
- town.generate_geo()
- if town.update_town_code():
- town.save()
-
-
-post_save.connect(post_save_town, sender=Town)
-
-
-def town_child_changed(sender, **kwargs):
- town = kwargs['instance']
- if town.update_town_code():
- town.save()
-
-
-m2m_changed.connect(town_child_changed, sender=Town.children.through)
-
-
-class Area(HierarchicalType):
- towns = models.ManyToManyField(Town, verbose_name=_(u"Towns"), blank=True,
- related_name='areas')
- reference = models.CharField(_(u"Reference"), max_length=200, blank=True,
- null=True)
- parent = models.ForeignKey(
- 'self', blank=True, null=True, verbose_name=_(u"Parent"),
- help_text=_(u"Only four level of parent are managed."),
- related_name='children', on_delete=models.SET_NULL
- )
-
- class Meta:
- verbose_name = _(u"Area")
- verbose_name_plural = _(u"Areas")
- ordering = ('label',)
-
- def __unicode__(self):
- if not self.reference:
- return self.label
- return u"{} ({})".format(self.label, self.reference)
-
- @property
- def full_label(self):
- label = [unicode(self)]
- if self.parent:
- label.append(self.parent.full_label)
- return u" / ".join(label)
-
-
class OperationType(GeneralType):
order = models.IntegerField(_(u"Order"), default=1)
preventive = models.BooleanField(_(u"Is preventive"), default=True)
@@ -4691,22 +4775,6 @@ post_save.connect(post_save_cache, sender=OperationType)
post_delete.connect(post_save_cache, sender=OperationType)
-class SpatialReferenceSystem(GeneralType):
- order = models.IntegerField(_(u"Order"), default=10)
- auth_name = models.CharField(
- _(u"Authority name"), default=u'EPSG', max_length=256)
- srid = models.IntegerField(_(u"Authority SRID"))
-
- class Meta:
- verbose_name = _(u"Spatial reference system")
- verbose_name_plural = _(u"Spatial reference systems")
- ordering = ('label',)
-
-
-post_save.connect(post_save_cache, sender=SpatialReferenceSystem)
-post_delete.connect(post_save_cache, sender=SpatialReferenceSystem)
-
-
class AdministrationScript(models.Model):
path = models.CharField(_(u"Filename"), max_length=30)
name = models.TextField(_(u"Name"),
diff --git a/ishtar_common/templates/ishtar/blocks/sheet_address_section.html b/ishtar_common/templates/ishtar/blocks/sheet_address_section.html
index fe4bd80bb..a42cd6cca 100644
--- a/ishtar_common/templates/ishtar/blocks/sheet_address_section.html
+++ b/ishtar_common/templates/ishtar/blocks/sheet_address_section.html
@@ -1,10 +1,10 @@
{% load i18n %}
-{% if item.address or item.address_complement or item.postal_code or item.town %}
-<div class="col-12 col-md-6 col-lg-4 d-flex flex-wrap row">
- <dt class="col-5">{% trans "Address" %}</dt>
- <dd class="col-7">
+{% if item.address or item.address_complement or item.postal_code or item.town or item.precise_town%}
+<dl class="{% if full %}col-12 col-lg-6{% else %}col-12 col-md-6 col-lg-4 d-flex row{% endif %} flex-wrap">
+ <dt>{% trans "Address" %}</dt>
+ <dd>
<pre>{% if item.address %}{{item.address}}{% endif %}{% if item.address_complement %}
-{{item.address_complement}}{% endif %}{% if item.postal_code or item.town %}
-{{item.postal_code}} {{item.town}}{% endif %}</pre>
+{{item.address_complement}}{% endif %}{% if item.postal_code or item.town or item.precise_town %}
+{{item.postal_code}} {% if item.precise_town %}{{item.precise_town}}{% else %}{{item.town}}{% endif %}{% endif %}</pre>
</dd>
-</div>{% endif %}
+</dl>{% endif %}
diff --git a/ishtar_common/templates/ishtar/blocks/sheet_coordinates.html b/ishtar_common/templates/ishtar/blocks/sheet_coordinates.html
index bd0c1ef19..d73ef9c96 100644
--- a/ishtar_common/templates/ishtar/blocks/sheet_coordinates.html
+++ b/ishtar_common/templates/ishtar/blocks/sheet_coordinates.html
@@ -1,7 +1,7 @@
{% load i18n window_field %}{% if geo_item.x or geo_item.y or geo_item.z %}
-<dl class="col-12 d-flex flex-wrap">
- <dt class="col-12">{% trans "Coordinates" %}</dt>
- <dd class="col-12">
+<dl class="col-12">
+ <dt>{% trans "Coordinates" %}</dt>
+ <dd>
{% trans "X:"%} {{geo_item.x|default_if_none:"-"}}
{% if geo_item.estimated_error_x %} ({% trans "error:" %} {{geo_item.estimated_error_x}}){% endif %},
{% trans "Y:"%} {{geo_item.y|default_if_none:"-"}},
diff --git a/ishtar_common/tests.py b/ishtar_common/tests.py
index 53ad67faa..55c43ac21 100644
--- a/ishtar_common/tests.py
+++ b/ishtar_common/tests.py
@@ -1576,6 +1576,7 @@ class GeomaticTest(TestCase):
self.y = y
self.z = z
self.spatial_reference_system = spatial_reference_system
+ self.point_source = 'P'
self.point = point
self.point_2d = point_2d
diff --git a/ishtar_common/utils.py b/ishtar_common/utils.py
index 408309c4e..2aa8db298 100644
--- a/ishtar_common/utils.py
+++ b/ishtar_common/utils.py
@@ -487,18 +487,28 @@ def post_save_geo(sender, **kwargs):
"""
Convert raw x, y, z point to real geo field
"""
- if not kwargs.get('instance'):
+ if not kwargs.get('instance') or getattr(kwargs['instance'],
+ '_post_saved_geo', False):
return
from ishtar_common.models import get_current_profile # not clean but utils
# is loaded before models
instance = kwargs.get('instance')
+ current_source = unicode(instance.__class__._meta.verbose_name)
+ if instance.point_source_item and \
+ instance.point_source_item != current_source: # refetch
+ instance.point, instance.point_2d = None, None
+ instance.x, instance.y = None, None
+ instance.point_source = None
+
point = instance.point
point_2d = instance.point_2d
profile = get_current_profile()
modified = False
+ print (instance)
- if (point or point_2d) and instance.x is None: # db source
+ if (point or point_2d) and instance.x is None and not \
+ instance.point_source: # db source
if point:
current_point = point
instance.z = point.z
@@ -509,11 +519,13 @@ def post_save_geo(sender, **kwargs):
srs = get_srid_obj_from_point(current_point)
instance.spatial_reference_system = srs
instance.point_source = 'P'
+ instance.point_source_item = current_source
if not point_2d:
instance.point_2d = convert_coordinates_to_point(
instance.point.x, instance.point.y,
srid=current_point.srid)
- elif not instance.point_source and instance.x and instance.y and \
+ modified = True
+ elif instance.x and instance.y and \
instance.spatial_reference_system and \
instance.spatial_reference_system.auth_name == 'EPSG' and \
instance.spatial_reference_system.srid != 0: # form input
@@ -523,43 +535,95 @@ def post_save_geo(sender, **kwargs):
srid=instance.spatial_reference_system.srid)
except forms.ValidationError:
return # irrelevant data in DB
+ distance = 1 # arbitrary
+ if point_2d and instance.point_2d:
+ distance = point_2d.transform(
+ 4326, clone=True).distance(
+ instance.point_2d.transform(4326, clone=True))
+
if instance.z:
point = convert_coordinates_to_point(
instance.x, instance.y, instance.z,
srid=instance.spatial_reference_system.srid)
- if point_2d != instance.point_2d or point != instance.point:
+ # no change if distance inf to 1 mm
+ if distance >= 0.0001 and (point_2d != instance.point_2d
+ or point != instance.point):
instance.point = point
instance.point_2d = point_2d
instance.point_source = 'P'
+ instance.point_source_item = current_source
modified = True
- elif not point_2d and profile.use_town_for_geo: # try to get from parent
- point_2d = instance.get_town_centroid()
- if point_2d != instance.point_2d:
+ else:
+ instance.point_source = None
+ # get coordinates from parents
+ precise_points = instance.get_precise_points()
+ if precise_points:
+ point_2d, point, source_item = precise_points
instance.point_2d = point_2d
- instance.point_source = 'T' # town
+ instance.point = point
+ instance.point_source = 'P'
+ instance.point_source_item = source_item
instance.x = point_2d.x
instance.y = point_2d.y
+ if point:
+ instance.z = point.z
srs = get_srid_obj_from_point(point_2d)
instance.spatial_reference_system = srs
modified = True
- if not hasattr(instance, 'multi_polygon'):
- return
-
- if instance.multi_polygon and not instance.multi_polygon_source:
- # should be a db source
- instance.multi_polygon_source = 'P'
- modified = True
- elif profile.use_town_for_geo and instance.multi_polygon_source != 'P':
- poly = instance.get_town_polygons()
- if poly and poly != instance.multi_polygon:
- instance.multi_polygon_source = 'T' # town
- instance.multi_polygon = poly
+ elif profile.use_town_for_geo: # try to get from parent
+ centroid = instance.get_town_centroid()
+ if centroid and centroid[0]:
+ instance.point_2d, instance.point_source_item = centroid
+ instance.point = None
+ instance.point_source = 'T'
+ instance.x = instance.point_2d.x
+ instance.y = instance.point_2d.y
+ srs = get_srid_obj_from_point(instance.point_2d)
+ instance.spatial_reference_system = srs
+ modified = True
+ else:
+ instance.point_2d, instance.point_source_item = None, None
+ instance.point = None
+ instance.point_source = None
+ modified = True
+
+ if hasattr(instance, 'multi_polygon'):
+ if instance.multi_polygon_source_item and \
+ instance.multi_polygon_source_item != current_source: # refetch
+ instance.multi_polygon = None
+ instance.multi_polygon_source = None
+
+ if instance.multi_polygon and not instance.multi_polygon_source:
+ # should be a db source
+ instance.multi_polygon_source = 'P'
+ instance.multi_polygon_source_item = current_source
modified = True
+ elif instance.multi_polygon_source != 'P':
+ precise_poly = instance.get_precise_polygons()
+ if precise_poly:
+ poly, source_item = precise_poly
+ instance.multi_polygon = poly
+ instance.multi_polygon_source = 'P'
+ instance.multi_polygon_source_item = source_item
+ modified = True
+ elif profile.use_town_for_geo:
+ poly = instance.get_town_polygons()
+ if poly:
+ poly, poly_source = poly
+ if poly != instance.multi_polygon:
+ instance.multi_polygon_source_item = poly_source
+ instance.multi_polygon_source = 'T' # town
+ try:
+ instance.multi_polygon = poly
+ modified = True
+ except TypeError:
+ print(instance, instance.pk)
if modified:
instance.skip_history_when_saving = True
+ instance._post_saved_geo = True
instance.save()
return