preparation grps
[auf_rh_dae.git] / project / dae / models.py
CommitLineData
bd28238f 1# -=- encoding: utf-8 -=-
3f3cf5f3 2
36341125
OL
3import os
4from django.core.files.storage import FileSystemStorage
a9c281dd 5from django.db import models
fc18c1ca 6from django.contrib.auth.models import Group
a9c281dd 7import reversion
afc204bf 8from workflow import PosteWorkflow, DossierWorkflow
bd28238f 9import datamaster_modeles.models as ref
a9c281dd 10from rh_v1 import models as rh
36341125 11import settings
bd28238f 12
fc18c1ca
OL
13# Groupes impliqués dans le Worflow
14grp_administrateurs, created = Group.objects.get_or_create(name='Administrateurs')
15grp_gestionnaires, created = Group.objects.get_or_create(name='Gestionnaires')
16grp_directeurs_bureau, created = Group.objects.get_or_create(name='Directeurs de bureau')
17grp_drh, created = Group.objects.get_or_create(name='DRH')
18grp_pole_financier, created = Group.objects.get_or_create(name='Pôle financier')
19grp_haute_direction, created = Group.objects.get_or_create(name='Haute direction')
20grp_service_utilisateurs, created = Group.objects.get_or_create(name='Service utilisateurs')
21grp_directeurs_service, created = Group.objects.get_or_create(name='Directeurs de service / pôle')
22grp_correspondants_rh, created = Group.objects.get_or_create(name='Correspondants RH')
23
bd28238f 24STATUT_RESIDENCE_CHOICES = (
5d680e84
NC
25 ('local', 'Local'),
26 ('expat', 'Expatrié'),
bd28238f
NC
27)
28
29POSTE_APPEL_CHOICES = (
5d680e84
NC
30 ('interne', 'Interne'),
31 ('externe', 'Externe'),
bd28238f
NC
32)
33
34POSTE_STATUT_CHOICES = (
35 ('MAD', 'Mise à disposition'),
36 ('DET', 'Détachement'),
37)
38
d766bf2c 39# Upload de fichiers
36341125
OL
40storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT, base_url=settings.PRIVE_MEDIA_URL)
41
42def poste_piece_dispatch(instance, filename):
fe13cd48 43 path = "poste/%s/%s" % (instance.poste_id, filename)
36341125
OL
44 return path
45
d766bf2c 46def dossier_piece_dispatch(instance, filename):
fe13cd48 47 path = "dossier/%s/%s" % (instance.dossier_id, filename)
d766bf2c
OL
48 return path
49
36341125
OL
50
51class PostePiece(models.Model):
52 poste = models.ForeignKey("Poste")
53 nom = models.CharField(verbose_name="Nom", max_length=255)
54 fichier = models.FileField(verbose_name="Fichier", upload_to=poste_piece_dispatch, storage=storage_prive)
55
1c7d67ce
OL
56class PosteManager(models.Manager):
57 """
58 Chargement de tous les objets FK existants sur chaque QuerySet.
59 """
60 def get_query_set(self):
61 fkeys = (
62 'id_rh',
63 'responsable',
64 'implantation',
65 'type_poste',
66 'service',
67 'classement_min',
68 'classement_max',
69 'valeur_point_min',
70 'valeur_point_max',
71 )
98d51b59
NC
72 return super(PosteManager, self).get_query_set() \
73 .select_related(*fkeys).all()
1c7d67ce
OL
74
75
8fa94e8b 76class Poste(PosteWorkflow, models.Model):
bd28238f 77 # Modèle existant
5d680e84 78 id_rh = models.ForeignKey(rh.Poste, null=True, related_name='+',
98d51b59
NC
79 editable=False,
80 verbose_name="Mise à jour du poste")
ce110fb9 81 nom = models.CharField(verbose_name="Titre du poste", max_length=255)
5d680e84
NC
82 implantation = models.ForeignKey(ref.Implantation)
83 type_poste = models.ForeignKey(rh.TypePoste, null=True, related_name='+')
98d51b59
NC
84 service = models.ForeignKey(rh.Service, related_name='+',
85 verbose_name=u"Direction/Service/Pôle support")
86 responsable = models.ForeignKey(rh.Poste, related_name='+',
5efcd48e 87 verbose_name="Poste du responsable")
9a85768a 88
5d680e84 89 regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
5efcd48e 90 default=100,
91 verbose_name="Temps de travail",
92 help_text="% du temps complet")
5d680e84 93 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
5efcd48e 94 decimal_places=2,
5fe0ced5 95 default=35,
5efcd48e 96 verbose_name="Nb. heures par semaine")
bd28238f
NC
97
98 # Recrutement
154677c3
OL
99 local = models.BooleanField(verbose_name="Local", default=True, blank=True)
100 expatrie = models.BooleanField(verbose_name="Expatrié", default=False, blank=True)
101
5d680e84 102 # TODO null?
a3508c67 103 mise_a_disposition = models.BooleanField(verbose_name="Mise à disposition")
98d51b59
NC
104 appel = models.CharField(max_length=10, default='interne',
105 verbose_name="Appel à candidature",
5d680e84 106 choices=POSTE_APPEL_CHOICES)
bd28238f
NC
107
108 # Rémunération
5d680e84
NC
109 classement_min = models.ForeignKey(rh.Classement, related_name='+')
110 classement_max = models.ForeignKey(rh.Classement, related_name='+')
7dcb8d40
OL
111
112 # En fait, les coefficient n'ont pas de valeur dans ces cas, les couts sont calculés
113 # et mis dans les coûts globals
114 #coefficient_min = models.FloatField(null=True) # pour classement "hors grille"
115 #coefficient_max = models.FloatField(null=True) # pour classement "hors grille"
116
4dd75e7b
OL
117 valeur_point_min = models.ForeignKey(rh.ValeurPoint, related_name='+', blank=True, null=True)
118 valeur_point_max = models.ForeignKey(rh.ValeurPoint, related_name='+', blank=True, null=True)
3d627bfd 119 devise_min = models.ForeignKey(rh.Devise, default=5, related_name='+')
120 devise_max = models.ForeignKey(rh.Devise, default=5, related_name='+')
5d680e84
NC
121 salaire_min = models.DecimalField(max_digits=12, decimal_places=2,
122 default=0)
123 salaire_max = models.DecimalField(max_digits=12, decimal_places=2,
124 default=0)
125 indemn_min = models.DecimalField(max_digits=12, decimal_places=2,
126 default=0)
127 indemn_max = models.DecimalField(max_digits=12, decimal_places=2,
128 default=0)
129 autre_min = models.DecimalField(max_digits=12, decimal_places=2,
130 default=0)
131 autre_max = models.DecimalField(max_digits=12, decimal_places=2,
132 default=0)
133
134 # Comparatifs de rémunération
135 devise_comparaison = models.ForeignKey(rh.Devise, related_name='+',
a3508c67 136 default=5)
5d680e84
NC
137 comp_locale_min = models.DecimalField(max_digits=12, decimal_places=2,
138 null=True, blank=True)
139 comp_locale_max = models.DecimalField(max_digits=12, decimal_places=2,
140 null=True, blank=True)
141 comp_universite_min = models.DecimalField(max_digits=12, decimal_places=2,
142 null=True, blank=True)
143 comp_universite_max = models.DecimalField(max_digits=12, decimal_places=2,
144 null=True, blank=True)
145 comp_fonctionpub_min = models.DecimalField(max_digits=12, decimal_places=2,
146 null=True, blank=True)
147 comp_fonctionpub_max = models.DecimalField(max_digits=12, decimal_places=2,
148 null=True, blank=True)
149 comp_ong_min = models.DecimalField(max_digits=12, decimal_places=2,
150 null=True, blank=True)
151 comp_ong_max = models.DecimalField(max_digits=12, decimal_places=2,
152 null=True, blank=True)
153 comp_autre_min = models.DecimalField(max_digits=12, decimal_places=2,
154 null=True, blank=True)
155 comp_autre_max = models.DecimalField(max_digits=12, decimal_places=2,
156 null=True, blank=True)
bd28238f 157
2e092e0c
OL
158 # Justification
159 justification = models.TextField()
160
bd28238f 161 # Méta
5d680e84
NC
162 date_creation = models.DateTimeField(auto_now_add=True)
163 date_modification = models.DateTimeField(auto_now=True)
98d51b59 164 date_debut = models.DateField(verbose_name="Date de début",
9fb2ccd9 165 help_text="format: aaaa-mm-jj")
166 date_fin = models.DateField(null=True, blank=True,
167 verbose_name="Date de fin",
168 help_text="format: aaaa-mm-jj")
bd28238f
NC
169 actif = models.BooleanField(default=True)
170
1c7d67ce
OL
171 # Managers
172 objects = PosteManager()
173
03858ba5
OL
174 def _get_key(self):
175 """
176 Les vues sont montées selon une clef spéciale pour identifier la provenance du poste.
177 Cette méthode fournit un moyen de reconstruire cette clef afin de générer les URLs.
178 """
179 return "dae-%s" % self.id
180 key = property(_get_key)
181
f3333b0e
OL
182 def get_dossiers(self):
183 """
184 Liste tous les anciens dossiers liés à ce poste.
185 (Le nom de la relation sur le rh.Poste est mal choisi poste1 au lieu de dossier1)
186 Note1 : seulement le dosssier principal fait l'objet de la recherche.
187 Note2 : les dossiers sont retournés du plus récent au plus vieux. (Ce test est fait
188 en fonction du id, car les dates de création sont absentes de rh v1).
189 """
190 if self.id_rh is None:
191 return []
192 postes = [p for p in self.id_rh.poste1.all()]
193 return sorted(postes, key=lambda poste: poste.id, reverse=True)
194
195 def get_complement_nom(self):
196 """
197 Inspecte les modèles rh v1 pour trouver dans le dernier dossier un complément de titre de poste.
198 """
199 dossiers = self.get_dossiers()
200 if len(dossiers) > 0:
201 nom = dossiers[0].complement1
202 else:
203 nom = ""
a9c281dd 204 return nom
f3333b0e
OL
205
206 def get_employe(self):
207 """
208 Inspecte les modèles rh v1 pour trouver l'employé du dernier dossier.
209 """
210 dossiers = self.get_dossiers()
211 if len(dossiers) > 0:
212 return dossiers[0].employe
213 else:
214 return None
215
179f6b49
OL
216 def get_default_devise(self):
217 """Récupère la devise par défaut en fonction de l'implantation (EUR autrement)"""
218 try:
219 implantation_devise = rh.TauxChange.objects.filter(implantation=self.implantation)[0].devise
220 except:
221 implantation_devise = 5 # EUR
222 return implantation_devise
223
c0413a6f
OL
224 #####################
225 # Classement de poste
226 #####################
227
228 def get_couts_minimum(self):
229 return (float)(self.salaire_min + self.indemn_min + self.autre_min)
230
231 def get_taux_minimum(self):
232 try:
233 return rh.TauxChange.objects.filter(implantation=self.implantation, devise=self.devise_min)[0].taux
234 except:
235 return 1
236
237 def get_couts_minimum_euros(self):
238 return self.get_couts_minimum() * self.get_taux_minimum()
239
240 def get_couts_maximum(self):
241 return (float)(self.salaire_max + self.indemn_max + self.autre_max)
242
243 def get_taux_maximum(self):
244 try:
245 return rh.TauxChange.objects.filter(implantation=self.implantation, devise=self.devise_max)[0].taux
246 except:
247 return 1
248
249 def get_couts_maximum_euros(self):
250 return self.get_couts_maximum() * self.get_taux_maximum()
251
252 ######################
253 # Comparaison de poste
254 ######################
255
256 def get_taux_comparaison(self):
257 try:
258 return rh.TauxChange.objects.filter(implantation=self.implantation, devise=self.devise_comparaison)[0].taux
259 except:
260 return 1
261
262 def get_comp_universite_min_euros(self):
263 return (float)(self.comp_universite_min) * self.get_taux_comparaison()
264
265 def get_comp_fonctionpub_min_euros(self):
266 return (float)(self.comp_fonctionpub_min) * self.get_taux_comparaison()
267
268 def get_comp_locale_min_euros(self):
269 return (float)(self.comp_locale_min) * self.get_taux_comparaison()
270
271 def get_comp_ong_min_euros(self):
272 return (float)(self.comp_ong_min) * self.get_taux_comparaison()
273
274 def get_comp_autre_min_euros(self):
275 return (float)(self.comp_autre_min) * self.get_taux_comparaison()
276
277 def get_comp_universite_max_euros(self):
278 return (float)(self.comp_universite_max) * self.get_taux_comparaison()
279
280 def get_comp_fonctionpub_max_euros(self):
281 return (float)(self.comp_fonctionpub_max) * self.get_taux_comparaison()
282
283 def get_comp_locale_max_euros(self):
284 return (float)(self.comp_locale_max) * self.get_taux_comparaison()
285
286 def get_comp_ong_max_euros(self):
287 return (float)(self.comp_ong_max) * self.get_taux_comparaison()
288
289 def get_comp_autre_max_euros(self):
290 return (float)(self.comp_autre_max) * self.get_taux_comparaison()
291
a9e52624 292
5d680e84 293 def __unicode__(self):
f3333b0e
OL
294 """
295 Cette fonction est consommatrice SQL car elle cherche les dossiers qui ont été liés à celui-ci.
296 """
297 complement_nom_poste = self.get_complement_nom()
c7a4aa2b
OL
298 if complement_nom_poste is None:
299 complement_nom_poste = ""
f3333b0e 300 employe = self.get_employe()
c7a4aa2b
OL
301 if employe is None:
302 employe = "VACANT"
f3333b0e
OL
303 data = (
304 self.implantation,
305 self.type_poste.nom,
306 self.nom,
307 self.id,
308 complement_nom_poste,
309 employe,
310 )
311 return u'%s - %s (%s) [dae-%s %s %s]' % data
5d680e84 312
bd28238f 313
a9c281dd
OL
314# Tester l'enregistrement car les models.py sont importés au complet
315if not reversion.is_registered(Poste):
316 reversion.register(Poste)
317
bd28238f 318
5d680e84
NC
319POSTE_FINANCEMENT_CHOICES = (
320 ('A', 'A - Frais de personnel'),
321 ('B', 'B - Projet(s)-Titre(s)'),
322 ('C', 'C - Autre')
323)
bd28238f
NC
324
325
5d680e84 326class PosteFinancement(models.Model):
5d680e84
NC
327 poste = models.ForeignKey('Poste', related_name='financements')
328 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
43d04712 329 pourcentage = models.DecimalField(max_digits=12, decimal_places=2,
330 help_text="ex.: 33.33 % (décimale avec point)")
331 commentaire = models.TextField(
332 help_text="Spécifiez la source de financement.")
bd28238f 333
43d04712 334 class Meta:
335 ordering = ['type']
bd28238f 336
c0413a6f
OL
337 def __unicode__(self):
338 return u"%s %s %s" % (self.get_type_display(), self.pourcentage, self.commentaire)
339
bd28238f 340GENRE_CHOICES = (
139686f2
NC
341 ('m', 'Homme'),
342 ('f', 'Femme'),
bd28238f
NC
343)
344
345
346class Employe(models.Model):
bd28238f
NC
347
348 # Modèle existant
da3ca955 349 id_rh = models.ForeignKey(rh.Employe, null=True, related_name='+',
350 verbose_name='Employé')
bd28238f 351 nom = models.CharField(max_length=255)
da3ca955 352 prenom = models.CharField(max_length=255, verbose_name='Prénom')
494ff2be
NC
353 genre = models.CharField(max_length=1, choices=GENRE_CHOICES,
354 null=True, blank=True)
bd28238f 355
139686f2
NC
356 def __unicode__(self):
357 return u'%s %s' % (self.prenom, self.nom)
358
bd28238f
NC
359
360COMPTE_COMPTA_CHOICES = (
494ff2be
NC
361 ('coda', 'CODA'),
362 ('scs', 'SCS'),
363 ('aucun', 'Aucun'),
bd28238f
NC
364)
365
d766bf2c
OL
366class DossierPiece(models.Model):
367 dossier = models.ForeignKey("Dossier")
368 nom = models.CharField(verbose_name="Nom", max_length=255)
369 fichier = models.FileField(verbose_name="Fichier", upload_to=dossier_piece_dispatch, storage=storage_prive)
bd28238f 370
afc204bf 371class Dossier(DossierWorkflow, models.Model):
bd28238f
NC
372
373 # Modèle existant
139686f2
NC
374 employe = models.ForeignKey('Employe', related_name='+', editable=False)
375 poste = models.ForeignKey('Poste', related_name='+', editable=False)
5d680e84 376 statut = models.ForeignKey(rh.Statut, related_name='+')
dfc2c344 377 organisme_bstg = models.ForeignKey(rh.OrganismeBstg,
1f109689 378 null=True, blank=True,
dfc2c344 379 verbose_name="Organisme",
380 help_text="Si détaché (DET) ou mis à disposition (MAD), \
381 préciser l'organisme.",
382 related_name='+')
0288adb5
OL
383 organisme_bstg_autre = models.CharField(max_length=255,
384 verbose_name="Autre organisme",
385 help_text="indiquer l'organisme ici s'il n'est pas dans la liste",
386 null=True,
387 blank=True,)
bd28238f 388
139686f2
NC
389 # Données antérieures de l'employé
390 statut_anterieur = models.ForeignKey(
391 rh.Statut, related_name='+', null=True, blank=True,
da3ca955 392 verbose_name='Statut antérieur')
139686f2
NC
393 classement_anterieur = models.ForeignKey(
394 rh.Classement, related_name='+', null=True, blank=True,
395 verbose_name='Classement précédent')
396 salaire_anterieur = models.DecimalField(
397 max_digits=12, decimal_places=2, null=True, default=None,
398 blank=True, verbose_name='Salaire précédent')
399
400 # Données du titulaire précédent
401 employe_anterieur = models.ForeignKey(
402 rh.Employe, related_name='+', null=True, blank=True,
403 verbose_name='Employé précédent')
404 statut_titulaire_anterieur = models.ForeignKey(
405 rh.Statut, related_name='+', null=True, blank=True,
406 verbose_name='Statut titulaire précédent')
407 classement_titulaire_anterieur = models.ForeignKey(
408 rh.Classement, related_name='+', null=True, blank=True,
409 verbose_name='Classement titulaire précédent')
410 salaire_titulaire_anterieur = models.DecimalField(
411 max_digits=12, decimal_places=2, default=None, null=True,
412 blank=True, verbose_name='Salaire titulaire précédent')
494ff2be 413
bd28238f
NC
414 # Recrutement
415 remplacement = models.BooleanField()
4d25e2ba 416 statut_residence = models.CharField(max_length=10, default='local',
417 verbose_name="Statut",
bd28238f
NC
418 choices=STATUT_RESIDENCE_CHOICES)
419
420 # Rémunération
494ff2be
NC
421 classement = models.ForeignKey(rh.Classement, related_name='+',
422 verbose_name='Classement proposé')
423 salaire = models.DecimalField(max_digits=12, decimal_places=2,
bd28238f 424 verbose_name='Salaire de base',
494ff2be 425 null=True, default=None)
e8e75458 426 devise = models.ForeignKey(rh.Devise, default=5, related_name='+')
7ad7408e 427 regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
4d25e2ba 428 verbose_name="Régime de travail",
429 help_text="% du temps complet")
139686f2 430 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
4d25e2ba 431 decimal_places=2, verbose_name="Nb. heures par semaine")
bd28238f
NC
432
433 # Contrat
5d680e84 434 type_contrat = models.ForeignKey(rh.TypeContrat, related_name='+')
4d25e2ba 435 contrat_date_debut = models.DateField(help_text="format: aaaa-mm-jj")
1f109689 436 contrat_date_fin = models.DateField(null=True, blank=True,
437 help_text="format: aaaa-mm-jj")
bd28238f
NC
438
439 # Comptes
dfc2c344 440 compte_compta = models.CharField(max_length=10, default='aucun',
441 verbose_name=u'Compte comptabilité',
442 choices=COMPTE_COMPTA_CHOICES)
bd28238f 443 compte_courriel = models.BooleanField()
0140cbd2 444
445 # Méta
446 date_creation = models.DateTimeField(auto_now_add=True)
aec2c91e 447
448 def __unicode__(self):
449 return u'%s - %s' % (self.poste.nom, self.employe)
bd28238f 450
b1baa306
OL
451 def get_salaire_euros(self):
452 try:
453 tx = rh.TauxChange.objects.filter(implantation=self.poste.implantation, devise=self.devise)[0].taux
454 except:
455 tx = 1
456 return (float)(tx) * (float)(self.salaire)
457
57bd966c
OL
458 def get_couts_auf(self):
459 """
460 On retire les MAD BSTG
461 """
462 return [r for r in self.remuneration_set.all() if r.type_id not in (2, )]
a9e52624
OL
463
464 def get_total_couts_auf(self):
465 total = 0.0
466 for r in self.get_couts_auf():
467 total += r.montant_euro()
468 return total
57bd966c
OL
469
470 def get_aides_auf(self):
471 """
472 On récupère les MAD BSTG
473 """
474 return [r for r in self.remuneration_set.all() if r.type_id in (2, )]
475
a9e52624
OL
476 def get_total_aides_auf(self):
477 total = 0.0
478 for r in self.get_aides_auf():
479 total += r.montant_euro()
480 return total
481
482 def get_total_remun(self):
483 return self.get_total_couts_auf() + self.get_total_aides_auf()
484
0140cbd2 485# Tester l'enregistrement car les models.py sont importés au complet
486if not reversion.is_registered(Dossier):
487 reversion.register(Dossier)
bd28238f
NC
488
489class Remuneration(models.Model):
5d680e84 490 # Identification
bd28238f 491 dossier = models.ForeignKey('Dossier', db_column='dossier')
cb1d62b5
NC
492 type = models.ForeignKey(rh.TypeRemuneration, db_column='type',
493 related_name='+')
5d680e84
NC
494 # TODO: what's that?
495 # type_revalorisation = models.ForeignKey('TypeRevalorisation',
496 # db_column='type_revalorisation')
cb1d62b5
NC
497 montant = models.DecimalField(max_digits=12, decimal_places=2,
498 null=True) # Annuel
494ff2be 499 devise = models.ForeignKey(rh.Devise, to_field='code',
5d680e84 500 db_column='devise', related_name='+')
cb1d62b5
NC
501 precision = models.CharField(max_length=255, null=True, blank=True)
502 # date_effective = models.DateField(null=True, blank=True)
503 # pourcentage = models.IntegerField(null=True, blank=True)
bd28238f 504
5d680e84 505 # Méta
bd28238f 506 date_creation = models.DateField(auto_now_add=True)
494ff2be 507 user_creation = models.IntegerField(null=True, blank=True)
cb1d62b5
NC
508 # desactivation = models.BooleanField(default=False, blank=True)
509 # date_desactivation = models.DateField(null=True, blank=True)
510 # user_desactivation = models.IntegerField(null=True, blank=True)
511 # annulation = models.BooleanField(default=False, blank=True)
512 # date_annulation = models.DateField(null=True, blank=True)
513 # user_annulation = models.IntegerField(null=True, blank=True)
bd28238f 514
ee91bc95
NC
515 def montant_mois(self):
516 return round(self.montant / 12, 2)
517
518 def taux_devise(self):
519 return self.devise.tauxchange_set.order_by('-annee').all()[0].taux
520
521 def montant_euro(self):
a9e52624 522 return round(float(self.montant) * float(self.taux_devise()), 2)
ee91bc95
NC
523
524 def montant_euro_mois(self):
525 return round(self.montant_euro() / 12, 2)
bd28238f
NC
526
527
72db8238
OL
528TYPE_JUSTIFICATIONS = (
529 ('N', 'Nouvel employé'),
530 ('R', 'Renouvellement employé'),
531)
bd28238f 532
72db8238
OL
533class JustificationQuestion(models.Model):
534 question = models.CharField(max_length=255)
535 type = models.CharField(max_length=255, choices=TYPE_JUSTIFICATIONS)
536
537 def __unicode__(self,):
538 return self.question
bd28238f 539
72db8238
OL
540class JustificationNouvelEmploye(models.Model):
541 dossier = models.ForeignKey("Dossier")
542 question = models.ForeignKey("JustificationQuestion")
543 reponse = models.TextField()
bd28238f 544
72db8238
OL
545class JustificationAutreEmploye(models.Model):
546 dossier = models.ForeignKey("Dossier")
547 question = models.ForeignKey("JustificationQuestion")
548 reponse = models.TextField()