Gestion de contact et suivi de conversion pour les groupement d'agriculture biologique
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

2292 lines
73 KiB

# -*- 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
Map all the world