diff --git a/avisstage/templatetags/avisstage_tags.py b/avisstage/templatetags/avisstage_tags.py
index 7f9d03c..6da134b 100644
--- a/avisstage/templatetags/avisstage_tags.py
+++ b/avisstage/templatetags/avisstage_tags.py
@@ -1,28 +1,32 @@
-# coding: utf-8
+import re
+
from django import template
-from avisstage.forms import LieuForm, FeedbackForm
-import re
+from avisstage.forms import FeedbackForm, LieuForm
register = template.Library()
-@register.inclusion_tag('avisstage/templatetags/widget_lieu.html')
+
+@register.inclusion_tag("avisstage/templatetags/widget_lieu.html")
def lieu_widget():
form = LieuForm()
return {"form": form}
-@register.inclusion_tag('avisstage/templatetags/widget_feedback.html')
+
+@register.inclusion_tag("avisstage/templatetags/widget_feedback.html")
def feedback_widget():
form = FeedbackForm()
return {"form": form}
+
@register.filter
def typonazisme(value):
- value = re.sub(r'(\w)\s*([?!:])', u'\\1 \\2', value)
- value = re.sub(r'(\w)\s*([,.])', u'\\1\\2', value)
- value = re.sub(r'([?!:,.])(\w)', u'\\1 \\2', value)
+ value = re.sub(r"(\w)\s*([?!:])", "\\1 \\2", value)
+ value = re.sub(r"(\w)\s*([,.])", "\\1\\2", value)
+ value = re.sub(r"([?!:,.])(\w)", "\\1 \\2", value)
return value
+
@register.filter
def avis_len(value):
if value < 5:
@@ -32,6 +36,7 @@ def avis_len(value):
else:
return "long"
+
@register.simple_tag
def url_replace(request, field, value):
dict_ = request.GET.copy()
diff --git a/avisstage/tests.py b/avisstage/tests.py
index ed8721a..692dbe7 100644
--- a/avisstage/tests.py
+++ b/avisstage/tests.py
@@ -1,307 +1,343 @@
-from allauth.socialaccount.models import SocialAccount
-from allauth_cas.test.testcases import CASTestCase
-from allauth_ens.adapter import deprecate_clippers
+from datetime import date, timedelta
+from unittest import mock
-from datetime import date
+from authens.models import CASAccount, OldCASAccount
+from authens.tests.cas_utils import FakeCASClient
+from django.conf import settings
from django.test import TestCase
from django.urls import reverse
-from django.conf import settings
+from django.utils import timezone
-from .models import User, Normalien, Lieu, Stage, StageMatiere, AvisLieu
+from .models import AvisLieu, Lieu, Normalien, Stage, StageMatiere, User
-class ExperiENSTestCase(CASTestCase):
-
+
+class ExperiENSTestCase(TestCase):
# Dummy database
-
+
def setUp(self):
- self.u_conscrit = User.objects.create_user('conscrit',
- 'conscrit@ens.fr',
- 'conscrit')
+ self.u_conscrit = User.objects.create_user(
+ "conscrit", "conscrit@ens.fr", "conscrit"
+ )
self.p_conscrit = self.u_conscrit.profil
- self.p_conscrit.nom="Petit conscrit"
- self.p_conscrit.promotion="Serpentard 2000"
- self.p_conscrit.bio="Je suis un petit conscrit"
+ self.p_conscrit.nom = "Petit conscrit"
+ self.p_conscrit.promotion = "Serpentard 2020"
+ self.p_conscrit.bio = "Je suis un petit conscrit"
self.p_conscrit.save()
- self.sa_conscrit = SocialAccount(user=self.u_conscrit,
- provider="clipper",
- uid="conscrit")
+ self.sa_conscrit = CASAccount(
+ user=self.u_conscrit,
+ cas_login="conscrit",
+ entrance_year=2020,
+ )
self.sa_conscrit.save()
-
- self.u_archi = User.objects.create_user('archicube',
- 'archicube@ens.fr',
- 'archicube')
- self.p_archi = self.u_archi.profil
- self.p_archi.nom="Vieil archicube"
- self.p_archi.promotion="Gryffondor 1994"
- self.p_archi.bio="Je suis un vieil archicube"
- self.lieu1 = Lieu(nom="Beaux-Bâtons", type_lieu="universite",
- ville="Brocéliande", pays="FR",
- coord="POINT(-1.63971 48.116382)")
+ self.u_archi = User.objects.create_user(
+ "archicube", "archicube@ens.fr", "archicube"
+ )
+ self.p_archi = self.u_archi.profil
+ self.p_archi.nom = "Vieil archicube"
+ self.p_archi.promotion = "Gryffondor 2014"
+ self.p_archi.bio = "Je suis un vieil archicube"
+
+ self.lieu1 = Lieu(
+ nom="Beaux-Bâtons",
+ type_lieu="universite",
+ ville="Brocéliande",
+ pays="FR",
+ coord="POINT(-1.63971 48.116382)",
+ )
self.lieu1.save()
- self.lieu2 = Lieu(nom="Durmstrang", type_lieu="universite",
- ville="Edimbourg", pays="GB",
- coord="POINT(56.32153 -1.259715)")
+ self.lieu2 = Lieu(
+ nom="Durmstrang",
+ type_lieu="universite",
+ ville="Edimbourg",
+ pays="GB",
+ coord="POINT(56.32153 -1.259715)",
+ )
self.lieu2.save()
self.matiere1 = StageMatiere(nom="Arithmancie", slug="arithmancie")
self.matiere1.save()
self.matiere2 = StageMatiere(nom="Sortilège", slug="sortilege")
self.matiere2.save()
-
- self.cstage1 = Stage(auteur=self.p_conscrit, sujet="Wingardium Leviosa",
- date_debut=date(2000, 5, 10),
- date_fin=date(2000, 8, 26),
- type_stage="recherche",
- niveau_scol="M1", public=True)
+
+ self.cstage1 = Stage(
+ auteur=self.p_conscrit,
+ sujet="Wingardium Leviosa",
+ date_debut=date(2020, 5, 10),
+ date_fin=date(2020, 8, 26),
+ type_stage="recherche",
+ niveau_scol="M1",
+ public=True,
+ )
self.cstage1.save()
self.cstage1.matieres.add(self.matiere1)
- alieu1 = AvisLieu(stage=self.cstage1, lieu=self.lieu1,
- chapo="Trop bien")
+ alieu1 = AvisLieu(stage=self.cstage1, lieu=self.lieu1, chapo="Trop bien")
alieu1.save()
- self.cstage2 = Stage(auteur=self.p_conscrit, sujet="Avada Kedavra",
- date_debut=date(2001, 5, 10),
- date_fin=date(2001, 8, 26),
- type_stage="sejour_dri",
- niveau_scol="M2", public=False)
+ self.cstage2 = Stage(
+ auteur=self.p_conscrit,
+ sujet="Avada Kedavra",
+ date_debut=date(2021, 5, 10),
+ date_fin=date(2021, 8, 26),
+ type_stage="sejour_dri",
+ niveau_scol="M2",
+ public=False,
+ )
self.cstage2.save()
self.cstage2.matieres.add(self.matiere2)
- alieu2 = AvisLieu(stage=self.cstage2, lieu=self.lieu2,
- chapo="Trop nul")
+ alieu2 = AvisLieu(stage=self.cstage2, lieu=self.lieu2, chapo="Trop nul")
alieu2.save()
-
- self.astage1 = Stage(auteur=self.p_archi, sujet="Alohomora",
- date_debut=date(1994, 5, 10),
- date_fin=date(1994, 8, 26),
- type_stage="recherche",
- niveau_scol="M2", public=True)
+ self.astage1 = Stage(
+ auteur=self.p_archi,
+ sujet="Alohomora",
+ date_debut=date(2014, 5, 10),
+ date_fin=date(2014, 8, 26),
+ type_stage="recherche",
+ niveau_scol="M2",
+ public=True,
+ )
self.astage1.save()
self.astage1.matieres.add(self.matiere2)
- alieu3 = AvisLieu(stage=self.astage1, lieu=self.lieu1,
- chapo="Trop moyen")
+ alieu3 = AvisLieu(stage=self.astage1, lieu=self.lieu1, chapo="Trop moyen")
alieu3.save()
def assertRedirectToLogin(self, testurl):
r = self.client.get(testurl)
- return self.assertRedirects(r, settings.LOGIN_URL+"?next="+testurl)
+ return self.assertRedirects(r, settings.LOGIN_URL + "?next=" + testurl)
def assertPageNotFound(self, testurl):
r = self.client.get(testurl)
self.assertEqual(r.status_code, 404)
-
-
-
-
-
"""
-ACCÈS PUBLICS
+ACCÈS PUBLIC
"""
+
+
class PublicViewsTest(ExperiENSTestCase):
"""
Vérifie que les fiches de stages ne sont pas visibles hors connexion
"""
- def test_stage_visibility_public(self):
- self.assertRedirectToLogin(reverse('avisstage:stage',
- kwargs={'pk':self.cstage1.id}))
-
- self.assertRedirectToLogin(reverse('avisstage:stage',
- kwargs={'pk':self.cstage2.id}))
-
- self.assertRedirectToLogin(reverse('avisstage:stage',
- kwargs={'pk':self.astage1.id}))
+ def test_stage_visibility_public(self):
+ self.assertRedirectToLogin(
+ reverse("avisstage:stage", kwargs={"pk": self.cstage1.id})
+ )
+
+ self.assertRedirectToLogin(
+ reverse("avisstage:stage", kwargs={"pk": self.cstage2.id})
+ )
+
+ self.assertRedirectToLogin(
+ reverse("avisstage:stage", kwargs={"pk": self.astage1.id})
+ )
"""
Vérifie que les profils de normaliens ne sont pas visibles hors connexion
"""
+
def test_profil_visibility_public(self):
- self.assertRedirectToLogin(reverse(
- 'avisstage:profil', kwargs={'username': self.u_conscrit.username}))
+ self.assertRedirectToLogin(
+ reverse("avisstage:profil", kwargs={"username": self.u_conscrit.username})
+ )
- self.assertRedirectToLogin(reverse(
- 'avisstage:profil', kwargs={'username': self.u_archi.username}))
+ self.assertRedirectToLogin(
+ reverse("avisstage:profil", kwargs={"username": self.u_archi.username})
+ )
-
"""
Vérifie que la recherche n'est pas accessible hors connexion
"""
+
def test_pages_visibility_public(self):
- self.assertRedirectToLogin(reverse('avisstage:recherche'))
+ self.assertRedirectToLogin(reverse("avisstage:recherche"))
- self.assertRedirectToLogin(reverse('avisstage:recherche_resultats'))
+ self.assertRedirectToLogin(reverse("avisstage:recherche_resultats"))
- self.assertRedirectToLogin(reverse('avisstage:stage_items'))
+ self.assertRedirectToLogin(reverse("avisstage:stage_items"))
- self.assertRedirectToLogin(reverse('avisstage:feedback'))
+ self.assertRedirectToLogin(reverse("avisstage:feedback"))
- self.assertRedirectToLogin(reverse('avisstage:moderation'))
+ self.assertRedirectToLogin(reverse("avisstage:moderation"))
"""
Vérifie que l'API n'est pas accessible hors connexion
"""
+
def test_api_visibility_public(self):
- testurl = reverse('avisstage:api_dispatch_list',
- kwargs={"resource_name": "lieu",
- "api_name": "v1"})
- r = self.client.get(testurl)
- self.assertEqual(r.status_code, 401)
-
- testurl = reverse('avisstage:api_dispatch_list',
- kwargs={"resource_name": "stage",
- "api_name": "v1"})
- r = self.client.get(testurl)
- self.assertEqual(r.status_code, 401)
-
- testurl = reverse('avisstage:api_dispatch_list',
- kwargs={"resource_name": "profil",
- "api_name": "v1"})
+ testurl = reverse(
+ "avisstage:api_dispatch_list",
+ kwargs={"resource_name": "lieu", "api_name": "v1"},
+ )
+ r = self.client.get(testurl)
+ self.assertEqual(r.status_code, 401)
+
+ testurl = reverse(
+ "avisstage:api_dispatch_list",
+ kwargs={"resource_name": "stage", "api_name": "v1"},
+ )
+ r = self.client.get(testurl)
+ self.assertEqual(r.status_code, 401)
+
+ testurl = reverse(
+ "avisstage:api_dispatch_list",
+ kwargs={"resource_name": "profil", "api_name": "v1"},
+ )
r = self.client.get(testurl)
self.assertEqual(r.status_code, 401)
-
"""
Vérifie que les pages d'édition ne sont pas accessible hors connexion
"""
+
def test_edit_visibility_public(self):
- self.assertRedirectToLogin(reverse(
- 'avisstage:stage_edit', kwargs={'pk':self.cstage1.id}))
-
- self.assertRedirectToLogin(reverse(
- 'avisstage:stage_edit', kwargs={'pk':self.astage1.id}))
+ self.assertRedirectToLogin(
+ reverse("avisstage:stage_edit", kwargs={"pk": self.cstage1.id})
+ )
- self.assertRedirectToLogin(reverse(
- 'avisstage:stage_publication', kwargs={'pk':self.cstage1.id}))
-
- self.assertRedirectToLogin(reverse(
- 'avisstage:stage_publication', kwargs={'pk':self.astage1.id}))
-
- self.assertRedirectToLogin(reverse('avisstage:stage_ajout'))
-
- self.assertRedirectToLogin(reverse('avisstage:profil_edit'))
+ self.assertRedirectToLogin(
+ reverse("avisstage:stage_edit", kwargs={"pk": self.astage1.id})
+ )
+ self.assertRedirectToLogin(
+ reverse("avisstage:stage_publication", kwargs={"pk": self.cstage1.id})
+ )
+ self.assertRedirectToLogin(
+ reverse("avisstage:stage_publication", kwargs={"pk": self.astage1.id})
+ )
+ self.assertRedirectToLogin(reverse("avisstage:stage_ajout"))
+ self.assertRedirectToLogin(reverse("avisstage:profil_edit"))
"""
ACCÈS ARCHICUBE
"""
+
+
class ArchicubeViewsTest(ExperiENSTestCase):
def setUp(self):
super().setUp()
- self.client.login(username='archicube', password='archicube')
+ # Connexion with password
+ self.client.login(username="archicube", password="archicube")
def assert403Archicubes(self, testurl):
r = self.client.get(testurl)
- return self.assertRedirects(r, reverse('avisstage:403-archicubes'))
+ return self.assertRedirects(r, reverse("avisstage:403-archicubes"))
"""
Vérifie que les seules fiches de stages visibles sont les siennes
"""
+
def test_stage_visibility_archi(self):
- self.assertPageNotFound(reverse('avisstage:stage',
- kwargs={'pk':self.cstage1.id}))
-
- self.assertPageNotFound(reverse('avisstage:stage',
- kwargs={'pk':self.cstage2.id}))
-
- testurl = reverse('avisstage:stage',
- kwargs={'pk':self.astage1.id})
+ self.assertPageNotFound(
+ reverse("avisstage:stage", kwargs={"pk": self.cstage1.id})
+ )
+
+ self.assertPageNotFound(
+ reverse("avisstage:stage", kwargs={"pk": self.cstage2.id})
+ )
+
+ testurl = reverse("avisstage:stage", kwargs={"pk": self.astage1.id})
r = self.client.get(testurl)
self.assertEqual(r.status_code, 200)
-
"""
Vérifie que le seul profil visible est le sien
"""
- def test_profil_visibility_archi(self):
- self.assertPageNotFound(reverse(
- 'avisstage:profil', kwargs={'username': self.u_conscrit.username}))
- testurl = reverse('avisstage:profil',
- kwargs={'username': self.u_archi.username})
+ def test_profil_visibility_archi(self):
+ self.assertPageNotFound(
+ reverse("avisstage:profil", kwargs={"username": self.u_conscrit.username})
+ )
+
+ testurl = reverse(
+ "avisstage:profil", kwargs={"username": self.u_archi.username}
+ )
r = self.client.get(testurl)
self.assertEqual(r.status_code, 200)
-
"""
Vérifie que la recherche n'est pas accessible
"""
+
def test_pages_visibility_archi(self):
- self.assert403Archicubes(reverse('avisstage:recherche'))
-
- self.assert403Archicubes(reverse('avisstage:recherche_resultats'))
+ self.assert403Archicubes(reverse("avisstage:recherche"))
- self.assert403Archicubes(reverse('avisstage:stage_items'))
+ self.assert403Archicubes(reverse("avisstage:recherche_resultats"))
- testurl = reverse('avisstage:feedback')
- r = self.client.post(testurl, {"objet": "Contact",
- "message": "Ceci est un texte"})
- self.assertRedirects(r, reverse('avisstage:index'))
+ self.assert403Archicubes(reverse("avisstage:stage_items"))
- testurl = reverse('avisstage:moderation')
+ testurl = reverse("avisstage:feedback")
+ r = self.client.post(
+ testurl, {"objet": "Contact", "message": "Ceci est un texte"}
+ )
+ self.assertRedirects(r, reverse("avisstage:index"))
+
+ testurl = reverse("avisstage:moderation")
r = self.client.get(testurl)
- self.assertRedirects(r, reverse('admin:login')+"?next="+testurl)
-
+ self.assertRedirects(r, reverse("admin:login") + "?next=" + testurl)
"""
Vérifie que la seule API accessible est celle des lieux
"""
+
def test_api_visibility_archi(self):
- testurl = reverse('avisstage:api_dispatch_list',
- kwargs={"resource_name": "lieu",
- "api_name": "v1"})
+ testurl = reverse(
+ "avisstage:api_dispatch_list",
+ kwargs={"resource_name": "lieu", "api_name": "v1"},
+ )
r = self.client.get(testurl)
self.assertEqual(r.status_code, 200)
-
- testurl = reverse('avisstage:api_dispatch_list',
- kwargs={"resource_name": "stage",
- "api_name": "v1"})
- r = self.client.get(testurl)
- self.assertEqual(r.status_code, 401)
-
- testurl = reverse('avisstage:api_dispatch_list',
- kwargs={"resource_name": "profil",
- "api_name": "v1"})
+
+ testurl = reverse(
+ "avisstage:api_dispatch_list",
+ kwargs={"resource_name": "stage", "api_name": "v1"},
+ )
+ r = self.client.get(testurl)
+ self.assertEqual(r.status_code, 401)
+
+ testurl = reverse(
+ "avisstage:api_dispatch_list",
+ kwargs={"resource_name": "profil", "api_name": "v1"},
+ )
r = self.client.get(testurl)
self.assertEqual(r.status_code, 401)
-
"""
Vérifie que le seul stage modifiable est le sien
"""
+
def test_edit_visibility_archi(self):
- testurl = reverse('avisstage:stage_edit', kwargs={'pk':self.cstage1.id})
+ testurl = reverse("avisstage:stage_edit", kwargs={"pk": self.cstage1.id})
r = self.client.get(testurl)
self.assertEqual(r.status_code, 403)
- testurl = reverse('avisstage:stage_edit', kwargs={'pk':self.astage1.id})
+ testurl = reverse("avisstage:stage_edit", kwargs={"pk": self.astage1.id})
r = self.client.get(testurl)
self.assertEqual(r.status_code, 200)
- testurl = reverse('avisstage:stage_publication',
- kwargs={'pk':self.cstage1.id})
+ testurl = reverse("avisstage:stage_publication", kwargs={"pk": self.cstage1.id})
r = self.client.post(testurl, {"publier": True})
self.assertEqual(r.status_code, 403)
- testurl = reverse('avisstage:stage_publication',
- kwargs={'pk':self.astage1.id})
+ testurl = reverse("avisstage:stage_publication", kwargs={"pk": self.astage1.id})
r = self.client.post(testurl, {"publier": True})
- self.assertRedirects(r, reverse('avisstage:stage',
- kwargs={"pk": self.astage1.id}))
-
- testurl = reverse('avisstage:stage_ajout')
+ self.assertRedirects(
+ r, reverse("avisstage:stage", kwargs={"pk": self.astage1.id})
+ )
+
+ testurl = reverse("avisstage:stage_ajout")
r = self.client.get(testurl)
self.assertEqual(r.status_code, 200)
-
- testurl = reverse('avisstage:profil_edit')
+
+ testurl = reverse("avisstage:profil_edit")
r = self.client.get(testurl)
self.assertEqual(r.status_code, 200)
@@ -309,187 +345,219 @@ class ArchicubeViewsTest(ExperiENSTestCase):
class DeprecatedArchicubeViewsTest(ArchicubeViewsTest):
- def setUp(self):
+ @mock.patch("authens.backends.get_cas_client")
+ def setUp(self, mock_cas_client):
super().setUp()
-
- self.sa_archi = SocialAccount(user=self.u_archi,
- provider="clipper",
- uid="archicube")
+
+ fake_cas_client = FakeCASClient(cas_login="archicube", entrance_year=2012)
+ mock_cas_client.return_value = fake_cas_client
+
+ self.sa_archi = OldCASAccount(
+ user=self.u_archi,
+ cas_login="archicube",
+ entrance_year=2012,
+ )
self.sa_archi.save()
- deprecate_clippers()
-
- self.client.login(username='archicube', password='archicube')
+ # First connexion through CAS
+ self.client.login(ticket="dummy")
+ self.client.logout()
+ # Time flies
+ self.p_archi.last_cas_login = (timezone.now() - timedelta(days=365)).date()
+ self.p_archi.save()
+ # New connexion with password
+ self.client.login(username="archicube", password="archicube")
"""
ACCÈS EN SCOLARITE
"""
+
+
class ScolariteViewsTest(ExperiENSTestCase):
- def setUp(self):
+ @mock.patch("authens.backends.get_cas_client")
+ def setUp(self, mock_cas_client):
super().setUp()
-
- self.u_vieuxcon = User.objects.create_user('vieuxcon',
- 'vieuxcon@ens.fr',
- 'vieuxcon')
+
+ fake_cas_client = FakeCASClient(cas_login="vieuxcon", entrance_year=2017)
+ mock_cas_client.return_value = fake_cas_client
+
+ self.u_vieuxcon = User.objects.create_user(
+ "vieuxcon", "vieuxcon@ens.fr", "vieuxcon"
+ )
self.p_vieuxcon = self.u_vieuxcon.profil
- self.p_vieuxcon.nom="Vieux con"
- self.p_vieuxcon.promotion="Poufsouffle 1997"
- self.p_vieuxcon.bio="Je suis un vieux con encore en scolarité"
+ self.p_vieuxcon.nom = "Vieux con"
+ self.p_vieuxcon.promotion = "Poufsouffle 2017"
+ self.p_vieuxcon.bio = "Je suis un vieux con encore en scolarité"
self.p_vieuxcon.save()
- self.sa_vieuxcon = SocialAccount(user=self.u_vieuxcon,
- provider="clipper",
- uid="vieuxcon")
+ self.sa_vieuxcon = CASAccount(
+ user=self.u_vieuxcon,
+ cas_login="vieuxcon",
+ entrance_year=2017,
+ )
self.sa_vieuxcon.save()
-
- self.vstage1 = Stage(auteur=self.p_vieuxcon, sujet="Oubliettes",
- date_debut=date(1998, 5, 10),
- date_fin=date(1998, 8, 26),
- type_stage="recherche",
- niveau_scol="M1", public=False)
+
+ self.vstage1 = Stage(
+ auteur=self.p_vieuxcon,
+ sujet="Oubliettes",
+ date_debut=date(2018, 5, 10),
+ date_fin=date(2018, 8, 26),
+ type_stage="recherche",
+ niveau_scol="M1",
+ public=False,
+ )
self.vstage1.save()
self.vstage1.matieres.add(self.matiere2)
- alieu1 = AvisLieu(stage=self.vstage1, lieu=self.lieu2,
- chapo="Pas si mal")
+ alieu1 = AvisLieu(stage=self.vstage1, lieu=self.lieu2, chapo="Pas si mal")
alieu1.save()
-
- self.client.login(username='vieuxcon', password='vieuxcon')
+
+ # Connexion through CAS
+ self.client.login(ticket="dummy")
"""
Vérifie que les seules fiches de stages visibles sont les siennes ou celles
publiques
"""
+
def test_stage_visibility_scolarite(self):
- testurl = reverse('avisstage:stage',
- kwargs={'pk':self.cstage1.id})
+ testurl = reverse("avisstage:stage", kwargs={"pk": self.cstage1.id})
r = self.client.get(testurl)
self.assertEqual(r.status_code, 200)
- self.assertPageNotFound(reverse('avisstage:stage',
- kwargs={'pk':self.cstage2.id}))
-
- testurl = reverse('avisstage:stage',
- kwargs={'pk':self.vstage1.id})
+ self.assertPageNotFound(
+ reverse("avisstage:stage", kwargs={"pk": self.cstage2.id})
+ )
+
+ testurl = reverse("avisstage:stage", kwargs={"pk": self.vstage1.id})
r = self.client.get(testurl)
self.assertEqual(r.status_code, 200)
-
"""
Vérifie que tous les profils sont visibles
"""
+
def test_profil_visibility_scolarite(self):
- testurl = reverse('avisstage:profil',
- kwargs={'username': self.u_conscrit.username})
+ testurl = reverse(
+ "avisstage:profil", kwargs={"username": self.u_conscrit.username}
+ )
r = self.client.get(testurl)
self.assertEqual(r.status_code, 200)
- self.assertContains(r, "Wingardium Leviosa") # Public
- self.assertNotContains(r, "Avada Kedavra") # Brouillon
+ self.assertContains(r, "Wingardium Leviosa") # Public
+ self.assertNotContains(r, "Avada Kedavra") # Brouillon
- testurl = reverse('avisstage:profil',
- kwargs={'username': self.u_archi.username})
+ testurl = reverse(
+ "avisstage:profil", kwargs={"username": self.u_archi.username}
+ )
r = self.client.get(testurl)
self.assertEqual(r.status_code, 200)
- testurl = reverse('avisstage:profil',
- kwargs={'username': self.u_vieuxcon.username})
+ testurl = reverse(
+ "avisstage:profil", kwargs={"username": self.u_vieuxcon.username}
+ )
r = self.client.get(testurl)
self.assertEqual(r.status_code, 200)
-
"""
- Vérifie que la recherche et les autres pages sont accessible
+ Vérifie que la recherche et les autres pages sont accessibles
"""
+
def test_pages_visibility_scolarite(self):
- testurl = reverse('avisstage:recherche')
+ testurl = reverse("avisstage:recherche")
r = self.client.get(testurl)
self.assertEqual(r.status_code, 200)
- testurl = reverse('avisstage:recherche_resultats')
+ testurl = reverse("avisstage:recherche_resultats")
r = self.client.get(testurl)
self.assertEqual(r.status_code, 200)
- self.assertContains(r, "Wingardium Leviosa") # Public
- self.assertNotContains(r, "Avada Kedavra") # Brouillon
+ self.assertContains(r, "Wingardium Leviosa") # Public
+ self.assertNotContains(r, "Avada Kedavra") # Brouillon
- testurl = reverse('avisstage:stage_items') + "?ids=" \
- + ";".join(("%d" % k.id) for k in [self.cstage1,
- self.cstage2,
- self.astage1])
+ testurl = (
+ reverse("avisstage:stage_items")
+ + "?ids="
+ + ";".join(
+ ("%d" % k.id) for k in [self.cstage1, self.cstage2, self.astage1]
+ )
+ )
r = self.client.get(testurl)
self.assertEqual(r.status_code, 200)
- self.assertContains(r, "Wingardium Leviosa") # Public
- self.assertNotContains(r, "Avada Kedavra") # Brouillon
+ self.assertContains(r, "Wingardium Leviosa") # Public
+ self.assertNotContains(r, "Avada Kedavra") # Brouillon
- testurl = reverse('avisstage:feedback')
- r = self.client.post(testurl, {"objet": "Contact",
- "message": "Ceci est un texte"})
- self.assertRedirects(r, reverse('avisstage:index'))
+ testurl = reverse("avisstage:feedback")
+ r = self.client.post(
+ testurl, {"objet": "Contact", "message": "Ceci est un texte"}
+ )
+ self.assertRedirects(r, reverse("avisstage:index"))
- testurl = reverse('avisstage:moderation')
+ testurl = reverse("avisstage:moderation")
r = self.client.get(testurl)
- self.assertRedirects(r, reverse('admin:login')+"?next="+testurl)
-
+ self.assertRedirects(r, reverse("admin:login") + "?next=" + testurl)
"""
Vérifie que toutes les API sont accessibles et qu'elles ne montrent que les
stages publics
"""
+
def test_api_visibility_scolarite(self):
- testurl = reverse('avisstage:api_dispatch_list',
- kwargs={"resource_name": "lieu",
- "api_name": "v1"})
- r = self.client.get(testurl)
- self.assertEqual(r.status_code, 200)
-
- testurl = reverse('avisstage:api_dispatch_list',
- kwargs={"resource_name": "stage",
- "api_name": "v1"})
- r = self.client.get(testurl)
- self.assertEqual(r.status_code, 200)
- self.assertContains(r, "Wingardium Leviosa") # Public
- self.assertNotContains(r, "Avada Kedavra") # Brouillon
-
- testurl = reverse('avisstage:api_dispatch_list',
- kwargs={"resource_name": "profil",
- "api_name": "v1"})
+ testurl = reverse(
+ "avisstage:api_dispatch_list",
+ kwargs={"resource_name": "lieu", "api_name": "v1"},
+ )
+ r = self.client.get(testurl)
+ self.assertEqual(r.status_code, 200)
+
+ testurl = reverse(
+ "avisstage:api_dispatch_list",
+ kwargs={"resource_name": "stage", "api_name": "v1"},
+ )
+ r = self.client.get(testurl)
+ self.assertEqual(r.status_code, 200)
+ self.assertContains(r, "Wingardium Leviosa") # Public
+ self.assertNotContains(r, "Avada Kedavra") # Brouillon
+
+ testurl = reverse(
+ "avisstage:api_dispatch_list",
+ kwargs={"resource_name": "profil", "api_name": "v1"},
+ )
r = self.client.get(testurl)
self.assertEqual(r.status_code, 200)
-
"""
Vérifie que le seul stage modifiable est le sien
"""
+
def test_edit_visibility_scolarite(self):
- testurl = reverse('avisstage:stage_edit', kwargs={'pk':self.cstage1.id})
+ testurl = reverse("avisstage:stage_edit", kwargs={"pk": self.cstage1.id})
r = self.client.get(testurl)
self.assertEqual(r.status_code, 403)
- testurl = reverse('avisstage:stage_edit', kwargs={'pk':self.astage1.id})
+ testurl = reverse("avisstage:stage_edit", kwargs={"pk": self.astage1.id})
r = self.client.get(testurl)
self.assertEqual(r.status_code, 403)
-
- testurl = reverse('avisstage:stage_edit', kwargs={'pk':self.vstage1.id})
+
+ testurl = reverse("avisstage:stage_edit", kwargs={"pk": self.vstage1.id})
r = self.client.get(testurl)
self.assertEqual(r.status_code, 200)
- testurl = reverse('avisstage:stage_publication',
- kwargs={'pk':self.cstage1.id})
+ testurl = reverse("avisstage:stage_publication", kwargs={"pk": self.cstage1.id})
r = self.client.post(testurl, {"publier": True})
self.assertEqual(r.status_code, 403)
- testurl = reverse('avisstage:stage_publication',
- kwargs={'pk':self.vstage1.id})
+ testurl = reverse("avisstage:stage_publication", kwargs={"pk": self.vstage1.id})
r = self.client.post(testurl, {"publier": True})
- self.assertRedirects(r, reverse('avisstage:stage',
- kwargs={"pk": self.vstage1.id}))
-
- testurl = reverse('avisstage:stage_ajout')
+ self.assertRedirects(
+ r, reverse("avisstage:stage", kwargs={"pk": self.vstage1.id})
+ )
+
+ testurl = reverse("avisstage:stage_ajout")
r = self.client.get(testurl)
self.assertEqual(r.status_code, 200)
-
- testurl = reverse('avisstage:profil_edit')
+
+ testurl = reverse("avisstage:profil_edit")
r = self.client.get(testurl)
self.assertEqual(r.status_code, 200)
diff --git a/avisstage/urls.py b/avisstage/urls.py
index 3c39700..97647e7 100644
--- a/avisstage/urls.py
+++ b/avisstage/urls.py
@@ -1,33 +1,65 @@
-from django.conf.urls import include, url
-from . import views, api
from tastypie.api import Api
-v1_api = Api(api_name='v1')
+from django.urls import include, path
+
+from . import api, views
+
+v1_api = Api(api_name="v1")
v1_api.register(api.LieuResource())
v1_api.register(api.StageResource())
v1_api.register(api.AuteurResource())
+app_name = "avisstage"
urlpatterns = [
- url(r'^$', views.index, name='index'),
- url(r'^perso/$', views.perso, name='perso'),
- url(r'^faq/$', views.faq, name='faq'),
- url(r'^stage/nouveau/$', views.manage_stage, name='stage_ajout'),
- url(r'^stage/(?P
\w+)/$', views.StageView.as_view(), name='stage'),
- url(r'^stage/(?P\w+)/edit/$', views.manage_stage, name='stage_edit'),
- url(r'^stage/(?P\w+)/publication/$', views.publier_stage,
- name='stage_publication'),
- url(r'^403/archicubes/$', views.archicubes_interdits,
- name='403-archicubes'),
-
- url(r'^lieu/save/$', views.save_lieu, name='lieu_ajout'),
- url(r'^profil/show/(?P[\w@]+)/$', views.ProfilView.as_view(),
- name='profil'),
- url(r'^profil/edit/$', views.ProfilEdit.as_view(), name='profil_edit'),
- url(r'^recherche/$', views.recherche, name='recherche'),
- url(r'^recherche/resultats/$', views.recherche_resultats,
- name='recherche_resultats'),
- url(r'^recherche/items/$', views.stage_items, name='stage_items'),
- url(r'^feedback/$', views.feedback, name='feedback'),
- url(r'^moderation/$', views.statistiques, name='moderation'),
- url(r'^api/', include(v1_api.urls)),
+ path("", views.index, name="index"),
+ path("perso/", views.perso, name="perso"),
+ path("faq/", views.faq, name="faq"),
+ path("stage/nouveau/", views.manage_stage, name="stage_ajout"),
+ path("stage//", views.StageView.as_view(), name="stage"),
+ path("stage//edit/", views.manage_stage, name="stage_edit"),
+ path("stage//publication/", views.publier_stage, name="stage_publication"),
+ path("403/archicubes/", views.archicubes_interdits, name="403-archicubes"),
+ path("lieu/save/", views.save_lieu, name="lieu_ajout"),
+ path("profil/show//", views.ProfilView.as_view(), name="profil"),
+ path("profil/edit/", views.ProfilEdit.as_view(), name="profil_edit"),
+ path("profil/parametres/", views.MesParametres.as_view(), name="parametres"),
+ path(
+ "profil/emails//aconfirmer/",
+ views.AdresseAConfirmer.as_view(),
+ name="emails_aconfirmer",
+ ),
+ path(
+ "profil/emails//supprime/",
+ views.SupprimeAdresse.as_view(),
+ name="emails_supprime",
+ ),
+ path(
+ "profil/emails//reconfirme/",
+ views.ReConfirmeAdresse.as_view(),
+ name="emails_reconfirme",
+ ),
+ path(
+ "profil/emails//principal/",
+ views.RendAdressePrincipale.as_view(),
+ name="emails_principal",
+ ),
+ path(
+ "profil/emails/confirme//",
+ views.ConfirmeAdresse.as_view(),
+ name="emails_confirme",
+ ),
+ path(
+ "profil/mdp/demande/", views.EnvoieLienMotDePasse.as_view(), name="mdp_demande"
+ ),
+ path(
+ "profil/mdp///",
+ views.DefinirMotDePasse.as_view(),
+ name="mdp_edit",
+ ),
+ path("recherche/", views.recherche, name="recherche"),
+ path("recherche/resultats/", views.recherche_resultats, name="recherche_resultats"),
+ path("recherche/items/", views.stage_items, name="stage_items"),
+ path("feedback/", views.feedback, name="feedback"),
+ path("moderation/", views.statistiques, name="moderation"),
+ path("api/", include(v1_api.urls)),
]
diff --git a/avisstage/utils.py b/avisstage/utils.py
index 9aad26d..3ae2d28 100644
--- a/avisstage/utils.py
+++ b/avisstage/utils.py
@@ -1,20 +1,26 @@
-# coding: utf-8
-
-from allauth.socialaccount.models import SocialAccount
from functools import reduce
from math import cos, radians, sqrt
-def choices_length (choices):
- return reduce (lambda m, choice: max (m, len (choice[0])), choices, 0)
+
+def choices_length(choices):
+ return reduce(lambda m, choice: max(m, len(choice[0])), choices, 0)
+
def en_scolarite(user):
return user.profil.en_scolarite
+
def approximate_distance(a, b):
lat_a = radians(a.y)
lat_b = radians(b.y)
dlon = radians(b.x - a.x)
- dlon = dlon * cos((lat_a + lat_b)/2)
- dlat = (lat_a - lat_b)
- distance = 6371000 * sqrt(dlon*dlon + dlat*dlat)
+ dlon = dlon * cos((lat_a + lat_b) / 2)
+ dlat = lat_a - lat_b
+ distance = 6371000 * sqrt(dlon * dlon + dlat * dlat)
return distance
+
+
+def is_email_ens(mail, none=False):
+ if mail is None:
+ return none
+ return mail.endswith("ens.fr") or mail.endswith("ens.psl.eu")
diff --git a/avisstage/views.py b/avisstage/views.py
index 2bcb19c..4dd6caf 100644
--- a/avisstage/views.py
+++ b/avisstage/views.py
@@ -1,28 +1,47 @@
-# coding: utf-8
-
-from django.shortcuts import render, redirect, get_object_or_404
-
-from django.views.generic import DetailView, ListView
-from django.views.generic.edit import UpdateView, CreateView
-from django import forms
-from django.urls import reverse
-from django.conf import settings
-from django.contrib.admin.views.decorators import staff_member_required
-from django.contrib.auth.decorators import login_required
-from braces.views import LoginRequiredMixin
-from django.http import JsonResponse, HttpResponseForbidden, Http404
-from django.core.mail import send_mail
-from django.db.models import Q, Count
+import math
+import random
from collections import Counter, defaultdict
-from .models import Normalien, Stage, Lieu, AvisLieu, AvisStage
-from .forms import StageForm, LieuForm, AvisStageForm, AvisLieuForm, FeedbackForm
+from braces.views import LoginRequiredMixin
+from simple_email_confirmation.models import EmailAddress
+
+from django import forms
+from django.conf import settings
+from django.contrib import messages
+from django.contrib.admin.views.decorators import staff_member_required
+from django.contrib.auth.decorators import login_required
+from django.contrib.auth.tokens import default_token_generator
+from django.contrib.auth.views import PasswordResetConfirmView
+from django.core.mail import send_mail
+from django.db.models import Count, Q
+from django.http import Http404, HttpResponseForbidden, JsonResponse
+from django.shortcuts import get_object_or_404, redirect, render
+from django.urls import reverse, reverse_lazy
+from django.views.generic import (
+ CreateView,
+ DeleteView,
+ DetailView,
+ FormView,
+ ListView,
+ TemplateView,
+ UpdateView,
+ View,
+)
+from django.views.generic.detail import SingleObjectMixin
+
+from .forms import (
+ AdresseEmailForm,
+ AvisLieuForm,
+ AvisStageForm,
+ FeedbackForm,
+ LieuForm,
+ ReinitMdpForm,
+ StageForm,
+)
+from .models import AvisLieu, AvisStage, Lieu, Normalien, Stage
from .utils import en_scolarite
-
from .views_search import *
-import random, math
-
#
# LECTURE
#
@@ -30,8 +49,8 @@ import random, math
# Page d'accueil
def index(request):
num_stages = Stage.objects.filter(public=True).count()
- return render(request, 'avisstage/index.html',
- {"num_stages": num_stages})
+ return render(request, "avisstage/index.html", {"num_stages": num_stages})
+
# Espace personnel
@login_required
@@ -43,40 +62,46 @@ def perso(request):
profil, created = Normalien.objects.get_or_create(user=request.user)
profil.save()
- return render(request, 'avisstage/perso.html')
+ return render(request, "avisstage/perso.html")
+
# 403 Archicubes
@login_required
def archicubes_interdits(request):
- return render(request, 'avisstage/403-archicubes.html')
+ return render(request, "avisstage/403-archicubes.html")
+
# Profil
-#login_required
+# login_required
class ProfilView(LoginRequiredMixin, DetailView):
model = Normalien
- template_name = 'avisstage/detail/profil.html'
+ template_name = "avisstage/detail/profil.html"
# Récupération du profil
def get_object(self):
-
+
# Restriction d'accès pour les archicubes
- if (en_scolarite(self.request.user) or
- self.kwargs.get('username') == self.request.user.username):
+ if (
+ en_scolarite(self.request.user)
+ or self.kwargs.get("username") == self.request.user.username
+ ):
return get_object_or_404(
- Normalien, user__username=self.kwargs.get('username'))
+ Normalien, user__username=self.kwargs.get("username")
+ )
else:
raise Http404
+
# Stage
-#login_required
+# login_required
class StageView(LoginRequiredMixin, DetailView):
model = Stage
- template_name = 'avisstage/detail/stage.html'
+ template_name = "avisstage/detail/stage.html"
# Restriction aux stages publics ou personnels
def get_queryset(self):
filtre = Q(auteur__user_id=self.request.user.id)
-
+
# Restriction d'accès pour les archicubes
if en_scolarite(self.request.user):
filtre |= Q(public=True)
@@ -85,30 +110,33 @@ class StageView(LoginRequiredMixin, DetailView):
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
- context['MAPBOX_API_KEY'] = settings.MAPBOX_API_KEY
+ context["MAPBOX_API_KEY"] = settings.MAPBOX_API_KEY
return context
+
# FAQ
def faq(request):
- return render(request, 'avisstage/faq.html')
+ return render(request, "avisstage/faq.html")
+
#
# EDITION
#
# Profil
-#login_required
+# login_required
class ProfilEdit(LoginRequiredMixin, UpdateView):
model = Normalien
- fields = ['nom', 'promotion', 'contactez_moi', 'bio']
- template_name = 'avisstage/formulaires/profil.html'
+ fields = ["nom", "promotion", "contactez_moi", "bio"]
+ template_name = "avisstage/formulaires/profil.html"
# Limitation à son propre profil
def get_object(self):
return self.request.user.profil
-
+
def get_success_url(self):
- return reverse('avisstage:perso')
+ return reverse("avisstage:perso")
+
# Stage
@login_required
@@ -119,8 +147,9 @@ def manage_stage(request, pk=None):
stage = Stage(auteur=request.user.profil)
avis_stage = AvisStage(stage=stage)
c_del = False
- last_creation = Stage.objects.filter(auteur=request.user.profil)\
- .order_by("-date_creation")[:1]
+ last_creation = Stage.objects.filter(auteur=request.user.profil).order_by(
+ "-date_creation"
+ )[:1]
if len(last_creation) != 0:
last_maj = last_creation[0].date_creation
else:
@@ -134,57 +163,71 @@ def manage_stage(request, pk=None):
# Formset pour les avis des lieux
AvisLieuFormSet = forms.inlineformset_factory(
- Stage, AvisLieu, form=AvisLieuForm, can_delete=c_del, extra=0)
+ Stage, AvisLieu, form=AvisLieuForm, can_delete=c_del, extra=0
+ )
if request.method == "POST":
# Lecture des données
form = StageForm(request.POST, request=request, instance=stage, prefix="stage")
- avis_stage_form = AvisStageForm(request.POST,
- instance=avis_stage, prefix="avis")
- avis_lieu_formset = AvisLieuFormSet(request.POST, instance=stage,
- prefix="lieux")
+ avis_stage_form = AvisStageForm(
+ request.POST, instance=avis_stage, prefix="avis"
+ )
+ avis_lieu_formset = AvisLieuFormSet(
+ request.POST, instance=stage, prefix="lieux"
+ )
# Validation et enregistrement
- if (form.is_valid() and
- avis_stage_form.is_valid() and
- avis_lieu_formset.is_valid()):
+ if (
+ form.is_valid()
+ and avis_stage_form.is_valid()
+ and avis_lieu_formset.is_valid()
+ ):
stage = form.save()
avis_stage_form.instance.stage = stage
avis_stage_form.save()
avis_lieu_formset.save()
- #print(request.POST)
+ # print(request.POST)
if "continuer" in request.POST:
if pk is None:
- return redirect(reverse('avisstage:stage_edit',kwargs={'pk':stage.id}))
+ return redirect(
+ reverse("avisstage:stage_edit", kwargs={"pk": stage.id})
+ )
else:
- return redirect(reverse('avisstage:stage',
- kwargs={'pk':stage.id}))
+ return redirect(reverse("avisstage:stage", kwargs={"pk": stage.id}))
else:
form = StageForm(instance=stage, prefix="stage")
avis_stage_form = AvisStageForm(instance=avis_stage, prefix="avis")
avis_lieu_formset = AvisLieuFormSet(instance=stage, prefix="lieux")
# Affichage du formulaire
- return render(request, "avisstage/formulaires/stage.html",
- {'form': form, 'avis_stage_form': avis_stage_form,
- 'avis_lieu_formset': avis_lieu_formset,
- 'creation': pk is None, "last_maj": last_maj,
- 'GOOGLE_API_KEY': settings.GOOGLE_API_KEY,
- 'MAPBOX_API_KEY': settings.MAPBOX_API_KEY})
+ return render(
+ request,
+ "avisstage/formulaires/stage.html",
+ {
+ "form": form,
+ "avis_stage_form": avis_stage_form,
+ "avis_lieu_formset": avis_lieu_formset,
+ "creation": pk is None,
+ "last_maj": last_maj,
+ "GOOGLE_API_KEY": settings.GOOGLE_API_KEY,
+ "MAPBOX_API_KEY": settings.MAPBOX_API_KEY,
+ },
+ )
+
# Ajout d'un lieu de stage
-#login_required
+# login_required
# Stage
@login_required
def save_lieu(request):
normalien = request.user.profil
-
+
if request.method == "POST":
pk = request.POST.get("id", None)
- #print(request.POST)
+ # print(request.POST)
jitter = False
- if pk is None or pk == '':
+ if pk is None or pk == "":
lieu = Lieu()
else:
# Modification du lieu
@@ -198,7 +241,7 @@ def save_lieu(request):
lieu = Lieu()
# Servira à bouger un peu le lieu
jitter = True
-
+
# Lecture des données
form = LieuForm(request.POST, instance=lieu)
@@ -207,51 +250,54 @@ def save_lieu(request):
lieu = form.save(commit=False)
if jitter:
cdx, cdy = lieu.coord.get_coords()
- ang = random.random() * 6.29;
+ ang = random.random() * 6.29
rad = (random.random() + 0.5) * 3e-4
- cdx += math.cos(ang) * rad;
- cdy += math.sin(ang) * rad;
+ cdx += math.cos(ang) * rad
+ cdy += math.sin(ang) * rad
lieu.coord.set_coords((cdx, cdy))
lieu.save()
# Élimination des doublons
if pk is None or pk == "":
- olieux = Lieu.objects.filter(nom=lieu.nom, coord__distance_lte=(lieu.coord, 10))
+ olieux = Lieu.objects.filter(
+ nom=lieu.nom, coord__distance_lte=(lieu.coord, 10)
+ )
for olieu in olieux:
- if olieu.type_lieu == lieu.type_lieu and \
- olieu.ville == lieu.ville and \
- olieu.pays == lieu.pays:
+ if (
+ olieu.type_lieu == lieu.type_lieu
+ and olieu.ville == lieu.ville
+ and olieu.pays == lieu.pays
+ ):
return JsonResponse({"success": True, "id": olieu.id})
-
+
lieu.save()
return JsonResponse({"success": True, "id": lieu.id})
else:
- return JsonResponse({"success": False,
- "errors": form.errors})
+ return JsonResponse({"success": False, "errors": form.errors})
else:
return JsonResponse({"erreur": "Aucune donnée POST"})
+
class LieuAjout(LoginRequiredMixin, CreateView):
model = Lieu
form_class = LieuForm
- template_name = 'avisstage/formulaires/lieu.html'
+ template_name = "avisstage/formulaires/lieu.html"
# Retourne d'un JSON si requête AJAX
def form_valid(self, form):
if self.request.GET.get("format", "") == "json":
self.object = form.save()
- return JsonResponse({"success": True,
- "id": self.object.id})
+ return JsonResponse({"success": True, "id": self.object.id})
else:
super(LieuAjout, self).form_valid(form)
def form_invalid(self, form):
if self.request.GET.get("format", "") == "json":
- return JsonResponse({"success": False,
- "errors": form.errors})
+ return JsonResponse({"success": False, "errors": form.errors})
else:
super(LieuAjout, self).form_valid(form)
+
# Passage d'un stage en mode public
@login_required
def publier_stage(request, pk):
@@ -270,27 +316,28 @@ def publier_stage(request, pk):
stage.public = False
stage.save()
-
+
return redirect(reverse("avisstage:stage", kwargs={"pk": pk}))
+
#
# FEEDBACK
#
+
@login_required
def feedback(request):
if request.method == "POST":
form = FeedbackForm(request.POST)
if form.is_valid():
- objet = form.cleaned_data['objet']
- header = "[From : %s <%s>]\n" % (request.user,
- request.user.email)
- message = header + form.cleaned_data['message']
+ objet = form.cleaned_data["objet"]
+ header = "[From : %s <%s>]\n" % (request.user, request.user.email)
+ message = header + form.cleaned_data["message"]
send_mail(
- "[experiENS] "+ objet,
+ "[experiENS] " + objet,
message,
request.user.email,
- ['robin.champenois@ens.fr'],
+ ["robin.champenois@ens.fr"],
fail_silently=False,
)
if request.GET.get("format", None) == "json":
@@ -298,8 +345,7 @@ def feedback(request):
return redirect(reverse("avisstage:index"))
else:
if request.GET.get("format", None) == "json":
- return JsonResponse({"success": False,
- "errors": form.errors})
+ return JsonResponse({"success": False, "errors": form.errors})
else:
form = FeedbackForm()
raise Http404()
@@ -309,37 +355,210 @@ def feedback(request):
# STATISTIQUES
#
+
@login_required
@staff_member_required
def statistiques(request):
nstages = Stage.objects.count()
npubstages = Stage.objects.filter(public=True).count()
- nbymatiere_raw = Stage.objects.values('matieres__nom', 'public').annotate(scount=Count('matieres__nom'))
+ nbymatiere_raw = Stage.objects.values("matieres__nom", "public").annotate(
+ scount=Count("matieres__nom")
+ )
nbymatiere = defaultdict(dict)
for npm in nbymatiere_raw:
- nbymatiere[npm["matieres__nom"]]["publics" if npm["public"] else "drafts"] = npm["scount"]
+ nbymatiere[npm["matieres__nom"]][
+ "publics" if npm["public"] else "drafts"
+ ] = npm["scount"]
for mat, npm in nbymatiere.items():
npm["matiere"] = mat
- nbymatiere = sorted(list(nbymatiere.values()), key=lambda npm: -npm.get("publics", 0))
- nbylength = [("Vide", Stage.objects.filter(len_avis_stage__lt=5).count(),
- Stage.objects.filter(len_avis_lieux__lt=5).count()),
- ("Court", Stage.objects.filter(len_avis_stage__lt=30, len_avis_stage__gt=4).count(),
- Stage.objects.filter(len_avis_lieux__lt=30, len_avis_lieux__gt=4).count()),
- ("Moyen", Stage.objects.filter(len_avis_stage__lt=100, len_avis_stage__gt=29).count(),
- Stage.objects.filter(len_avis_lieux__lt=100, len_avis_lieux__gt=29).count()),
- ("Long", Stage.objects.filter(len_avis_stage__gt=99).count(),
- Stage.objects.filter(len_avis_lieux__gt=99).count())]
+ nbymatiere = sorted(
+ list(nbymatiere.values()), key=lambda npm: -npm.get("publics", 0)
+ )
+ nbylength = [
+ (
+ "Vide",
+ Stage.objects.filter(len_avis_stage__lt=5).count(),
+ Stage.objects.filter(len_avis_lieux__lt=5).count(),
+ ),
+ (
+ "Court",
+ Stage.objects.filter(len_avis_stage__lt=30, len_avis_stage__gt=4).count(),
+ Stage.objects.filter(len_avis_lieux__lt=30, len_avis_lieux__gt=4).count(),
+ ),
+ (
+ "Moyen",
+ Stage.objects.filter(len_avis_stage__lt=100, len_avis_stage__gt=29).count(),
+ Stage.objects.filter(len_avis_lieux__lt=100, len_avis_lieux__gt=29).count(),
+ ),
+ (
+ "Long",
+ Stage.objects.filter(len_avis_stage__gt=99).count(),
+ Stage.objects.filter(len_avis_lieux__gt=99).count(),
+ ),
+ ]
nusers = Normalien.objects.count()
nauts = Normalien.objects.filter(stages__isnull=False).distinct().count()
- nbyaut = Counter(Normalien.objects.filter(stages__isnull=False).annotate(scount=Count('stages')).values_list('scount', flat="True")).items()
+ nbyaut = Counter(
+ Normalien.objects.filter(stages__isnull=False)
+ .annotate(scount=Count("stages"))
+ .values_list("scount", flat="True")
+ ).items()
nlieux = Lieu.objects.filter(stages__isnull=False).distinct().count()
- return render(request, 'avisstage/moderation/statistiques.html',
- {'num_stages': nstages,
- 'num_stages_pub': npubstages,
- 'num_par_matiere': nbymatiere,
- 'num_users': nusers,
- 'num_auteurs': nauts,
- 'num_par_auteur': nbyaut,
- 'num_lieux_utiles': nlieux,
- 'num_par_longueur': nbylength,
- })
+ return render(
+ request,
+ "avisstage/moderation/statistiques.html",
+ {
+ "num_stages": nstages,
+ "num_stages_pub": npubstages,
+ "num_par_matiere": nbymatiere,
+ "num_users": nusers,
+ "num_auteurs": nauts,
+ "num_par_auteur": nbyaut,
+ "num_lieux_utiles": nlieux,
+ "num_par_longueur": nbylength,
+ },
+ )
+
+
+#
+# Compte
+#
+
+
+class MesAdressesMixin(LoginRequiredMixin):
+ slug_url_kwarg = "email"
+ slug_field = "email"
+ confirmed_only = False
+
+ def get_queryset(self, *args, **kwargs):
+ qs = self.request.user.email_address_set.all()
+ if self.confirmed_only:
+ qs = qs.filter(confirmed_at__isnull=False)
+ return qs
+
+
+def _send_confirm_mail(email, request):
+ confirm_url = request.build_absolute_uri(
+ reverse("avisstage:emails_confirme", kwargs={"key": email.key})
+ )
+ send_mail(
+ "[ExperiENS] Confirmez votre adresse a-mail",
+ """Bonjour,
+
+Vous venez d'ajouter cette adresse e-mail à votre compte ExperiENS.
+
+Pour la vérifier, merci de cliquer sur le lien suivant, ou de copier l'adresse dans votre navigateur :
+
+ {confirm_url}
+
+Cordialement,
+L'équipe ExperiENS""".format(
+ confirm_url=confirm_url
+ ),
+ "experiens-nepasrepondre@eleves.ens.fr",
+ [email.email],
+ fail_silently=False,
+ )
+ return redirect(
+ reverse("avisstage:emails_aconfirmer", kwargs={"email": email.email})
+ )
+
+
+class MesParametres(LoginRequiredMixin, FormView):
+ model = EmailAddress
+ template_name = "avisstage/compte/parametres.html"
+ form_class = AdresseEmailForm
+
+ def get_form_kwargs(self, *args, **kwargs):
+ kwargs = super().get_form_kwargs(*args, **kwargs)
+ kwargs["_user"] = self.request.user
+ return kwargs
+
+ def form_valid(self, form):
+ new = EmailAddress.objects.create_unconfirmed(
+ form.cleaned_data["email"], self.request.user
+ )
+ return _send_confirm_mail(new, self.request)
+
+
+class RendAdressePrincipale(MesAdressesMixin, SingleObjectMixin, View):
+ model = EmailAddress
+ confirmed_only = True
+
+ def post(self, *args, **kwargs):
+ if not hasattr(self, "object"):
+ self.object = self.get_object()
+ self.request.user.email = self.object.email
+ self.request.user.save()
+ return redirect(reverse("avisstage:parametres"))
+
+
+class AdresseAConfirmer(MesAdressesMixin, DetailView):
+ model = EmailAddress
+ template_name = "avisstage/compte/aconfirmer.html"
+
+
+class ReConfirmeAdresse(MesAdressesMixin, DetailView):
+ model = EmailAddress
+
+ def post(self, *args, **kwargs):
+ email = self.get_object()
+ if email.confirmed_at is None:
+ return _send_confirm_mail(email, self.request)
+ return redirect(reverse("avisstage:parametres"))
+
+
+class ConfirmeAdresse(LoginRequiredMixin, View):
+ def get(self, *args, **kwargs):
+ try:
+ email = EmailAddress.objects.confirm(
+ self.kwargs["key"], self.request.user, True
+ )
+ except Exception as e:
+ raise Http404()
+ messages.add_message(
+ self.request,
+ messages.SUCCESS,
+ "L'adresse email {email} a bien été confirmée".format(email=email.email),
+ )
+ return redirect(reverse("avisstage:parametres"))
+
+
+class SupprimeAdresse(MesAdressesMixin, DeleteView):
+ model = EmailAddress
+ template_name = "avisstage/compte/email_supprime.html"
+ success_url = reverse_lazy("avisstage:parametres")
+
+ def get_queryset(self, *args, **kwargs):
+ qs = super().get_queryset(*args, **kwargs)
+ return qs.exclude(email=self.request.user.email)
+
+
+class EnvoieLienMotDePasse(LoginRequiredMixin, View):
+ def post(self, *args, **kwargs):
+ form = ReinitMdpForm({"email": self.request.user.email})
+ form.is_valid()
+ form.save(
+ email_template_name="avisstage/mails/reinit_mdp.html",
+ from_email="experiens-nepasrepondre@eleves.ens.fr",
+ subject_template_name="avisstage/mails/reinit_mdp.txt",
+ )
+ messages.add_message(
+ self.request,
+ messages.INFO,
+ "Un mail a été envoyé à {email}. Merci de vérifier vos indésirables si vous ne le recevez pas bientôt".format(
+ email=self.request.user.email
+ ),
+ )
+ return redirect(reverse("avisstage:parametres"))
+
+
+class DefinirMotDePasse(PasswordResetConfirmView):
+ template_name = "avisstage/compte/edit_mdp.html"
+ success_url = reverse_lazy("avisstage:perso")
+
+ def get_user(self, *args, **kwargs):
+ user = super().get_user(*args, **kwargs)
+ if self.request.user.is_authenticated and user != self.request.user:
+ raise Http404("Ce token n'est pas valide pour votre compte")
+ return user
diff --git a/avisstage/views_search.py b/avisstage/views_search.py
index bedc8a1..5f4289a 100644
--- a/avisstage/views_search.py
+++ b/avisstage/views_search.py
@@ -1,18 +1,15 @@
-# coding: utf-8
-
+import json
+import logging
from datetime import date
from django import forms
-from django.contrib.auth.decorators import login_required
from django.conf import settings
+from django.contrib.auth.decorators import login_required
from django.core.cache import cache
from django.core.paginator import Paginator
-from django.db.models import Q, Case, When
-from django.http import JsonResponse, HttpResponseBadRequest
-from django.shortcuts import render, redirect, get_object_or_404
-
-import json
-import logging
+from django.db.models import Case, Q, When
+from django.http import HttpResponseBadRequest, JsonResponse
+from django.shortcuts import get_object_or_404, redirect, render
USE_ELASTICSEARCH = getattr(settings, "USE_ELASTICSEARCH", True)
@@ -21,36 +18,43 @@ if USE_ELASTICSEARCH:
from .decorators import en_scolarite_required
from .models import Stage
-from .statics import TYPE_LIEU_OPTIONS, TYPE_STAGE_OPTIONS, NIVEAU_SCOL_OPTIONS
-
+from .statics import NIVEAU_SCOL_OPTIONS, TYPE_LIEU_OPTIONS, TYPE_STAGE_OPTIONS
logger = logging.getLogger("recherche")
# Recherche
class SearchForm(forms.Form):
generique = forms.CharField(required=False)
- sujet = forms.CharField(label=u'À propos de', required=False)
- contexte = forms.CharField(label=u'Contexte (lieu, encadrant⋅e⋅s, structure)',
- required=False)
-
- apres_annee = forms.IntegerField(label=u'Après cette année', required=False)
- avant_annee = forms.IntegerField(label=u'Avant cette année', required=False)
+ sujet = forms.CharField(label="À propos de", required=False)
+ contexte = forms.CharField(
+ label="Contexte (lieu, encadrant⋅e⋅s, structure)", required=False
+ )
- type_stage = forms.ChoiceField(label="Type de stage", choices=([('', u'')]
- + list(TYPE_STAGE_OPTIONS)),
- required=False)
- niveau_scol = forms.ChoiceField(label="Année d'étude", choices=([('', u'')]
- + list(NIVEAU_SCOL_OPTIONS)),
- required=False)
-
- type_lieu = forms.ChoiceField(label=u"Type de lieu d'accueil",
- choices=([('', u'')]
- + list(TYPE_LIEU_OPTIONS)),
- required=False)
- tri = forms.ChoiceField(label=u'Tri par',
- choices=[('pertinence', u'Pertinence'),
- ('-date_maj',u'Dernière mise à jour')],
- required=False, initial='pertinence')
+ apres_annee = forms.IntegerField(label="Après cette année", required=False)
+ avant_annee = forms.IntegerField(label="Avant cette année", required=False)
+
+ type_stage = forms.ChoiceField(
+ label="Type de stage",
+ choices=([("", "")] + list(TYPE_STAGE_OPTIONS)),
+ required=False,
+ )
+ niveau_scol = forms.ChoiceField(
+ label="Année d'étude",
+ choices=([("", "")] + list(NIVEAU_SCOL_OPTIONS)),
+ required=False,
+ )
+
+ type_lieu = forms.ChoiceField(
+ label="Type de lieu d'accueil",
+ choices=([("", "")] + list(TYPE_LIEU_OPTIONS)),
+ required=False,
+ )
+ tri = forms.ChoiceField(
+ label="Tri par",
+ choices=[("pertinence", "Pertinence"), ("-date_maj", "Dernière mise à jour")],
+ required=False,
+ initial="pertinence",
+ )
def cherche(**kwargs):
@@ -58,9 +62,11 @@ def cherche(**kwargs):
use_dsl = False
def field_relevant(field, test_string=True):
- return field in kwargs and \
- kwargs[field] is not None and \
- ((not test_string) or kwargs[field].strip() != '')
+ return (
+ field in kwargs
+ and kwargs[field] is not None
+ and ((not test_string) or kwargs[field].strip() != "")
+ )
if USE_ELASTICSEARCH:
dsl = StageDocument.search()
@@ -71,28 +77,36 @@ def cherche(**kwargs):
# Champ générique : recherche dans tous les champs
if field_relevant("generique"):
- #print("Filtre generique", kwargs['generique'])
+ # print("Filtre generique", kwargs['generique'])
dsl = dsl.query(
- "match",
- _all={"query": kwargs["generique"],
- "fuzziness": "auto"})
+ "match", _all={"query": kwargs["generique"], "fuzziness": "auto"}
+ )
use_dsl = True
# Sujet -> Recherche dan les noms de sujets et les thématiques
if field_relevant("sujet"):
- dsl = dsl.query("multi_match",
- query = kwargs["sujet"],
- fields = ['sujet^2', 'thematiques', 'matieres'],
- fuzziness = "auto")
+ dsl = dsl.query(
+ "multi_match",
+ query=kwargs["sujet"],
+ fields=["sujet^2", "thematiques", "matieres"],
+ fuzziness="auto",
+ )
use_dsl = True
# Contexte -> Encadrants, structure, lieu
if field_relevant("contexte"):
- dsl = dsl.query("multi_match",
- query = kwargs["contexte"],
- fields = ['encadrants', 'structure^2',
- 'lieux.nom', 'lieux.pays', 'lieux.ville'],
- fuzziness = "auto")
+ dsl = dsl.query(
+ "multi_match",
+ query=kwargs["contexte"],
+ fields=[
+ "encadrants",
+ "structure^2",
+ "lieux.nom",
+ "lieux.pays",
+ "lieux.ville",
+ ],
+ fuzziness="auto",
+ )
use_dsl = True
else:
@@ -100,71 +114,74 @@ def cherche(**kwargs):
# recherche en base de données
if field_relevant("generique"):
generique = kwargs["generique"]
- filtres = (Q(sujet__icontains=generique)
- | Q(thematiques__name__icontains=generique)
- | Q(matieres__nom__icontains=generique)
- | Q(lieux__nom__icontains=generique))
+ filtres = (
+ Q(sujet__icontains=generique)
+ | Q(thematiques__name__icontains=generique)
+ | Q(matieres__nom__icontains=generique)
+ | Q(lieux__nom__icontains=generique)
+ )
- # Autres champs -> non fonctionnels
+ # Autres champs -> non fonctionnels
if field_relevant("sujet") or field_relevant("contexte"):
raise NotImplementedError(
- "ElasticSearch doit être activé pour ce type de recherche")
+ "ElasticSearch doit être activé pour ce type de recherche"
+ )
#
# Filtres directs db
#
-
+
# Dates
- if field_relevant('avant_annee', False):
- dte = date(kwargs['avant_annee']+1, 1, 1)
+ if field_relevant("avant_annee", False):
+ dte = date(kwargs["avant_annee"] + 1, 1, 1)
filtres &= Q(date_fin__lt=dte)
- if field_relevant('apres_annee', False):
- dte = date(kwargs['apres_annee'], 1, 1)
+ if field_relevant("apres_annee", False):
+ dte = date(kwargs["apres_annee"], 1, 1)
filtres &= Q(date_debut__gte=dte)
# Type de stage
- if field_relevant('type_stage'):
+ if field_relevant("type_stage"):
filtres &= Q(type_stage=kwargs["type_stage"])
-
- if field_relevant('niveau_scol'):
+
+ if field_relevant("niveau_scol"):
filtres &= Q(niveau_scol=kwargs["niveau_scol"])
# Type de lieu
- if field_relevant('type_lieu'):
+ if field_relevant("type_lieu"):
filtres &= Q(lieux__type_lieu=kwargs["type_lieu"])
-
# Application
if USE_ELASTICSEARCH and use_dsl:
filtres &= Q(id__in=[s.meta.id for s in dsl.scan()])
- #print(filtres)
+ # print(filtres)
resultat = Stage.objects.filter(filtres)
- tri = 'pertinence'
+ tri = "pertinence"
if not use_dsl:
- kwargs['tri'] = '-date_maj'
-
- if field_relevant('tri') and kwargs['tri'] in ['-date_maj']:
- tri = kwargs['tri']
+ kwargs["tri"] = "-date_maj"
+
+ if field_relevant("tri") and kwargs["tri"] in ["-date_maj"]:
+ tri = kwargs["tri"]
resultat = resultat.order_by(tri)
return resultat, tri
+
@login_required
@en_scolarite_required
def recherche(request):
form = SearchForm()
- return render(request, 'avisstage/recherche/recherche.html',
- {"form": form})
+ return render(request, "avisstage/recherche/recherche.html", {"form": form})
+
@login_required
@en_scolarite_required
def recherche_resultats(request):
stages = []
- tri = ''
- vue = 'vue-liste'
+ tri = ""
+ vue = "vue-liste"
lieux = []
stageids = []
if request.method == "GET":
@@ -174,17 +191,22 @@ def recherche_resultats(request):
search_args = form.cleaned_data
# Gestion du cache
- lsearch_args = {key: val for key, val in search_args.items()
- if val != "" and val is not None}
+ lsearch_args = {
+ key: val
+ for key, val in search_args.items()
+ if val != "" and val is not None
+ }
cache_key = json.dumps(lsearch_args, sort_keys=True)
cached = cache.get(cache_key)
if cached is None:
# Requête effective
stages, tri = cherche(**search_args)
- stageids = list(stages.values_list('id', flat=True))
- lieux = [[stageid, lieuid] for (stageid, lieuid)
- in stages.values_list('id', 'lieux')
- if lieuid is not None]
+ stageids = list(stages.values_list("id", flat=True))
+ lieux = [
+ [stageid, lieuid]
+ for (stageid, lieuid) in stages.values_list("id", "lieux")
+ if lieuid is not None
+ ]
# Sauvegarde dans le cache
to_cache = {"stages": stageids, "lieux": lieux, "tri": tri}
@@ -205,42 +227,55 @@ def recherche_resultats(request):
stageids = []
if cached is None:
- stages = stages[max(0, stageids.start_index()-1):
- stageids.end_index()]
+ stages = stages[
+ max(0, stageids.start_index() - 1) : stageids.end_index()
+ ]
else:
- orderer = Case(*[When(pk=pk, then=pos)
- for pos, pk in enumerate(stageids)])
+ orderer = Case(
+ *[When(pk=pk, then=pos) for pos, pk in enumerate(stageids)]
+ )
stages = Stage.objects.filter(id__in=stageids).order_by(orderer)
- stages = stages.prefetch_related('lieux', 'auteur',
- 'matieres', 'thematiques')
+ stages = stages.prefetch_related(
+ "lieux", "auteur", "matieres", "thematiques"
+ )
else:
form = SearchForm()
if stages:
- vue = 'vue-hybride'
+ vue = "vue-hybride"
# Version JSON pour recherche dynamique
if request.GET.get("format") == "json":
- return JsonResponse({"stages": stages, "page": page,
- "num_pages": paginator.num_pages})
-
- template_name = 'avisstage/recherche/resultats.html'
+ return JsonResponse(
+ {"stages": stages, "page": page, "num_pages": paginator.num_pages}
+ )
+
+ template_name = "avisstage/recherche/resultats.html"
if request.GET.get("format") == "raw":
- template_name = 'avisstage/recherche/stage_items.html'
- return render(request, template_name,
- {"form": form, "stages": stages, "paginator": stageids,
- "tri": tri, "vue": vue, "lieux": lieux,
- "MAPBOX_API_KEY": settings.MAPBOX_API_KEY})
+ template_name = "avisstage/recherche/stage_items.html"
+ return render(
+ request,
+ template_name,
+ {
+ "form": form,
+ "stages": stages,
+ "paginator": stageids,
+ "tri": tri,
+ "vue": vue,
+ "lieux": lieux,
+ "MAPBOX_API_KEY": settings.MAPBOX_API_KEY,
+ },
+ )
+
@login_required
@en_scolarite_required
def stage_items(request):
try:
- stageids = [int(a) for a in request.GET.get("ids", "").split(';')]
+ stageids = [int(a) for a in request.GET.get("ids", "").split(";")]
except ValueError:
return HttpResponseBadRequest("Paramètre incorrect")
- stages = Stage.objects.filter(id__in=stageids)\
- .prefetch_related('lieux', 'auteur',
- 'matieres', 'thematiques')
- return render(request, 'avisstage/recherche/stage_items.html',
- {"stages": stages})
+ stages = Stage.objects.filter(id__in=stageids).prefetch_related(
+ "lieux", "auteur", "matieres", "thematiques"
+ )
+ return render(request, "avisstage/recherche/stage_items.html", {"stages": stages})
diff --git a/avisstage/widgets.py b/avisstage/widgets.py
index 020807f..3b332e9 100644
--- a/avisstage/widgets.py
+++ b/avisstage/widgets.py
@@ -1,14 +1,14 @@
from django import forms
from django.core import validators
+
class LatLonWidget(forms.MultiWidget):
"""
A Widget that splits Point input into two latitude/longitude boxes.
"""
def __init__(self, attrs=None, date_format=None, time_format=None):
- widgets = (forms.HiddenInput(attrs=attrs),
- forms.HiddenInput(attrs=attrs))
+ widgets = (forms.HiddenInput(attrs=attrs), forms.HiddenInput(attrs=attrs))
super(LatLonWidget, self).__init__(widgets, attrs)
def decompress(self, value):
@@ -23,13 +23,15 @@ class LatLonField(forms.MultiValueField):
srid = 4326
default_error_messages = {
- 'invalid_latitude' : (u'Entrez une latitude valide.'),
- 'invalid_longitude' : (u'Entrez une longitude valide.'),
+ "invalid_latitude": ("Entrez une latitude valide."),
+ "invalid_longitude": ("Entrez une longitude valide."),
}
def __init__(self, *args, **kwargs):
- fields = (forms.FloatField(min_value=-90, max_value=90),
- forms.FloatField(min_value=-180, max_value=180))
+ fields = (
+ forms.FloatField(min_value=-90, max_value=90),
+ forms.FloatField(min_value=-180, max_value=180),
+ )
super(LatLonField, self).__init__(fields, *args, **kwargs)
def compress(self, data_list):
@@ -37,11 +39,11 @@ class LatLonField(forms.MultiValueField):
# Raise a validation error if latitude or longitude is empty
# (possible if LatLongField has required=False).
if data_list[0] in validators.EMPTY_VALUES:
- raise forms.ValidationError(self.error_messages['invalid_latitude'])
+ raise forms.ValidationError(self.error_messages["invalid_latitude"])
if data_list[1] in validators.EMPTY_VALUES:
- raise forms.ValidationError(self.error_messages['invalid_longitude'])
+ raise forms.ValidationError(self.error_messages["invalid_longitude"])
# SRID=4326;POINT(1.12345789 1.123456789)
- srid_str = 'SRID=%d'%self.srid
- point_str = 'POINT(%f %f)'%tuple(reversed(data_list))
- return ';'.join([srid_str, point_str])
+ srid_str = "SRID=%d" % self.srid
+ point_str = "POINT(%f %f)" % tuple(reversed(data_list))
+ return ";".join([srid_str, point_str])
return None
diff --git a/experiENS/auth.py b/experiENS/auth.py
index e310a39..1c54a8a 100644
--- a/experiENS/auth.py
+++ b/experiENS/auth.py
@@ -1,5 +1,13 @@
-from django_cas_ng.backends import CASBackend
+from authens.backends import ENSCASBackend as AuthENSBackend
+from authens.utils import parse_entrance_year
-class ENSCASBackend(CASBackend):
- def clean_username(self, username):
- return username.lower().strip()
+
+class ENSCASBackend(AuthENSBackend):
+ # Override AuthENS backend user creation to implement the @ logic
+
+ def get_free_username(self, cas_login, attributes):
+ entrance_year = parse_entrance_year(attributes.get("homeDirectory"))
+ if entrance_year is None:
+ return super().get_free_username(cas_login, attributes)
+ entrance_year %= 100
+ return "%s@%02d" % (cas_login, entrance_year)
diff --git a/experiENS/settings_base.py b/experiENS/settings_base.py
index 8e7f517..77e4e6e 100644
--- a/experiENS/settings_base.py
+++ b/experiENS/settings_base.py
@@ -10,9 +10,10 @@ https://docs.djangoproject.com/en/1.7/ref/settings/
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
-from django.core.urlresolvers import reverse_lazy
-from .secrets import SECRET_KEY, GOOGLE_API_KEY, MAPBOX_API_KEY
+from django.urls import reverse_lazy
+
+from .secrets import GOOGLE_API_KEY, MAPBOX_API_KEY, SECRET_KEY
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
@@ -24,72 +25,65 @@ ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
- 'django.contrib.admin',
- 'django.contrib.auth',
- 'django.contrib.contenttypes',
- 'django.contrib.sessions',
- 'django.contrib.messages',
- 'django.contrib.staticfiles',
- 'django.contrib.gis',
- 'django.contrib.sites',
-
- 'django_elasticsearch_dsl',
-
- 'widget_tweaks',
- 'allauth_ens',
-
- 'allauth',
- 'allauth.account',
- 'allauth.socialaccount',
- 'allauth_cas',
-
- 'allauth_ens.providers.clipper',
-
- 'tastypie',
- 'braces',
- 'tinymce',
- 'taggit',
- 'taggit_autosuggest',
- 'avisstage'
+ "django.contrib.admin",
+ "django.contrib.auth",
+ "django.contrib.contenttypes",
+ "django.contrib.sessions",
+ "django.contrib.messages",
+ "django.contrib.staticfiles",
+ "django.contrib.gis",
+ "django.contrib.sites",
+ "django_elasticsearch_dsl",
+ #'allauth', # Uncomment that part when you
+ #'allauth.account', # apply migration
+ #'allauth.socialaccount', # Allauth -> AuthENS
+ "simple_email_confirmation",
+ "authens",
+ "tastypie",
+ "braces",
+ "tinymce",
+ "taggit",
+ "taggit_autosuggest",
+ "avisstage",
]
-MIDDLEWARE_CLASSES = (
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.middleware.common.CommonMiddleware',
- 'django.middleware.csrf.CsrfViewMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
- 'django.contrib.messages.middleware.MessageMiddleware',
- 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+MIDDLEWARE = (
+ "django.middleware.security.SecurityMiddleware",
+ "django.contrib.sessions.middleware.SessionMiddleware",
+ "django.middleware.common.CommonMiddleware",
+ "django.middleware.csrf.CsrfViewMiddleware",
+ "django.contrib.auth.middleware.AuthenticationMiddleware",
+ "django.contrib.messages.middleware.MessageMiddleware",
+ "django.middleware.clickjacking.XFrameOptionsMiddleware",
)
TEMPLATES = [
{
- 'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'DIRS': [
+ "BACKEND": "django.template.backends.django.DjangoTemplates",
+ "DIRS": [
# insert your TEMPLATE_DIRS here
],
- 'APP_DIRS': True,
- 'OPTIONS': {
- 'context_processors': [
- 'django.contrib.auth.context_processors.auth',
- 'django.template.context_processors.debug',
- 'django.template.context_processors.i18n',
- 'django.template.context_processors.media',
- 'django.template.context_processors.static',
- 'django.template.context_processors.tz',
- 'django.template.context_processors.request',
- 'django.contrib.messages.context_processors.messages',
+ "APP_DIRS": True,
+ "OPTIONS": {
+ "context_processors": [
+ "django.contrib.auth.context_processors.auth",
+ "django.template.context_processors.debug",
+ "django.template.context_processors.i18n",
+ "django.template.context_processors.media",
+ "django.template.context_processors.static",
+ "django.template.context_processors.tz",
+ "django.template.context_processors.request",
+ "django.contrib.messages.context_processors.messages",
],
},
},
]
-ROOT_URLCONF = 'experiENS.urls'
+ROOT_URLCONF = "experiENS.urls"
-WSGI_APPLICATION = 'experiENS.wsgi.application'
+WSGI_APPLICATION = "experiENS.wsgi.application"
# Database
@@ -98,9 +92,9 @@ WSGI_APPLICATION = 'experiENS.wsgi.application'
# Internationalization
# https://docs.djangoproject.com/en/1.7/topics/i18n/
-LANGUAGE_CODE = 'fr'
+LANGUAGE_CODE = "fr"
-TIME_ZONE = 'Europe/Paris'
+TIME_ZONE = "Europe/Paris"
USE_I18N = True
@@ -113,44 +107,37 @@ SITE_ID = 1
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.7/howto/static-files/
-STATIC_URL = '/static/'
+STATIC_URL = "/static/"
AUTHENTICATION_BACKENDS = (
- 'django.contrib.auth.backends.ModelBackend',
- 'experiENS.auth.ENSCASBackend',
+ "django.contrib.auth.backends.ModelBackend",
+ "experiENS.auth.ENSCASBackend",
)
-CAS_SERVER_URL = "https://cas.eleves.ens.fr/" #SPI CAS
-CAS_VERIFY_URL = "https://cas.eleves.ens.fr/"
-CAS_IGNORE_REFERER = True
-CAS_REDIRECT_URL = reverse_lazy('avisstage:perso')
-CAS_EMAIL_FORMAT = "%s@clipper.ens.fr"
-CAS_FORCE_CHANGE_USERNAME_CASE = "lower"
-CAS_VERSION = 'CAS_2_SAML_1_0'
+CAS_SERVER_URL = "https://cas.eleves.ens.fr/" # SPI CAS
-ACCOUNT_ADAPTER = 'avisstage.allauth_adapter.AccountAdapter'
-SOCIALACCOUNT_ADAPTER = 'avisstage.allauth_adapter.SocialAccountAdapter'
+AUTHENS_USE_OLDCAS = False
-LOGIN_URL = reverse_lazy('account_login')
-LOGOUT_URL = reverse_lazy('account_logout')
-LOGIN_REDIRECT_URL = reverse_lazy('avisstage:perso')
-ACCOUNT_HOME_URL = reverse_lazy('avisstage:index')
+LOGIN_URL = reverse_lazy("authens:login")
+LOGOUT_URL = reverse_lazy("authens:logout")
+LOGIN_REDIRECT_URL = reverse_lazy("avisstage:perso")
+LOGOUT_REDIRECT_URL = reverse_lazy("avisstage:index")
LOGGING = {
- 'version': 1,
- 'disable_existing_loggers': False,
- 'handlers': {
- 'file': {
- 'level': 'INFO',
- 'class': 'logging.FileHandler',
- 'filename': os.path.join(BASE_DIR, 'recherche.log'),
+ "version": 1,
+ "disable_existing_loggers": False,
+ "handlers": {
+ "file": {
+ "level": "INFO",
+ "class": "logging.FileHandler",
+ "filename": os.path.join(BASE_DIR, "recherche.log"),
},
},
- 'loggers': {
- 'recherche': {
- 'handlers': ['file'],
- 'level': 'INFO',
- 'propagate': True,
+ "loggers": {
+ "recherche": {
+ "handlers": ["file"],
+ "level": "INFO",
+ "propagate": True,
},
},
}
diff --git a/experiENS/settings_dev.py b/experiENS/settings_dev.py
index 295def3..acfdc73 100644
--- a/experiENS/settings_dev.py
+++ b/experiENS/settings_dev.py
@@ -3,45 +3,40 @@ from .settings_base import *
DEBUG = True
DATABASES = {
- 'default': {
- 'ENGINE': 'django.contrib.gis.db.backends.spatialite',
- 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
+ "default": {
+ "ENGINE": "django.contrib.gis.db.backends.spatialite",
+ "NAME": os.path.join(BASE_DIR, "db.sqlite3"),
}
}
-USE_DEBUG_TOOLBAR = True
+USE_DEBUG_TOOLBAR = False
if USE_DEBUG_TOOLBAR:
INSTALLED_APPS += [
- 'debug_toolbar',
+ "debug_toolbar",
]
- MIDDLEWARE_CLASSES = (
- 'debug_toolbar.middleware.DebugToolbarMiddleware',
- ) + MIDDLEWARE_CLASSES
+ MIDDLEWARE = ("debug_toolbar.middleware.DebugToolbarMiddleware",) + MIDDLEWARE
-INTERNAL_IPS = ['127.0.0.1']
+INTERNAL_IPS = ["127.0.0.1"]
-SPATIALITE_LIBRARY_PATH = 'mod_spatialite'
+SPATIALITE_LIBRARY_PATH = "mod_spatialite"
STATIC_ROOT = "/home/evarin/Bureau/experiENS/static/"
-EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
+EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
STATIC_URL = "/experiens/static/"
ELASTICSEARCH_DSL = {
- 'default': {
- 'hosts': 'localhost:9200'
- },
+ "default": {"hosts": "localhost:9200"},
}
-CLIPPER_LDAP_SERVER = 'ldaps://localhost:636'
+CLIPPER_LDAP_SERVER = "ldaps://localhost:636"
# Changer à True pour développer avec ES
USE_ELASTICSEARCH = False
if not USE_ELASTICSEARCH:
- INSTALLED_APPS.remove('django_elasticsearch_dsl')
-
+ INSTALLED_APPS.remove("django_elasticsearch_dsl")
diff --git a/experiENS/settings_prod.py b/experiENS/settings_prod.py
index 08306f6..749f80b 100644
--- a/experiENS/settings_prod.py
+++ b/experiENS/settings_prod.py
@@ -1,8 +1,10 @@
-from .settings_base import *
+import os
+import sys
-import os, sys
from django.core.urlresolvers import reverse_lazy
+from .settings_base import *
+
PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))
BASE_DIR = os.path.dirname(PROJECT_DIR)
@@ -10,9 +12,7 @@ DEBUG = False
ALLOWED_HOSTS = ["www.eleves.ens.fr"]
-ADMINS = (
- ('Robin Champenois', 'champeno@clipper.ens.fr'),
- )
+ADMINS = (("Robin Champenois", "champeno@clipper.ens.fr"),)
ADMIN_LOGINS = [
"champeno",
@@ -22,33 +22,31 @@ SERVER_EMAIL = "experiens@www.eleves.ens.fr"
ROOT_URL = "/experiens/"
-WSGI_APPLICATION = 'experiENS.wsgi.application'
+WSGI_APPLICATION = "experiENS.wsgi.application"
DATABASES = {
- 'default': {
- 'ENGINE': 'django.contrib.gis.db.backends.postgis',
- 'NAME': 'experiens',
- 'USER': 'experiens',
- 'PASSWORD': '',
- 'HOST': '',
- 'PORT': '5432',
+ "default": {
+ "ENGINE": "django.contrib.gis.db.backends.postgis",
+ "NAME": "experiens",
+ "USER": "experiens",
+ "PASSWORD": "",
+ "HOST": "",
+ "PORT": "5432",
}
}
-STATIC_URL = ROOT_URL + 'static/'
-MEDIA_URL = ROOT_URL + 'media/'
+STATIC_URL = ROOT_URL + "static/"
+MEDIA_URL = ROOT_URL + "media/"
-STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
+STATIC_ROOT = os.path.join(BASE_DIR, "static/")
EMAIL_HOST = "nef.ens.fr"
ELASTICSEARCH_DSL = {
- 'default': {
- 'hosts': '127.0.0.1:9200'
- },
+ "default": {"hosts": "127.0.0.1:9200"},
}
-CLIPPER_LDAP_SERVER = 'ldaps://ldap.spi.ens.fr:636'
+CLIPPER_LDAP_SERVER = "ldaps://ldap.spi.ens.fr:636"
DEFAULT_FROM_EMAIL = "experiens-no-reply@www.eleves.ens.fr"
diff --git a/experiENS/urls.py b/experiENS/urls.py
index 91c6191..d7ec561 100644
--- a/experiENS/urls.py
+++ b/experiENS/urls.py
@@ -1,19 +1,18 @@
from django.conf import settings
-from django.conf.urls import include, url
from django.contrib import admin
+from django.urls import include, path
urlpatterns = [
- url(r'^', include('avisstage.urls', namespace='avisstage')),
-
- url(r'^account/', include('allauth_ens.urls')),
-
- url(r'^tinymce/', include('tinymce.urls')),
- url(r'^taggit_autosuggest/', include('taggit_autosuggest.urls')),
- url(r'^admin/', include(admin.site.urls)),
+ path("", include("avisstage.urls")),
+ path("authens/", include("authens.urls")),
+ path("tinymce/", include("tinymce.urls")),
+ path("taggit_autosuggest/", include("taggit_autosuggest.urls")),
+ path("admin/", admin.site.urls),
]
if settings.DEBUG:
import debug_toolbar
+
urlpatterns = [
- url(r'^__debug__/', include(debug_toolbar.urls)),
+ path("__debug__/", include(debug_toolbar.urls)),
] + urlpatterns
diff --git a/experiENS/wsgi.py b/experiENS/wsgi.py
index 8650d72..c9810b7 100644
--- a/experiENS/wsgi.py
+++ b/experiENS/wsgi.py
@@ -8,7 +8,9 @@ https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
"""
import os
+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "experiENS.settings")
from django.core.wsgi import get_wsgi_application
+
application = get_wsgi_application()
diff --git a/requirements.txt b/requirements.txt
index 08eceff..16e58ed 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,12 +1,11 @@
-django==1.11.*
-django-cas-ng==3.5.*
-django-taggit==0.22.*
-python-ldap==3.0.*
-django-tinymce==2.7.*
-django-braces==1.12.*
+django==2.2.*
+django-taggit==1.3.*
+django-tinymce==3.2.*
+django-braces==1.14.*
django-taggit-autosuggest==0.3.*
-pytz==2018.*
+pytz==2020.*
django-tastypie==0.14.*
-lxml==4.2.*
-django-elasticsearch-dsl==0.4.*
-django-allauth-ens==1.1.*
+lxml==4.6.*
+django-elasticsearch-dsl==7.1.*
+authens
+django-simple-email-confirmation==0.*
diff --git a/scripts/initalize_allauth.py b/scripts/initalize_allauth.py
index 8b18e6c..1e0f515 100644
--- a/scripts/initalize_allauth.py
+++ b/scripts/initalize_allauth.py
@@ -1,12 +1,11 @@
import sys
+from collections import defaultdict
from allauth.account.models import EmailAddress
from allauth.socialaccount.models import SocialAccount
from avisstage.models import Normalien
-from collections import defaultdict
-
accounts = SocialAccount.objects.all().prefetch_related("user")
profils = Normalien.objects.all()
addresses = EmailAddress.objects.all()
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..81f23f8
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,10 @@
+[flake8]
+max-line-length = 99
+exclude = .git, *.pyc, __pycache__, migrations
+extend-ignore = E231, E203
+
+[isort]
+profile = black
+known_django = django
+known_first_party = avisstage
+sections = FUTURE,STDLIB,THIRDPARTY,DJANGO,FIRSTPARTY,LOCALFOLDER