Merge branch 'maj2021' into 'master'
Mise à jour 2021 Un certain nombre de changements qu'il était temps d'apporter : - passage à Django 2.2 - basculement de Allauth à AuthENS (=> gestion des adresses mail avec django-simple-email-confirmation) - statut "en scolarité/archicube" automatique - débugs divers (carte des lieux, ...) - mise à jour des dépendances See merge request klub-dev-ens/experiENS!13
This commit is contained in:
commit
9c1092cf8f
36 changed files with 1082 additions and 420 deletions
|
@ -9,7 +9,7 @@ Il est visible sur https://www.eleves.ens.fr/experiens/
|
|||
|
||||
Clonez le dépôt. Installez les pré-requis :
|
||||
|
||||
sudo apt-get install libxlst-dev python3.4-dev
|
||||
sudo apt-get install libxlst-dev libsals2-dev libxml2-dev libldap2-dev libssl-dev
|
||||
|
||||
On a besoin de SpatiaLite pour une base de données GIS. Essayez
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@ from django.contrib.auth.admin import UserAdmin
|
|||
from django.contrib.auth.models import User
|
||||
from avisstage.models import *
|
||||
|
||||
import authens.models as authmod
|
||||
|
||||
class NormalienInline(admin.StackedInline):
|
||||
model = Normalien
|
||||
inline_classes = ("collapse open",)
|
||||
|
@ -32,3 +34,6 @@ admin.site.register(User, UserAdmin)
|
|||
admin.site.register(Lieu)
|
||||
admin.site.register(StageMatiere, StageMatiereAdmin)
|
||||
admin.site.register(Stage, StageAdmin)
|
||||
|
||||
admin.site.register(authmod.CASAccount)
|
||||
admin.site.register(authmod.OldCASAccount)
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
from allauth.account.adapter import DefaultAccountAdapter
|
||||
from allauth.socialaccount.models import SocialAccount
|
||||
from allauth_ens.adapter import LongTermClipperAccountAdapter, get_ldap_infos
|
||||
|
||||
class AccountAdapter(DefaultAccountAdapter):
|
||||
def is_open_for_signup(self, request):
|
||||
return False
|
||||
|
||||
|
||||
class SocialAccountAdapter(LongTermClipperAccountAdapter):
|
||||
def is_open_for_signup(self, request, sociallogin):
|
||||
# sociallogin.account is a SocialAccount instance.
|
||||
# See https://github.com/pennersr/django-allauth/blob/master/allauth/socialaccount/models.py
|
||||
|
||||
if sociallogin.account.provider == 'clipper':
|
||||
return True
|
||||
|
||||
# It returns AccountAdapter.is_open_for_signup().
|
||||
# See https://github.com/pennersr/django-allauth/blob/master/allauth/socialaccount/adapter.py
|
||||
return super().is_open_for_signup(request, sociallogin)
|
||||
|
||||
|
||||
# TODO : HOTFIX pour un bug d'allauth_ens
|
||||
# On remplace la déduplication des comptes faites avec "entrance_year"
|
||||
# par une déduplication sur le nom d'utilisateur
|
||||
# (Copié de allauth_ens)
|
||||
def pre_social_login(self, request, sociallogin):
|
||||
if sociallogin.account.provider != "clipper":
|
||||
return super(LongTermClipperAccountAdapter,
|
||||
self).pre_social_login(request, sociallogin)
|
||||
|
||||
clipper_uid = sociallogin.account.uid
|
||||
try:
|
||||
old_conn = SocialAccount.objects.get(provider='clipper_inactive',
|
||||
uid=clipper_uid)
|
||||
except SocialAccount.DoesNotExist:
|
||||
return
|
||||
|
||||
ldap_data = get_ldap_infos(clipper_uid)
|
||||
sociallogin._ldap_data = ldap_data
|
||||
|
||||
if ldap_data is None or 'entrance_year' not in ldap_data:
|
||||
raise ValueError("No entrance year in LDAP data")
|
||||
|
||||
old_conn_username = old_conn.user.username
|
||||
|
||||
# HOTFIX ICI
|
||||
if self.get_username(clipper_uid, ldap_data) != old_conn_username:
|
||||
email = ldap_data.get('email', get_clipper_email(clipper_uid))
|
||||
remove_email(old_conn.user, email)
|
||||
|
||||
return
|
||||
|
||||
old_conn.provider = 'clipper'
|
||||
old_conn.save()
|
||||
|
||||
sociallogin.lookup()
|
|
@ -1,7 +1,4 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AvisstageConfig(AppConfig):
|
||||
name = 'avisstage'
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
# coding: utf-8
|
||||
import unicodedata
|
||||
|
||||
from django import forms
|
||||
from django.contrib.auth.forms import PasswordResetForm
|
||||
from django.utils import timezone
|
||||
|
||||
from simple_email_confirmation.models import EmailAddress
|
||||
|
||||
import re
|
||||
|
||||
from .models import Normalien, Stage, Lieu, AvisLieu, AvisStage
|
||||
from .models import Normalien, Stage, Lieu, AvisLieu, AvisStage, User
|
||||
from .widgets import LatLonField
|
||||
|
||||
# Sur-classe utile
|
||||
|
@ -107,3 +110,40 @@ class FeedbackForm(forms.Form):
|
|||
objet = forms.CharField(label="Objet", required=True)
|
||||
message = forms.CharField(label="Message", required=True, widget=forms.widgets.Textarea())
|
||||
|
||||
# Nouvelle adresse mail
|
||||
class AdresseEmailForm(forms.Form):
|
||||
def __init__(self, _user, **kwargs):
|
||||
self._user = _user
|
||||
super().__init__(**kwargs)
|
||||
email = forms.EmailField(widget=forms.widgets.EmailInput(attrs={"placeholder": "Nouvelle adresse"}))
|
||||
|
||||
def clean_email(self):
|
||||
email = self.cleaned_data["email"]
|
||||
if EmailAddress.objects.filter(user=self._user, email=email).exists():
|
||||
raise forms.ValidationError(
|
||||
"Cette adresse est déjà associée à ce compte")
|
||||
return email
|
||||
|
||||
|
||||
def _unicode_ci_compare(s1, s2):
|
||||
"""
|
||||
Perform case-insensitive comparison of two identifiers, using the
|
||||
recommended algorithm from Unicode Technical Report 36, section
|
||||
2.11.2(B)(2).
|
||||
"""
|
||||
return unicodedata.normalize('NFKC', s1).casefold() == unicodedata.normalize('NFKC', s2).casefold()
|
||||
|
||||
|
||||
# (Ré)initialisation du mot de passe
|
||||
class ReinitMdpForm(PasswordResetForm):
|
||||
def get_users(self, email):
|
||||
"""Override default method to allow unusable passwords"""
|
||||
email_field_name = User.get_email_field_name()
|
||||
active_users = User._default_manager.filter(**{
|
||||
'%s__iexact' % email_field_name: email,
|
||||
'is_active': True,
|
||||
})
|
||||
return (
|
||||
u for u in active_users
|
||||
if _unicode_ci_compare(email, getattr(u, email_field_name))
|
||||
)
|
||||
|
|
16
avisstage/management/commands/termine_scolarite.py
Normal file
16
avisstage/management/commands/termine_scolarite.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
from django.core.management.base import BaseCommand, CommandError
|
||||
from avisstage.models import Normalien
|
||||
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Réinitialise les statuts "en scolarité" de tout le monde'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
return
|
||||
|
||||
def handle(self, *args, **options):
|
||||
old_conn = timezone.now() - timedelta(days=365)
|
||||
Normalien.objects.all().update(last_cas_connect=t)
|
||||
self.stdout.write(self.style.SUCCESS(u'Terminé'))
|
45
avisstage/migrations/0003_auto_20210117_1208.py
Normal file
45
avisstage/migrations/0003_auto_20210117_1208.py
Normal file
File diff suppressed because one or more lines are too long
107
avisstage/migrations/0004_allauth_to_authens.py
Normal file
107
avisstage/migrations/0004_allauth_to_authens.py
Normal file
|
@ -0,0 +1,107 @@
|
|||
from django.apps import apps as global_apps
|
||||
from django.db import migrations
|
||||
|
||||
from django.utils import timezone
|
||||
|
||||
def forwards(apps, schema_editor):
|
||||
User = apps.get_model('auth', 'User')
|
||||
|
||||
try:
|
||||
CASAccount = apps.get_model('authens', 'CASAccount')
|
||||
except LookupError:
|
||||
return
|
||||
|
||||
try:
|
||||
SocialAccount = apps.get_model('socialaccount', 'SocialAccount')
|
||||
OldEmailAddress = apps.get_model('account', 'EmailAddress')
|
||||
except LookupError:
|
||||
# Allauth not installed
|
||||
# Simply create CAS accounts for every profile
|
||||
# This procedure is not meant to be fast
|
||||
from authens.shortcuts import fetch_cas_account
|
||||
|
||||
def migrate_user(user):
|
||||
ldap_info = fetch_cas_account(user.username)
|
||||
if ldap_info:
|
||||
entrance_year = ldap_info["entrance_year"]
|
||||
CASAccount.objects.create(
|
||||
user=user, cas_login=user.username,
|
||||
entrance_year=entrance_year
|
||||
)
|
||||
|
||||
for user in User.objects.all():
|
||||
migrate_user(user)
|
||||
return
|
||||
|
||||
|
||||
NewEmailAddress = apps.get_model('simple_email_confirmation',
|
||||
'EmailAddress')
|
||||
from simple_email_confirmation.models import EmailAddressManager
|
||||
|
||||
# Transfer from allauth to authens
|
||||
# Assumes usernames have the format <clipper>@<promo>
|
||||
# Assumes no clashing clipper accounts have ever been found
|
||||
oldusers = (
|
||||
User.objects.all().prefetch_related(
|
||||
"emailaddress_set", "socialaccount_set")
|
||||
)
|
||||
|
||||
is_ens_mail = lambda mail: (
|
||||
mail is not None and (mail.endswith("ens.fr") or mail.endswith("ens.psl.eu")))
|
||||
new_conns = []
|
||||
new_mails = []
|
||||
|
||||
for user in oldusers:
|
||||
# Move EmailAddress to new model
|
||||
addresses = user.emailaddress_set.all()
|
||||
for addr in addresses:
|
||||
newaddr = NewEmailAddress(
|
||||
user=user, email=addr.email,
|
||||
set_at=timezone.now(),
|
||||
confirmed_at=(timezone.now() if addr.verified else None),
|
||||
key=EmailAddressManager().generate_key(),
|
||||
)
|
||||
if addr.primary and user.email != addr.email:
|
||||
print("Adresse principale inconsistante",
|
||||
user.email, addr.email)
|
||||
new_mails.append(newaddr)
|
||||
|
||||
# Create new CASAccount connexion
|
||||
saccounts = user.socialaccount_set.all()
|
||||
if not saccounts:
|
||||
continue
|
||||
if len(saccounts) > 1:
|
||||
print(saccounts)
|
||||
saccount = saccounts[0]
|
||||
clipper = saccount.uid
|
||||
if "@" not in user.username:
|
||||
print(user.username)
|
||||
continue
|
||||
entrance_year = saccount.extra_data.get(
|
||||
"entrance_year", user.username.split("@")[1])
|
||||
try:
|
||||
entrance_year = 2000 + int(entrance_year)
|
||||
except ValueError:
|
||||
print(entrance_year)
|
||||
continue
|
||||
|
||||
new_conns.append(CASAccount(user=user, cas_login=clipper,
|
||||
entrance_year=int(entrance_year)))
|
||||
|
||||
NewEmailAddress.objects.bulk_create(new_mails)
|
||||
CASAccount.objects.bulk_create(new_conns)
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
operations = [
|
||||
migrations.RunPython(forwards, migrations.RunPython.noop),
|
||||
]
|
||||
dependencies = [
|
||||
('avisstage', '0003_auto_20210117_1208'),
|
||||
('authens', '0002_old_cas_account'),
|
||||
]
|
||||
|
||||
if global_apps.is_installed('allauth'):
|
||||
dependencies.append(('socialaccount', '0003_extra_data_default_dict'))
|
||||
|
||||
if global_apps.is_installed('simple_email_confirmation'):
|
||||
dependencies.append(('simple_email_confirmation', '0001_initial'))
|
18
avisstage/migrations/0005_normalien_en_scolarite.py
Normal file
18
avisstage/migrations/0005_normalien_en_scolarite.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.2.17 on 2021-01-17 20:46
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('avisstage', '0004_allauth_to_authens'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='normalien',
|
||||
name='en_scolarite',
|
||||
field=models.BooleanField(blank=True, default=False),
|
||||
),
|
||||
]
|
32
avisstage/migrations/0006_auto_20210131_1954.py
Normal file
32
avisstage/migrations/0006_auto_20210131_1954.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
# Generated by Django 2.2.17 on 2021-01-31 18:54
|
||||
|
||||
import avisstage.models
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('avisstage', '0005_normalien_en_scolarite'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='normalien',
|
||||
name='en_scolarite',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='normalien',
|
||||
name='mail',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='normalien',
|
||||
name='last_cas_login',
|
||||
field=models.DateField(default=avisstage.models._default_cas_login),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='normalien',
|
||||
name='contactez_moi',
|
||||
field=models.BooleanField(default=True, help_text='Affiche votre adresse e-mail principale sur votre profil public', verbose_name='Inviter les visiteurs à me contacter'),
|
||||
),
|
||||
]
|
|
@ -1,10 +1,3 @@
|
|||
# coding: utf-8
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from allauth.account.models import EmailAddress
|
||||
from allauth.socialaccount.models import SocialAccount
|
||||
|
||||
from django.db import models
|
||||
from django.db.models.signals import post_save
|
||||
from django.contrib.auth.models import User
|
||||
|
@ -20,26 +13,36 @@ from django.utils.html import strip_tags
|
|||
from taggit_autosuggest.managers import TaggableManager
|
||||
from tinymce.models import HTMLField as RichTextField
|
||||
|
||||
from .utils import choices_length
|
||||
from .statics import DEPARTEMENTS_DEFAUT, PAYS_OPTIONS, TYPE_LIEU_OPTIONS, TYPE_STAGE_OPTIONS, TYPE_LIEU_DICT, TYPE_STAGE_DICT, NIVEAU_SCOL_OPTIONS, NIVEAU_SCOL_DICT
|
||||
from authens.signals import post_cas_connect
|
||||
from authens.models import CASAccount
|
||||
|
||||
import ldap
|
||||
from datetime import timedelta
|
||||
|
||||
from .utils import choices_length, is_email_ens
|
||||
from .statics import (
|
||||
DEPARTEMENTS_DEFAUT, PAYS_OPTIONS, TYPE_LIEU_OPTIONS, TYPE_STAGE_OPTIONS, TYPE_LIEU_DICT,
|
||||
TYPE_STAGE_DICT, NIVEAU_SCOL_OPTIONS, NIVEAU_SCOL_DICT
|
||||
)
|
||||
|
||||
def _default_cas_login():
|
||||
return (timezone.now()-timedelta(days=365)).date()
|
||||
|
||||
#
|
||||
# Profil Normalien (extension du modèle User)
|
||||
#
|
||||
|
||||
class Normalien(models.Model):
|
||||
user = models.OneToOneField(User, related_name="profil")
|
||||
user = models.OneToOneField(User, related_name="profil",
|
||||
on_delete=models.SET_NULL, null=True)
|
||||
|
||||
# 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.EmailField(u"Adresse e-mail permanente",
|
||||
max_length=200, blank=True)
|
||||
contactez_moi = models.BooleanField(u"Inviter les visiteurs à me contacter",
|
||||
default=True)
|
||||
bio = models.TextField(u"À propos de moi", blank=True, default="");
|
||||
contactez_moi = models.BooleanField(
|
||||
u"Inviter les visiteurs à me contacter",
|
||||
default=True, help_text="Affiche votre adresse e-mail principale sur votre profil public")
|
||||
bio = models.TextField(u"À propos de moi", blank=True, default="")
|
||||
last_cas_login = models.DateField(default=_default_cas_login)
|
||||
|
||||
class Meta:
|
||||
verbose_name = u"Profil élève"
|
||||
|
@ -52,53 +55,66 @@ class Normalien(models.Model):
|
|||
def stages_publics(self):
|
||||
return self.stages.filter(public=True).order_by('-date_debut')
|
||||
|
||||
@cached_property
|
||||
def en_scolarite(self):
|
||||
return SocialAccount.objects.filter(user_id=self.user_id,
|
||||
provider="clipper").exists()
|
||||
|
||||
def has_nonENS_email(self):
|
||||
a = EmailAddress.objects.filter(user_id=self.user_id,
|
||||
verified=True) \
|
||||
.exclude(email__endswith="ens.fr")
|
||||
return a.exists()
|
||||
return (
|
||||
self.user.email_address_set
|
||||
.exclude(confirmed_at__isnull=True)
|
||||
.exclude(email__endswith="ens.fr")
|
||||
.exclude(email__endswith="ens.psl.eu")
|
||||
.exists()
|
||||
)
|
||||
|
||||
def nom_complet(self):
|
||||
if self.nom.strip():
|
||||
return self.nom
|
||||
return self.user.username
|
||||
|
||||
@property
|
||||
def en_scolarite(self):
|
||||
return self.last_cas_login > (timezone.now() - timedelta(days=60)).date()
|
||||
|
||||
@property
|
||||
def preferred_email(self):
|
||||
a = EmailAddress.objects.filter(user_id=self.user_id,
|
||||
verified=True) \
|
||||
.exclude(email__endswith="ens.fr")\
|
||||
.order_by('-primary')
|
||||
if len(a) == 0:
|
||||
a = EmailAddress.objects.filter(user_id=self.user_id,
|
||||
verified=True) \
|
||||
.order_by('-primary')
|
||||
if len(a) == 0:
|
||||
return ""
|
||||
else:
|
||||
return a[0].email
|
||||
return self.user.email
|
||||
|
||||
# Hook à la création d'un nouvel utilisateur : récupération de ses infos par LDAP
|
||||
def create_user_profile(sender, instance, created, **kwargs):
|
||||
# Hook à la création d'un nouvel utilisateur : information de base
|
||||
def create_basic_user_profile(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
profil, created = Normalien.objects.get_or_create(user=instance)
|
||||
try:
|
||||
saccount = SocialAccount.objects.get(user=instance,
|
||||
provider="clipper")
|
||||
except SocialAccount.DoesNotExist:
|
||||
profil.save()
|
||||
return
|
||||
edata = saccount.extra_data.get("ldap", {})
|
||||
dep = ""
|
||||
if "department_code" in edata:
|
||||
dep = dict(DEPARTEMENTS_DEFAUT).get(
|
||||
edata["department_code"].lower(), '')
|
||||
|
||||
profil.promotion = "%s %s" % (dep, edata["entrance_year"])
|
||||
profil.nom = edata.get("name", "")
|
||||
profil.save()
|
||||
if not created and profil.promotion != "":
|
||||
return
|
||||
|
||||
if "@" in instance.username:
|
||||
profil.promotion = instance.username.split("@")[1]
|
||||
profil.save()
|
||||
|
||||
post_save.connect(create_user_profile, sender=User)
|
||||
post_save.connect(create_basic_user_profile, sender=User)
|
||||
|
||||
# Hook d'authENS : information du CAS
|
||||
def handle_cas_connection(sender, instance, created, cas_login, attributes, **kwargs):
|
||||
profil, created = Normalien.objects.get_or_create(user=instance)
|
||||
|
||||
profil.last_cas_login = timezone.now().date()
|
||||
if not created:
|
||||
profil.save()
|
||||
return
|
||||
|
||||
dirs = attributes.get("homeDirectory", "").split("/")
|
||||
if len(dirs) < 4:
|
||||
print("HomeDirectory invalide", dirs)
|
||||
return
|
||||
|
||||
year = dirs[2]
|
||||
departement = dirs[3]
|
||||
|
||||
dep = dict(DEPARTEMENTS_DEFAUT).get(departement.lower(), "")
|
||||
|
||||
profil.promotion = "%s %s" % (dep, year)
|
||||
profil.nom = attributes.get("name", "")
|
||||
profil.save()
|
||||
|
||||
post_cas_connect.connect(handle_cas_connection, sender=User)
|
||||
|
||||
#
|
||||
# Lieu de stage
|
||||
|
@ -121,7 +137,7 @@ class Lieu(models.Model):
|
|||
max_length=choices_length(PAYS_OPTIONS))
|
||||
|
||||
# Coordonnées
|
||||
objects = geomodels.GeoManager() # Requis par GeoDjango
|
||||
#objects = geomodels.GeoManager() # Requis par GeoDjango
|
||||
coord = geomodels.PointField(u"Coordonnées",
|
||||
geography=True,
|
||||
srid = 4326)
|
||||
|
@ -163,7 +179,8 @@ class StageMatiere(models.Model):
|
|||
|
||||
class Stage(models.Model):
|
||||
# Misc
|
||||
auteur = models.ForeignKey(Normalien, related_name="stages")
|
||||
auteur = models.ForeignKey(Normalien, related_name="stages",
|
||||
on_delete=models.SET_NULL, null=True)
|
||||
public = models.BooleanField(u"Visible publiquement", default=False)
|
||||
date_creation = models.DateTimeField(u"Créé le", default=timezone.now)
|
||||
date_maj = models.DateTimeField(u"Mis à jour le", default=timezone.now)
|
||||
|
@ -260,7 +277,8 @@ class Stage(models.Model):
|
|||
#
|
||||
|
||||
class AvisStage(models.Model):
|
||||
stage = models.OneToOneField(Stage, related_name="avis_stage")
|
||||
stage = models.OneToOneField(Stage, related_name="avis_stage",
|
||||
on_delete=models.CASCADE)
|
||||
|
||||
chapo = models.TextField(u"En quelques mots", blank=True)
|
||||
avis_ambiance = RichTextField(u"L'ambiance de travail", blank=True)
|
||||
|
@ -283,8 +301,8 @@ class AvisStage(models.Model):
|
|||
|
||||
|
||||
class AvisLieu(models.Model):
|
||||
stage = models.ForeignKey(Stage)
|
||||
lieu = models.ForeignKey(Lieu)
|
||||
stage = models.ForeignKey(Stage, on_delete=models.CASCADE)
|
||||
lieu = models.ForeignKey(Lieu, on_delete=models.CASCADE)
|
||||
order = models.IntegerField("Ordre", default=0)
|
||||
|
||||
chapo = models.TextField(u"En quelques mots", blank=True)
|
||||
|
|
|
@ -177,14 +177,14 @@
|
|||
display: block;
|
||||
text-align: left;
|
||||
font-size: 0.95em;
|
||||
color: $compl * 0.8;
|
||||
color: darken($compl, 20%);
|
||||
margin-top: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.help_text {
|
||||
text-align: right;
|
||||
color: $fond * 0.4;
|
||||
color: darken($fond, 60%);
|
||||
}
|
||||
|
||||
.input {
|
||||
|
|
|
@ -46,7 +46,7 @@ em, i {
|
|||
|
||||
a {
|
||||
font-weight: bold;
|
||||
color: $compl * 0.9;
|
||||
color: darken($compl, 10%);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
@ -107,7 +107,7 @@ header {
|
|||
color: lighten($fond, 40%);
|
||||
|
||||
&:hover {
|
||||
background: $barre * 0.6;
|
||||
background: darken($barre, 40%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -181,7 +181,6 @@ p.warning {
|
|||
li {
|
||||
display: table;
|
||||
width: 100%;
|
||||
//border: 1px solid $fond * 1.3;
|
||||
background: #fff;
|
||||
margin: 12px;
|
||||
|
||||
|
@ -363,6 +362,57 @@ section.profil {
|
|||
}
|
||||
}
|
||||
|
||||
section.two-cols {
|
||||
display: flex;
|
||||
display: flexbox;
|
||||
align-items: center;
|
||||
& > * {
|
||||
flex: 1;
|
||||
width: 50%;
|
||||
margin: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
ul.mes-emails {
|
||||
li {
|
||||
display: flex;
|
||||
background: #fff;
|
||||
margin: 5px;
|
||||
padding: 10px;
|
||||
min-height: 70px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
& > * {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.adresse {
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.confirmee {
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.supprimer {
|
||||
flex: 0.7;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
.field {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// Détail d'un stage
|
||||
|
@ -432,7 +482,7 @@ input[type="submit"], .btn {
|
|||
font: $textfontsize $textfont;
|
||||
background-color: $fond;
|
||||
color: #fff;
|
||||
border: 1px solid $fond * 0.7;
|
||||
border: 1px solid darken($fond, 30%);
|
||||
border-radius: 5px;
|
||||
padding: 8px 12px;
|
||||
display: inline-block;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -63,10 +63,10 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, MAPBOX_API_KEY, target, callbac
|
|||
|
||||
// Affiche la carte
|
||||
map = L.map(map_el[0]).setView([48.8422411,2.3430553], 15);
|
||||
var layer = L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}', {
|
||||
var layer = L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', {
|
||||
attribution: 'Map data © <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
|
||||
maxZoom: 18,
|
||||
id: 'mapbox.streets',
|
||||
id: 'mapbox/streets-v11',
|
||||
accessToken: MAPBOX_API_KEY
|
||||
});
|
||||
map.addLayer(layer);
|
||||
|
|
|
@ -37,9 +37,9 @@
|
|||
<li><a href="{% url 'avisstage:moderation' %}">Modo</a></li>
|
||||
{% endif %}
|
||||
{% if user.is_authenticated %}
|
||||
<li><a href="{% url "account_logout" %}"><span class="username">{{ user.username }}</span><br/> Déconnexion</a></li>
|
||||
<li><a href="{% url "authens:logout" %}"><span class="username">{{ user.username }}</span><br/> Déconnexion</a></li>
|
||||
{% else %}
|
||||
<li><a href="{% url "account_login" %}">Connexion</a></li>
|
||||
<li><a href="{% url "authens:login" %}">Connexion</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
|
|
19
avisstage/templates/avisstage/compte/aconfirmer.html
Normal file
19
avisstage/templates/avisstage/compte/aconfirmer.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
{% extends "avisstage/base.html" %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block title %}Confirmation requise - ExperiENS{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Confirmation requise</h1>
|
||||
|
||||
<article>
|
||||
{% if object.confirmed_at %}
|
||||
<p>L'adresse {{ object.email }} a déjà été confirmée.</p>
|
||||
{% else %}
|
||||
<p>Un mail de confirmation vous a été envoyé à l'adresse {{ object.email }} pour la vérifier.</p>
|
||||
<p>Merci de cliquer sur le lien inclus pour confirmer qu'elle est correcte.</p>
|
||||
<p>Si vous ne recevez rien, vérifier dans vos indésirables.</p>
|
||||
{% endif %}
|
||||
<p><a href="{% url "avisstage:parametres" %}">Retour</a></p>
|
||||
</article>
|
||||
{% endblock %}
|
29
avisstage/templates/avisstage/compte/edit_mdp.html
Normal file
29
avisstage/templates/avisstage/compte/edit_mdp.html
Normal file
|
@ -0,0 +1,29 @@
|
|||
{% extends "avisstage/base.html" %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Définir un mot de passe</h1>
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.non_field_errors }}
|
||||
<div class="field">
|
||||
<label>Nom d'utilisateur</label>
|
||||
<div class="input">
|
||||
{{ user.username }}
|
||||
</div>
|
||||
</div>
|
||||
{% for field in form %}
|
||||
{{ field.errors }}
|
||||
<div class="field">
|
||||
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
|
||||
<div class="input">
|
||||
{{ field }}
|
||||
{% if field.help_text %}
|
||||
<p class="help_text">{{ field.help_text }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<input type="submit" value="Enregistrer" />
|
||||
</form>
|
||||
{% endblock %}
|
18
avisstage/templates/avisstage/compte/email_supprime.html
Normal file
18
avisstage/templates/avisstage/compte/email_supprime.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
{% extends "avisstage/base.html" %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block title %}Supprimer une adresse mail - ExperiENS{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Supprimer une adresse mail</h1>
|
||||
|
||||
<article>
|
||||
<section class="profil">
|
||||
<form action="" method="POST">
|
||||
{% csrf_token %}
|
||||
<p>Êtes-vous sûr⋅e de vouloir supprimer l'adresse mail {{ object.email }} ?</p>
|
||||
<p><a href="{% url "avisstage:parametres" %}">Retour</a> <input type="submit" value="Confirmer la suppression"></p>
|
||||
</form>
|
||||
</section>
|
||||
</article>
|
||||
{% endblock %}
|
76
avisstage/templates/avisstage/compte/parametres.html
Normal file
76
avisstage/templates/avisstage/compte/parametres.html
Normal file
|
@ -0,0 +1,76 @@
|
|||
{% extends "avisstage/base.html" %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block title %}Mes paramètres - ExperiENS{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Mes paramètres</h1>
|
||||
|
||||
<article>
|
||||
<h2>Adresses e-mail</h2>
|
||||
<ul class="mes-emails">
|
||||
{% for email in request.user.email_address_set.all %}
|
||||
<li>
|
||||
<span class="adresse">{{ email.email }}
|
||||
<span class="confirmee">{{ email.confirmed_at|yesno:"✓,✗"|safe }}</span></span>
|
||||
{% if email.confirmed_at %}
|
||||
<span class="principale">
|
||||
{% if email.email == user.email %}
|
||||
Principale
|
||||
{% else %}
|
||||
<form action="{% url "avisstage:emails_principal" email.email %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<input type="submit" value="Rendre principale">
|
||||
</form>
|
||||
{% endif %}
|
||||
</span>
|
||||
|
||||
{% else %}
|
||||
<span class="confirmer">
|
||||
<form action="{% url "avisstage:emails_reconfirme" email.email %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<input type="submit" value="Renvoyer le lien de confirmation">
|
||||
</form>
|
||||
</span>
|
||||
{% endif %}
|
||||
<span class="supprimer">
|
||||
{% if not email.email == user.email %}
|
||||
<a href="{% url "avisstage:emails_supprime" email.email %}">Supprimer</a>
|
||||
{% endif %}
|
||||
</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
<li>
|
||||
<form action="" method="POST">
|
||||
{% csrf_token %}
|
||||
{{ form.non_field_errors }}
|
||||
{% for field in form %}
|
||||
{{ field.errors }}
|
||||
<div class="field">
|
||||
<div class="input">
|
||||
{{ field }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<input type="submit" value="Ajouter l'adresse">
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<h2>Mot de passe</h2>
|
||||
<section class="profil">
|
||||
{% if request.user.password and request.user.has_usable_password %}
|
||||
<p>Un mot de passe interne est déjà défini pour ce compte.</p>
|
||||
{% else %}
|
||||
<p>Aucun mot de passe n'est défini pour ce compte. Créez-en un pour pouvoir vous connecter après la fin de votre scolarité à l'ENS.</p>
|
||||
{% endif %}
|
||||
<form action="{% url "avisstage:mdp_demande" %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<input type="submit" value="Définir un nouveau mot de passe" />
|
||||
</form>
|
||||
<p>En cliquant sur ce bouton, un lien unique vous sera envoyé à votre adresse e-mail principale ({{ request.user.email }}) qui vous donnera accès au formulaire d'édition du mot de passe.</p>
|
||||
</section>
|
||||
</article>
|
||||
{% endblock %}
|
|
@ -5,10 +5,10 @@
|
|||
<script type="text/javascript" src="{% static "js/render.js" %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}Profil de {{ object.nom }} - ExperiENS{% endblock %}
|
||||
{% block title %}Profil de {{ object.nom_complet }} - ExperiENS{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Profil de {{ object.nom }}
|
||||
<h1>Profil de {{ object.nom_complet }}
|
||||
{% if object.user == user %}
|
||||
<a href="{% url "avisstage:profil_edit" %}" class="btn edit-btn">Modifier mes infos</a>
|
||||
{% endif %}
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
<h1>{{ object.sujet }}</h1>
|
||||
<p class="dates"><span class="year">{{ object.date_debut|date:"Y" }}</span><span class="debut">{{ object.date_debut|date:"d/m" }}</span><span class="fin">{{ object.date_fin|date:"d/m" }}</span></p>
|
||||
</div>
|
||||
<p><a href="{% url "avisstage:profil" object.auteur.user.username %}">{{ object.auteur.nom }}</a>
|
||||
<p><a href="{% url "avisstage:profil" object.auteur.user.username %}">{{ object.auteur.nom_complet }}</a>
|
||||
a fait {{ object.type_stage_fem|yesno:"cette,ce" }} <b>{{ object.type_stage_fancy }}</b>
|
||||
{% if object.niveau_scol %}{{ object.niveau_scol_fancy }},{% endif %}
|
||||
{% if object.structure %}au sein de {{ object.structure }}{% endif %}{% if object.encadrants %}, supervisé par {{ object.encadrants }}{% endif %}.</p>
|
||||
|
|
|
@ -18,6 +18,13 @@
|
|||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<input type="submit" />
|
||||
<div class="field">
|
||||
<label>Adresse e-mail</label>
|
||||
<div class="input">
|
||||
{{ request.user.email }}
|
||||
<p class="help_text">Allez dans <a href="{% url "avisstage:parametres" %}">les paramètres de connexion</a> pour modifier votre adresse principale</p>
|
||||
</div>
|
||||
</div>
|
||||
<input type="submit" value="Enregistrer" />
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
|
@ -11,9 +11,9 @@
|
|||
|
||||
{% if not user.is_authenticated %}
|
||||
<div class="entrer">
|
||||
<p><a href="{% url "clipper_login" %}" class="btn">Connexion</a></p>
|
||||
<p><a href="{% url "authens:login.cas" %}" class="btn">Connexion</a></p>
|
||||
<p class="helptext">Connexion via le serveur central d'authentification ENS <br />(identifiants clipper)</p>
|
||||
<p class="archicubes"><a href="{% url "account_login" %}">Accès archicubes</a> <br /><i>Pour continuer à tenir à jour ses fiches, sans voir celles des autres</i></p>
|
||||
<p class="archicubes"><a href="{% url "authens:login.pwd" %}">Accès archicubes</a> <br /><i>Pour continuer à tenir à jour ses fiches, sans voir celles des autres</i></p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
|
8
avisstage/templates/avisstage/mails/reinit_mdp.html
Normal file
8
avisstage/templates/avisstage/mails/reinit_mdp.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
Bonjour,
|
||||
|
||||
Pour créer ou modifier le mot de passe associé à votre compte {{ user.get_username }}, merci de cliquer sur le lien suivant ou de le copier dans votre navigateur :
|
||||
|
||||
{{ protocol }}://{{ domain }}{% url 'avisstage:mdp_edit' uidb64=uid token=token %}
|
||||
|
||||
Cordialement,
|
||||
L'équipe ExperiENS
|
1
avisstage/templates/avisstage/mails/reinit_mdp.txt
Normal file
1
avisstage/templates/avisstage/mails/reinit_mdp.txt
Normal file
|
@ -0,0 +1 @@
|
|||
[ExperiENS] Définition du mot de passe
|
|
@ -4,38 +4,42 @@
|
|||
{% block title %}Espace personnel - ExperiENS{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Bonjour {{ user.profil.nom }} !</h1>
|
||||
<h1>Bonjour {{ user.profil.nom_complet }} !</h1>
|
||||
|
||||
<article>
|
||||
<h2>Mon compte</h2>
|
||||
<section class="profil">
|
||||
{% if user.profil.en_scolarite %}
|
||||
<h3 class="scolarite">Statut : En scolarité</h3>
|
||||
<p>Vous pouvez accéder à l'ensemble du site, et aux fiches de stages.</p>
|
||||
<p>Quand vous n'aurez plus de compte clipper (après votre scolarité), votre accès sera restreint à vos propres expériences, que vous pourrez ajouter, modifier, supprimer.</p>
|
||||
<p>Pensez à renseigner une adresse e-mail non-ENS pour conserver cet accès, et permettre aux futur⋅e⋅s normalien⋅ne⋅s de toujours vous contacter !</p>
|
||||
{% else %}
|
||||
<h3 class="scolarite">Statut : Archicube</h3>
|
||||
<p>Vous ne pouvez plus accéder qu'à vos propres expériences pour les modifier, et tenir à jour votre profil.</p>
|
||||
<p>Si vous êtes encore en scolarité, merci de vous <a href="{% url "clipper_login" %}?process=connect">reconnecter en passant par le serveur d'authentification de l'ENS</a> pour mettre à jour votre statut.</p>
|
||||
{% endif %}
|
||||
<p><i>Le statut est mis à jour automatiquement chaque année selon le mode de connexion que vous utilisez.</i></p>
|
||||
</section>
|
||||
<section class="profil">
|
||||
<h3>Adresses e-mail</h3>
|
||||
{% if not user.profil.has_nonENS_email %}<p align="center" class="warning">Vous n'avez pas renseigné d'adresse mail autre que celle de l'ENS. Pensez à le faire, pour que les générations futures puissent toujours vous contacter !</p>{% endif %}
|
||||
<p><a href="{% url "account_email" %}">Gérer les adresses e-mail liées à mon compte</a></p>
|
||||
</section>
|
||||
<section class="profil">
|
||||
<h3>Mode de connexion</h3>
|
||||
{% if user.profil.en_scolarite %}<p>En scolarité, utilisez le serveur central d'authentification pour vous connecter. Quand vous n'aurez plus de compte clipper, vous devrez vous connecter directement via l'accès archicubes, avec l'identifiant {{ user.username }}</p>{% endif %}
|
||||
{% if not user.password %}<p class="warning" align="center">Vous n'avez pas créé de mot de passe interne à ExperiENS. Pensez-y pour garder l'accès au site quand vous n'aurez plus de compte clipper !</p>{% endif %}
|
||||
<p><a href="{% url "account_change_password" %}">Créer / changer mon mot de passe ExperiENS</a></p>
|
||||
<section class="two-cols">
|
||||
<section class="profil">
|
||||
{% if user.profil.en_scolarite %}
|
||||
<h3 class="scolarite">Statut : En scolarité</h3>
|
||||
<p>Vous pouvez accéder à l'ensemble du site, et aux fiches de stages.</p>
|
||||
<p>Quand vous n'aurez plus de compte clipper (après votre scolarité), votre accès sera restreint à vos propres expériences, que vous pourrez ajouter, modifier, supprimer.</p>
|
||||
<p>Pensez à renseigner une adresse e-mail non-ENS pour conserver cet accès, et permettre aux futur⋅e⋅s normalien⋅ne⋅s de toujours vous contacter !</p>
|
||||
{% else %}
|
||||
<h3 class="scolarite">Statut : Archicube</h3>
|
||||
<p>Vous ne pouvez plus accéder qu'à vos propres expériences pour les modifier, et tenir à jour votre profil.</p>
|
||||
<p>Si vous êtes encore en scolarité, merci de vous <a href="{% url "authens:login.cas" %}">reconnecter en passant par le serveur d'authentification de l'ENS</a> pour mettre à jour votre statut.</p>
|
||||
{% endif %}
|
||||
<hr />
|
||||
<p><i>Le statut est mis à jour automatiquement tous les deux mois selon le mode de connexion que vous utilisez.</i></p>
|
||||
</section>
|
||||
<section class="profil">
|
||||
<h3>Connexion</h3>
|
||||
<p><b>Adresse e-mail principale :</b><br/> {{ user.email }}</p>
|
||||
{% if not user.profil.has_nonENS_email %}<p align="center" class="warning">Vous n'avez pas renseigné d'adresse mail autre que celle de l'ENS. Pensez à le faire, pour que les générations futures puissent toujours vous contacter !</p>{% endif %}
|
||||
<hr/>
|
||||
|
||||
<p><b>Mot de passe interne :</b> {% if user.password and user.has_usable_password %}Défini{% else %}Non défini{% endif %}</p>
|
||||
{% if not user.password or not user.has_usable_password %}<p class="warning" align="center">Pensez à définir un mot de passe propre à ExperiENS pour garder l'accès au site quand vous n'aurez plus de compte clipper !</p>{% endif %}
|
||||
{% if user.profil.en_scolarite %}<p>En scolarité, utilisez le serveur central d'authentification pour vous connecter. Quand vous n'aurez plus de compte clipper, vous devrez vous connecter directement via l'accès archicubes, avec votre login <b>{{ user.username }}</b> et le mot de passe spécifique à ExperiENS que vous aurez défini.</p>{% endif %}
|
||||
<hr/>
|
||||
<p><a href="{% url "avisstage:parametres" %}">Gérer mes paramètres de connexion</a></p>
|
||||
</section>
|
||||
</section>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<h2><a href="{% url "avisstage:profil" user.username %}">Mon profil public</a> <a href="{% url "avisstage:profil_edit" %}" class="edit-btn btn">Modifier mes infos</a></h2>
|
||||
<h2>Mon profil public <a href="{% url "avisstage:profil" user.username %}" class="btn">Voir</a> <a href="{% url "avisstage:profil_edit" %}" class="edit-btn btn">Modifier mes infos</a></h2>
|
||||
{% with object=user.profil %}
|
||||
<section class="profil">
|
||||
<div class="infos">
|
||||
|
|
|
@ -1,32 +1,36 @@
|
|||
from allauth.socialaccount.models import SocialAccount
|
||||
from allauth_cas.test.testcases import CASTestCase
|
||||
from allauth_ens.adapter import deprecate_clippers
|
||||
from authens.tests.cas_utils import FakeCASClient
|
||||
from authens.models import CASAccount, OldCASAccount
|
||||
|
||||
from datetime import date
|
||||
from datetime import date, timedelta
|
||||
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
|
||||
from unittest import mock
|
||||
|
||||
from .models import User, Normalien, Lieu, Stage, StageMatiere, AvisLieu
|
||||
|
||||
class ExperiENSTestCase(CASTestCase):
|
||||
class ExperiENSTestCase(TestCase):
|
||||
|
||||
# Dummy database
|
||||
|
||||
|
||||
def setUp(self):
|
||||
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',
|
||||
|
@ -34,7 +38,7 @@ class ExperiENSTestCase(CASTestCase):
|
|||
'archicube')
|
||||
self.p_archi = self.u_archi.profil
|
||||
self.p_archi.nom="Vieil archicube"
|
||||
self.p_archi.promotion="Gryffondor 1994"
|
||||
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",
|
||||
|
@ -52,8 +56,8 @@ class ExperiENSTestCase(CASTestCase):
|
|||
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),
|
||||
date_debut=date(2020, 5, 10),
|
||||
date_fin=date(2020, 8, 26),
|
||||
type_stage="recherche",
|
||||
niveau_scol="M1", public=True)
|
||||
self.cstage1.save()
|
||||
|
@ -63,8 +67,8 @@ class ExperiENSTestCase(CASTestCase):
|
|||
alieu1.save()
|
||||
|
||||
self.cstage2 = Stage(auteur=self.p_conscrit, sujet="Avada Kedavra",
|
||||
date_debut=date(2001, 5, 10),
|
||||
date_fin=date(2001, 8, 26),
|
||||
date_debut=date(2021, 5, 10),
|
||||
date_fin=date(2021, 8, 26),
|
||||
type_stage="sejour_dri",
|
||||
niveau_scol="M2", public=False)
|
||||
self.cstage2.save()
|
||||
|
@ -75,8 +79,8 @@ class ExperiENSTestCase(CASTestCase):
|
|||
|
||||
|
||||
self.astage1 = Stage(auteur=self.p_archi, sujet="Alohomora",
|
||||
date_debut=date(1994, 5, 10),
|
||||
date_fin=date(1994, 8, 26),
|
||||
date_debut=date(2014, 5, 10),
|
||||
date_fin=date(2014, 8, 26),
|
||||
type_stage="recherche",
|
||||
niveau_scol="M2", public=True)
|
||||
self.astage1.save()
|
||||
|
@ -100,7 +104,7 @@ class ExperiENSTestCase(CASTestCase):
|
|||
|
||||
|
||||
"""
|
||||
ACCÈS PUBLICS
|
||||
ACCÈS PUBLIC
|
||||
"""
|
||||
class PublicViewsTest(ExperiENSTestCase):
|
||||
"""
|
||||
|
@ -196,6 +200,7 @@ ACCÈS ARCHICUBE
|
|||
class ArchicubeViewsTest(ExperiENSTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
# Connexion with password
|
||||
self.client.login(username='archicube', password='archicube')
|
||||
|
||||
def assert403Archicubes(self, testurl):
|
||||
|
@ -309,16 +314,29 @@ 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()
|
||||
|
||||
# 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')
|
||||
|
||||
|
||||
|
@ -328,26 +346,34 @@ class DeprecatedArchicubeViewsTest(ArchicubeViewsTest):
|
|||
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()
|
||||
|
||||
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.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.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),
|
||||
date_debut=date(2018, 5, 10),
|
||||
date_fin=date(2018, 8, 26),
|
||||
type_stage="recherche",
|
||||
niveau_scol="M1", public=False)
|
||||
self.vstage1.save()
|
||||
|
@ -355,8 +381,9 @@ class ScolariteViewsTest(ExperiENSTestCase):
|
|||
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
|
||||
|
@ -400,7 +427,7 @@ class ScolariteViewsTest(ExperiENSTestCase):
|
|||
|
||||
|
||||
"""
|
||||
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')
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from django.conf.urls import include, url
|
||||
from django.urls import include, path
|
||||
from . import views, api
|
||||
from tastypie.api import Api
|
||||
|
||||
|
@ -7,27 +7,45 @@ 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<pk>\w+)/$', views.StageView.as_view(), name='stage'),
|
||||
url(r'^stage/(?P<pk>\w+)/edit/$', views.manage_stage, name='stage_edit'),
|
||||
url(r'^stage/(?P<pk>\w+)/publication/$', views.publier_stage,
|
||||
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/<int:pk>/', views.StageView.as_view(), name='stage'),
|
||||
path('stage/<int:pk>/edit/', views.manage_stage, name='stage_edit'),
|
||||
path('stage/<int:pk>/publication/', views.publier_stage,
|
||||
name='stage_publication'),
|
||||
url(r'^403/archicubes/$', views.archicubes_interdits,
|
||||
path('403/archicubes/', views.archicubes_interdits,
|
||||
name='403-archicubes'),
|
||||
|
||||
url(r'^lieu/save/$', views.save_lieu, name='lieu_ajout'),
|
||||
url(r'^profil/show/(?P<username>[\w@]+)/$', views.ProfilView.as_view(),
|
||||
path('lieu/save/', views.save_lieu, name='lieu_ajout'),
|
||||
path('profil/show/<str:username>/', 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,
|
||||
path('profil/edit/', views.ProfilEdit.as_view(), name='profil_edit'),
|
||||
path('profil/parametres/', views.MesParametres.as_view(), name='parametres'),
|
||||
path('profil/emails/<str:email>/aconfirmer/',
|
||||
views.AdresseAConfirmer.as_view(), name="emails_aconfirmer"),
|
||||
path('profil/emails/<str:email>/supprime/', views.SupprimeAdresse.as_view(),
|
||||
name="emails_supprime"),
|
||||
path('profil/emails/<str:email>/reconfirme/',
|
||||
views.ReConfirmeAdresse.as_view(),
|
||||
name="emails_reconfirme"),
|
||||
path('profil/emails/<str:email>/principal/',
|
||||
views.RendAdressePrincipale.as_view(), name="emails_principal"),
|
||||
path('profil/emails/confirme/<str:key>/', views.ConfirmeAdresse.as_view(),
|
||||
name="emails_confirme"),
|
||||
path('profil/mdp/demande/',
|
||||
views.EnvoieLienMotDePasse.as_view(), name="mdp_demande"),
|
||||
path('profil/mdp/<str:uidb64>/<str:token>/',
|
||||
views.DefinirMotDePasse.as_view(), name="mdp_edit"),
|
||||
|
||||
path('recherche/', views.recherche, name='recherche'),
|
||||
path('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('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)),
|
||||
]
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
# coding: utf-8
|
||||
|
||||
from allauth.socialaccount.models import SocialAccount
|
||||
from functools import reduce
|
||||
from math import cos, radians, sqrt
|
||||
|
||||
|
@ -18,3 +15,8 @@ def approximate_distance(a, b):
|
|||
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")
|
||||
|
|
|
@ -2,21 +2,31 @@
|
|||
|
||||
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.views.generic import (
|
||||
DetailView, ListView, UpdateView, CreateView, TemplateView, DeleteView,
|
||||
FormView, View
|
||||
)
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
from django import forms
|
||||
from django.urls import reverse
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.conf import settings
|
||||
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.contrib import messages
|
||||
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
|
||||
from collections import Counter, defaultdict
|
||||
from simple_email_confirmation.models import EmailAddress
|
||||
|
||||
from .models import Normalien, Stage, Lieu, AvisLieu, AvisStage
|
||||
from .forms import StageForm, LieuForm, AvisStageForm, AvisLieuForm, FeedbackForm
|
||||
from .forms import (
|
||||
StageForm, LieuForm, AvisStageForm, AvisLieuForm, FeedbackForm, AdresseEmailForm,
|
||||
ReinitMdpForm
|
||||
)
|
||||
from .utils import en_scolarite
|
||||
|
||||
from .views_search import *
|
||||
|
@ -343,3 +353,126 @@ def statistiques(request):
|
|||
'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
|
||||
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
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 @<promo> 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)
|
||||
|
|
|
@ -10,7 +10,7 @@ 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 django.urls import reverse_lazy
|
||||
|
||||
from .secrets import SECRET_KEY, GOOGLE_API_KEY, MAPBOX_API_KEY
|
||||
|
||||
|
@ -35,16 +35,12 @@ INSTALLED_APPS = [
|
|||
|
||||
'django_elasticsearch_dsl',
|
||||
|
||||
'widget_tweaks',
|
||||
'allauth_ens',
|
||||
|
||||
'allauth',
|
||||
'allauth.account',
|
||||
'allauth.socialaccount',
|
||||
'allauth_cas',
|
||||
|
||||
'allauth_ens.providers.clipper',
|
||||
#'allauth', # Uncomment that part when you
|
||||
#'allauth.account', # apply migration
|
||||
#'allauth.socialaccount', # Allauth -> AuthENS
|
||||
|
||||
'simple_email_confirmation',
|
||||
'authens',
|
||||
'tastypie',
|
||||
'braces',
|
||||
'tinymce',
|
||||
|
@ -53,12 +49,12 @@ INSTALLED_APPS = [
|
|||
'avisstage'
|
||||
]
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
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.auth.middleware.SessionAuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
)
|
||||
|
@ -121,20 +117,13 @@ AUTHENTICATION_BACKENDS = (
|
|||
)
|
||||
|
||||
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'
|
||||
|
||||
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_URL = reverse_lazy('authens:login')
|
||||
LOGOUT_URL = reverse_lazy('authens:logout')
|
||||
LOGIN_REDIRECT_URL = reverse_lazy('avisstage:perso')
|
||||
ACCOUNT_HOME_URL = reverse_lazy('avisstage:index')
|
||||
LOGOUT_REDIRECT_URL = reverse_lazy('avisstage:index')
|
||||
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
|
|
|
@ -9,16 +9,16 @@ DATABASES = {
|
|||
}
|
||||
}
|
||||
|
||||
USE_DEBUG_TOOLBAR = True
|
||||
USE_DEBUG_TOOLBAR = False
|
||||
|
||||
if USE_DEBUG_TOOLBAR:
|
||||
INSTALLED_APPS += [
|
||||
'debug_toolbar',
|
||||
]
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
MIDDLEWARE = (
|
||||
'debug_toolbar.middleware.DebugToolbarMiddleware',
|
||||
) + MIDDLEWARE_CLASSES
|
||||
) + MIDDLEWARE
|
||||
|
||||
INTERNAL_IPS = ['127.0.0.1']
|
||||
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
from django.conf import settings
|
||||
from django.conf.urls import include, url
|
||||
from django.urls import include, path
|
||||
from django.contrib import admin
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^', include('avisstage.urls', namespace='avisstage')),
|
||||
path('', include('avisstage.urls')),
|
||||
|
||||
url(r'^account/', include('allauth_ens.urls')),
|
||||
|
||||
path("authens/", include("authens.urls")),
|
||||
|
||||
url(r'^tinymce/', include('tinymce.urls')),
|
||||
url(r'^taggit_autosuggest/', include('taggit_autosuggest.urls')),
|
||||
url(r'^admin/', include(admin.site.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
|
||||
|
|
|
@ -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.*
|
||||
|
|
Loading…
Reference in a new issue