Intégration AuthENS
This commit is contained in:
parent
79eb294ce5
commit
5644ea9290
12 changed files with 116 additions and 146 deletions
|
@ -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
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class AvisstageConfig(AppConfig):
|
class AvisstageConfig(AppConfig):
|
||||||
name = 'avisstage'
|
name = 'avisstage'
|
||||||
|
|
12
avisstage/management/commands/termine_scolarite.py
Normal file
12
avisstage/management/commands/termine_scolarite.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
from avisstage.models import Normalien
|
||||||
|
|
||||||
|
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):
|
||||||
|
Normalien.objects.all().update(en_scolarite=False)
|
||||||
|
self.stdout.write(self.style.SUCCESS(u'Terminé'))
|
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),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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 import models
|
||||||
from django.db.models.signals import post_save
|
from django.db.models.signals import post_save
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
@ -20,10 +13,14 @@ from django.utils.html import strip_tags
|
||||||
from taggit_autosuggest.managers import TaggableManager
|
from taggit_autosuggest.managers import TaggableManager
|
||||||
from tinymce.models import HTMLField as RichTextField
|
from tinymce.models import HTMLField as RichTextField
|
||||||
|
|
||||||
from .utils import choices_length
|
from authens.signals import post_cas_connect
|
||||||
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.models import CASAccount
|
||||||
|
|
||||||
import ldap
|
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
|
||||||
|
)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Profil Normalien (extension du modèle User)
|
# Profil Normalien (extension du modèle User)
|
||||||
|
@ -40,7 +37,8 @@ class Normalien(models.Model):
|
||||||
max_length=200, blank=True)
|
max_length=200, blank=True)
|
||||||
contactez_moi = models.BooleanField(u"Inviter les visiteurs à me contacter",
|
contactez_moi = models.BooleanField(u"Inviter les visiteurs à me contacter",
|
||||||
default=True)
|
default=True)
|
||||||
bio = models.TextField(u"À propos de moi", blank=True, default="");
|
bio = models.TextField(u"À propos de moi", blank=True, default="")
|
||||||
|
en_scolarite = models.BooleanField(default=False, blank=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = u"Profil élève"
|
verbose_name = u"Profil élève"
|
||||||
|
@ -53,53 +51,62 @@ class Normalien(models.Model):
|
||||||
def stages_publics(self):
|
def stages_publics(self):
|
||||||
return self.stages.filter(public=True).order_by('-date_debut')
|
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):
|
def has_nonENS_email(self):
|
||||||
a = EmailAddress.objects.filter(user_id=self.user_id,
|
return not (
|
||||||
verified=True) \
|
is_email_ens(self.mail, True)
|
||||||
.exclude(email__endswith="ens.fr")
|
and is_email_ens(self.user.email, True)
|
||||||
return a.exists()
|
)
|
||||||
|
|
||||||
|
def nom_complet(self):
|
||||||
|
if self.nom.strip():
|
||||||
|
return self.nom
|
||||||
|
return self.user.username
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def preferred_email(self):
|
def preferred_email(self):
|
||||||
a = EmailAddress.objects.filter(user_id=self.user_id,
|
return self.user.email
|
||||||
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
|
|
||||||
|
|
||||||
# Hook à la création d'un nouvel utilisateur : récupération de ses infos par LDAP
|
# Hook à la création d'un nouvel utilisateur : information de base
|
||||||
def create_user_profile(sender, instance, created, **kwargs):
|
def create_basic_user_profile(sender, instance, created, **kwargs):
|
||||||
if created:
|
if created:
|
||||||
profil, created = Normalien.objects.get_or_create(user=instance)
|
profil, created = Normalien.objects.get_or_create(user=instance)
|
||||||
try:
|
|
||||||
saccount = SocialAccount.objects.get(user=instance,
|
if not created and profil.promotion != "":
|
||||||
provider="clipper")
|
|
||||||
except SocialAccount.DoesNotExist:
|
|
||||||
profil.save()
|
|
||||||
return
|
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"])
|
if "@" in instance.username:
|
||||||
profil.nom = edata.get("name", "")
|
profil.promotion = instance.username.split("@")[1]
|
||||||
profil.save()
|
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)
|
||||||
|
|
||||||
|
if not created:
|
||||||
|
if not profil.en_scolarite:
|
||||||
|
profil.en_scolarite = True
|
||||||
|
profil.save()
|
||||||
|
return
|
||||||
|
|
||||||
|
dirs = attributes.get("homeDirectory", "").split("/")
|
||||||
|
if len(dirs) < 4:
|
||||||
|
print("HomeDirectory invalide", dirs)
|
||||||
|
return
|
||||||
|
|
||||||
|
year = dirs[2]
|
||||||
|
departement = dirs[3]
|
||||||
|
print(departement, dirs)
|
||||||
|
|
||||||
|
dep = dict(DEPARTEMENTS_DEFAUT).get(departement.lower(), "")
|
||||||
|
|
||||||
|
profil.promotion = "%s %s" % (dep, year)
|
||||||
|
profil.nom = attributes.get("name", "")
|
||||||
|
profil.en_scolarite = True
|
||||||
|
profil.save()
|
||||||
|
|
||||||
|
post_cas_connect.connect(handle_cas_connection, sender=User)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Lieu de stage
|
# Lieu de stage
|
||||||
|
|
|
@ -5,10 +5,10 @@
|
||||||
<script type="text/javascript" src="{% static "js/render.js" %}"></script>
|
<script type="text/javascript" src="{% static "js/render.js" %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block title %}Profil de {{ object.nom }} - ExperiENS{% endblock %}
|
{% block title %}Profil de {{ object.nom_complet }} - ExperiENS{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Profil de {{ object.nom }}
|
<h1>Profil de {{ object.nom_complet }}
|
||||||
{% if object.user == user %}
|
{% if object.user == user %}
|
||||||
<a href="{% url "avisstage:profil_edit" %}" class="btn edit-btn">Modifier mes infos</a>
|
<a href="{% url "avisstage:profil_edit" %}" class="btn edit-btn">Modifier mes infos</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -72,7 +72,7 @@
|
||||||
<h1>{{ object.sujet }}</h1>
|
<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>
|
<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>
|
</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>
|
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.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>
|
{% if object.structure %}au sein de {{ object.structure }}{% endif %}{% if object.encadrants %}, supervisé par {{ object.encadrants }}{% endif %}.</p>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
{% block title %}Espace personnel - ExperiENS{% endblock %}
|
{% block title %}Espace personnel - ExperiENS{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Bonjour {{ user.profil.nom }} !</h1>
|
<h1>Bonjour {{ user.profil.nom_complet }} !</h1>
|
||||||
|
|
||||||
<article>
|
<article>
|
||||||
<h2>Mon compte</h2>
|
<h2>Mon compte</h2>
|
||||||
|
@ -17,20 +17,20 @@
|
||||||
{% else %}
|
{% else %}
|
||||||
<h3 class="scolarite">Statut : Archicube</h3>
|
<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>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>
|
<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 %}
|
{% endif %}
|
||||||
<p><i>Le statut est mis à jour automatiquement chaque année selon le mode de connexion que vous utilisez.</i></p>
|
<p><i>Le statut est mis à jour automatiquement chaque année selon le mode de connexion que vous utilisez.</i></p>
|
||||||
</section>
|
</section>
|
||||||
<section class="profil">
|
<section class="profil">
|
||||||
<h3>Adresses e-mail</h3>
|
<h3>Adresse 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 %}
|
{% 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>
|
<p><a href="{% url "avisstage:perso" %}">Gérer les adresses e-mail liées à mon compte</a></p>
|
||||||
</section>
|
</section>
|
||||||
<section class="profil">
|
<section class="profil">
|
||||||
<h3>Mode de connexion</h3>
|
<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 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 {{ user.cas_account.cas_login }} et le mot de passe spécifique à ExperiENS que vous aurez défini.</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 %}
|
{% if not user.password or not user.has_usable_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>
|
<p><a href="{% url "authens:reset.pwd" %}">Créer / changer mon mot de passe ExperiENS</a></p>
|
||||||
</section>
|
</section>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
# coding: utf-8
|
|
||||||
|
|
||||||
from allauth.socialaccount.models import SocialAccount
|
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from math import cos, radians, sqrt
|
from math import cos, radians, sqrt
|
||||||
|
|
||||||
|
@ -18,3 +15,8 @@ def approximate_distance(a, b):
|
||||||
dlat = (lat_a - lat_b)
|
dlat = (lat_a - lat_b)
|
||||||
distance = 6371000 * sqrt(dlon*dlon + dlat*dlat)
|
distance = 6371000 * sqrt(dlon*dlon + dlat*dlat)
|
||||||
return distance
|
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")
|
||||||
|
|
|
@ -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):
|
class ENSCASBackend(AuthENSBackend):
|
||||||
def clean_username(self, username):
|
# Override AuthENS backend user creation to implement the @<promo> logic
|
||||||
return username.lower().strip()
|
|
||||||
|
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)
|
||||||
|
|
|
@ -36,14 +36,6 @@ INSTALLED_APPS = [
|
||||||
'django_elasticsearch_dsl',
|
'django_elasticsearch_dsl',
|
||||||
|
|
||||||
'widget_tweaks',
|
'widget_tweaks',
|
||||||
'allauth_ens',
|
|
||||||
|
|
||||||
'allauth',
|
|
||||||
'allauth.account',
|
|
||||||
'allauth.socialaccount',
|
|
||||||
'allauth_cas',
|
|
||||||
|
|
||||||
'allauth_ens.providers.clipper',
|
|
||||||
|
|
||||||
'authens',
|
'authens',
|
||||||
'tastypie',
|
'tastypie',
|
||||||
|
@ -118,24 +110,17 @@ STATIC_URL = '/static/'
|
||||||
|
|
||||||
AUTHENTICATION_BACKENDS = (
|
AUTHENTICATION_BACKENDS = (
|
||||||
'django.contrib.auth.backends.ModelBackend',
|
'django.contrib.auth.backends.ModelBackend',
|
||||||
'authens.backends.ENSCASBackend',
|
'experiENS.auth.ENSCASBackend',
|
||||||
)
|
)
|
||||||
|
|
||||||
CAS_SERVER_URL = "https://cas.eleves.ens.fr/" #SPI CAS
|
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'
|
AUTHENS_USE_OLDCAS = False
|
||||||
SOCIALACCOUNT_ADAPTER = 'avisstage.allauth_adapter.SocialAccountAdapter'
|
|
||||||
|
|
||||||
LOGIN_URL = reverse_lazy('authens:login')
|
LOGIN_URL = reverse_lazy('authens:login')
|
||||||
LOGOUT_URL = reverse_lazy('authens:logout')
|
LOGOUT_URL = reverse_lazy('authens:logout')
|
||||||
LOGIN_REDIRECT_URL = reverse_lazy('avisstage:perso')
|
LOGIN_REDIRECT_URL = "/perso/"#reverse_lazy('avisstage:perso')
|
||||||
ACCOUNT_HOME_URL = reverse_lazy('avisstage:index')
|
LOGOUT_REDIRECT_URL = "/"
|
||||||
|
|
||||||
LOGGING = {
|
LOGGING = {
|
||||||
'version': 1,
|
'version': 1,
|
||||||
|
|
|
@ -7,7 +7,6 @@ urlpatterns = [
|
||||||
|
|
||||||
|
|
||||||
path("authens/", include("authens.urls")),
|
path("authens/", include("authens.urls")),
|
||||||
path('account/', include('allauth_ens.urls')),
|
|
||||||
|
|
||||||
path('tinymce/', include('tinymce.urls')),
|
path('tinymce/', include('tinymce.urls')),
|
||||||
path('taggit_autosuggest/', include('taggit_autosuggest.urls')),
|
path('taggit_autosuggest/', include('taggit_autosuggest.urls')),
|
||||||
|
|
Loading…
Reference in a new issue