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()