2293 lignes
73 KiB
Python
2293 lignes
73 KiB
Python
# -*- coding: utf-8 -*-
|
|
# contacts.models
|
|
import datetime
|
|
|
|
import django.contrib.auth.models as auth_models
|
|
from django.contrib import messages
|
|
from django.core.exceptions import ValidationError
|
|
from django.db import models
|
|
from django.urls import reverse
|
|
from django.utils.html import format_html
|
|
from django.utils.safestring import mark_safe
|
|
|
|
import contacts.fields_choices as fch
|
|
import references.models as ref
|
|
from contacts.settings_messages import AVERTISSEMENT_MESSAGE as w_msg
|
|
from contacts.settings_messages import ERROR_MESSAGE as e_msg
|
|
|
|
# from functools import partial
|
|
|
|
|
|
LOCAL_DATE_FORMAT = '%d/%m/%Y'
|
|
|
|
|
|
# ## Utilitaires
|
|
def get_rem_user():
|
|
return auth_models.User.objects.get_or_create(username='deleted')[0]
|
|
|
|
|
|
def no_attrib_error(orig_func):
|
|
"return empty unicode if attrib does not exist"
|
|
|
|
def wrapper(*args, **kwargs):
|
|
try:
|
|
return orig_func(*args, **kwargs)
|
|
except AttributeError:
|
|
return ""
|
|
|
|
return wrapper
|
|
|
|
|
|
def _field_fb(self, field, field_cond=None, get_other=None):
|
|
"return a field if present else call get_other"
|
|
pcache = '_fb_' + field
|
|
field_cond = field_cond or field
|
|
if not hasattr(self, pcache):
|
|
if getattr(self, field_cond, None):
|
|
itm = getattr(self, field)
|
|
elif get_other:
|
|
itm = get_other()
|
|
else:
|
|
itm = None
|
|
if callable(itm):
|
|
itm = itm()
|
|
setattr(self, pcache, itm)
|
|
return getattr(self, pcache)
|
|
|
|
|
|
def _field_fb_struct(self, field, field_cond=None):
|
|
"return person's field of if empty, structure's one"
|
|
|
|
def get_other():
|
|
return self.structure_id and getattr(self.structure, field)
|
|
|
|
return _field_fb(self, field, field_cond, get_other)
|
|
|
|
|
|
# ## Classes principales
|
|
class AdhManager(auth_models.UserManager):
|
|
def get_queryset(self):
|
|
return (
|
|
super(AdhManager, self)
|
|
.get_queryset()
|
|
.filter(groups__isnull=True, is_superuser=False)
|
|
)
|
|
|
|
|
|
class AdhLogin(auth_models.User):
|
|
class Meta:
|
|
proxy = True
|
|
verbose_name = "Compte adhérent"
|
|
verbose_name_plural = " Comptes adhérents"
|
|
|
|
objects_adh = AdhManager()
|
|
objects = auth_models.UserManager()
|
|
|
|
def is_adherent(self):
|
|
"poor man test: si ne fait partie de aucun groupe, c'est un adherent"
|
|
return bool(self.groups)
|
|
|
|
|
|
class Structure(models.Model):
|
|
"Persones Morales"
|
|
|
|
class Meta:
|
|
ordering = ['designation']
|
|
verbose_name = "Personne Morale"
|
|
verbose_name_plural = " Personnes Morales"
|
|
|
|
# Champs à inclure dans un export CSV complet
|
|
attrs_export = (
|
|
'id',
|
|
'type',
|
|
'f_juridique',
|
|
'get_designation',
|
|
'personne_set',
|
|
'get_youngest_person__date_naissance',
|
|
'siret',
|
|
'statut',
|
|
'adresse',
|
|
'ville',
|
|
'code_postal',
|
|
'commune__code_commune',
|
|
'commune',
|
|
'commune__arrondissement',
|
|
'commune__territoire',
|
|
'commune__enjeu_eau',
|
|
'tel_fixe',
|
|
'tel_mobile',
|
|
'fax',
|
|
'courriel',
|
|
'site_web',
|
|
'date_creation',
|
|
'date_derniere_modification',
|
|
'utilisateur_derniere_modification',
|
|
'origine_contact',
|
|
'annee_contact',
|
|
'mois_contact',
|
|
'commentaire',
|
|
'etiquettes',
|
|
'visibilite',
|
|
'type_activite_pri',
|
|
'qual_compl_conv',
|
|
'type_activite',
|
|
'qual_compl',
|
|
'last_donneesbio__ass_total',
|
|
'last_donneesbio__ass_total_bio',
|
|
'sau_eau',
|
|
'sau_eau_bio',
|
|
'premiere_adhesion',
|
|
'get_date_derniere_adhesion',
|
|
'get_date_premiere_conversion',
|
|
)
|
|
|
|
attrs_publipostage = (
|
|
('designation', 'Désignation PM'),
|
|
('non_courrier', 'Ne pas envoyer de courrier PM'),
|
|
('adresse', 'Adresse PM'),
|
|
('code_postal', 'Code postal PM'),
|
|
('ville', 'Ville PM'),
|
|
('tel_fixe', 'tel fixe PM'),
|
|
('tel_mobile', 'tel mob. PM'),
|
|
('fax', 'fax PM'),
|
|
('courriel', 'courriel PM'),
|
|
)
|
|
|
|
adherent = models.ForeignKey(
|
|
AdhLogin,
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.SET_NULL,
|
|
verbose_name="compte adhérent",
|
|
help_text="compte permettant la connexion à l'application",
|
|
)
|
|
designation = models.CharField("désignation", max_length=250)
|
|
f_juridique = models.ForeignKey(
|
|
ref.FormeJuridique,
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.PROTECT,
|
|
verbose_name="Forme juridique",
|
|
)
|
|
statut = models.ForeignKey(
|
|
ref.StatutStructure, null=True, blank=True, on_delete=models.SET_NULL
|
|
)
|
|
adresse = models.TextField(null=True, blank=True)
|
|
commune = models.ForeignKey(
|
|
ref.Commune, null=True, blank=True, on_delete=models.PROTECT
|
|
)
|
|
non_courrier = models.BooleanField(
|
|
"ne pas envoyer de courrier à cette adresse", blank=True, default=False
|
|
)
|
|
# Certaines communes peuvent avoir plusieurs codes postaux
|
|
code_postal = models.CharField(
|
|
max_length=10,
|
|
null=True,
|
|
blank=True,
|
|
help_text="Laisser vide pour utiliser celui associé à la commune",
|
|
)
|
|
# la ville contient également le code cedex (à utilser pour l'adresse)
|
|
ville = models.CharField(
|
|
max_length=255,
|
|
null=True,
|
|
blank=True,
|
|
help_text="Nom de ville avec code cédex si nécessaire, "
|
|
" si différent du nom de la commune.",
|
|
)
|
|
tel_fixe = models.CharField(
|
|
"téléphone fixe", max_length=100, null=True, blank=True
|
|
)
|
|
tel_mobile = models.CharField(
|
|
"téléphone mobile", max_length=100, null=True, blank=True
|
|
)
|
|
fax = models.CharField(max_length=100, null=True, blank=True)
|
|
courriel = models.CharField(max_length=250, null=True, blank=True)
|
|
siret = models.CharField(
|
|
"N. Siret",
|
|
max_length=14,
|
|
null=True,
|
|
blank=True,
|
|
help_text="14 chiffres sans espaces",
|
|
)
|
|
pas_de_courriel = models.BooleanField(
|
|
"pas d'envoi automatique de courriel",
|
|
default=False,
|
|
help_text="Cochez cette case si cette structure ne souhaite"
|
|
" pas recevoir de courriel envoyé automatiquement.",
|
|
)
|
|
site_web = models.URLField(
|
|
"URL du site web", max_length=250, null=True, blank=True
|
|
)
|
|
date_creation = models.DateField(
|
|
"date de saisie de l'enregistrement", editable=False
|
|
)
|
|
date_derniere_modification = models.DateField(
|
|
"date de dernière modification des informations", editable=False
|
|
)
|
|
utilisateur_derniere_modification = models.ForeignKey(
|
|
auth_models.User,
|
|
editable=False,
|
|
on_delete=models.PROTECT,
|
|
verbose_name="dernière modification par",
|
|
related_name='adherent_structure',
|
|
)
|
|
origine_contact = models.CharField(
|
|
"origine du contact", max_length=255, null=True, blank=True
|
|
)
|
|
annee_contact = models.IntegerField(
|
|
"Année du premier contact",
|
|
null=True,
|
|
blank=True,
|
|
choices=fch.CHOIX_ANNEE,
|
|
)
|
|
mois_contact = models.IntegerField(
|
|
"Mois du premier contact",
|
|
null=True,
|
|
blank=True,
|
|
choices=fch.CHOIX_MOIS,
|
|
)
|
|
sage = models.CharField("SAGE", max_length=255, null=True, blank=True)
|
|
commentaire = models.TextField(null=True, blank=True)
|
|
etiquettes = models.ManyToManyField(
|
|
ref.Etiquette, blank=True, verbose_name='étiquettes'
|
|
)
|
|
type_pm = models.IntegerField(
|
|
"Type personne morale", default=0, choices=fch.TYPE_PM
|
|
)
|
|
# from agriculteur
|
|
visibilite = models.CharField(
|
|
max_length=255,
|
|
choices=fch.CHOIX_VISIBILITE,
|
|
default="personne",
|
|
verbose_name="publication sur l'annuaire du site",
|
|
)
|
|
type_activite = models.ForeignKey(
|
|
ref.TypeActivite,
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.PROTECT,
|
|
verbose_name="Activité principale bio",
|
|
)
|
|
type_activite_pri = models.ForeignKey(
|
|
ref.TypeActivite,
|
|
null=True,
|
|
blank=True,
|
|
related_name="structure_pri",
|
|
on_delete=models.PROTECT,
|
|
verbose_name="Activité principale",
|
|
)
|
|
qual_compl = models.ManyToManyField(
|
|
ref.AutreQualificatifExploitation,
|
|
blank=True,
|
|
verbose_name="autres activités bio",
|
|
)
|
|
qual_compl_conv = models.ManyToManyField(
|
|
ref.AutreQualificatifExploitation,
|
|
blank=True,
|
|
verbose_name="autres activités conventionnelles",
|
|
related_name="structure_conv",
|
|
)
|
|
# si commune.enjeu_eau
|
|
sau_eau = models.DecimalField(
|
|
max_digits=10,
|
|
decimal_places=2,
|
|
null=True,
|
|
blank=True,
|
|
verbose_name="SAU à enjeu eau",
|
|
help_text='Surface agricole utile sur commune à enjeu eau (en ha)',
|
|
)
|
|
sau_eau_bio = models.DecimalField(
|
|
max_digits=10,
|
|
decimal_places=2,
|
|
null=True,
|
|
blank=True,
|
|
verbose_name="SAU en biologique à enjeu eau",
|
|
help_text='Surface agricole utile en biologique'
|
|
' sur commune à enjeu eau (en ha)',
|
|
)
|
|
# fin si commune.enjeu_eau
|
|
parc_naturel_regional = models.CharField(
|
|
max_length=200,
|
|
verbose_name="parc naturel régional",
|
|
choices=fch.CHOIX_PARCS_NATURELS_REGIONAUX,
|
|
null=True,
|
|
blank=True,
|
|
)
|
|
# from agriculteurbio
|
|
premiere_adhesion = models.ForeignKey(
|
|
'AdhesionGabnor',
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.SET_NULL,
|
|
related_name="premieres_adhesions",
|
|
verbose_name="Première adhésion",
|
|
)
|
|
renouvellement_adhesion = models.ForeignKey(
|
|
'AdhesionGabnor',
|
|
null=True,
|
|
blank=True,
|
|
related_name="renouvellements",
|
|
on_delete=models.SET_NULL,
|
|
verbose_name="dernier renouvellement d'adhésion",
|
|
)
|
|
|
|
def __str__(self):
|
|
"Retourne une représentation unicode de la structure."
|
|
if self.designation:
|
|
return self.designation
|
|
if self.personne_set.count() > 0:
|
|
return str(self.personne_set.all()[0]) # noqa: F821
|
|
return "Structure (#%d)" % self.id
|
|
|
|
def get_designation(self):
|
|
return str(self) # noqa: F821
|
|
|
|
get_designation.short_description = "Désignation ou personne"
|
|
|
|
def get_absolute_url(self):
|
|
"Retourne l'URL associée à l'objet."
|
|
return reverse(
|
|
'admin:{0}_{1}_change'.format(
|
|
self._meta.app_label, self.__class__.__name__.lower()
|
|
),
|
|
args=[self.pk],
|
|
)
|
|
|
|
def liste_contacts_associes(self):
|
|
"""
|
|
Surcharge de la méthode de Contact.
|
|
Retourne la liste des contacts associés à cet objet. Il
|
|
s'agit ici des personnes associées à la structure.
|
|
"""
|
|
return self.personne_set.all()
|
|
|
|
def adresse_electronique(self):
|
|
"""
|
|
Retourne une liste de 2 éléments:
|
|
- le nom de la structure
|
|
- l'adresse email associée
|
|
Retourne une liste vide si aucune adresse courriel n'est renseignée.
|
|
"""
|
|
if not self.courriel:
|
|
return ()
|
|
return (str(self), self.courriel) # noqa: F821
|
|
|
|
def get_courriels(self):
|
|
"""
|
|
Retourne le courriel de la structure et des personnes associées
|
|
(sauf s'ils ne souhaitent pas recevoir d'email automatique)
|
|
"""
|
|
courriels = []
|
|
if self.courriel:
|
|
courriels.append(self.adresse_electronique())
|
|
for p in self.liste_contacts_associes():
|
|
courriels.extend(p.get_courriels())
|
|
return courriels
|
|
|
|
def type(self):
|
|
"retourne le type parmi structure, agri, agribio"
|
|
return self.get_type_pm_display()
|
|
|
|
def clean(self):
|
|
if self.type_activite and self.type_pm != 2:
|
|
raise ValidationError(e_msg["acti_bio_sans_agri_bio"])
|
|
if self.type_pm == 2 and not self.type_activite:
|
|
raise ValidationError(e_msg["agri_bio_sans_acti_bio"])
|
|
if self.type_activite_pri and self.type_pm == 0:
|
|
raise ValidationError(e_msg["acti_pri_sans_agri"])
|
|
if self.type_pm >= 1 and not (
|
|
self.type_activite or self.type_activite_pri
|
|
):
|
|
raise ValidationError(e_msg["agri_sans_acti"])
|
|
if self.f_juridique is None:
|
|
raise ValidationError(e_msg["pas_f_juridique"])
|
|
|
|
def save(self, *args, **kwargs):
|
|
"""
|
|
Remplit les champs date_creation et date_derniere_modification
|
|
manuellement (la fonctionnalité auto_now de django est douteuse,
|
|
mieux vaut le gérer manuellement).
|
|
"""
|
|
rawsave = kwargs.pop('rawsave', False)
|
|
if rawsave:
|
|
models.Model.save(self, *args, **kwargs)
|
|
if self.id is None and self.date_creation is None:
|
|
self.date_creation = datetime.date.today()
|
|
self.date_derniere_modification = datetime.date.today()
|
|
models.Model.save(self, *args, **kwargs)
|
|
|
|
def get_date_premiere_adhesion(self):
|
|
"""
|
|
Retourne la date de première adhésion, ou None s'il n'est pas abonné.
|
|
"""
|
|
try:
|
|
return self.premiere_adhesion.date
|
|
except AdhesionGabnor.DoesNotExist:
|
|
return None
|
|
|
|
get_date_premiere_adhesion.short_description = "date de première adhésion"
|
|
|
|
def get_date_derniere_adhesion(self):
|
|
"""
|
|
Retourne la date de dernière adhésion, ou None s'il n'est pas abonné.
|
|
"""
|
|
try:
|
|
return self.adhesiongabnor_set.latest().date
|
|
except AdhesionGabnor.DoesNotExist:
|
|
return None
|
|
|
|
get_date_derniere_adhesion.short_description = "date de dernière adhésion"
|
|
|
|
def get_date_premiere_conversion(self):
|
|
"""
|
|
Retourne la date de première conversion bio, ou None s'il n'y en a pas
|
|
(ce qui ne devrait pas être le cas pour un agriculteur bio, mais
|
|
restons parano^Wprudent).
|
|
"""
|
|
conv = self.projetbio_set.filter(
|
|
date_engagement_oc__isnull=False
|
|
).order_by('date_engagement_oc')
|
|
if len(conv) == 0:
|
|
return None
|
|
return conv[0].date_engagement_oc
|
|
|
|
get_date_premiere_conversion.verbose_name = "date de première conversion"
|
|
get_date_premiere_conversion.short_description = (
|
|
"date de première conversion"
|
|
)
|
|
|
|
def premiere_conversion(self):
|
|
return self.get_date_premiere_conversion()
|
|
|
|
def last_donneesbio(self):
|
|
if not hasattr(self, '_last_dbio'):
|
|
try:
|
|
self._last_dbio = self.donneesbio_set.order_by('-annee')[:1][0]
|
|
except IndexError:
|
|
self._last_dbio = None
|
|
return self._last_dbio
|
|
|
|
last_donneesbio.target_model = 'DonneesBio'
|
|
|
|
def ass_total_bio(self):
|
|
ldb = self.last_donneesbio()
|
|
return ldb and ldb.ass_total_bio
|
|
|
|
ass_total_bio.short_description = "Assolement total (en Ha)"
|
|
|
|
def ass_total(self):
|
|
ldb = self.last_donneesbio()
|
|
return ldb and ldb.ass_total
|
|
|
|
ass_total.short_description = "Assolement total bio (en Ha)"
|
|
|
|
def get_liste_donneesbio(self):
|
|
"""
|
|
Retourne la listes des données de l'observatoire de l'agriculteur
|
|
(utilisé pour afficher les liens vers ceux-ci dans la vue de
|
|
modification de l'agriculteur).
|
|
"""
|
|
return [
|
|
{'url': dbio.get_absolute_url(), 'annee': dbio.annee}
|
|
for dbio in self.donneesbio_set.order_by('-annee')
|
|
]
|
|
|
|
def get_url_cree_donneesbio(self):
|
|
"""
|
|
Retourne l'URL de la vue permettant de créer un nouvel objet DonneesBio
|
|
pour cet agriculteur et d'afficher sa page de modification.
|
|
"""
|
|
return reverse('admin:cree_donneesbio', args=[self.id])
|
|
|
|
def get_youngest_person(self):
|
|
"return youngest person attached to this structure if any"
|
|
try:
|
|
return self.personne_set.latest('date_naissance')
|
|
except models.ObjectDoesNotExist:
|
|
return None
|
|
|
|
get_youngest_person.target_model = 'Personne'
|
|
|
|
def get_rdv_coll(self):
|
|
rdv_liste = []
|
|
rdvP_liste = RdvPersonne.objects.filter(
|
|
personne__structure_id=self.id
|
|
).order_by('-rdv__date')[:10]
|
|
return rdvP_liste
|
|
for rdvP in rdvP_liste:
|
|
if rdvP.rdv not in rdv_liste:
|
|
rdv_liste.append(rdvP.rdv)
|
|
return rdv_liste
|
|
|
|
def get_rdv_ind(self):
|
|
return self.rdv_ind_set.all()
|
|
|
|
def get_messages(self):
|
|
"fill info/warning messages at save"
|
|
mlist = []
|
|
if self.type_pm >= 1 and not self.personne_set.exists():
|
|
pass
|
|
mlist.append(
|
|
(messages.WARNING, w_msg["message_personne_manquante"])
|
|
)
|
|
if self.type_pm >= 2:
|
|
if self.projetbio_set.exists():
|
|
if not self.projetbio_set.filter(
|
|
date_engagement_oc__isnull=False
|
|
).exists():
|
|
mlist.append(
|
|
(
|
|
messages.WARNING,
|
|
w_msg["message_projet_bio_manquant"],
|
|
)
|
|
)
|
|
else:
|
|
mlist.append(
|
|
(messages.WARNING, w_msg["message_projet_bio_manquant"])
|
|
)
|
|
if self.type_pm >= 1 and not (
|
|
self.ass_total() or self.ass_total_bio()
|
|
):
|
|
mlist.append((messages.WARNING, w_msg["sau_vide"]))
|
|
return mlist
|
|
|
|
|
|
class StructureRo(Structure):
|
|
"La version read-only de structure"
|
|
|
|
class Meta:
|
|
proxy = True
|
|
verbose_name = "Personne Morale (lecture seule)"
|
|
verbose_name_plural = " Personnes Morales (lecture seule)"
|
|
|
|
|
|
class ProjetBio(models.Model):
|
|
"""
|
|
Projet de passage en bio d'un agriculteur.
|
|
Chaque agriculteur peut en avoir plusieurs, à conserver même après le
|
|
passage en Bio.
|
|
"""
|
|
|
|
class Meta:
|
|
verbose_name = "projet de passage en bio"
|
|
verbose_name_plural = " Projets de passage en bio"
|
|
|
|
# Champs à inclure dans un export CSV complet
|
|
attrs_export = [
|
|
'id',
|
|
'agriculteur',
|
|
'agri_id',
|
|
'technicien',
|
|
'description',
|
|
'freins_identifies',
|
|
'suites_a_donner',
|
|
'remarques',
|
|
'etude_demandee',
|
|
'date_etude',
|
|
'date_probable_certification',
|
|
'date_certification',
|
|
'engagement_surf',
|
|
'engagement_oc_enjeu_eau',
|
|
'installation_bio',
|
|
'date_engagement_oc',
|
|
'commentaire',
|
|
]
|
|
|
|
agriculteur = models.ForeignKey('Structure', on_delete=models.PROTECT)
|
|
technicien = models.ForeignKey(
|
|
auth_models.User,
|
|
null=True,
|
|
blank=True,
|
|
verbose_name="Suivi par",
|
|
on_delete=models.SET_NULL,
|
|
)
|
|
description = models.TextField(
|
|
null=True, blank=True, verbose_name="description"
|
|
)
|
|
freins_identifies = models.TextField(
|
|
"Points favorables et freins à la conversion", null=True, blank=True
|
|
)
|
|
|
|
pond_savoirfaire = models.IntegerField(
|
|
"Savoir faire",
|
|
default=0,
|
|
choices=fch.POND_CONV,
|
|
help_text="Manque de savoir-faire ou de connaissances (projet mal "
|
|
"défini...), peur de ne pas être à la hauteur, manque de rigueur, "
|
|
"manque de qualification de l'exploitant, formation initiale non "
|
|
"adaptée",
|
|
)
|
|
pond_motivation = models.IntegerField(
|
|
"Motivation",
|
|
default=0,
|
|
choices=fch.POND_CONV,
|
|
help_text="Manque de motivation, faible sensibilité à la bio, peu "
|
|
"enclin au changement, proche de la retraite, pas envie de réorienter"
|
|
" un système intensif, pas de frein identifié, mauvaise image de la "
|
|
"bio, peur de perte de revenu non-avérée.",
|
|
)
|
|
pond_entourage = models.IntegerField(
|
|
"Entourage",
|
|
default=0,
|
|
choices=fch.POND_CONV,
|
|
help_text=""
|
|
"Pression de l'entourage (familles, conseillers techniques ou "
|
|
"financier, système intégré...)",
|
|
)
|
|
pond_foncier = models.IntegerField(
|
|
"Foncier",
|
|
default=0,
|
|
choices=fch.POND_CONV,
|
|
help_text=""
|
|
"Problème de foncier (manque de surface, menace de perdre des "
|
|
"surfaces, manque de surface accessible aux vaches, qualité des "
|
|
"terres...), pas de foncier dans le cas d'une installation",
|
|
)
|
|
pond_materiel = models.IntegerField(
|
|
"Matériel",
|
|
default=0,
|
|
choices=fch.POND_CONV,
|
|
help_text=""
|
|
"Moyens de productions faibles ou inadaptés et difficilement "
|
|
"adaptables, génétique animale ou végétale peu adaptée, race de "
|
|
"vache, variété de pomme, manque de main d'œuvre, bâtiment inadapté"
|
|
" trop petit ou pas aux normes, bâtiment de stockage inadapté",
|
|
)
|
|
pond_reglementation = models.IntegerField(
|
|
"Réglementation",
|
|
default=0,
|
|
choices=fch.POND_CONV,
|
|
help_text=""
|
|
"Problème réglementaires, mixité, engagé dans un cahier des charges,"
|
|
" forfait fiscal, règle ICPE, MAEt ou PAE.",
|
|
)
|
|
pond_commercialisation = models.IntegerField(
|
|
"Commercialisation",
|
|
default=0,
|
|
choices=fch.POND_CONV,
|
|
help_text=""
|
|
"Commercialisation, difficulté à passer des hausses de prix en vente"
|
|
" directe, produit sans valorisation bio ou sans filière longue "
|
|
"(betterave sucrières, moutons, volaille de chair), plus value jugée"
|
|
" faible en bio, perte de revenu, cahier des charges privé",
|
|
)
|
|
pond_financement = models.IntegerField(
|
|
"Financement",
|
|
default=0,
|
|
choices=fch.POND_CONV,
|
|
help_text=""
|
|
"Manque de financement du projet, manque d'aides financières (Peu ou"
|
|
"pas d'aide à la conversion), dans le cas des installations : peu ou"
|
|
" pas d'apport de capital/incapacité à investir/pas d'accès aux aides"
|
|
" installation",
|
|
)
|
|
pond_maindoeuvre = models.IntegerField(
|
|
"Main d'Oeuvre ",
|
|
default=0,
|
|
choices=fch.POND_CONV,
|
|
help_text=""
|
|
"Manque de qualification de la main d'œuvre à embaucher, Manque de "
|
|
"main d'œuvre",
|
|
)
|
|
suites_a_donner = models.TextField(
|
|
null=True, blank=True, verbose_name="suites à donner"
|
|
)
|
|
remarques = models.TextField(
|
|
null=True, blank=True, verbose_name="remarques"
|
|
)
|
|
etude_demandee = models.NullBooleanField("Étude demandée ?")
|
|
date_etude = models.DateField("Date de l'étude", null=True, blank=True)
|
|
date_probable_certification = models.CharField(
|
|
"date probable de début de conversion",
|
|
null=True,
|
|
blank=True,
|
|
max_length=20,
|
|
choices=fch.CHOIX_TERME,
|
|
help_text="Si vous "
|
|
"choisissez l'option « Date précisée ci-après », merci de préciser"
|
|
" la date.",
|
|
)
|
|
date_certification = models.DateField(
|
|
"Date de début de conversion prévue",
|
|
null=True,
|
|
blank=True,
|
|
help_text="à remplir si vous avez choisi l'option \"Date précisée\"",
|
|
)
|
|
engagement_surf = models.DecimalField(
|
|
"Surface engagée (en ha)",
|
|
null=True,
|
|
blank=True,
|
|
decimal_places=4,
|
|
max_digits=10,
|
|
help_text="utilisez . comme séparateur décimal",
|
|
)
|
|
engagement_oc_enjeu_eau = models.DecimalField(
|
|
"Surface engagée sur commune à enjeu eau (ha)",
|
|
null=True,
|
|
blank=True,
|
|
decimal_places=4,
|
|
max_digits=10,
|
|
help_text="utilisez . comme séparateur décimal",
|
|
)
|
|
installation_bio = models.NullBooleanField("installation bio")
|
|
date_engagement_oc = models.DateField(
|
|
"date d'engagement de l'OC", null=True, blank=True, db_index=True
|
|
)
|
|
commentaire = models.TextField(null=True, blank=True)
|
|
|
|
def __str__(self):
|
|
"""
|
|
Retourne une représentation unicode de l'objet.
|
|
Attention, problème vicieux: l'admin django appelle cette méthode juste
|
|
après la suppression de l'objet dans la base: il ne faut donc pas
|
|
utiliser %d pour self.id car dans ce cas, self.id vaut None et provoque
|
|
une erreur.
|
|
"""
|
|
return "projet bio de %s (id #%s)" % (self.agriculteur, self.id)
|
|
|
|
def agri_id(self):
|
|
if self.agriculteur:
|
|
return self.agriculteur.id
|
|
return ""
|
|
|
|
agri_id.short_description = "ID agriculteur"
|
|
agri_id.verbose_name = "ID agriculteur"
|
|
|
|
def get_messages(self):
|
|
mlist = []
|
|
# check that at least one pond_* is set to '-1'
|
|
res_pond = {}
|
|
for attr in dir(self):
|
|
if attr.startswith('pond_'):
|
|
res_pond.setdefault(getattr(self, attr), 0)
|
|
res_pond[getattr(self, attr)] += 1
|
|
if res_pond.get(-1, 0) == 0:
|
|
mlist.append((messages.WARNING, e_msg["projet_sans_contrainte"]))
|
|
if self.installation_bio is None:
|
|
mlist.append(
|
|
(messages.WARNING, e_msg["projet_installation_non_choisi"])
|
|
)
|
|
return mlist
|
|
|
|
def get_absolute_url(self):
|
|
"""
|
|
Retourne l'URL associée à l'objet.
|
|
"""
|
|
return reverse(
|
|
'admin:{0}_{1}_change'.format(
|
|
self._meta.app_label, self.__class__.__name__.lower()
|
|
),
|
|
args=[self.pk],
|
|
)
|
|
|
|
|
|
class ProjetBioRo(ProjetBio):
|
|
"La version read-only de ProjetBio"
|
|
|
|
class Meta:
|
|
proxy = True
|
|
verbose_name = "Projet de passage en bio (lect. seule)"
|
|
verbose_name_plural = " Projets de passage en bio (lecture seule)"
|
|
|
|
|
|
class DonneesBio(models.Model):
|
|
"Données de l'observatoire du Gabnor pour une année spécifique."
|
|
|
|
class Meta:
|
|
verbose_name = "Info annuelle sur l'exploitation"
|
|
verbose_name_plural = " Infos annuelles sur l'exploitation"
|
|
ordering = ['-annee', 'agriculteur_bio']
|
|
|
|
# Champs à inclure dans un export CSV complet
|
|
attrs_export = (
|
|
'agriculteur_bio',
|
|
'agri_id',
|
|
'id',
|
|
'annee',
|
|
'agriculteur_bio__commune__code_commune',
|
|
'agriculteur_bio__commune__arrondissement',
|
|
'agriculteur_bio__commune__enjeu_eau',
|
|
'agriculteur_bio__commune__territoire',
|
|
'organisme_certification',
|
|
'aide_conversion_ab',
|
|
'date_aide_conversion_ab',
|
|
'aide_maintien_ab',
|
|
'aide_credit_impot',
|
|
'aide_certification_bio',
|
|
'aide_investissement',
|
|
'aide_installation',
|
|
'aide_mae_t',
|
|
'aide_autres',
|
|
'aide_accompagnement',
|
|
'aide_accompagnement_precisions',
|
|
'biodynamie',
|
|
'etp_associes',
|
|
'etp_familiaux',
|
|
'etp_salaries_permanents',
|
|
'etp_saisonniers',
|
|
'etp_encadrants',
|
|
'personnes_insertion',
|
|
'reseaux_engagements',
|
|
'remarques',
|
|
'tr_date_fin_prevue',
|
|
'tr_repreneur_ident',
|
|
'tr_precisions',
|
|
'statut_commercialisation',
|
|
'statut_choisi',
|
|
'lait_vaches',
|
|
'lait_nb_vaches',
|
|
'lait_quota_laiterie',
|
|
'lait_quota_vente_directe',
|
|
'lait_prod_laiterie',
|
|
'lait_prod_vente_directe',
|
|
'lait_laiterie',
|
|
'lait_reformes_cl',
|
|
'lait_taurillons_cl',
|
|
'lait_adultes_cc',
|
|
'lait_veaux_cc',
|
|
'lait_chevres',
|
|
'lait_nb_chevres',
|
|
'lait_chevres_conversion',
|
|
'lait_chevres_prod',
|
|
'bovine',
|
|
'bovine_nb_va',
|
|
'bovine_reformes_cl',
|
|
'bovine_taurillons_cl',
|
|
'bovine_adultes_cc',
|
|
'bovine_veaux_cc',
|
|
'porcs',
|
|
'porcs_nb_truies',
|
|
'porcs_nb_com',
|
|
'moutons',
|
|
'moutons_nb_brebis',
|
|
'moutons_nb_agn_com',
|
|
'animaux_lapins',
|
|
'animaux_volailles',
|
|
'animaux_volailles_chair',
|
|
'animaux_nb_poulets',
|
|
'animaux_nb_lapins',
|
|
'animaux_nb_autres_volailles',
|
|
'animaux_autres_prod',
|
|
'animaux_details_autres_prod',
|
|
'oeufs_nb_pondeuses',
|
|
'oeufs_nb_oeufs',
|
|
'oeufs_acheteur_cl',
|
|
'miel_nb_ruches',
|
|
'formation_annee',
|
|
'formation_annee_laquelle',
|
|
'formation_annee_commentaire',
|
|
'formation_avenir',
|
|
'formation_avenir_type',
|
|
'formation_avenir_details',
|
|
)
|
|
|
|
date_collecte = models.DateField(null=True, blank=True)
|
|
date_modification = models.DateField(null=True, blank=True, editable=False)
|
|
date_validation = models.DateField(null=True, blank=True, editable=False)
|
|
technicien = models.ForeignKey(
|
|
auth_models.User,
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.SET_NULL,
|
|
help_text="Personne ayant collecté les données",
|
|
)
|
|
agriculteur_bio = models.ForeignKey(
|
|
'Structure',
|
|
verbose_name="Agriculteur",
|
|
limit_choices_to={'type_pm__gte': 1},
|
|
on_delete=models.CASCADE,
|
|
)
|
|
annee = models.IntegerField(
|
|
"année", db_index=True, default=datetime.date.today().year
|
|
)
|
|
organisme_certification = models.ForeignKey(
|
|
ref.OrganismeDeCertification,
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.PROTECT,
|
|
verbose_name="organisme de certification",
|
|
)
|
|
biodynamie = models.NullBooleanField()
|
|
remarques = models.TextField(
|
|
null=True, blank=True, verbose_name="remarques de l'équipe"
|
|
)
|
|
# Aides
|
|
aide_conversion_ab = models.NullBooleanField(
|
|
verbose_name="aide à la conversion à l'agriculture biologique"
|
|
)
|
|
date_aide_conversion_ab = models.DateField(
|
|
'date de cette aide',
|
|
null=True,
|
|
blank=True,
|
|
help_text="En cas d'aide à la conversion, indiquez la date de "
|
|
"signature du contrat (ou de dépôt pour les MAE)",
|
|
)
|
|
aide_maintien_ab = models.NullBooleanField(
|
|
verbose_name="aide au maintien à l'agriculture biologique"
|
|
)
|
|
aide_credit_impot = models.NullBooleanField(
|
|
verbose_name="crédit d'impôt bio"
|
|
)
|
|
aide_certification_bio = models.NullBooleanField(
|
|
verbose_name="aide à la certification bio"
|
|
)
|
|
aide_investissement = models.NullBooleanField(
|
|
verbose_name="aide à l'investissement"
|
|
)
|
|
aide_installation = models.NullBooleanField(
|
|
verbose_name="aide à l'installation"
|
|
)
|
|
aide_mae_t = models.NullBooleanField("MAE T")
|
|
aide_autres = models.CharField(
|
|
"autres mesures, précisez", max_length=255, null=True, blank=True
|
|
)
|
|
aide_accompagnement = models.NullBooleanField(
|
|
"avez-vous besoin d'un accompagnement pour solliciter une aide ?"
|
|
)
|
|
aide_accompagnement_precisions = models.TextField(
|
|
"précisions", null=True, blank=True
|
|
)
|
|
# Main d'oeuvre
|
|
etp_associes = models.DecimalField(
|
|
"ETP d'associés", null=True, blank=True, max_digits=6, decimal_places=2
|
|
)
|
|
etp_familiaux = models.DecimalField(
|
|
"ETP familiaux", null=True, blank=True, max_digits=6, decimal_places=2
|
|
)
|
|
etp_salaries_permanents = models.DecimalField(
|
|
"ETP de salariés permanents",
|
|
null=True,
|
|
blank=True,
|
|
max_digits=6,
|
|
decimal_places=2,
|
|
)
|
|
etp_saisonniers = models.DecimalField(
|
|
"ETP saisonniers",
|
|
null=True,
|
|
blank=True,
|
|
max_digits=6,
|
|
decimal_places=2,
|
|
)
|
|
etp_encadrants = models.DecimalField(
|
|
"ETP encadrants", null=True, blank=True, max_digits=6, decimal_places=2
|
|
)
|
|
personnes_insertion = models.IntegerField(
|
|
"Personnes en contrat d'insertion", null=True, blank=True
|
|
)
|
|
reseaux_engagements = models.TextField(
|
|
"Appartenance à des réseaux et engagements", null=True, blank=True
|
|
)
|
|
# Transmission
|
|
tr_date_fin_prevue = models.DateField(
|
|
"Date de fin d'activité prévue",
|
|
null=True,
|
|
blank=True,
|
|
help_text="donnez une date approximative si possible",
|
|
)
|
|
tr_repreneur_ident = models.NullBooleanField(
|
|
"Repreneur identifié", null=True, blank=True
|
|
)
|
|
tr_precisions = models.TextField(
|
|
"Précisions",
|
|
blank=True,
|
|
default="",
|
|
help_text="Identité du repreneur et autre précisions",
|
|
)
|
|
# Commercialisation
|
|
statut_commercialisation = models.NullBooleanField(
|
|
"utilisation d'un statut spécifique pour la commercialisation"
|
|
)
|
|
statut_choisi = models.ForeignKey(
|
|
ref.StatutCommercialisation,
|
|
null=True,
|
|
blank=True,
|
|
verbose_name="statut choisi",
|
|
help_text="en cas d'utilisation d'un"
|
|
" statut spécifique pour la commercialisation, indiquez-le ici.",
|
|
on_delete=models.PROTECT,
|
|
)
|
|
com_ca_total = models.DecimalField(
|
|
"Chiffre d'affaire total HT",
|
|
max_digits=10,
|
|
decimal_places=0,
|
|
null=True,
|
|
editable=False,
|
|
blank=True,
|
|
)
|
|
com_pct_ca_bio = models.DecimalField(
|
|
"Part du CA bio (%)",
|
|
max_digits=4,
|
|
decimal_places=0,
|
|
null=True,
|
|
editable=False,
|
|
blank=True,
|
|
)
|
|
# Accueil
|
|
acc_act_tour = models.NullBooleanField("Activité touristique")
|
|
acc_act_peda = models.NullBooleanField("Activité pédagogique")
|
|
acc_rest_bio = models.NullBooleanField("Restauration bio")
|
|
acc_act_autre = models.NullBooleanField("Autres activité")
|
|
detail_activites = models.TextField(
|
|
null=True, blank=True, verbose_name="détail des activités"
|
|
)
|
|
# Lait
|
|
lait_vaches = models.NullBooleanField("lait de vaches bio")
|
|
lait_nb_vaches = models.PositiveIntegerField(
|
|
"nombre de vaches laitières", null=True, blank=True
|
|
)
|
|
lait_quota_laiterie = models.PositiveIntegerField(
|
|
"quota laiterie (en L)", null=True, blank=True
|
|
)
|
|
lait_quota_vente_directe = models.PositiveIntegerField(
|
|
"quota vente directe (en L)", null=True, blank=True
|
|
)
|
|
lait_prod_laiterie = models.PositiveIntegerField(
|
|
"Lait vendu en laiterie (en L)", null=True, blank=True
|
|
)
|
|
lait_prod_vente_directe = models.PositiveIntegerField(
|
|
"Lait vendu en direct (en L)", null=True, blank=True
|
|
)
|
|
lait_laiterie = models.ForeignKey(
|
|
ref.Laiterie,
|
|
null=True,
|
|
blank=True,
|
|
verbose_name="laiterie",
|
|
on_delete=models.PROTECT,
|
|
)
|
|
# viande issue du troupeau laitier
|
|
lait_reformes_cl = models.PositiveIntegerField(
|
|
"Nb d'animaux adultes type lait valorisés en circuit long (Réformes,"
|
|
"Génisses, Boeufs)",
|
|
null=True,
|
|
blank=True,
|
|
)
|
|
lait_taurillons_cl = models.PositiveIntegerField(
|
|
"Nb de veaux type lait valorisés en circuit long",
|
|
null=True,
|
|
blank=True,
|
|
)
|
|
lait_adultes_cc = models.PositiveIntegerField(
|
|
"nb d'adultes laitiers vendus en circuit court / an",
|
|
null=True,
|
|
blank=True,
|
|
)
|
|
lait_veaux_cc = models.PositiveIntegerField(
|
|
"nb de veaux laitiers vendus en circuit court / an",
|
|
null=True,
|
|
blank=True,
|
|
)
|
|
# Lait de chevre
|
|
lait_chevres = models.NullBooleanField("chèvres bio")
|
|
lait_nb_chevres = models.PositiveIntegerField(
|
|
"nombre de chèvres", null=True, blank=True
|
|
)
|
|
lait_chevres_conversion = models.PositiveIntegerField(
|
|
"nombre de chèvres en conversion", null=True, blank=True
|
|
)
|
|
lait_chevres_prod = models.PositiveIntegerField(
|
|
"litres de lait de chèvre produits par an", null=True, blank=True
|
|
)
|
|
# Viande bovine
|
|
bovine = models.NullBooleanField("viande bovine bio")
|
|
bovine_nb_va = models.PositiveIntegerField(
|
|
"nombre VA", null=True, blank=True
|
|
)
|
|
bovine_reformes_cl = models.PositiveIntegerField(
|
|
"Nb d'animaux adultes type viande valorisés en circuit long",
|
|
null=True,
|
|
blank=True,
|
|
help_text="(Réformes,Génisses, Boeufs)",
|
|
)
|
|
bovine_taurillons_cl = models.PositiveIntegerField(
|
|
verbose_name="Nb de veaux type viande valorisés en circuit long",
|
|
null=True,
|
|
blank=True,
|
|
)
|
|
bovine_adultes_cc = models.PositiveIntegerField(
|
|
"nb d'adultes race à viande vendus en circuit court / an",
|
|
null=True,
|
|
blank=True,
|
|
)
|
|
bovine_veaux_cc = models.PositiveIntegerField(
|
|
"nb de veaux race à viande vendus en circuit court / an",
|
|
null=True,
|
|
blank=True,
|
|
)
|
|
# Porcs
|
|
porcs = models.NullBooleanField("porc bio")
|
|
porcs_nb_truies = models.PositiveIntegerField(
|
|
"nombre de truies", null=True, blank=True
|
|
)
|
|
porcs_nb_com = models.PositiveIntegerField(
|
|
"nombre de porcs commercialisés", null=True, blank=True
|
|
)
|
|
# Moutons
|
|
moutons = models.NullBooleanField("moutons bio")
|
|
moutons_nb_brebis = models.PositiveIntegerField(
|
|
"nombre de brebis", null=True, blank=True
|
|
)
|
|
moutons_nb_agn_com = models.PositiveIntegerField(
|
|
"nombre d'agneaux commercialisés", null=True, blank=True
|
|
)
|
|
# Petites animaux
|
|
animaux_lapins = models.NullBooleanField("production de lapins bio")
|
|
animaux_volailles = models.NullBooleanField(
|
|
"production de volailles de chair bio"
|
|
)
|
|
animaux_volailles_chair = models.TextField(
|
|
"Détail type de volailles (poulets, pintades, dindes)",
|
|
null=True,
|
|
blank=True,
|
|
)
|
|
animaux_nb_poulets = models.PositiveIntegerField(
|
|
"nb poulets (produits par an)", null=True, blank=True
|
|
)
|
|
animaux_nb_lapins = models.PositiveIntegerField(
|
|
"nb lapins", null=True, blank=True
|
|
)
|
|
animaux_nb_autres_volailles = models.PositiveIntegerField(
|
|
"nb autres volailles", null=True, blank=True
|
|
)
|
|
# Autres viandes
|
|
animaux_autres_prod = models.NullBooleanField(
|
|
"Autres productions animales bio"
|
|
)
|
|
animaux_details_autres_prod = models.TextField(
|
|
"détail autres productions animale", null=True, blank=True
|
|
)
|
|
# Œufs
|
|
oeufs_production_bio = models.NullBooleanField("production d'œufs bio")
|
|
oeufs_nb_pondeuses = models.PositiveIntegerField(
|
|
"nb de pondeuses", null=True, blank=True
|
|
)
|
|
oeufs_nb_oeufs = models.PositiveIntegerField(
|
|
"nb d'œufs", null=True, blank=True
|
|
)
|
|
oeufs_acheteur_cl = models.ForeignKey(
|
|
ref.AcheteurOeufs,
|
|
null=True,
|
|
blank=True,
|
|
verbose_name="acheteur circuit long",
|
|
on_delete=models.PROTECT,
|
|
)
|
|
# Miel
|
|
miel_production_bio = models.NullBooleanField("apiculture bio")
|
|
miel_nb_ruches = models.PositiveIntegerField(
|
|
"nb de ruches", null=True, blank=True
|
|
)
|
|
# Formation
|
|
formation_annee = models.NullBooleanField(
|
|
"avez-vous participé à une formation au cours de l'année écoulée ?"
|
|
)
|
|
formation_annee_laquelle = models.ForeignKey(
|
|
ref.Formation,
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.PROTECT,
|
|
verbose_name="si oui, laquelle ?",
|
|
)
|
|
formation_annee_commentaire = models.TextField(
|
|
"commentaires", null=True, blank=True
|
|
)
|
|
formation_avenir = models.NullBooleanField(
|
|
"avez-vous des besoins en formation à l'avenir ?"
|
|
)
|
|
formation_avenir_type = models.ForeignKey(
|
|
ref.TypeFormation,
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.PROTECT,
|
|
verbose_name="si oui, lesquelles ?",
|
|
)
|
|
formation_avenir_details = models.TextField(
|
|
"détails", null=True, blank=True
|
|
)
|
|
# Infos assolement
|
|
ass_total = models.DecimalField(
|
|
"Assolement total",
|
|
blank=True,
|
|
default=0,
|
|
max_digits=10,
|
|
decimal_places=4,
|
|
help_text="Note: ce total n'est mis à jour "
|
|
"qu'après enregistrement de la page",
|
|
)
|
|
ass_total_bio = models.DecimalField(
|
|
"Assolement total bio",
|
|
blank=True,
|
|
default=0,
|
|
max_digits=10,
|
|
decimal_places=4,
|
|
help_text=""
|
|
"Note: ce total n'est mis à jour qu'après enregistrement de la page",
|
|
)
|
|
# block A garder pour usage futur 20150907
|
|
lait_vaches_conversion = models.PositiveIntegerField(
|
|
"nombre de vaches en conversion", null=True, blank=True
|
|
)
|
|
bovine_nb_conversion = models.PositiveIntegerField(
|
|
"VA conversion", null=True, blank=True
|
|
)
|
|
# endblock A garder
|
|
|
|
def __str__(self):
|
|
"Retourne une représentation unicode de la structure."
|
|
return "Données de %s pour l'année %s" % (
|
|
self.agriculteur_bio,
|
|
self.annee,
|
|
)
|
|
|
|
def agri_id(self):
|
|
return self.agriculteur_bio.id if self.agriculteur_bio else ""
|
|
|
|
agri_id.short_description = "ID agriculteur bio"
|
|
agri_id.verbose_name = agri_id.short_description
|
|
|
|
def get_absolute_url(self):
|
|
"Retourne l'URL du formulaire de modification de l'objet."
|
|
return reverse(
|
|
'admin:%s_%s_change'
|
|
% (self._meta.app_label, self.__class__.__name__.lower()),
|
|
args=[self.pk],
|
|
)
|
|
|
|
def update_assolement(self, commit=False):
|
|
"""return change status, ass. total and ass. bio total,
|
|
update field if update=True"""
|
|
total = total_bio = 0
|
|
for ass in self.assolement_set.all():
|
|
sub_total, sub_total_bio = ass.get_total_sau()
|
|
total += sub_total
|
|
total_bio += sub_total_bio
|
|
change = self.ass_total != total or self.ass_total_bio != total_bio
|
|
if commit:
|
|
self.ass_total = total
|
|
self.ass_total_bio = total_bio
|
|
self.save()
|
|
return (change, total, total_bio)
|
|
|
|
def agri_detail(self):
|
|
if self.agriculteur_bio_id is None:
|
|
return "-"
|
|
return format_html(
|
|
'<a href="{0}">{1}</a>',
|
|
mark_safe(self.agriculteur_bio.get_absolute_url()),
|
|
self.agriculteur_bio,
|
|
)
|
|
|
|
agri_detail.short_description = "Détail agriculteur"
|
|
|
|
def clean(self):
|
|
if self.lait_nb_vaches and self.lait_vaches is None:
|
|
raise ValidationError(e_msg["production_vache_bio_non_choisi"])
|
|
if self.lait_nb_chevres and self.lait_chevres is None:
|
|
raise ValidationError(e_msg["production_chevre_bio_non_choisi"])
|
|
if (
|
|
self.bovine_nb_va
|
|
or self.bovine_nb_conversion
|
|
or self.bovine_reformes_cl
|
|
or self.bovine_taurillons_cl
|
|
or self.bovine_adultes_cc
|
|
or self.bovine_veaux_cc
|
|
):
|
|
if self.bovine is None:
|
|
raise ValidationError(e_msg["production_bovin_bio_non_choisi"])
|
|
if (self.porcs_nb_truies or self.porcs_nb_com) and self.porcs is None:
|
|
raise ValidationError(e_msg["production_porc_bio_non_choisi"])
|
|
if self.moutons_nb_brebis and self.moutons is None:
|
|
raise ValidationError(e_msg["production_mouton_bio_non_choisi"])
|
|
if self.animaux_nb_lapins and self.animaux_lapins is None:
|
|
raise ValidationError(e_msg["production_lapin_bio_non_choisi"])
|
|
if (
|
|
self.animaux_nb_poulets or self.animaux_nb_autres_volailles
|
|
) and self.animaux_volailles is None:
|
|
raise ValidationError(e_msg["production_volaille_bio_non_choisi"])
|
|
if self.oeufs_nb_pondeuses and self.oeufs_production_bio is None:
|
|
raise ValidationError(e_msg["production_oeuf_bio_non_choisi"])
|
|
if (
|
|
self.animaux_details_autres_prod
|
|
and self.animaux_autres_prod is None
|
|
):
|
|
raise ValidationError(e_msg["production_autre_bio_non_choisi"])
|
|
if self.miel_nb_ruches and self.miel_production_bio is None:
|
|
raise ValidationError(e_msg["production_miel_bio_non_choisi"])
|
|
|
|
def get_messages(self, refresh=False):
|
|
"fill info/warning messages at save"
|
|
mlist = []
|
|
if not self.com_ca_total:
|
|
mlist.append((messages.WARNING, w_msg["commercialisation_vide"]))
|
|
return mlist
|
|
|
|
|
|
class DonneesBioRo(DonneesBio):
|
|
"La version read-only de DonneesBio"
|
|
|
|
class Meta:
|
|
proxy = True
|
|
# tricky: auth_permission automatically create field based on
|
|
# verbose_name: "can <action> <verbose_name>"
|
|
# this field should not exceed 50 char
|
|
verbose_name = "Info annuelle sur l'expl. (lect. seule)"
|
|
verbose_name_plural = (
|
|
" Infos annuelles sur l'exploitation (lecture seule)"
|
|
)
|
|
|
|
|
|
class Assolement(models.Model):
|
|
"Informations d'assolement liées à DonneesBio (observatoire Gabnor)."
|
|
attrs_export = (
|
|
'donneesbio__agriculteur_bio',
|
|
('donneesbio__agriculteur_bio__id', 'ID agriculteur bio'),
|
|
('donneesbio__id', 'ID donnees'),
|
|
('id', 'ID assolement'),
|
|
'culture__categorie_cul',
|
|
'culture',
|
|
'detail_culture',
|
|
'surface_conv',
|
|
'surface_c1',
|
|
'surface_c2',
|
|
'surface_c3',
|
|
'surface_bio',
|
|
'tonnage',
|
|
'prix',
|
|
'client',
|
|
'type_circuit',
|
|
)
|
|
donneesbio = models.ForeignKey(
|
|
'DonneesBio', verbose_name="objet donneesbio", on_delete=models.CASCADE
|
|
)
|
|
culture = models.ForeignKey(
|
|
ref.Culture, verbose_name="culture", on_delete=models.PROTECT
|
|
)
|
|
detail_culture = models.CharField(
|
|
"détail des cultures (si nécessaire)",
|
|
max_length=255,
|
|
null=True,
|
|
help_text="Complétez si nécessaire la description des cultures",
|
|
blank=True,
|
|
)
|
|
surface_conv = models.DecimalField(
|
|
"conventionnelle",
|
|
null=True,
|
|
blank=True,
|
|
max_digits=10,
|
|
decimal_places=4,
|
|
)
|
|
surface_c1 = models.DecimalField(
|
|
"surface C1", null=True, blank=True, max_digits=10, decimal_places=4
|
|
)
|
|
surface_c2 = models.DecimalField(
|
|
"surface C2", null=True, blank=True, max_digits=10, decimal_places=4
|
|
)
|
|
surface_c3 = models.DecimalField(
|
|
"surface C3", null=True, blank=True, max_digits=10, decimal_places=4
|
|
)
|
|
surface_bio = models.DecimalField(
|
|
"surface bio", null=True, blank=True, max_digits=10, decimal_places=4
|
|
)
|
|
tonnage = models.FloatField("tonnage", null=True, blank=True)
|
|
client = models.ForeignKey(
|
|
ref.ClientCulture,
|
|
null=True,
|
|
blank=True,
|
|
verbose_name="client",
|
|
on_delete=models.PROTECT,
|
|
)
|
|
prix = models.FloatField("Prix", null=True, blank=True)
|
|
type_circuit = models.CharField(
|
|
max_length=10, null=True, blank=True, choices=fch.CHOIX_TYPE_CIRCUIT
|
|
)
|
|
|
|
def get_total_sau(self):
|
|
"return sau and sau bio for this record"
|
|
sub_total = sub_total_bio = 0
|
|
for field in ('conv', 'c1', 'c2', 'c3', 'bio'):
|
|
sub_total += getattr(self, 'surface_' + field, 0) or 0
|
|
if field not in ('conv',):
|
|
sub_total_bio += getattr(self, 'surface_' + field, 0) or 0
|
|
return (sub_total, sub_total_bio)
|
|
|
|
def save(self, *args, **kwargs):
|
|
super(Assolement, self).save(*args, **kwargs)
|
|
if not kwargs.pop('raw', False):
|
|
self.donneesbio.update_assolement(commit=True)
|
|
|
|
def delete(self, *args, **kwargs):
|
|
super(Assolement, self).delete(*args, **kwargs)
|
|
if not kwargs.pop('raw', False):
|
|
self.donneesbio.update_assolement(commit=True)
|
|
|
|
def __str__(self):
|
|
"representation unicode de l'objet"
|
|
return "Assolement de %s" % self.culture
|
|
|
|
|
|
class Transformation(models.Model):
|
|
"sous table des donnees bio"
|
|
|
|
class Meta:
|
|
verbose_name = "Transformation"
|
|
verbose_name_plural = "Transformations"
|
|
|
|
attrs_export = (
|
|
'donneesbio__agriculteur_bio',
|
|
('donneesbio__agriculteur_bio__id', 'ID agriculteur bio'),
|
|
('donneesbio__id', 'ID donnees'),
|
|
'ferme',
|
|
'type_pduit',
|
|
'agrmt',
|
|
'list_pduit',
|
|
'part_vol_glob',
|
|
'est_bio',
|
|
'lait_abattoirs_vl',
|
|
)
|
|
donneesbio = models.ForeignKey(
|
|
'DonneesBio', verbose_name="objet donneesbio", on_delete=models.CASCADE
|
|
)
|
|
ferme = models.NullBooleanField("Transformation à la ferme")
|
|
type_pduit = models.CharField(
|
|
"Type de produit", max_length=20, choices=fch.TYPE_TRANS
|
|
)
|
|
agrmt = models.CharField(
|
|
"Agrément", max_length=20, choices=fch.TYPE_AGREMENT
|
|
)
|
|
list_pduit = models.TextField(
|
|
"Détail (liste des produits transformés)", blank=True
|
|
)
|
|
part_vol_glob = models.DecimalField(
|
|
"Part du volume global",
|
|
max_digits=10,
|
|
decimal_places=0,
|
|
null=True,
|
|
blank=True,
|
|
help_text="en pourcentage",
|
|
)
|
|
est_bio = models.NullBooleanField("Est bio ?")
|
|
lait_abattoirs_vl = models.ManyToManyField(
|
|
ref.Abattoir, blank=True, verbose_name="Abattoirs circuit court"
|
|
)
|
|
|
|
def __str__(self):
|
|
return self.get_type_pduit_display() + " bio" if self.est_bio else ""
|
|
|
|
|
|
class Commercialisation(models.Model):
|
|
"sous table des donnees bio"
|
|
|
|
class Meta:
|
|
verbose_name = "Commercialisation"
|
|
verbose_name_plural = "Commercialisations"
|
|
|
|
attrs_export = (
|
|
'donneesbio__agriculteur_bio',
|
|
('donneesbio__agriculteur_bio__id', 'ID agriculteur bio'),
|
|
('donneesbio__id', 'ID donnees'),
|
|
'type_produit',
|
|
'type_circuit',
|
|
'ch_aff',
|
|
'detail',
|
|
'bio',
|
|
)
|
|
donneesbio = models.ForeignKey(
|
|
'DonneesBio', verbose_name="objet donneesbio", on_delete=models.CASCADE
|
|
)
|
|
type_produit = models.CharField(
|
|
"Type de produit",
|
|
max_length=20,
|
|
choices=fch.TYPE_PRODUIT,
|
|
default=fch.TYPE_PRODUIT[0][0],
|
|
)
|
|
type_circuit = models.CharField(
|
|
"Type de circuit",
|
|
max_length=20,
|
|
choices=fch.TYPE_CIRCUIT,
|
|
default=fch.TYPE_CIRCUIT[0][0],
|
|
)
|
|
ch_aff = models.DecimalField(
|
|
"Chiffre d'affaire HT",
|
|
max_digits=10,
|
|
decimal_places=0,
|
|
blank=True,
|
|
null=True,
|
|
)
|
|
detail = models.TextField(blank=True, help_text="horaire, date, lieux")
|
|
bio = models.NullBooleanField("Est bio ?")
|
|
|
|
def get_ch_aff(self):
|
|
if self.ch_aff is None:
|
|
return 0
|
|
return self.ch_aff
|
|
|
|
def get_total_ca_for_all_comm(self):
|
|
"compute the total CA and the ratio of bio CA"
|
|
ca_total = 0
|
|
ca_bio = 0
|
|
for comm in self.donneesbio.commercialisation_set.all():
|
|
ca_total += comm.get_ch_aff()
|
|
if comm.bio:
|
|
ca_bio += comm.get_ch_aff()
|
|
return (ca_total, (ca_bio * 100) / ca_total) if ca_total else (0, 0)
|
|
|
|
def clean(self, *args, **kwargs):
|
|
if self.bio is None:
|
|
raise ValidationError(e_msg["commerce_bio_non_choisi"])
|
|
return super(Commercialisation, self).clean(*args, **kwargs)
|
|
|
|
def save(self, *args, **kwargs):
|
|
super(Commercialisation, self).save(*args, **kwargs)
|
|
if not kwargs.pop('raw', False):
|
|
self.donneesbio.com_ca_total, self.donneesbio.com_pct_ca_bio = (
|
|
self.get_total_ca_for_all_comm()
|
|
)
|
|
self.donneesbio.save()
|
|
|
|
def delete(self, *args, **kwargs):
|
|
super(Commercialisation, self).delete(*args, **kwargs)
|
|
if not kwargs.pop('raw', False):
|
|
self.donneesbio.com_ca_total, self.donneesbio.com_pct_ca_bio = (
|
|
self.get_total_ca_for_all_comm()
|
|
)
|
|
self.donneesbio.save()
|
|
|
|
|
|
class Personne(models.Model):
|
|
"Personne physique dépendant d'une structure."
|
|
|
|
class Meta:
|
|
ordering = ['nom', 'prenom', 'structure__designation']
|
|
verbose_name_plural = " Personne"
|
|
|
|
# Champs à inclure dans un export CSV complet
|
|
attrs_export = (
|
|
'civilite',
|
|
'nom',
|
|
'prenom',
|
|
'date_naissance',
|
|
'date_installation',
|
|
'structure',
|
|
'fonction',
|
|
'adresse',
|
|
'commune',
|
|
'code_postal',
|
|
'courrier_structure',
|
|
'tel_fixe',
|
|
'tel_mobile',
|
|
'fax',
|
|
'courriel',
|
|
'pas_de_courriel',
|
|
'date_creation',
|
|
'date_derniere_modification',
|
|
'reseaux_engagements',
|
|
'utilisateur_derniere_modification',
|
|
)
|
|
|
|
# Champs à inclure dans un export CSV pour publipostage (detail personne)
|
|
attrs_publipostage = (
|
|
('structure__designation', 'Désignation PM'),
|
|
('structure__non_courrier', 'Ne pas envoyer de courrier PM'),
|
|
('structure__adresse', 'Adresse PM'),
|
|
('structure__code_postal', 'Code postal PM'),
|
|
('structure__ville', 'Ville PM'),
|
|
('structure__tel_fixe', 'tel fixe PM'),
|
|
('structure__tel_mobile', 'tel mob. PM'),
|
|
('structure__fax', 'fax PM'),
|
|
('structure__courriel', 'courriel PM'),
|
|
'civilite',
|
|
'nom',
|
|
'prenom',
|
|
'courrier_structure',
|
|
'adresse',
|
|
'code_postal',
|
|
'ville',
|
|
'tel_fixe',
|
|
'tel_mobile',
|
|
'fax',
|
|
'pas_de_courriel',
|
|
'courriel',
|
|
)
|
|
|
|
civilite = models.CharField(
|
|
"civilité",
|
|
max_length=10,
|
|
null=True,
|
|
blank=True,
|
|
choices=fch.CHOIX_CIVILITE,
|
|
)
|
|
nom = models.CharField(max_length=255)
|
|
prenom = models.CharField(max_length=255, verbose_name="prénom")
|
|
date_naissance = models.DateField(
|
|
"date de naissance", null=True, blank=True
|
|
)
|
|
structure = models.ForeignKey(
|
|
"Structure", verbose_name="affilié à", on_delete=models.CASCADE
|
|
)
|
|
date_installation = models.DateField(
|
|
"date d'installation", null=True, blank=True
|
|
)
|
|
fonction = models.CharField(
|
|
"fonction", max_length=255, null=True, blank=True
|
|
)
|
|
adresse = models.TextField(
|
|
"adresse personnelle",
|
|
blank=True,
|
|
help_text="Saisissez l'adresse de la personne, sur 4 lignes maximum.",
|
|
)
|
|
commune = models.ForeignKey(
|
|
ref.Commune, null=True, blank=True, on_delete=models.PROTECT
|
|
)
|
|
# Certaines communes peuvent avoir plusieurs codes postaux
|
|
code_postal = models.CharField(
|
|
max_length=10,
|
|
null=True,
|
|
blank=True,
|
|
help_text="Laisser vide pour utiliser celui associé à la commune",
|
|
)
|
|
# la ville contient également le code cedex (à utiliser pour l'adresse)
|
|
ville = models.CharField(
|
|
max_length=255,
|
|
null=True,
|
|
blank=True,
|
|
help_text="Nom de ville"
|
|
" avec code cédex si nécessaire, si différent du nom de la commune.",
|
|
)
|
|
courrier_structure = models.BooleanField(
|
|
"Recevoir les courriers addressés à la structure",
|
|
default=False,
|
|
help_text="Cochez cette case si la personne doit recevoir les "
|
|
"courriers papier envoyés à la structure.",
|
|
)
|
|
tel_fixe = models.CharField("téléphone fixe", max_length=100, blank=True)
|
|
tel_mobile = models.CharField(
|
|
"téléphone mobile", max_length=100, blank=True
|
|
)
|
|
fax = models.CharField(max_length=100, null=True, blank=True)
|
|
courriel = models.CharField(max_length=255, blank=True)
|
|
pas_de_courriel = models.BooleanField(
|
|
"pas d'envoi automatique de courriel",
|
|
default=False,
|
|
help_text="Cochez cette case si la personne ne souhaite"
|
|
" pas recevoir de courriel envoyé automatiquement.",
|
|
)
|
|
reseaux_engagements = models.TextField(
|
|
"Appartenance à des réseaux et engagements", null=True, blank=True
|
|
)
|
|
date_creation = models.DateField(
|
|
"date de saisie de l'enregistrement", editable=False
|
|
)
|
|
date_derniere_modification = models.DateField(
|
|
"date de dernière modification de informations", editable=False
|
|
)
|
|
utilisateur_derniere_modification = models.ForeignKey(
|
|
auth_models.User,
|
|
editable=False,
|
|
on_delete=models.SET(get_rem_user),
|
|
verbose_name="dernière modification par",
|
|
)
|
|
|
|
def __str__(self):
|
|
"""
|
|
Retourne une représentation unicode de la structure.
|
|
"""
|
|
if not self.nom and not self.prenom:
|
|
return "Personne non nommée #%s" % self.id
|
|
return "%s %s" % (self.prenom, self.nom)
|
|
|
|
def get_absolute_url(self):
|
|
"""
|
|
Retourne l'URl associée à l'objet.
|
|
"""
|
|
return reverse(
|
|
'admin:%s_%s_change'
|
|
% (self._meta.app_label, self.__class__.__name__.lower()),
|
|
args=[self.pk],
|
|
)
|
|
|
|
def nom_prenom(self):
|
|
"""
|
|
Retourne les nom et prénom de la personne sous forme de chaîne de
|
|
caractères.
|
|
"""
|
|
return "%s %s" % (self.nom.upper(), self.prenom)
|
|
|
|
def adresse_electronique(self):
|
|
"""
|
|
Retourne une liste de 2 éléments:
|
|
- le nom de la structure
|
|
- l'adresses email associée
|
|
Retourne une liste vide si aucune adresse courriel n'est renseignée.
|
|
"""
|
|
if not self.courriel:
|
|
return ()
|
|
if self.nom:
|
|
nom = "%s %s" % (self.prenom, self.nom)
|
|
else:
|
|
nom = ""
|
|
return (nom, self.courriel)
|
|
|
|
def get_courriels(self):
|
|
"""
|
|
Méthode commune aux structures et personnes: retourne la liste des
|
|
adresses emails (dans Personne il n'y en a qu'une)
|
|
"""
|
|
adr = self.adresse_electronique()
|
|
if adr and not self.pas_de_courriel:
|
|
return [adr]
|
|
return []
|
|
|
|
def save(self, *args, **kwargs):
|
|
"""
|
|
Remplit les champs date_creation et date_derniere_modification
|
|
manuellement (la fonctionnalité auto_now de django est douteuse,
|
|
mieux vaut le gérer manuellement).
|
|
"""
|
|
if self.id is None:
|
|
self.date_creation = datetime.date.today()
|
|
self.date_derniere_modification = datetime.date.today()
|
|
super(Personne, self).save(*args, **kwargs)
|
|
|
|
def get_messages(self, refresh=False):
|
|
"fill info/warning messages at save"
|
|
mlist = []
|
|
if (
|
|
self.structure_id
|
|
and self.structure.type_pm > 0
|
|
and not self.date_naissance
|
|
):
|
|
mlist.append(
|
|
(messages.WARNING, w_msg["pers_af_agri_sans_date_naiss"])
|
|
)
|
|
return mlist
|
|
|
|
def liste_contacts_associes(self):
|
|
return []
|
|
|
|
def get_designation(self):
|
|
return str(self) # noqa: F821
|
|
|
|
get_designation.short_description = "Désignation ou personne"
|
|
|
|
def email_fb(self):
|
|
return _field_fb_struct(self, 'courriel')
|
|
|
|
email_fb.short_description = "courriel (pers>pm)"
|
|
|
|
def tel_fixe_fb(self):
|
|
return _field_fb_struct(self, 'tel_fixe')
|
|
|
|
tel_fixe_fb.short_description = "tel fixe (pers>pm)"
|
|
|
|
def tel_mobile_fb(self):
|
|
return _field_fb_struct(self, 'tel_mobile')
|
|
|
|
tel_mobile_fb.short_description = "tel mob (pers>pm)"
|
|
|
|
def adr_adresse_fb(self):
|
|
return _field_fb_struct(self, 'adresse')
|
|
|
|
adr_adresse_fb.short_description = "adresse (pers>pm)"
|
|
|
|
def adr_ville_fb(self):
|
|
return _field_fb_struct(self, 'ville', 'adresse')
|
|
|
|
adr_ville_fb.short_description = "ville (pers>pm)"
|
|
|
|
def adr_cp_fb(self):
|
|
return _field_fb_struct(self, 'code_postal', 'adresse')
|
|
|
|
adr_cp_fb.short_description = "code postal (pers>pm)"
|
|
|
|
|
|
class PersonneRo(Personne):
|
|
"La version read-only de personne"
|
|
|
|
class Meta:
|
|
proxy = True
|
|
verbose_name = "Personne (lecture seule)"
|
|
verbose_name_plural = " Personnes (lecture seule)"
|
|
|
|
|
|
# ## Classes secondaires
|
|
class RdvPersonne(models.Model):
|
|
"modele intermediaire: participation d'une personne a un evt"
|
|
|
|
class Meta:
|
|
db_table = "contacts_rdv_personnes"
|
|
unique_together = ('rdv', 'personne')
|
|
|
|
attrs_export = (
|
|
('rdv__id', 'ID_rdv'),
|
|
'rdv__titre_evt',
|
|
'rdv__lieu',
|
|
'rdv__lieu__code_commune',
|
|
'rdv__lieu__arrondissement',
|
|
'rdv__date',
|
|
'rdv__technicien',
|
|
'rdv__get_type_rdv_display',
|
|
'rdv__commentaire',
|
|
'rdv__acc_duree',
|
|
'rdv__acc_distance',
|
|
'rdv__personnes__count',
|
|
'rdv__finance_prog',
|
|
'rdv__est_confirme',
|
|
'rdv__territoire_cible',
|
|
'rdv__public_cible',
|
|
'rdv__organisateur',
|
|
'rdv__organisateur__structure',
|
|
'rdv__intervenant',
|
|
'rdv__intervenant__structure',
|
|
'rdv__objectif',
|
|
'rdv__res_attendu',
|
|
'personne__civilite',
|
|
'personne__nom',
|
|
'personne__prenom',
|
|
'personne__date_naissance',
|
|
'personne__adr_adresse_fb',
|
|
'personne__adr_cp_fb',
|
|
'personne__adr_ville_fb',
|
|
'personne__email_fb',
|
|
'personne__tel_fixe_fb',
|
|
'personne__tel_mobile_fb',
|
|
'personne__structure__f_juridique',
|
|
'personne__structure',
|
|
'personne__structure__siret',
|
|
'personne__structure__type',
|
|
'personne__structure__statut',
|
|
)
|
|
|
|
rdv = models.ForeignKey("Rdv", on_delete=models.CASCADE)
|
|
personne = models.ForeignKey(
|
|
"Personne",
|
|
verbose_name="Personne physique",
|
|
help_text="" "pour les rendez vous collectifs",
|
|
on_delete=models.CASCADE,
|
|
)
|
|
|
|
def is_organisateur(self):
|
|
if self.personne.structure:
|
|
return format_html(
|
|
'<a href="{0}">{1}</a>',
|
|
mark_safe(self.personne.structure.get_absolute_url()),
|
|
self.personne.structure,
|
|
)
|
|
return ""
|
|
|
|
is_organisateur.short_description = "Organisation"
|
|
|
|
def __str__(self):
|
|
return "Participation de {0} à {1}".format(self.personne, self.rdv)
|
|
|
|
|
|
class RdvPersonneMorale(models.Model):
|
|
"modele intermediaire: participation d'une structure a un rdv"
|
|
|
|
class Meta:
|
|
db_table = "contacts_rdv_personnes_morales"
|
|
unique_together = ('rdv', 'structure')
|
|
|
|
rdv = models.ForeignKey("Rdv", on_delete=models.CASCADE)
|
|
structure = models.ForeignKey(
|
|
"Structure",
|
|
verbose_name="Personne morale",
|
|
help_text="pour les rendez vous collectifs",
|
|
on_delete=models.CASCADE,
|
|
)
|
|
|
|
def __str__(self):
|
|
return "Participation de {0} à {1}".format(self.structure, self.rdv)
|
|
|
|
|
|
class Rdv(models.Model):
|
|
"Rendez vous generiques"
|
|
|
|
class Meta:
|
|
verbose_name = "Rendez vous ou Évenement"
|
|
verbose_name_plural = "Rendez vous et Évenements"
|
|
ordering = ['-date']
|
|
get_latest_by = 'date'
|
|
|
|
attrs_rdv_coll = (
|
|
('id', 'ID_rdv'),
|
|
'titre_evt',
|
|
'lieu',
|
|
'lieu__code_commune',
|
|
'lieu__arrondissement',
|
|
'date',
|
|
'technicien',
|
|
'get_type_rdv_display',
|
|
'commentaire',
|
|
'acc_duree',
|
|
'acc_distance',
|
|
'personnes__count',
|
|
'finance_prog',
|
|
'est_confirme',
|
|
'territoire_cible',
|
|
'organisateur',
|
|
'intervenant',
|
|
'objectif',
|
|
'res_attendu',
|
|
)
|
|
|
|
# rdv ind
|
|
attrs_export = (
|
|
('id', 'ID_rdv'),
|
|
('proj_id', 'ID_projet'),
|
|
('personne_morale__id', 'ID_Agri'),
|
|
'personne_morale__designation',
|
|
'personne_morale__get_type_pm_display',
|
|
'projet_bio__technicien',
|
|
'personne_morale__adresse',
|
|
'personne_morale__commune__code_postal',
|
|
'personne_morale__commune__nom',
|
|
'personne_morale__commune__code_commune',
|
|
'personne_morale__commune__arrondissement',
|
|
'personne_morale__commune__enjeu_eau',
|
|
'personne_morale__etiquettes',
|
|
'personne_morale__type_activite',
|
|
'personne_morale__qual_compl',
|
|
'personne_morale__type_activite_pri',
|
|
'personne_morale__qual_compl_conv',
|
|
'personne_morale__last_donneesbio__ass_total',
|
|
'personne_morale__last_donneesbio__ass_total_bio',
|
|
'personne_morale__sau_eau_bio',
|
|
'personne_morale__sau_eau',
|
|
'date',
|
|
'technicien',
|
|
'get_type_rdv_display',
|
|
'commentaire',
|
|
'acc_duree',
|
|
'acc_distance',
|
|
'projet_bio__description',
|
|
'projet_bio__freins_identifies',
|
|
'projet_bio__pond_savoirfaire',
|
|
'projet_bio__pond_motivation',
|
|
'projet_bio__pond_entourage',
|
|
'projet_bio__pond_foncier',
|
|
'projet_bio__pond_materiel',
|
|
'projet_bio__pond_reglementation',
|
|
'projet_bio__pond_commercialisation',
|
|
'projet_bio__pond_financement',
|
|
'projet_bio__pond_maindoeuvre',
|
|
'projet_bio__suites_a_donner',
|
|
'projet_bio__remarques',
|
|
'projet_bio__date_probable_certification',
|
|
'projet_bio__date_certification',
|
|
'projet_bio__engagement_surf',
|
|
'projet_bio__engagement_oc_enjeu_eau',
|
|
'projet_bio__installation_bio',
|
|
'projet_bio__date_engagement_oc',
|
|
'projet_bio__commentaire',
|
|
'lieu',
|
|
)
|
|
|
|
date = models.DateField(db_index=True)
|
|
technicien = models.ForeignKey(
|
|
auth_models.User, on_delete=models.SET(get_rem_user)
|
|
)
|
|
type_rdv = models.CharField(
|
|
max_length=25, choices=fch.CHOIX_TYPE_RDV, verbose_name="type"
|
|
)
|
|
projet_bio = models.ForeignKey(
|
|
'ProjetBio',
|
|
blank=True,
|
|
null=True,
|
|
verbose_name="Projet bio",
|
|
on_delete=models.SET_NULL,
|
|
)
|
|
titre_evt = models.ForeignKey(
|
|
'references.Formation',
|
|
blank=True,
|
|
null=True,
|
|
verbose_name="Désignation évenement",
|
|
on_delete=models.SET_NULL,
|
|
)
|
|
personne_morale = models.ForeignKey(
|
|
'Structure',
|
|
related_name="rdv_ind_set",
|
|
blank=True,
|
|
null=True,
|
|
verbose_name="Personne morale",
|
|
help_text="pour les rendez vous individuels",
|
|
on_delete=models.CASCADE,
|
|
)
|
|
personnes_morales = models.ManyToManyField(
|
|
'Structure',
|
|
related_name="evt_set",
|
|
through="RdvPersonneMorale",
|
|
verbose_name="Participants",
|
|
help_text="pour les rendez vous collectifs",
|
|
)
|
|
personnes = models.ManyToManyField(
|
|
'Personne', through="RdvPersonne", verbose_name="Participant(s) ind."
|
|
)
|
|
territoire_cible = models.ManyToManyField(
|
|
'references.Territoire', blank=True
|
|
)
|
|
public_cible = models.ManyToManyField('references.PublicCible', blank=True)
|
|
organisateur = models.ForeignKey(
|
|
Personne,
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.PROTECT,
|
|
related_name="organisation_pers",
|
|
)
|
|
intervenant = models.ForeignKey(
|
|
Personne,
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.PROTECT,
|
|
related_name="intervenant_pers",
|
|
)
|
|
objectif = models.TextField(blank=True, default="")
|
|
res_attendu = models.TextField(blank=True, default="")
|
|
lieu = models.ForeignKey(
|
|
ref.Commune,
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.SET_NULL,
|
|
help_text="si vide, rempli automatiquement",
|
|
)
|
|
# accounting: duree et distance associees au rendez vous
|
|
acc_duree = models.DecimalField(
|
|
"Temps passé",
|
|
max_digits=3,
|
|
decimal_places=1,
|
|
null=True,
|
|
blank=True,
|
|
help_text="en heure",
|
|
)
|
|
acc_distance = models.DecimalField(
|
|
"Distance associée",
|
|
max_digits=4,
|
|
decimal_places=1,
|
|
null=True,
|
|
blank=True,
|
|
help_text="en km",
|
|
)
|
|
finance_prog = models.ForeignKey(
|
|
ref.FinanceProg,
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.SET_NULL,
|
|
verbose_name="Financement / Programme concerné",
|
|
)
|
|
est_confirme = models.BooleanField(
|
|
"est confirmé", blank=True, default=False
|
|
)
|
|
commentaire = models.TextField(blank=True)
|
|
pk_rdv_suivi = models.IntegerField(default=0, editable=False)
|
|
pk_rdv_bio = models.IntegerField(default=0, editable=False)
|
|
|
|
def struct_af_item(self, item):
|
|
if (
|
|
getattr(self, item) is None
|
|
or getattr(self, item).structure is None
|
|
):
|
|
return 'N/A'
|
|
return format_html(
|
|
'<a href="{0}" target="_BLANK">{1}</a>',
|
|
mark_safe(getattr(self, item).structure.get_absolute_url()),
|
|
getattr(self, item).structure,
|
|
)
|
|
|
|
def struct_af_org(self):
|
|
return self.struct_af_item('organisateur')
|
|
|
|
struct_af_org.short_description = "P. morale affiliée à l'organisation"
|
|
|
|
def struct_af_int(self):
|
|
return self.struct_af_item('intervenant')
|
|
|
|
struct_af_int.short_description = "P. morale affiliée à l'intervenant"
|
|
|
|
def proj_id(self):
|
|
"retoune l'identifiant du projet"
|
|
return self.projet_bio.id if self.projet_bio else ""
|
|
|
|
proj_id.short_description = "ID Projet"
|
|
|
|
def get_cat(self):
|
|
"retourne la categorie de rendez vous (indiv, collect)"
|
|
resp = set(
|
|
key
|
|
for key, value in fch.MTX_TYPE_RDV.items()
|
|
if self.type_rdv in value
|
|
)
|
|
return resp
|
|
|
|
def get_absolute_url(self):
|
|
return reverse(
|
|
'admin:{0}_{1}_change'.format(
|
|
self._meta.app_label, self.__class__.__name__.lower()
|
|
),
|
|
args=[self.pk],
|
|
)
|
|
|
|
def save(self, *args, **kwargs):
|
|
if self.lieu is None:
|
|
pers_morale = self.rdvpersonnemorale_set.all()[:1]
|
|
if len(pers_morale) == 1:
|
|
pers_morale = pers_morale[0].structure
|
|
self.lieu = pers_morale.commune
|
|
super(Rdv, self).save(*args, **kwargs)
|
|
|
|
def clean(self):
|
|
cat = self.get_cat()
|
|
if self.est_confirme:
|
|
if 'pbio' in cat and not self.projet_bio:
|
|
raise ValidationError(e_msg["rdv_pbio_sans_pbio"])
|
|
if 'indiv' in cat and not self.personne_morale:
|
|
raise ValidationError(e_msg["rdv_indiv_sans_part"])
|
|
if 'collect' in cat:
|
|
if not self.titre_evt:
|
|
raise ValidationError(e_msg["rdv_collec_sans_titre"])
|
|
|
|
def __str__(self):
|
|
return "{0}-{1}-{2}".format(
|
|
self.date.strftime(LOCAL_DATE_FORMAT),
|
|
self.get_type_rdv_display(),
|
|
self.technicien,
|
|
)
|
|
|
|
def get_messages(self):
|
|
""
|
|
mlist = []
|
|
if (
|
|
'collect' in self.get_cat()
|
|
and self.est_confirme
|
|
and not self.rdvpersonne_set.exists()
|
|
):
|
|
mlist.append((messages.WARNING, w_msg["rdv_collec_sans_part"]))
|
|
return mlist
|
|
|
|
|
|
class RdvRo(Rdv):
|
|
"La version read-only de rdv"
|
|
|
|
class Meta:
|
|
proxy = True
|
|
verbose_name = "Rendez vous ou Évenement (lect. seule)"
|
|
verbose_name_plural = "Rendez vous et Évenements (lecture seule)"
|
|
|
|
|
|
class AdhesionGabnor(models.Model):
|
|
"Adhésion Gabnor."
|
|
|
|
class Meta:
|
|
ordering = ['-date']
|
|
get_latest_by = 'date'
|
|
verbose_name = "adhésion GAB"
|
|
verbose_name_plural = " Adhésions GAB"
|
|
|
|
# Champs à inclure dans un export CSV complet
|
|
attrs_export = ['agriculteur_bio', 'date', 'montant', 'mode_reg']
|
|
|
|
# todo: renommer agriculteur_bio en agriculteur
|
|
agriculteur_bio = models.ForeignKey(
|
|
'Structure',
|
|
limit_choices_to={'type_pm__gte': 1},
|
|
on_delete=models.CASCADE,
|
|
)
|
|
date = models.DateField(db_index=True)
|
|
montant = models.DecimalField(
|
|
"montant de l'adhésion (en euros)",
|
|
max_digits=8,
|
|
decimal_places=2,
|
|
null=True,
|
|
blank=True,
|
|
)
|
|
annee = models.IntegerField(
|
|
"Adhésion de l'année",
|
|
null=True,
|
|
blank=True,
|
|
help_text="Laisser vide pour utiliser l'année de la date d'adhésion",
|
|
)
|
|
mode_reg = models.CharField(
|
|
"Mode de réglement",
|
|
max_length=16,
|
|
null=True,
|
|
blank=True,
|
|
choices=fch.MODE_REGLT,
|
|
)
|
|
|
|
def __str__(self):
|
|
"""
|
|
Représentation unicode de l'objet (utilisé notamment pour affichage
|
|
dans l'objet AgriculteurBio).
|
|
"""
|
|
return "Adhésion du %s de %.2f EUR de %s" % (
|
|
self.date.strftime(LOCAL_DATE_FORMAT),
|
|
self.montant,
|
|
self.agriculteur_bio,
|
|
)
|
|
|
|
def save(self, *args, **kwargs):
|
|
"""
|
|
Surchargé pour mettre à jour les dates d'adhésions de l'agriculteur.
|
|
"""
|
|
if self.annee is None:
|
|
self.annee = self.date.year
|
|
super(AdhesionGabnor, self).save(*args, **kwargs)
|
|
self._update_dates_agriculteurbio()
|
|
|
|
def delete(self, *args, **kwargs):
|
|
"""
|
|
Surchargé pour mettre à jour les dates d'adhésions de l'agriculteur.
|
|
(cette méthode n'est pas appelée pour les suppressions en masse,
|
|
mais ça n'arrivera pas dans un usage normal de l'application).
|
|
"""
|
|
super(AdhesionGabnor, self).delete(*args, **kwargs)
|
|
self._update_dates_agriculteurbio()
|
|
|
|
def _update_dates_agriculteurbio(self):
|
|
"""
|
|
Calcule et sauvegarde les dates de première et dernière adhésion,
|
|
repectivement date_adhesion et date_renouvellement dans l'objet
|
|
AgriculteurBio.
|
|
"""
|
|
adhesions_qs = AdhesionGabnor.objects.filter(
|
|
agriculteur_bio=self.agriculteur_bio
|
|
).order_by('date')
|
|
if len(adhesions_qs) != 0:
|
|
premiere_adhesion = adhesions_qs[0]
|
|
else:
|
|
premiere_adhesion = None
|
|
if len(adhesions_qs) > 1:
|
|
# le renouvellement est la dernière adhésion en date
|
|
renouvellemenent = adhesions_qs[len(adhesions_qs) - 1]
|
|
else:
|
|
renouvellemenent = None
|
|
self.agriculteur_bio.premiere_adhesion = premiere_adhesion
|
|
self.agriculteur_bio.renouvellement_adhesion = renouvellemenent
|
|
self.agriculteur_bio.save()
|
|
|
|
|
|
class AdhesionGabnorRo(AdhesionGabnor):
|
|
"La version read-only de AdhesionGabnor"
|
|
|
|
class Meta:
|
|
proxy = True
|
|
verbose_name = "adhésion GAB (lecture seule)"
|
|
verbose_name_plural = " Adhésions GAB (lecture seule)"
|
|
|
|
|
|
class DocumentJoint(models.Model):
|
|
"Documents joints aux rendez-vous."
|
|
|
|
class Meta:
|
|
verbose_name = "document joint"
|
|
verbose_name_plural = "Documents Joints"
|
|
|
|
document = models.FileField(upload_to="pj/%Y/%m/%d/")
|
|
date_depot = models.DateTimeField(auto_now=True)
|
|
rdv = models.ForeignKey(
|
|
Rdv, null=True, blank=True, on_delete=models.CASCADE
|
|
)
|
|
|
|
def __str__(self):
|
|
"Retourne une représentation unicode de l'objet."
|
|
return "%s : %s" % (self.document, self.date_depot)
|
|
|
|
|
|
# EOF
|