diff --git a/.gitignore b/.gitignore index 35849b3..d73eec0 100644 --- a/.gitignore +++ b/.gitignore @@ -105,4 +105,6 @@ test.py # Migrations monstage/migrations/ *.venv/ -*~ \ No newline at end of file +*~ +\#* +.#* \ No newline at end of file diff --git a/avisstage/.#models.py b/avisstage/.#models.py deleted file mode 120000 index 9dc600e..0000000 --- a/avisstage/.#models.py +++ /dev/null @@ -1 +0,0 @@ -evarin@Igloo.6706:1491250550 \ No newline at end of file diff --git a/avisstage/models.py b/avisstage/models.py index bd4b2ab..b930edb 100644 --- a/avisstage/models.py +++ b/avisstage/models.py @@ -1,5 +1,205 @@ +# coding: utf-8 + from __future__ import unicode_literals from django.db import models +from django.contrib.auth.models import User +from django.db.models.signals import post_save +from django.template.defaultfilters import slugify +from django.contrib.gis.db import models as geomodels -# Create your models here. +from taggit.managers import TaggableManager + +from djrichtextfield.models import RichTextField +from .utils import choices_length +from .statics import DEPARTEMENTS_DEFAUT, PAYS_OPTIONS, TYPE_LIEU_OPTIONS, TYPE_STAGE_OPTIONS + +import ldap + +# +# Profil Normalien (extension du modèle User) +# + +class Normalien(models.Model): + user = models.OneToOneField(User, related_name="profil") + + # Infos spécifiques + nom = models.CharField(u"Nom complet", max_length=255, blank=True) + promotion = models.CharField(u"Promotion", max_length=40, blank=True) + mail = models.CharField(u"Adresse e-mail permanente", + max_length=200, blank=True) + + class Meta: + verbose_name = u"Profil élève" + verbose_name_plural = u"Profils élèves" + + def __unicode__(self): + return unicode("%s (%s)", self.nom, self.user.username) + +# Hook à la création d'un nouvel utilisateur : récupération de ses infos par LDAP +def create_user_profile(sender, instance, created, **kwargs): + if created: + profil = Normalien.objects.get_or_create(user=instance) + try: + ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) + l = ldap.initialize("ldaps://ldap.spi.ens.fr:636") + l.set_option(ldap.OPT_REFERRALS, 0) + l.set_option(ldap.OPT_PROTOCOL_VERSION, 3) + l.set_option(ldap.OPT_X_TLS,ldap.OPT_X_TLS_DEMAND) + l.set_option( ldap.OPT_X_TLS_DEMAND, True ) + l.set_option( ldap.OPT_DEBUG_LEVEL, 255 ) + info = l.search_s('dc=spi,dc=ens,dc=fr', + ldap.SCOPE_SUBTREE, + '(uid=%s)' % (instance.username,), + ['cn','mailRoutingAddress', 'homeDirectory']) + if len(info) > 0: + infos = info[0][1] + profil.nom = infos.get('cn', [''])[0] + if 'homeDirectory' in infos: + dirs = infos['homeDirectory'][0].split('/') + if dirs[1] == 'users': + annee = dirs[2] + dep = dirs[4] + dep = dict(DEPARTEMENTS_DEFAUT).get('dep') + profil.promotion = u'%s %s' % (dep, annee) + profil.mail = infos.get('mailRoutingAddress', + ['%s@clipper.ens.fr'%instance.username]) + profil.save() + except ldap.LDAPError: + pass +post_save.connect(create_user_profile, sender=User) + +# +# Lieu de stage +# + +class Lieu(models.Model): + # Général + nom = models.CharField(u"Nom de l'institution d'accueil", + max_length=250) + type_lieu = models.CharField(u"Type de structure d'accueil", + default="universite", + choices=TYPE_LIEU_OPTIONS, + max_length=choices_length(TYPE_LIEU_OPTIONS)) + + # Infos géographiques + ville = models.CharField(u"Ville", + max_length=200) + pays = models.CharField(u"Pays", + choices=PAYS_OPTIONS, + max_length=choices_length(PAYS_OPTIONS)) + + # Coordonnées + objects = geomodels.GeoManager() # Requis par GeoDjango + coord = geomodels.PointField(u"Coordonnées", + geography=True) + + + def __unicode__(self): + return u"%s (%s)" % (self.name, self.ville) + + class Meta: + verbose_name = "Lieu" + verbose_name_plural = "Lieux" + +# +# Matières des stages +# + +class StageMatiere(models.Model): + nom = models.CharField(u"Nom", max_length=30) + slug = models.SlugField() + + class Meta: + verbose_name = "Matière des stages" + verbose_name_plural = "Matières des stages" + + def __unicode__(self): + return self.name + + def save(self, *args, **kwargs): + if not self.id: + self.slug = slugify(self.nom) + super(StageMatiere, self).save(*args, **kwargs) + +# +# Un stage +# + +class Stage(models.Model): + # Misc + auteur = models.ForeignKey(Normalien, related_name="stages") + public = models.BooleanField(u"Visible publiquement", default=False) + + # Caractéristiques du stage + sujet = models.CharField(u"Sujet", max_length=500) + + date_debut = models.DateField(u"Date de début", null=True) + date_fin = models.DateField(u"Date de fin", null=True) + + type_stage = models.CharField(u"Type", + default="stage", + choices=TYPE_STAGE_OPTIONS, + max_length=choices_length(TYPE_STAGE_OPTIONS)) + + thematiques = TaggableManager(u"Thématiques", blank=True) + matieres = models.ManyToManyField(StageMatiere, related_name="stages") + encadrants = models.CharField(u"Encadrants", max_length=500, blank=True) + + # Avis + lieux = models.ManyToManyField(Lieu, related_name="stages", + through="AvisLieu") + avis_stage = models.OneToOneField("AvisStage", related_name="stage") + + @property + def avis_lieux(self): + return self.avislieu_set.order_by('order') + + @property + def lieu_principal(self): + return self.avis_lieux[0].lieu + + def __unicode__(self): + return u"%s (par %s)" % (self.sujet, self.auteur.user_id) + + class Meta: + verbose_name = "Stage" + +# +# Les avis +# + +class AvisStage(models.Model): + stage = models.OneToOneField(Stage, related_name="avis_stage") + + chapo = models.TextField(u"En quelques mots") + avis_personnes = RichTextField(u"Les encadrants et l'équipe") + avis_sujet = RichTextField(u"Le sujet de stage") + avis_admin = RichTextField(u"Formalités et administration") + + les_plus = models.TextField(u"Les plus du stage") + les_moins = models.TextField(u"Les moins du stage") + + def __unicode__(self): + return u"Avis sur {%s} par %" % (stage.sujet, stage.auteur.user_id) + +class AvisLieu(models.Model): + stage = models.ForeignKey(Stage) + lieu = models.ForeignKey(Lieu) + order = models.IntegerField("Ordre", default=0) + + chapo = models.TextField(u"En quelques mots") + avis_lieustage = RichTextField(u"Le lieu du stage") + avis_pratique = RichTextField(u"S'installer - conseils pratiques") + avis_tourisme = RichTextField(u"Dans les parages") + + les_plus = models.TextField(u"Les plus du lieu") + les_moins = models.TextField(u"Les moins du lieu") + + class Meta: + verbose_name = "Avis sur un lieu de stage" + verbose_name_plural = "Avis sur un lieu de stage" + + def __unicode__(self): + return u"Avis sur {%s} par %" % (lieu.nom, stage.auteur.user_id) + diff --git a/avisstage/statics.py b/avisstage/statics.py new file mode 100644 index 0000000..356b39e --- /dev/null +++ b/avisstage/statics.py @@ -0,0 +1,270 @@ +# coding: utf-8 + +DEPARTEMENTS_DEFAUT = ( + ('phy', 'Physique'), + ('maths', 'Maths'), + ('bio', 'Biologie'), + ('chimie', 'Chimie'), + ('geol', u'Géosciences'), + ('dec', 'DEC'), + ('info', 'Informatique'), + ('litt', u'Littéraire'), + ('guests', u'Pensionnaires étrangers'), + ('pei', u'PEI'), +) + +TYPE_STAGE_OPTIONS = ( + ('stage', u"Stage"), +) + +TYPE_LIEU_OPTIONS = ( + ('universite', u"Université"), + ('entreprise', u"Entreprise"), + ('centrerecherche', u"Centre de recherche"), + ('administration', u"Administration"), + ('autre', u"Autre"), +) + +PAYS_OPTIONS = ( + ("AF", u"Afghanistan"), + ("AL", u"Albanie"), + ("AQ", u"Antarctique"), + ("DZ", u"Algérie"), + ("AS", u"Samoa Américaines"), + ("AD", u"Andorre"), + ("AO", u"Angola"), + ("AG", u"Antigua-et-Barbuda"), + ("AZ", u"Azerbaïdjan"), + ("AR", u"Argentine"), + ("AU", u"Australie"), + ("AT", u"Autriche"), + ("BS", u"Bahamas"), + ("BH", u"Bahreïn"), + ("BD", u"Bangladesh"), + ("AM", u"Arménie"), + ("BB", u"Barbade"), + ("BE", u"Belgique"), + ("BM", u"Bermudes"), + ("BT", u"Bhoutan"), + ("BO", u"Bolivie"), + ("BA", u"Bosnie-Herzégovine"), + ("BW", u"Botswana"), + ("BV", u"Île Bouvet"), + ("BR", u"Brésil"), + ("BZ", u"Belize"), + ("IO", u"Territoire Britannique de l'Océan Indien"), + ("SB", u"Îles Salomon"), + ("VG", u"Îles Vierges Britanniques"), + ("BN", u"Brunéi Darussalam"), + ("BG", u"Bulgarie"), + ("MM", u"Myanmar"), + ("BI", u"Burundi"), + ("BY", u"Bélarus"), + ("KH", u"Cambodge"), + ("CM", u"Cameroun"), + ("CA", u"Canada"), + ("CV", u"Cap-vert"), + ("KY", u"Îles Caïmanes"), + ("CF", u"République Centrafricaine"), + ("LK", u"Sri Lanka"), + ("TD", u"Tchad"), + ("CL", u"Chili"), + ("CN", u"Chine"), + ("TW", u"Taïwan"), + ("CX", u"Île Christmas"), + ("CC", u"Îles Cocos (Keeling)"), + ("CO", u"Colombie"), + ("KM", u"Comores"), + ("YT", u"Mayotte"), + ("CG", u"République du Congo"), + ("CD", u"République Démocratique du Congo"), + ("CK", u"Îles Cook"), + ("CR", u"Costa Rica"), + ("HR", u"Croatie"), + ("CU", u"Cuba"), + ("CY", u"Chypre"), + ("CZ", u"République Tchèque"), + ("BJ", u"Bénin"), + ("DK", u"Danemark"), + ("DM", u"Dominique"), + ("DO", u"République Dominicaine"), + ("EC", u"Équateur"), + ("SV", u"El Salvador"), + ("GQ", u"Guinée Équatoriale"), + ("ET", u"Éthiopie"), + ("ER", u"Érythrée"), + ("EE", u"Estonie"), + ("FO", u"Îles Féroé"), + ("FK", u"Îles (malvinas) Falkland"), + ("GS", u"Géorgie du Sud et les Îles Sandwich du Sud"), + ("FJ", u"Fidji"), + ("FI", u"Finlande"), + ("AX", u"Îles Åland"), + ("FR", u"France"), + ("GF", u"Guyane Française"), + ("PF", u"Polynésie Française"), + ("TF", u"Terres Australes Françaises"), + ("DJ", u"Djibouti"), + ("GA", u"Gabon"), + ("GE", u"Géorgie"), + ("GM", u"Gambie"), + ("PS", u"Territoire Palestinien Occupé"), + ("DE", u"Allemagne"), + ("GH", u"Ghana"), + ("GI", u"Gibraltar"), + ("KI", u"Kiribati"), + ("GR", u"Grèce"), + ("GL", u"Groenland"), + ("GD", u"Grenade"), + ("GP", u"Guadeloupe"), + ("GU", u"Guam"), + ("GT", u"Guatemala"), + ("GN", u"Guinée"), + ("GY", u"Guyana"), + ("HT", u"Haïti"), + ("HM", u"Îles Heard et Mcdonald"), + ("VA", u"Saint-Siège (état de la Cité du Vatican)"), + ("HN", u"Honduras"), + ("HK", u"Hong-Kong"), + ("HU", u"Hongrie"), + ("IS", u"Islande"), + ("IN", u"Inde"), + ("ID", u"Indonésie"), + ("IR", u"République Islamique d'Iran"), + ("IQ", u"Iraq"), + ("IE", u"Irlande"), + ("IL", u"Israël"), + ("IT", u"Italie"), + ("CI", u"Côte d'Ivoire"), + ("JM", u"Jamaïque"), + ("JP", u"Japon"), + ("KZ", u"Kazakhstan"), + ("JO", u"Jordanie"), + ("KE", u"Kenya"), + ("KP", u"République Populaire Démocratique de Corée"), + ("KR", u"République de Corée"), + ("KW", u"Koweït"), + ("KG", u"Kirghizistan"), + ("LA", u"République Démocratique Populaire Lao"), + ("LB", u"Liban"), + ("LS", u"Lesotho"), + ("LV", u"Lettonie"), + ("LR", u"Libéria"), + ("LY", u"Jamahiriya Arabe Libyenne"), + ("LI", u"Liechtenstein"), + ("LT", u"Lituanie"), + ("LU", u"Luxembourg"), + ("MO", u"Macao"), + ("MG", u"Madagascar"), + ("MW", u"Malawi"), + ("MY", u"Malaisie"), + ("MV", u"Maldives"), + ("ML", u"Mali"), + ("MT", u"Malte"), + ("MQ", u"Martinique"), + ("MR", u"Mauritanie"), + ("MU", u"Maurice"), + ("MX", u"Mexique"), + ("MC", u"Monaco"), + ("MN", u"Mongolie"), + ("MD", u"République de Moldova"), + ("MS", u"Montserrat"), + ("MA", u"Maroc"), + ("MZ", u"Mozambique"), + ("OM", u"Oman"), + ("NA", u"Namibie"), + ("NR", u"Nauru"), + ("NP", u"Népal"), + ("NL", u"Pays-Bas"), + ("AN", u"Antilles Néerlandaises"), + ("AW", u"Aruba"), + ("NC", u"Nouvelle-Calédonie"), + ("VU", u"Vanuatu"), + ("NZ", u"Nouvelle-Zélande"), + ("NI", u"Nicaragua"), + ("NE", u"Niger"), + ("NG", u"Nigéria"), + ("NU", u"Niué"), + ("NF", u"Île Norfolk"), + ("NO", u"Norvège"), + ("MP", u"Îles Mariannes du Nord"), + ("UM", u"Îles Mineures Éloignées des États-Unis"), + ("FM", u"États Fédérés de Micronésie"), + ("MH", u"Îles Marshall"), + ("PW", u"Palaos"), + ("PK", u"Pakistan"), + ("PA", u"Panama"), + ("PG", u"Papouasie-Nouvelle-Guinée"), + ("PY", u"Paraguay"), + ("PE", u"Pérou"), + ("PH", u"Philippines"), + ("PN", u"Pitcairn"), + ("PL", u"Pologne"), + ("PT", u"Portugal"), + ("GW", u"Guinée-Bissau"), + ("TL", u"Timor-Leste"), + ("PR", u"Porto Rico"), + ("QA", u"Qatar"), + ("RE", u"Réunion"), + ("RO", u"Roumanie"), + ("RU", u"Fédération de Russie"), + ("RW", u"Rwanda"), + ("SH", u"Sainte-Hélène"), + ("KN", u"Saint-Kitts-et-Nevis"), + ("AI", u"Anguilla"), + ("LC", u"Sainte-Lucie"), + ("PM", u"Saint-Pierre-et-Miquelon"), + ("VC", u"Saint-Vincent-et-les Grenadines"), + ("SM", u"Saint-Marin"), + ("ST", u"Sao Tomé-et-Principe"), + ("SA", u"Arabie Saoudite"), + ("SN", u"Sénégal"), + ("SC", u"Seychelles"), + ("SL", u"Sierra Leone"), + ("SG", u"Singapour"), + ("SK", u"Slovaquie"), + ("VN", u"Viet Nam"), + ("SI", u"Slovénie"), + ("SO", u"Somalie"), + ("ZA", u"Afrique du Sud"), + ("ZW", u"Zimbabwe"), + ("ES", u"Espagne"), + ("EH", u"Sahara Occidental"), + ("SD", u"Soudan"), + ("SR", u"Suriname"), + ("SJ", u"Svalbard etÎle Jan Mayen"), + ("SZ", u"Swaziland"), + ("SE", u"Suède"), + ("CH", u"Suisse"), + ("SY", u"République Arabe Syrienne"), + ("TJ", u"Tadjikistan"), + ("TH", u"Thaïlande"), + ("TG", u"Togo"), + ("TK", u"Tokelau"), + ("TO", u"Tonga"), + ("TT", u"Trinité-et-Tobago"), + ("AE", u"Émirats Arabes Unis"), + ("TN", u"Tunisie"), + ("TR", u"Turquie"), + ("TM", u"Turkménistan"), + ("TC", u"Îles Turks et Caïques"), + ("TV", u"Tuvalu"), + ("UG", u"Ouganda"), + ("UA", u"Ukraine"), + ("MK", u"L'ex-République Yougoslave de Macédoine"), + ("EG", u"Égypte"), + ("GB", u"Royaume-Uni"), + ("IM", u"Île de Man"), + ("TZ", u"République-Unie de Tanzanie"), + ("US", u"États-Unis"), + ("VI", u"Îles Vierges des États-Unis"), + ("BF", u"Burkina Faso"), + ("UY", u"Uruguay"), + ("UZ", u"Ouzbékistan"), + ("VE", u"Venezuela"), + ("WF", u"Wallis et Futuna"), + ("WS", u"Samoa"), + ("YE", u"Yémen"), + ("CS", u"Serbie-et-Monténégro"), + ("ZM", u"Zambie"), +) diff --git a/avisstage/utils.py b/avisstage/utils.py new file mode 100644 index 0000000..4f08e52 --- /dev/null +++ b/avisstage/utils.py @@ -0,0 +1,4 @@ +# coding: utf-8 + +def choices_length (choices): + return reduce (lambda m, choice: max (m, len (choice[0])), choices, 0) diff --git a/experiENS/settings_base.py b/experiENS/settings_base.py index aaaf816..40e2293 100644 --- a/experiENS/settings_base.py +++ b/experiENS/settings_base.py @@ -27,8 +27,9 @@ INSTALLED_APPS = ( 'django.contrib.messages', 'django.contrib.staticfiles', 'django.contrib.gis', - - 'django-taggit', + + 'djrichtextfield', + 'taggit', 'avisstage' ) @@ -53,9 +54,9 @@ WSGI_APPLICATION = 'experiENS.wsgi.application' # Internationalization # https://docs.djangoproject.com/en/1.7/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = 'fr-fr' -TIME_ZONE = 'UTC' +TIME_ZONE = 'Europe/Paris' USE_I18N = True @@ -68,3 +69,14 @@ USE_TZ = True # https://docs.djangoproject.com/en/1.7/howto/static-files/ STATIC_URL = '/static/' + +DJRICHTEXTFIELD_CONFIG = { + 'js': ['//tinymce.cachefly.net/4.1/tinymce.min.js'], + 'init_template': 'djrichtextfield/init/tinymce.js', + 'settings': { + 'menubar': False, + 'plugins': 'link image', + 'toolbar': 'bold italic | link image | removeformat', + 'width': 700 + } +} diff --git a/experiENS/urls.py b/experiENS/urls.py index 078fad9..1e61a2a 100644 --- a/experiENS/urls.py +++ b/experiENS/urls.py @@ -1,10 +1,12 @@ from django.conf.urls import patterns, include, url from django.contrib import admin -urlpatterns = patterns('', +urlpatterns = patterns( + '', # Examples: # url(r'^$', 'experiENS.views.home', name='home'), # url(r'^blog/', include('blog.urls')), - + + url(r'^djrichtextfield/', include('djrichtextfield.urls')) url(r'^admin/', include(admin.site.urls)), ) diff --git a/requirements.txt b/requirements.txt index 7c8b216..10aa196 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ django django-cas-ng django-taggit python-ldap +django-richtextfield