477 lines
18 KiB
Python
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'
|
|
|