import datetime import logging import os.path import uuid from django.conf import settings from django.contrib.gis.db import models as gismodels from django.contrib.gis.db.models import Union from django.contrib.gis.geos import GEOSGeometry from django.contrib.postgres.fields import JSONField from django.core.files.storage import FileSystemStorage from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from django.db.models import Sum from django.db.models.deletion import DO_NOTHING from django.db.models.signals import post_delete, post_save from django.dispatch import receiver from django.urls import reverse from django.utils.translation import ugettext_lazy as _ from core.functions import get_sight_period from dicts.models import ( Age, BiomChinspot, BiomDent, BiomEpiphyse, BiomEpipidyme, BiomGestation, BiomGlandCoul, BiomGlandTaille, BiomMamelle, BiomTesticule, BiomTuniqVag, BuildCover, Contact, CountPrecision, CountUnit, GiteBatAccess, Interest, MetaPlaceType, Method, PlaceManagementAction, PlacePrecision, PropertyDomain, Sex, Specie, TreeCircumstance, TreeContext, TreeForestStands, TreeGiteOrigin, TreeGitePlace, TreeGiteType, TreeHealth, TreeSpecies, TypeDevice, TypePlace, ) from geodata.models import LandCover, Municipality, Territory from management.models import Study, Transmitter logger = logging.getLogger(__name__) fs = FileSystemStorage() def update_metaplace_autogeom(id_metaplace): """Update metaplace geom when autogeom is checked :param id_metaplace: :return: """ metaplace = MetaPlace.objects.get(id_metaplace=id_metaplace) logger.debug("metaplace", metaplace) places = Place.objects.filter(metaplace_id=id_metaplace) logger.debug("places {} : {}".format(len(places), places)) if metaplace.autogeom: if len(places) == 1: place_geom = places.first().geom metaplace.geom = place_geom.buffer(0.0005) elif len(places) > 1: places_collection = Place.objects.filter(metaplace_id=id_metaplace).aggregate( union=Union("geom") ) if len(places) == 2: metaplace.geom = GEOSGeometry(places_collection["union"]).envelope elif len(places) > 2: metaplace.geom = GEOSGeometry(places_collection["union"]).convex_hull metaplace.save() class MetaPlace(gismodels.Model): id_metaplace = models.AutoField(primary_key=True, db_index=True) name = models.CharField(max_length=255, db_index=True, verbose_name=_("nom du métasite")) type = models.ForeignKey( MetaPlaceType, db_index=True, blank=True, null=True, verbose_name=_("Type de métasite"), on_delete=models.SET_NULL, ) comment = models.TextField(blank=True, null=True, verbose_name=_("Commentaire")) geom = gismodels.PolygonField( srid=settings.GEODATA_SRID, null=True, blank=True, verbose_name="Enveloppe du métasite", ) autogeom = models.BooleanField( db_index=True, verbose_name="Géométrie automatiquement générée", default=True, help_text=_( "La géométrie sera automatiquement générée selon les localités associés au métasite." " Décocher l'option pour dessiner un polygone à main levée." ), ) extra_data = JSONField(blank=True, null=True) timestamp_create = models.DateTimeField(auto_now_add=True, editable=False) timestamp_update = models.DateTimeField(auto_now=True, editable=False) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, db_index=True, editable=False, related_name="metaplace_creator", on_delete=models.SET_NULL, ) updated_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, db_index=True, editable=False, related_name="metaplace_modifier", on_delete=models.SET_NULL, ) def __str__(self): return "%s ∙ %s" % (self.name, self.type) def save(self, *args, **kwargs): # Regenerate autogeom if checked after update id_metaplace = self.id_metaplace if id_metaplace is not None: places = Place.objects.filter(metaplace_id=id_metaplace) logger.debug("places {} : {}".format(len(places), places)) if self.autogeom: if len(places) == 1: place_geom = places.first().geom self.geom = place_geom.buffer(0.0005) elif len(places) > 1: places_collection = Place.objects.filter(metaplace_id=id_metaplace).aggregate( union=Union("geom") ) if len(places) == 2: self.geom = GEOSGeometry(places_collection["union"]).envelope elif len(places) > 2: self.geom = GEOSGeometry(places_collection["union"]).convex_hull super(MetaPlace, self).save(*args, **kwargs) def get_absolute_url(self): return reverse("sights:metaplace_detail", kwargs={"pk": self.id_metaplace}) # Calculate polygon centroid on the fly def get_centroid(self): c = str(self.geom.centroid) c = c[c.find("(") + 1 : c.find(")")].split(" ") return c[1] + ";" + c[0] class Meta: verbose_name = _("Métasite") verbose_name_plural = _("Métasites") class Place(models.Model): id_place = models.AutoField(primary_key=True, db_index=True) uuid = models.UUIDField(default=uuid.uuid4, unique=True, editable=False) name = models.CharField(max_length=255, db_index=True, verbose_name=_("nom du lieu")) metaplace = models.ForeignKey( MetaPlace, db_index=True, blank=True, null=True, verbose_name=_("Métasite associé"), on_delete=models.SET_NULL, ) is_hidden = models.BooleanField(db_index=True, default=False, verbose_name="Site sensible") authorized_user = models.ManyToManyField( settings.AUTH_USER_MODEL, blank=True, related_name="hiddenplaceauthuser", verbose_name=_("Personnes authorisées si site sensible"), ) type = models.ForeignKey( TypePlace, db_index=True, blank=True, null=True, verbose_name=_("Type de gîte"), on_delete=models.SET_NULL, ) is_gite = models.BooleanField(db_index=True, verbose_name="Est un gîte", default=False) is_managed = models.BooleanField(db_index=True, default=False, verbose_name="Site géré") proprietary = models.CharField( max_length=100, db_index=True, blank=True, null=True, verbose_name="Nom du propriétaire", ) convention = models.BooleanField(verbose_name="Convention refuge", default=False) convention_file = models.FileField( upload_to="place/convention/", blank=True, null=True, verbose_name="Exemplaire de la convention", ) map_file = models.FileField( upload_to="place/map/", blank=True, null=True, verbose_name="Carte détaillée, topo, etc.", ) photo_file = models.ImageField( upload_to="photo/", blank=True, null=True, verbose_name="Photo de la localité" ) habitat = models.CharField(max_length=255, blank=True, null=True, verbose_name=_("Habitat")) altitude = models.PositiveIntegerField( blank=True, null=True, verbose_name=_("Altitude"), validators=[ MaxValueValidator(settings.GEODATA_ALTI_MAX), MinValueValidator(settings.GEODATA_ALTI_MIN), ], ) domain = models.ForeignKey( PropertyDomain, max_length=100, on_delete=DO_NOTHING, null=True, blank=True, verbose_name=_("Domaine"), ) municipality = models.ForeignKey( Municipality, models.DO_NOTHING, db_index=True, blank=True, null=True, verbose_name=_("Commune"), ) territory = models.ForeignKey( Territory, models.SET_NULL, db_index=True, blank=True, null=True, verbose_name=_("Territoire"), ) landcover = models.ForeignKey( LandCover, db_index=True, blank=True, null=True, verbose_name=_("Occupation du sol"), on_delete=models.SET_NULL, ) id_bdcavite = models.CharField(max_length=15, blank=True, null=True) plan_localite = models.FileField( upload_to="place/plan_localite/", blank=True, null=True, verbose_name="Plan du site et/ou topo de la cavité", ) comment = models.TextField(blank=True, null=True, verbose_name=_("Commentaire")) other_imported_data = models.TextField( blank=True, null=True, verbose_name=_("Autres données importées") ) telemetric_crossaz = models.BooleanField( default=False, verbose_name=_("Croisement d 'azimuth télémétrique") ) bdsource = models.CharField(max_length=100, blank=True, null=True) id_bdsource = models.CharField(max_length=100, blank=True, null=True) extra_data = JSONField(blank=True, null=True) precision = models.ForeignKey( PlacePrecision, models.DO_NOTHING, verbose_name="Précision géographique" ) x = models.FloatField( blank=True, null=True, verbose_name="Longitude", help_text=_("Coordonnées est-ouest en WGS84 (projection habituelle des gps)"), ) y = models.FloatField( blank=True, null=True, verbose_name="Latitude", help_text=_("Coordonnées nord-sud en WGS84 (projection habituelle des gps)"), ) geom = gismodels.PointField( srid=settings.GEODATA_SRID, null=True, blank=True, verbose_name="Localisation géographique", ) timestamp_create = models.DateTimeField(auto_now_add=True, editable=False) timestamp_update = models.DateTimeField(auto_now=True, db_index=True, editable=False) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, db_index=True, editable=False, related_name="place_creator", on_delete=models.SET_NULL, ) updated_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, db_index=True, editable=False, related_name="place_modifier", on_delete=models.SET_NULL, ) def __str__(self): return "%s (%s)" % (self.name, self.municipality) def get_absolute_url(self): return reverse("sights:place_detail", kwargs={"pk": self.id_place}) def save(self, *args, **kwargs): self.y = self.geom.x self.x = self.geom.y try: self.territory = Territory.objects.get(geom__contains=self.geom) except Exception as e: logger.debug(str(e)) pass try: self.municipality = Municipality.objects.get(geom__contains=self.geom) except Exception as e: logger.debug(str(e)) pass # self.creator = current_user() super(Place, self).save(*args, **kwargs) class Meta: verbose_name = "Localité" verbose_name_plural = "Localités" unique_together = ["name", "municipality", "type"] class PlaceManagement(models.Model): id_placemanagement = models.AutoField(primary_key=True) place = models.ForeignKey( Place, editable=False, related_name="management", on_delete=models.CASCADE ) date = models.DateField(verbose_name=_("Date")) action = models.ForeignKey( PlaceManagementAction, null=True, blank=True, verbose_name=_("Action"), on_delete=models.SET_NULL, ) comment = models.TextField(blank=True, null=True, verbose_name=_("Commentaire")) referent = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name=_("Référent de l'action"), null=True, blank=True, on_delete=models.SET_NULL, ) file = models.FileField( upload_to="place/management/", blank=True, null=True, verbose_name=_("Fichier"), help_text=_( "Si vous voulez charger plusieurs fichiers, créez une archive avec un logiciel de " "compression comme 7zip" ), ) bdsource = models.CharField(max_length=100, blank=True, null=True) id_bdsource = models.CharField(max_length=100, blank=True, null=True) extra_data = JSONField(blank=True, null=True) timestamp_create = models.DateTimeField(auto_now_add=True, editable=False) timestamp_update = models.DateTimeField(auto_now=True, editable=False) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, db_index=True, editable=False, related_name="placemanagement_creator", on_delete=models.SET_NULL, ) updated_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, db_index=True, editable=False, related_name="placemanagement_modifier", on_delete=models.SET_NULL, ) def __str__(self): return "%s ∙ %s" % (self.date, self.action) def get_absolute_url(self): return reverse("sights:place_detail", kwargs={"pk": self.place_id}) class Meta: verbose_name = _("Action de gestion") verbose_name_plural = _("Actions de gestion") class Bridge(models.Model): id_bridge = models.AutoField(primary_key=True) place = models.ForeignKey(Place, on_delete=models.CASCADE) visit_date = models.DateField() interest = models.ForeignKey( Interest, null=True, blank=True, verbose_name="Intérêt pour les chiros", on_delete=models.SET_NULL, ) renovated = models.NullBooleanField(verbose_name="Rénové") renovated_date = models.DateField(null=True, blank=True, verbose_name="Date de rénovation") joint = models.NullBooleanField(verbose_name="Joints") rift = models.NullBooleanField(verbose_name="Fissures") expansion = models.NullBooleanField(verbose_name="Joints de dilatations") drain = models.NullBooleanField(verbose_name="Drains") cornice = models.NullBooleanField(verbose_name="Corniche") volume = models.NullBooleanField(verbose_name="Volume") comment = models.TextField(blank=True, null=True, verbose_name=_("Commentaire")) bdsource = models.CharField(max_length=100, blank=True, null=True) id_bdsource = models.CharField(max_length=100, blank=True, null=True) extra_data = JSONField(blank=True, null=True) timestamp_create = models.DateTimeField(auto_now_add=True, editable=False) timestamp_update = models.DateTimeField(auto_now=True, editable=False) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, db_index=True, editable=False, related_name="bridge_creator", on_delete=models.SET_NULL, ) updated_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, db_index=True, editable=False, related_name="bridge_modifier", on_delete=models.SET_NULL, ) def __str__(self): return "Pont : %s ∙ %s" % (self.place.name, self.visit_date) def get_absolute_url(self): return reverse("sights:place_detail", kwargs={"pk": self.place_id}) class Meta: verbose_name = "Détails d'un pont" verbose_name_plural = "Détails des ponts" unique_together = ["visit_date", "place"] class Build(models.Model): id_build = models.AutoField(primary_key=True) place = models.ForeignKey(Place, on_delete=models.CASCADE) visit_date = models.DateField(verbose_name="Date de visite") cavity_front = models.NullBooleanField(verbose_name="Cavité en façade") attic = models.NullBooleanField(verbose_name="Combles présents") attic_access = models.ForeignKey( GiteBatAccess, models.DO_NOTHING, blank=True, null=True, verbose_name="Accessibilité aux chiroptères", related_name="attic", ) bell_tower = models.NullBooleanField(verbose_name="Clocher présent") bell_tower_screen = models.NullBooleanField(verbose_name="Clocher grillagé") bell_tower_access = models.ForeignKey( GiteBatAccess, models.DO_NOTHING, blank=True, null=True, verbose_name="Accessibilité aux chiroptères", related_name="belltower", ) cover = models.ForeignKey( BuildCover, models.DO_NOTHING, blank=True, null=True, verbose_name="Type de couverture", ) cellar = models.NullBooleanField(verbose_name="Cave présente") cellar_access = models.ForeignKey( GiteBatAccess, models.DO_NOTHING, blank=True, null=True, verbose_name="Accessibilité aux chiroptères", related_name="cellar", ) ext_light = models.NullBooleanField(verbose_name="Eclairage extérieur") access_light = models.NullBooleanField(verbose_name="Entrées éclairées") comment = models.TextField(blank=True, null=True, verbose_name=_("Commentaire")) bdsource = models.CharField(max_length=100, blank=True, null=True) id_bdsource = models.CharField(max_length=100, blank=True, null=True) extra_data = JSONField(blank=True, null=True) timestamp_create = models.DateTimeField(auto_now_add=True, editable=False) timestamp_update = models.DateTimeField(auto_now=True, editable=False) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, db_index=True, editable=False, related_name="build_creator", on_delete=models.SET_NULL, ) updated_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, db_index=True, editable=False, related_name="build_modifier", on_delete=models.SET_NULL, ) def __str__(self): return "Pont : %s ∙ %s" % (self.place.name, self.visit_date) def get_absolute_url(self): return reverse("sights:place_detail", kwargs={"pk": self.place_id}) class Meta: verbose_name = "Détails d'un pont" verbose_name_plural = "Détails des ponts" unique_together = ["visit_date", "place"] class Tree(models.Model): """ # SubModel of place model for tree bat gites. """ id_tree = models.AutoField(primary_key=True) place = models.ForeignKey(Place, on_delete=models.CASCADE) visit_date = models.DateField(verbose_name="Date", help_text="Date de visite") context = models.ForeignKey( TreeContext, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_("Contexte"), ) forest_stands = models.ForeignKey( TreeForestStands, on_delete=models.SET_NULL, blank=True, null=True, verbose_name=_("Peuplement"), help_text=_("Peuplement forestier"), ) situation = models.CharField( max_length=100, blank=True, null=True, verbose_name=_("Situation"), help_text=_("... dans le milieu (dans le bois, en lisière, etc.)"), ) circumstance = models.ForeignKey( TreeCircumstance, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_("Circonstance"), help_text=_("Circonstance de la découverte"), ) tree_specie = models.ForeignKey( TreeSpecies, null=True, blank=True, verbose_name=_("Espèce"), help_text=_("Espèce de l'arbre"), on_delete=models.SET_NULL, ) health = models.ForeignKey( TreeHealth, null=True, blank=True, verbose_name=_("Etat"), help_text=_("Etat sanitaire"), on_delete=models.SET_NULL, ) tree_diameter = models.PositiveIntegerField( null=True, blank=True, verbose_name=_("Diamètre"), help_text=_("à 1.30m du sol, en cm"), validators=[MaxValueValidator(500)], ) standing = models.NullBooleanField(verbose_name=_("Arbre debout")) protected = models.NullBooleanField(verbose_name=_("Arbre protégé")) bat_specie = models.ManyToManyField( Specie, blank=True, related_name="treebatspecie", verbose_name=_("Espèces observées"), ) gite_type = models.ManyToManyField( TreeGiteType, blank=True, verbose_name=_("Types de gîte(s)") ) gite_origin = models.ManyToManyField( TreeGiteOrigin, blank=True, verbose_name=_("Origines du/des gîte(s)") ) gite_localisation = models.ManyToManyField( TreeGitePlace, blank=True, verbose_name=_("Emplacement du/des gîte(s)") ) comment = models.TextField(blank=True, null=True, verbose_name=_("Commentaire")) bdsource = models.CharField(max_length=100, blank=True, null=True) id_bdsource = models.CharField(max_length=100, blank=True, null=True) extra_data = JSONField(blank=True, null=True) timestamp_create = models.DateTimeField(auto_now_add=True, editable=False) timestamp_update = models.DateTimeField(auto_now=True, editable=False) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, db_index=True, editable=False, related_name="tree_creator", on_delete=models.SET_NULL, ) updated_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, db_index=True, editable=False, related_name="tree_modifier", on_delete=models.SET_NULL, ) def __str__(self): return "Arbre : %s ∙ %s" % (self.place.name, self.visit_date) def get_absolute_url(self): return reverse("sights:tree_detail", kwargs={"pk": self.id_tree}) class Meta: verbose_name = "Détail d'un arbre" verbose_name_plural = "Détails des arbres" unique_together = ["visit_date", "place"] class TreeGite(models.Model): id_treegite = models.AutoField(primary_key=True) tree = models.ForeignKey(Tree, on_delete=models.CASCADE) bat_specie = models.ManyToManyField( Specie, blank=True, related_name="treegitebatspecie", verbose_name=_("Espèces observées"), ) gite_type = models.ForeignKey( TreeGiteType, null=True, blank=True, verbose_name=_("Type de gîte"), on_delete=models.SET_NULL, ) gite_origin = models.ForeignKey( TreeGiteOrigin, null=True, blank=True, verbose_name=_("Origine du gîte"), on_delete=models.SET_NULL, ) gite_localisation = models.ForeignKey( TreeGitePlace, null=True, blank=True, verbose_name=_("Emplacement du gîte"), on_delete=models.SET_NULL, ) gite_high = models.PositiveIntegerField( null=True, blank=True, verbose_name=_("Hauteur gîte (au plus haut point d’accès)"), ) gite_tree_diameter = models.DecimalField( max_digits=3, decimal_places=1, null=True, blank=True, verbose_name=_("Diamètre (branche, tronc) à hauteur du gîte"), help_text=_("en cm"), ) gite_access_orientation = models.CharField( max_length=50, blank=True, null=True, verbose_name=_("Orientation du ou des accès"), ) gite_access_size = models.FloatField( blank=True, null=True, verbose_name=_("largeur ou diamètre de l'ouverture en cm"), ) comment = models.TextField(blank=True, null=True, verbose_name=_("Commentaire")) bdsource = models.CharField(max_length=100, blank=True, null=True) id_bdsource = models.CharField(max_length=100, blank=True, null=True) extra_data = JSONField(blank=True, null=True) timestamp_create = models.DateTimeField(auto_now_add=True, editable=False) timestamp_update = models.DateTimeField(auto_now=True, editable=False) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, db_index=True, editable=False, related_name="treegite_creator", on_delete=models.SET_NULL, ) updated_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, db_index=True, editable=False, related_name="treegite_modifier", on_delete=models.SET_NULL, ) def get_absolute_url(self): return reverse("sights:tree_detail", kwargs={"pk": self.tree_id}) class Meta: verbose_name = "Détail des gîtes d'un arbre" verbose_name_plural = "Détails des gîtes d'un arbre" class Cave(models.Model): id_cave = models.AutoField(primary_key=True) place = models.ForeignKey(Place, on_delete=models.CASCADE) bd_cavite = models.CharField( max_length=20, blank=True, null=True, verbose_name="Identifiant de la BD Cavités du BRGM", ) visit_date = models.DateField() interest = models.ForeignKey( Interest, null=True, blank=True, verbose_name="Intérêt pour les chiros", on_delete=models.SET_NULL, ) length = models.PositiveIntegerField(blank=True, null=True, verbose_name="Développement") altdiff = models.IntegerField(blank=True, null=True, verbose_name="Dénivellé vers la cavité") n_entry = models.PositiveIntegerField(blank=True, null=True, verbose_name="Nombre d" "entrées") equip_req = models.NullBooleanField(verbose_name="Equipement requis?") equipment = models.TextField(blank=True, null=True, verbose_name=_("Equipement nécessaire")) access_walk_duration = models.TimeField( blank=True, null=True, verbose_name=_("Durée de la marche d'accès") ) comment = models.TextField(blank=True, null=True, verbose_name=_("Commentaire")) bdsource = models.CharField(max_length=100, blank=True, null=True) id_bdsource = models.CharField(max_length=100, blank=True, null=True) extra_data = JSONField(blank=True, null=True) timestamp_create = models.DateTimeField(auto_now_add=True, editable=False) timestamp_update = models.DateTimeField(auto_now=True, editable=False) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, db_index=True, editable=False, related_name="cave_creator", on_delete=models.SET_NULL, ) updated_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, db_index=True, editable=False, related_name="cave_modifier", on_delete=models.SET_NULL, ) def __str__(self): return _("Cavité : {} ∙ {}").format(self.place.name, self.visit_date) def get_absolute_url(self): return reverse("sights:place_detail", kwargs={"pk": self.place_id}) class Meta: verbose_name = "Détail d'une cavité" verbose_name_plural = "Détails des cavités" unique_together = ["visit_date", "place"] class Session(models.Model): id_session = models.AutoField(primary_key=True) uuid = models.UUIDField(default=uuid.uuid4, unique=True, editable=False) name = models.CharField(max_length=150, blank=True, verbose_name="Intitulé de la session") contact = models.ForeignKey( Contact, models.DO_NOTHING, default=10, verbose_name=_("Type de contact") ) place = models.ForeignKey(Place, on_delete=models.CASCADE, verbose_name=_("Localité associée")) date_start = models.DateField( verbose_name=_("Date de début"), help_text=_("Format de date: 01/01/2017."), ) time_start = models.TimeField( blank=True, null=True, verbose_name=_("Heure de début"), help_text=_("Format d'heure: 02:15."), ) date_end = models.DateField( blank=True, null=True, verbose_name=_("Date de fin"), help_text=_("Format de date: 01/01/2017."), ) time_end = models.TimeField( blank=True, null=True, verbose_name=_("Heure de fin"), help_text=_("Format d'heure: 02:15."), ) data_file = models.FileField( upload_to="session/file/", blank=True, null=True, verbose_name=_( "Fichier de données (tableur d'analyse acoustique, fiche de terrain, etc.)" ), ) main_observer = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=DO_NOTHING, related_name="mainobs" ) other_observer = models.ManyToManyField( settings.AUTH_USER_MODEL, blank=True, related_name="otherobs" ) is_confidential = models.BooleanField(default=False, verbose_name=_("Données confidentielles")) study = models.ForeignKey( Study, models.DO_NOTHING, verbose_name="Etude", related_name="study", blank=True, null=True, ) # dataset = models.ForeignKey( # "metadata.Dataset", # models.DO_NOTHING, # verbose_name="Jeu de données", # related_name="dataset", # blank=True, # null=True, # ) comment = models.TextField(blank=True, null=True, verbose_name=_("Commentaire")) bdsource = models.CharField(max_length=100, blank=True, null=True) id_bdsource = models.TextField(blank=True, null=True) extra_data = JSONField(blank=True, null=True) timestamp_create = models.DateTimeField(auto_now_add=True, editable=False) timestamp_update = models.DateTimeField(auto_now=True, editable=False) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, db_index=True, editable=False, related_name="session_creator", on_delete=models.SET_NULL, ) updated_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, db_index=True, editable=False, related_name="session_modifier", on_delete=models.SET_NULL, ) @property def data_filename(self): return os.path.basename(self.data_file.name) def __str__(self): return "%s ∙ %s ∙ %s" % ( self.place, datetime.date(self.date_start.year, self.date_start.month, self.date_start.day), self.contact, ) def save(self, *args, **kwargs): self.name = "loc%s %s %s %s" % ( self.place_id, datetime.date(self.date_start.year, self.date_start.month, self.date_start.day), self.contact.code, self.main_observer.username, ) super(Session, self).save(*args, **kwargs) def get_absolute_url(self): return reverse("sights:session_detail", kwargs={"pk": self.id_session}) class Meta: verbose_name = "Session d'inventaire/observations" verbose_name_plural = "Sessions d'inventaire/observations" unique_together = ["place", "contact", "date_start"] class SessionLink(models.Model): id_session_link = models.AutoField(verbose_name=_("id unique"), primary_key=True) type_link = models.ForeignKey( "dicts.LinkType", on_delete=models.DO_NOTHING, verbose_name=_("Type de lien") ) link = models.URLField(null=False, verbose_name=_("Hyperlien"), help_text=_("http(s)://...")) comment = models.CharField( max_length=500, null=True, blank=True, verbose_name=_("Commentaire") ) session = models.ForeignKey( Session, on_delete=models.CASCADE, related_name="session_link_related", related_query_name="session_links", ) class Meta: unique_together = [["link", "session"]] def __str__(self): return f"{self.type_link.label} ∙ {self.link}" def get_absolute_url(self): return reverse("sights:session_detail", kwargs={"pk": self.session.pk}) class Sighting(models.Model): id_sighting = models.AutoField(_("id unique"), primary_key=True) uuid = models.UUIDField(default=uuid.uuid4, unique=True, editable=False) session = models.ForeignKey( Session, on_delete=models.CASCADE, verbose_name=_("Session associée") ) period = models.CharField( max_length=50, blank=True, null=True, verbose_name=_("Période d'observation") ) codesp = models.ForeignKey( Specie, on_delete=DO_NOTHING, verbose_name=_("Espèce ou groupe d'espèce") ) total_count = models.PositiveIntegerField( "Nombre total", blank=True, null=True, help_text=_("Désactivé pour les données acoustiques et en main"), ) breed_colo = models.NullBooleanField(verbose_name="Colonie de reproduction") observer = models.ForeignKey( settings.AUTH_USER_MODEL, blank=True, null=True, related_name="sightingobs", verbose_name=_("Observateur"), on_delete=models.SET_NULL, ) is_doubtful = models.BooleanField(default=False, verbose_name=_("Donnée douteuse")) id_bdsource = models.TextField("id unique de la bdd source", blank=True, null=True) bdsource = models.CharField("bdd source", max_length=100, blank=True, null=True) comment = models.TextField(blank=True, null=True, verbose_name=_("Commentaire")) extra_data = JSONField(blank=True, null=True) timestamp_create = models.DateTimeField(auto_now_add=True, editable=False) timestamp_update = models.DateTimeField(auto_now=True, editable=False) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, db_index=True, editable=False, related_name="sighting_creator", on_delete=models.SET_NULL, ) updated_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, db_index=True, editable=False, related_name="sighting_modifier", on_delete=models.SET_NULL, ) def __str__(self): return "%s ∙ %s ∙ %s" % (self.session, self.codesp, self.total_count) def get_absolute_url(self): return reverse("sights:sighting_detail", kwargs={"pk": self.id_sighting}) def save(self, *args, **kwargs): self.period = get_sight_period(self.session.date_start) super(Sighting, self).save(*args, **kwargs) class Meta: verbose_name = "Observation" verbose_name_plural = "Observations" unique_together = ["codesp", "session"] class Device(models.Model): id_device = models.AutoField(primary_key=True) session = models.ForeignKey(Session, on_delete=models.CASCADE, verbose_name="Session associée") ref = models.CharField( max_length=30, verbose_name=_("Référence du dispositif pour la session"), default="nd", ) type = models.ForeignKey(TypeDevice, on_delete=models.CASCADE) height = models.FloatField(blank=True, null=True, default=None, verbose_name=_("Hauteur")) width = models.FloatField(blank=True, null=True, default=None, verbose_name=_("Largeur")) context = models.CharField( max_length=150, blank=True, null=True, verbose_name=_("Contexte"), default=None ) photo_file = models.ImageField( upload_to="photo/", blank=True, null=True, verbose_name=_("Photo du dispositif"), default=None, ) # geom = models.GeometryCollectionField(srid=2154, null=True, blank=True, # verbose_name=_('Localisation du dispositif')) id_fromsrc = models.CharField( "id unique de la bdd source", max_length=100, blank=True, null=True ) bdsource = models.CharField("bdd source", max_length=100, blank=True, null=True) extra_data = JSONField(blank=True, null=True) comment = models.TextField(blank=True, null=True, verbose_name=_("Commentaire")) timestamp_create = models.DateTimeField(auto_now_add=True, editable=False) timestamp_update = models.DateTimeField(auto_now=True, editable=False) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, db_index=True, editable=False, related_name="device_creator", on_delete=models.SET_NULL, ) updated_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, db_index=True, editable=False, related_name="device_modifier", on_delete=models.SET_NULL, ) def __str__(self): return self.ref def get_absolute_url(self): return reverse("sights:session_detail", kwargs={"pk": self.session.id_session}) class CountDetail(models.Model): id_countdetail = models.AutoField(primary_key=True) uuid = models.UUIDField(default=uuid.uuid4, unique=True, editable=False) sighting = models.ForeignKey(Sighting, on_delete=models.CASCADE, related_name="countdetails") method = models.ForeignKey( Method, on_delete=models.DO_NOTHING, verbose_name=_("Méthode"), blank=True, null=True, ) sex = models.ForeignKey(Sex, models.DO_NOTHING, blank=True, null=True, verbose_name=_("Sexe")) age = models.ForeignKey( Age, models.DO_NOTHING, blank=True, null=True, verbose_name=_("Age estimé") ) precision = models.ForeignKey( CountPrecision, on_delete=DO_NOTHING, blank=True, null=True, verbose_name=_("Précision du comptage"), ) count = models.PositiveIntegerField(blank=True, null=True, verbose_name=_("Effectif")) unit = models.ForeignKey( CountUnit, on_delete=DO_NOTHING, blank=True, null=True, verbose_name=_("Unité") ) time = models.TimeField( blank=True, null=True, verbose_name=_("Heure"), help_text=_("au format hh:mm") ) device = models.ForeignKey( Device, on_delete=DO_NOTHING, blank=True, null=True, default=None, verbose_name=_("Dispositif"), ) manipulator = models.ForeignKey( settings.AUTH_USER_MODEL, related_name="observer", blank=True, null=True, verbose_name=_("Manipulateur"), on_delete=models.SET_NULL, ) validator = models.ForeignKey( settings.AUTH_USER_MODEL, related_name="validator", blank=True, null=True, verbose_name=_("Validateur"), on_delete=models.SET_NULL, ) transmitter = models.ForeignKey( Transmitter, blank=True, null=True, verbose_name=_("Emetteur de télémétrie"), on_delete=models.SET_NULL, ) ab = models.FloatField(blank=True, null=True, verbose_name=_("AB"), help_text=_("en mm")) d5 = models.FloatField(blank=True, null=True, verbose_name=_("D5"), help_text=_("en mm")) d3 = models.FloatField(blank=True, null=True, verbose_name=_("D3"), help_text=_("en mm")) pouce = models.FloatField(blank=True, null=True, verbose_name=_("Pouce"), help_text=_("en mm")) queue = models.FloatField(blank=True, null=True, verbose_name=_("Queue"), help_text=_("en mm")) tibia = models.FloatField(blank=True, null=True, verbose_name=_("Tibia"), help_text=_("en mm")) pied = models.FloatField(blank=True, null=True, verbose_name=_("Pied"), help_text=_("en mm")) cm3 = models.FloatField(blank=True, null=True, verbose_name=_("CM3"), help_text=_("en mm")) tragus = models.FloatField( blank=True, null=True, verbose_name=_("Tragus"), help_text=_("en mm") ) poids = models.FloatField(blank=True, null=True, verbose_name=_("Poids"), help_text=_("en g")) testicule = models.ForeignKey( BiomTesticule, blank=True, null=True, verbose_name="Testicules", on_delete=models.SET_NULL, ) epididyme = models.ForeignKey( BiomEpipidyme, blank=True, null=True, verbose_name="Epididymes", on_delete=models.SET_NULL, ) tuniq_vag = models.ForeignKey( BiomTuniqVag, blank=True, null=True, verbose_name="Tuniques vaginales", on_delete=models.SET_NULL, ) gland_taille = models.ForeignKey( BiomGlandTaille, blank=True, null=True, verbose_name="Taille des glandes", on_delete=models.SET_NULL, ) gland_coul = models.ForeignKey( BiomGlandCoul, blank=True, null=True, verbose_name="couleur des glandes", on_delete=models.SET_NULL, ) mamelle = models.ForeignKey( BiomMamelle, blank=True, null=True, verbose_name="Mamelles", on_delete=models.SET_NULL, ) gestation = models.ForeignKey( BiomGestation, blank=True, null=True, verbose_name="Gestation", on_delete=models.SET_NULL, ) epiphyse = models.ForeignKey( BiomEpiphyse, blank=True, null=True, verbose_name="Epiphyse articulaire", on_delete=models.SET_NULL, ) chinspot = models.ForeignKey( BiomChinspot, blank=True, null=True, verbose_name="Tâche mentonière", on_delete=models.SET_NULL, ) usure_dent = models.ForeignKey( BiomDent, blank=True, null=True, verbose_name="Usure des dents", on_delete=models.SET_NULL, ) etat_sexuel = models.CharField( max_length=2, blank=True, null=True, editable=False, verbose_name="Etat sexuel" ) comment = models.TextField(blank=True, null=True, verbose_name=_("Commentaire")) id_fromsrc = models.CharField( "id unique de la bdd source", max_length=100, blank=True, null=True ) bdsource = models.CharField("bdd source", max_length=100, blank=True, null=True) other_imported_data = models.TextField( blank=True, null=True, verbose_name=_("Autres données importées") ) extra_data = JSONField(blank=True, null=True) timestamp_create = models.DateTimeField(auto_now_add=True, editable=False) timestamp_update = models.DateTimeField(auto_now=True, editable=False) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, db_index=True, editable=False, related_name="countdetail_creator", on_delete=models.SET_NULL, ) updated_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, db_index=True, editable=False, related_name="countdetail_modifier", on_delete=models.SET_NULL, ) def __str__(self): return "%s ∙ %s ∙ %s ∙ %s" % ( self.sighting.session.place.name, self.sex, self.age, self.count, ) def get_absolute_url(self): return reverse("sights:sighting_detail", kwargs={"pk": self.sighting_id}) class Meta: verbose_name = "4 ∙ Comptage détaillé" verbose_name_plural = "4 ∙ Comptages détaillés" @receiver([post_save, post_delete], sender=Place) def generate_metaplace_geom_on_place_save(sender, instance, **kwargs): if instance.metaplace_id: metaplace_id = instance.metaplace_id update_metaplace_autogeom(metaplace_id) @receiver([post_save, post_delete], sender=CountDetail) def sighting_total_count(sender, instance, **kwargs): sighting_id = instance.sighting_id contact = Sighting.objects.get(id_sighting=sighting_id).session.contact.code sighting = Sighting.objects.get(id_sighting=sighting_id) if contact != "du": sighting.total_count = CountDetail.objects.filter(sighting_id=sighting_id).aggregate( Sum("count") )["count__sum"] sighting.save()