This repository has been archived on 2023-02-21. You can view files and clone it, but cannot push or open issues or pull requests.
dauphine-logement/appli_project/appli_socle/models.py

477 lines
18 KiB
Python

# vim:spell:spelllang=fr
# -*- encoding: utf-8 -*-
import datetime
from django.db.models import (BooleanField, Model, DecimalField, CharField,
TextField, ManyToManyField, ForeignKey, IntegerField, DateTimeField,
ImageField, PositiveIntegerField, Manager, URLField)
from django.db import models
from django.contrib.auth.models import User, Group
from django.contrib.gis.db.models import PolygonField, PointField, Model as GisModel, GeoManager
from django.contrib.gis.gdal.error import OGRException
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from django import forms
from django.utils.safestring import mark_safe
from django.contrib.gis import gdal
from utils import build_random_username, track_data
from picklefield import PickledObjectField
import permission_hack
if gdal.HAS_GDAL:
# Use the official spherical mercator projection SRID on versions
# of GDAL that support it; otherwise, fallback to 900913.
if gdal.GDAL_VERSION >= (1, 7):
spherical_mercator_srid = 3857
else:
spherical_mercator_srid = 900913
'''
Modèle de données de l'application du logement de l'Université Paris Dauphine
'''
QUADRANTS = (
('NE', _(u'nord-est')),
('NO', _(u'nord-ouest')),
('SE', _(u'sud-est')),
('SO', _(u'sud-ouest')))
quadrants_map = dict(QUADRANTS)
MONNAIES = (
('EUR', u''),)
PAYS = (
('FR', _(u'France')),)
TYPAGE_PRESTATIONS = (
('B', _(u'booléen')),
('N', _(u'valeur numérique entière')))
class ModeleNomme(Model):
def __unicode__(self):
return self.nom
class Meta:
abstract = True
class EmailUnique(object):
def clean(self):
if not self.email:
raise forms.ValidationError(_(u"L'email est obligatoire"))
qs = User.objects.filter(email=self.email)
if self.id:
qs = qs.exclude(id=self.id)
if qs:
raise forms.ValidationError(_(u"L'adresse email %s est déjà utilisé") % self.email)
super(EmailUnique, self).clean()
class NoCollisionWithUser(User):
def save(self, *args, **kwargs):
if not self.username:
self.username = build_random_username(self.username_prefix)
super(NoCollisionWithUser, self).save(*args, **kwargs)
class Meta:
abstract = True
class TypeOffre(ModeleNomme):
'''Type d'offre d'une annonce, concerne principalement la catégorie de
loyer.
'''
nom = CharField(max_length=128,
verbose_name=_(u"Type d'offre"))
class Meta:
verbose_name = _(u"Type d'offre")
verbose_name_plural = _(u"Types d'offre")
class TypeDePrestation(ModeleNomme):
'''Type de prestations associables à une annonce'''
nom = CharField(max_length=128,
verbose_name=_(u"Description de la prestation"),
help_text=_(u"mettre une expression simple en minuscule"))
ordre = IntegerField(verbose_name=_(u"Numéro d'ordre"),
help_text=_(u'les prestations seront triées en fonction '
'de ce champ dans les interfaces'))
type_de_valeur = CharField(max_length=1, default='B',
choices=TYPAGE_PRESTATIONS)
class Meta:
verbose_name = _(u"Type de prestation")
verbose_name_plural = _(u"Types de prestation")
ordering = ('ordre', 'nom')
@track_data('is_active')
class ProfilOffre(EmailUnique, NoCollisionWithUser):
COMMENT = (
('bouche-a-oreille', _(u'Bouche à oreille')),
('encart-publicitaire', _(u'Encart publicitaire')),
('medias', _(u'Médias')),
('reseau-dauphinois', _(u'Alumni / Réseau ')),
('via-etudiant-dauphine', _(u'Via un étudiant de Dauphine')),
('site-internet', _(u'Site internet de Dauphine')),
('flyer', _(u'Flyer publicitaire')),
('autre', _(u'Autre')),
)
'''Modèle de donnée du profil offre'''
acceptation_cgu = BooleanField(blank=True, default=False,
verbose_name=_(u"A accepté les conditions générales "
u"d'utilisation ?"))
acceptation_charte_qualite = BooleanField(blank=True, default=False,
verbose_name=_(u"A accepté la charte qualité"))
sans_validation = BooleanField(blank=True, default=False,
verbose_name=_(u"Ne nécessite pas de validation de "
u"ses annonces par les administrateurs."))
accepte_notif_depublication = BooleanField(blank=True, default=True,
verbose_name=_(u"Email dépublication (opt-out)"))
accepte_notif_alertes = BooleanField(blank=True, default=False,
verbose_name=_(u"Email d'information (opt-in)"))
accepte_invitations = BooleanField(blank=True, default=False,
verbose_name=_(u"Email invitations (opt-in)"))
url_de_contact = URLField(max_length=1024, blank=True,
verbose_name=_(u'URL de contact'),
help_text=_(u'remplace le mail pour le contact du propriétaire'))
comment = CharField(max_length=32,
blank=True,
verbose_name=_(u'Comment avez-vous connu le site ?'),
choices=COMMENT, default='')
address = TextField(verbose_name=_(u'Adresse personnelle'), blank=True)
phone = CharField(max_length=20,
verbose_name=_(u'Téléphone fixe'), blank=True)
mobile = CharField(max_length=20,
verbose_name=_(u'Mobile'), blank=True)
job = CharField(max_length=128,
verbose_name=_(u'Profession'), blank=True)
username_prefix = 'offre'
def save(self, *args, **kwargs):
result = super(ProfilOffre, self).save(*args, **kwargs)
self.groups.add(Group.objects.get_or_create(name=u'Profil offre')[0])
return result
def __unicode__(self):
return '%s:%s' % (self.email, self.id)
def is_profiloffre(self):
return True
class Meta:
verbose_name = _(u'Profil offre')
verbose_name_plural = _(u'Profils offre')
permissions = (
('envoyer_notification', "Peut envoyer des mails de notification"),)
def get_default_TypeOffre():
return TypeOffre.objects.order_by('id')[0]
class Prestation(Model):
type_de_prestation = ForeignKey(TypeDePrestation)
valeur_numerique = IntegerField(blank=True, null=True)
def __unicode__(self):
if self.type_de_prestation.type_de_valeur == 'N':
return '%s %s' % (unicode(self.type_de_prestation),
self.valeur_numerique)
else:
return unicode(self.type_de_prestation)
class Meta:
verbose_name = _(u'Prestation')
verbose_name_plural = _(u'Prestations')
class Zone(ModeleNomme, GisModel):
objects = GeoManager()
nom = CharField(max_length=128,
verbose_name=_(u"Zone"))
polygon = PolygonField(verbose_name=_(u'Polygone de la zone'))
def save(self, *args, **kwargs):
super(Zone, self).save(*args, **kwargs)
for annonce in Annonce.objects.filter(position_geographique__contained=self.polygon):
annonce.zones.add(self)
def expand(self):
return dict(QUADRANTS).get(self.nom, self.nom)
ETATS_ANNONCE_TUPLE = (
('attend_validation', _(u"soumis à la validation")),
('invalide', _(u"invalidé")),
('publiee', _(u'valide et publiée')),
('valide', _(u'valide et dé-publiée')))
ETATS_ANNONCE = dict(ETATS_ANNONCE_TUPLE)
TYPE_LOGEMENT = (
('studio', _(u'Studio')),
('chambre', _(u'Chambre')),
('chambre-habitant', _(u'Chambre chez l\'habitant')),
('colocation', _(u'Colocation')),
('service', _(u'Logement contre service')),
('t1', _('T1')),
('t2', _('T2')),
('t3', _('T3')),
('t4', _('T4')),
('t5', _('T5 et +')),
)
DUREE_LOCATION = (
(1, _(u'moins de 3 mois')),
(2, _(u'de 3 mois a 1 an')),
(3, _(u'plus d\'un an')))
class Annonce(GisModel):
objects = GeoManager()
etat_validation = CharField(max_length=32, choices=ETATS_ANNONCE_TUPLE,
default='attend_validation', db_index=True)
derniere_publication = DateTimeField(blank=True, null=True,
verbose_name=_(u"Date de dernière publication"))
derniere_validation_par = ForeignKey(User, blank=True, null=True,
on_delete=models.SET_NULL)
derniere_validation = DateTimeField(blank=True, null=True,
verbose_name=_(u"Date de dernière validation"))
raison_invalidation = TextField(default='', blank=True, null=True)
type_de_logement = CharField(max_length=16, choices=TYPE_LOGEMENT,
verbose_name=_(u'Type de logement'), default='studio')
date_de_creation = DateTimeField(auto_now_add=True,
verbose_name=_(u"Date de création"))
proprietaire = ForeignKey(ProfilOffre, related_name='annonces',
verbose_name=_(u"Auteur de l'annonce"))
prix_par_mois = DecimalField(max_digits=6, decimal_places=2,
verbose_name=_(u"Prix par mois"), db_index=True)
monnaie = CharField(max_length=3, default='EUR', choices=MONNAIES,
verbose_name=_(u"Monnaie"))
surface_en_m2 = PositiveIntegerField(
verbose_name=_(u"Surface en m²"),
help_text=_(u'ne pas mettre de décimales'))
pays = CharField(max_length=3, default='FR', choices=PAYS,
verbose_name=_(u"Pays"))
ville = CharField(max_length=64, default='Paris',
verbose_name=_(u"Ville"),
help_text=_(u'ex.: Paris 14ème, Nanterre'))
zones = ManyToManyField(Zone, blank=True, db_index=True)
position_geographique = PointField(
verbose_name=_(u"Géolocalisation"),
help_text=_(u'cliquez sur la carte pour placer votre logement;'
u' vous pouvez zoomer avec votre molette ou en cliquant sur +'))
type_d_offre = ForeignKey(TypeOffre, default=get_default_TypeOffre,
verbose_name=_(u"Type d'offre"), db_index=True,
help_text=mark_safe(_('<a target="_blank" href="/solidarite_logement/proprietaires/">En savoir plus sur les types d\'offre...</a>')),
on_delete=models.SET_DEFAULT)
prestations = ManyToManyField(Prestation, blank=True,
verbose_name=_(u'Prestations offertes par ce logement'))
photo = ImageField(upload_to='photos/%Y/%m/%d', blank=True,
verbose_name=_(u'Photo du logement'),
help_text=_(u'formats acceptés: JPG, GIF ou PNG, taille maximum: 1Mo'))
description = TextField(blank=True,
verbose_name=_(u'Description'),
help_text=_(u'Contenu libre'))
nos_logements = BooleanField(default=False,
verbose_name=_(u"Afficher l'annonce en page "
u"d'accueil dans Nos Logements"),
help_text=_(u'Attention, seulement les trois annonces'
u' les plus récentes seront affichées'), db_index=True)
duree_location = IntegerField(verbose_name=_(u'Durée de location'),
blank=True, null=True, choices=DUREE_LOCATION)
def save(self, *args, **kwargs):
super(Annonce, self).save(*args, **kwargs)
self.zones = Zone.objects.filter(polygon__contains=self.position_geographique)
def osm_wkt(self):
try:
return self.position_geographique.transform(spherical_mercator_srid, clone=True).wkt
except OGRException:
return ''
def liste_des_prestations(self):
return ', '.join(map(unicode, self.prestations.all()))
def type(self):
return dict(TYPE_LOGEMENT)[self.type_de_logement]
def publie(self):
self.etat_validation = 'attend_validation'
self.derniere_publication = datetime.datetime.now()
self.save()
if self.proprietaire.sans_validation:
self.valide(self.proprietaire)
def depublie(self):
self.etat_validation = 'valide'
self.save()
def invalide(self, user, raison):
self.derniere_validation = datetime.datetime.now()
self.derniere_validation_par = user
self.raison_invalidation = raison
self.etat_validation = 'invalide'
self.save()
def valide(self, user):
self.derniere_validation = datetime.datetime.now()
self.derniere_validation_par = user
self.raison_invalidation = u''
if self.etat_validation in ('invalide', 'attend_validation', 'publiee'):
self.etat_validation = 'publiee'
else:
self.etat_validation = 'valide'
self.save()
def statut(self):
return ETATS_ANNONCE[self.etat_validation]
def __unicode__(self):
return unicode(self.id)
class Meta:
verbose_name = _(u'Annonce')
verbose_name_plural = _(u'Annonces')
ordering = ('etat_validation', 'derniere_publication')
class RechercheSauvegardee(Model):
'''Recherches sauvegardées par un profil recherche'''
types_de_logement = CharField(max_length=128, blank=True, null=True)
prix_maximum = DecimalField(max_digits=6, decimal_places=2,
verbose_name=_(u'Prix maximum'), blank=True, null=True)
intra_muros = BooleanField(blank=True,
verbose_name=('Paris intra-muros'))
zones = ManyToManyField(Zone, blank=True, null=True)
duree_location = IntegerField(verbose_name=_(u'Durée de location'),
blank=True, null=True, choices=DUREE_LOCATION)
montant_loyer = IntegerField(blank=True, null=True)
class Meta:
verbose_name = _(u'Recherche sauvegardée')
verbose_name_plural = _(u'Recherches sauvegardées')
def filtre_types(self):
if not self.types_de_logement:
return []
return map(unicode.strip, self.types_de_logement.split(','))
def types(self):
types = dict(TYPE_LOGEMENT)
return [types[t] for t
in self.filtre_types() if t in types]
class ProfilRechercheManager(Manager):
def get_query_set(self):
qs = super(ProfilRechercheManager, self).get_query_set()
qs = qs.filter(username__startswith=self.model.username_prefix)
return qs
@track_data('is_active')
class ProfilRecherche(EmailUnique, NoCollisionWithUser):
'''Profil recherche'''
acceptation_cgu = BooleanField(blank=True, default=False,
verbose_name=_(u"A accepté les conditions générales "
"d'utilisation ?"))
acceptation_charte_qualite = BooleanField(blank=True, default=False,
verbose_name=_(u"A accepté la charte qualité"))
type_d_offre = ManyToManyField(TypeOffre, blank=True,
verbose_name=_(u"Types d'offre"))
recherches_sauvegardees = ManyToManyField(RechercheSauvegardee, blank=True,
verbose_name=_(u'Recherches sauvegardées'))
annonces_sauvegardees = ManyToManyField(Annonce, blank=True,
verbose_name=_(u'Annonces sauvegardées'),
related_name='sauvegardee_par')
username_prefix = 'rech'
objects = ProfilRechercheManager()
@classmethod
def new_cas_user(cls, identifiant, email, domain):
profil_recherche = cls.objects.create(email=email)
profil_recherche.set_unusable_password()
profil_recherche.save()
UtilisateurCAS.objects.create(identifiant=identifiant,
user=profil_recherche, domain=domain)
return profil_recherche
def save(self, *args, **kwargs):
super(ProfilRecherche, self).save(*args, **kwargs)
self.groups.add(Group.objects.get_or_create(name=u'Profil recherche')[0])
if not self.type_d_offre.exists():
self.type_d_offre = TypeOffre.objects.filter(id=1)
_peut_demander_bourse_cache = None
def peut_demander_bourse(self):
if self._peut_demander_bourse_cache is None:
self._peut_demander_bourse_cache = self.type_d_offre.filter(id=2).exists()
return self._peut_demander_bourse_cache
def __unicode__(self):
return '%s:%s' % (self.email, self.id)
def is_profilrecherche(self):
return True
class Meta:
verbose_name = _(u'Profil recherche')
verbose_name_plural = _(u'Profils recherche')
class EmailModele(ModeleNomme):
'''Modèle pour un email envoyé aux utilisateurs'''
nom = CharField(max_length=128,
verbose_name=_(u"Nom du modèle d'email"))
sujet = CharField(max_length=256,
verbose_name=_(u'Modèle pour le sujet du mail'))
texte = TextField(verbose_name=_(u'Modèle pour le texte du mail'))
class Meta:
verbose_name = _(u"Modèle d'email")
verbose_name_plural = _(u"Modèles d'email")
def __unicode__(self):
return u"Modèle d'email nommé %s" % self.nom
class UtilisateurCAS(Model):
user = ForeignKey(User, verbose_name=_(u'Utilisateur Django'), unique=True,
related_name='utilisateur_cas')
identifiant = CharField(max_length=128,
verbose_name=_(u'Identifiant CAS ou LDAP'))
domain = CharField(max_length=128,
default=settings.UTILISATEUR_CAS_DOMAINE_PAR_DEFAUT,
verbose_name=_(u"Domaine de l'identifiant"))
def __unicode__(self):
return '%s@%s' % (self.identifiant, self.domain)
class Meta:
verbose_name = _(u'Utilisateur CAS')
verbose_name_plural = _(u'Utilisateurs CAS')
unique_together = (('identifiant', 'domain'),)
class Parametre(Model):
'''Modele pour stocker les parametres de l'applications. Les parametres
sont decrits dans settings.py.
'''
nom = CharField(max_length=64)
valeur = PickledObjectField()
valeur_email = ForeignKey(EmailModele, null=True, blank=True, on_delete=models.PROTECT)
def __unicode__(self):
if self.valeur:
return u'%s %s' % (self.nom, unicode(self.valeur))
else:
return u'%s %s' % (self.nom, unicode(self.valeur_email))
class Administrateur(User):
class Meta:
proxy = True
app_label = 'appli_socle'