Compare commits

..

15 commits

Author SHA1 Message Date
Ludovic Stephan
317077951b Markdown fix 2020-11-16 19:12:22 +01:00
Ludovic Stephan
5ee66b453a README 2020-11-16 19:10:02 +01:00
Ludovic Stephan
3fee57061d Fix : side effects in local.py 2020-11-16 19:07:10 +01:00
Ludovic Stephan
d3ec952de8 Debug toolbar on vagrant 2020-11-16 19:03:24 +01:00
Ludovic Stephan
f45812a076 Fix les permissions des fichiers statiques sous vagrant 2020-11-16 18:50:45 +01:00
Ludovic Stephan
700272ac22 VAGRANT 2020-11-16 18:41:23 +01:00
Ludovic Stephan
764865c9d8 Ajoute la debug_toolbar 2020-11-16 18:41:06 +01:00
Ludovic Stephan
43026cecd2 Split settings between local and prod 2020-11-16 18:40:46 +01:00
Ludovic Stephan
d7263fc9e0 Split requirements 2020-11-16 18:39:29 +01:00
Ludovic Stephan
1d24478c1d Split in 2 commands 2020-11-16 18:33:41 +01:00
Ludovic Stephan
df10e754a5 requirements 2020-11-16 18:33:41 +01:00
Ludovic Stephan
bf05b0a7d4 Commande pour ajouter les conscrit·e·s 2020-11-16 18:33:41 +01:00
Ludovic Stephan
ef0076781e Rajoute les serveurs ldap aux settings 2020-11-16 18:33:41 +01:00
Ludovic Stephan
d01d4d4d51 Blackify settings (oups) 2020-11-16 18:33:41 +01:00
Ludovic Stephan
4289eed6d5 Small model tweaks 2020-11-16 18:33:41 +01:00
68 changed files with 914 additions and 4341 deletions

View file

@ -1,5 +0,0 @@
{
"PROTOCOL": "http",
"URL": "annuaire.example.com",
"PORT": 80
}

View file

@ -1,12 +0,0 @@
{
"SPI": {
"PROTOCOL": "ldaps",
"URL": "ldap.example.com",
"PORT": 636
},
"CRI": {
"PROTOCOL": "ldap",
"URL": "ldap.example.com",
"PORT": 636
}
}

View file

@ -1 +0,0 @@
insecure-key

1
.envrc
View file

@ -1 +0,0 @@
use nix

4
.gitignore vendored
View file

@ -9,12 +9,10 @@
/venv/
.vagrant/
/static/
static/
*.sqlite3
fiches/templates/fiches/base_old.html
fiches/static/fiches/css_old/
.vscode
.direnv
annuaire/settings/secret.py

View file

@ -1,6 +1,8 @@
# Annuaire des élèves de l'ENS
## Installation
## Environnement de développement
### Méthode facile : environnement virtuel python
Il est fortement conseillé d'utiliser un environnement virtuel pour Python.
@ -33,6 +35,29 @@ Vous êtes prêts à développer ! Lancez l'annuaire avec :
python manage.py runserver
### Plus compliqué : machine virtuelle Vagrant
Pour avoir une situation plus proche de la situation en production, il est possible de faire tourner le site depuis une
machine virtuelle Windows. Pour cela, il faut d'abord installer `vagrant` et `virtualbox` :
sudo apt install vagrant virtualbox
Ensuite, la commande `vagrant up` devrait créer et configurer la machine virtuelle ; un peu de patience, cela peut prendre
du temps ! Une fois fini, il y a deux possibilités :
- `vagrant ssh` permet de se connecter à la machine, et d'effectuer des opérations à l'aide de `manage.py`. On peut effectuer
à peu près les mêmes opérations que pour un virtualenv classique, à une différence près : il faut utiliser
```
python manage.py runserver 0.0.0.0:8000
```
pour lancer le serveur, afin d'y avoir accès depuis son navigateur.
- un serveur normalement très proche de celui de production (avec `gunicorn` + `nginx`) tourne en permanence sur la machine ;
il suffit de visiter `127.0.0.1:8080` pour y avoir accès !
## Développement
En manque d'inspiration ? N'hésitez pas à aller lire les issues ouvertes actuellement, il y en a pour tous les niveaux !

47
Vagrantfile vendored Normal file
View file

@ -0,0 +1,47 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure(2) do |config|
# The most common configuration options are documented and commented below.
# For a complete reference, please see the online documentation at
# https://docs.vagrantup.com.
config.vm.box = "ubuntu/focal64"
# On associe le port 80 dans la machine virtuelle avec le port 8080 de notre
# ordinateur, et le port 8000 avec le port 8000.
config.vm.network :forwarded_port, guest: 80, host: 8080
config.vm.network :forwarded_port, guest: 8000, host: 8000
# Create a private network, which allows host-only access to the machine
# using a specific IP.
# config.vm.network "private_network", ip: "192.168.33.10"
# Provider-specific configuration so you can fine-tune various
# backing providers for Vagrant. These expose provider-specific options.
# Example for VirtualBox:
#
# config.vm.provider "virtualbox" do |vb|
# # Display the VirtualBox GUI when booting the machine
# vb.gui = true
#
# # Customize the amount of memory on the VM:
# vb.memory = "1024"
# end
#
# View the documentation for the provider you are using for more
# information on available options.
# Enable provisioning with a shell script. Additional provisioners such as
# Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
# documentation for more information about their specific syntax and use.
# config.vm.provision "shell", inline: <<-SHELL
# sudo apt-get update
# sudo apt-get install -y apache2
# SHELL
config.vm.provision :shell, path: "provisioning/bootstrap.sh", args: ENV['PWD']
end

1
annuaire/settings/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
secret.py

View file

130
annuaire/settings/common.py Normal file
View file

@ -0,0 +1,130 @@
"""
Settings communs entre setups de dev et de production.
"""
import os
import sys
# ---
# Secrets
# ---
try:
from . import secret
except ImportError:
raise ImportError(
"The secret.py file is missing.\n"
"For a development environment, simply copy secret_example.py"
)
def import_secret(name):
"""
Shorthand for importing a value from the secret module and raising an
informative exception if a secret is missing.
"""
try:
return getattr(secret, name)
except AttributeError:
raise RuntimeError("Secret missing: {}".format(name))
SECRET_KEY = import_secret("SECRET_KEY")
ADMINS = import_secret("ADMINS")
SERVER_EMAIL = import_secret("SERVER_EMAIL")
EMAIL_HOST = import_secret("EMAIL_HOST")
# ---
# Défauts Django
# ---
DEBUG = False # False by default feels safer
TESTING = len(sys.argv) > 1 and sys.argv[1] == "test"
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"django_cas_ng",
"fiches",
]
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",
]
ROOT_URLCONF = "annuaire.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
AUTHENTICATION_BACKENDS = (
"django.contrib.auth.backends.ModelBackend",
"django_cas_ng.backends.CASBackend",
)
WSGI_APPLICATION = "annuaire.wsgi.application"
# Internationalization
# https://docs.djangoproject.com/en/dev/topics/i18n/
LANGUAGE_CODE = "fr-fr"
TIME_ZONE = "UTC"
USE_I18N = True
USE_L10N = True
USE_TZ = True
# ---
# Settings CAS
# ---
CAS_SERVER_URL = "https://cas.eleves.ens.fr/"
CAS_VERSION = "2"
# ---
# LDAP et annuaire ENS
# ---
# Est-ce vraiment nécessaire de les garder secrets ?
LDAP = {
"SPI": {
"PROTOCOL": "ldaps",
"URL": "ldap.spi.ens.fr",
"PORT": 636,
},
"CRI": {
"PROTOCOL": "ldap",
"URL": "annuaire.ens.fr",
"PORT": 389,
},
}
ANNUAIRE = {
"PROTOCOL": "http",
"URL": "annuaireweb.ens.fr",
"PORT": 80,
}

View file

@ -0,0 +1,62 @@
"""
Settings pour le dev local de l'annuaire (hors vagrant).
"""
import os
from .common import * # NOQA
from .common import BASE_DIR, INSTALLED_APPS, MIDDLEWARE, TESTING
# ---
# Tweaks for debug/local development
# ---
ALLOWED_HOSTS = []
DEBUG = True
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
STATIC_URL = "/static/"
MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": os.path.join(BASE_DIR, "db.sqlite3"),
}
}
# Use the default cache backend for local development
CACHES = {"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"}}
# Pas besoin de sécurité en local
AUTH_PASSWORD_VALIDATORS = []
PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]
# ---
# Debug tool bar
# ---
def show_toolbar(request):
"""
On active la debug-toolbar en mode développement local sauf :
- dans l'admin où ça ne sert pas à grand chose;
- si la variable d'environnement DJANGO_NO_DDT est à 1 → ça permet de la désactiver
sans modifier ce fichier en exécutant `export DJANGO_NO_DDT=1` dans le terminal
qui lance `./manage.py runserver`.
Autre side effect de cette fonction : on ne fait pas la vérification de INTERNAL_IPS
que ferait la debug-toolbar par défaut, ce qui la fait fonctionner aussi à
l'intérieur de Vagrant (comportement non testé depuis un moment…)
"""
env_no_ddt = bool(os.environ.get("DJANGO_NO_DDT", None))
return DEBUG and not env_no_ddt and not request.path.startswith("/admin/")
if not TESTING:
INSTALLED_APPS = INSTALLED_APPS + ["debug_toolbar"]
MIDDLEWARE = ["debug_toolbar.middleware.DebugToolbarMiddleware"] + MIDDLEWARE
DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": show_toolbar}

81
annuaire/settings/prod.py Normal file
View file

@ -0,0 +1,81 @@
"""
Settings pour la mise en production de l'annuaire.
"""
import os
from .common import * # NOQA
from .common import (
BASE_DIR,
import_secret,
)
# ---
# Prod-specific secrets
# ---
REDIS_PASSWD = import_secret("REDIS_PASSWD")
REDIS_DB = import_secret("REDIS_DB")
REDIS_HOST = import_secret("REDIS_HOST")
REDIS_PORT = import_secret("REDIS_PORT")
DBNAME = import_secret("DBNAME")
DBUSER = import_secret("DBUSER")
DBPASSWD = import_secret("DBPASSWD")
# ---
# À modifier possiblement lors de la mise en production
# ---
ALLOWED_HOSTS = ["annuaire.eleves.ens.fr", "www.annuaire.eleves.ens.fr"]
STATIC_ROOT = os.path.join(
os.path.dirname(os.path.dirname(BASE_DIR)), "public", "annuaire", "static"
)
STATIC_URL = "/static/"
MEDIA_ROOT = os.path.join(os.path.dirname(BASE_DIR), "media")
MEDIA_URL = "/media/"
# ---
# Cache settings
# ---
CACHES = {
"default": {
"BACKEND": "redis_cache.RedisCache",
"LOCATION": "redis://:{passwd}@{host}:{port}/{db}".format(
passwd=REDIS_PASSWD, host=REDIS_HOST, port=REDIS_PORT, db=REDIS_DB
),
}
}
# ---
# Prod database settings
# ---
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql_psycopg2",
"NAME": DBNAME,
"USER": DBUSER,
"PASSWORD": DBPASSWD,
"HOST": os.environ.get("DBHOST", "localhost"),
}
}
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", # noqa
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]

View file

@ -0,0 +1,15 @@
SECRET_KEY = "$=kp$3e=xh)*4h8(_g#lprlmve_vs9_xv9hlgse%+uk9nhc==x"
ADMINS = None
SERVER_EMAIL = "root@localhost"
EMAIL_HOST = None
# Ne pas modifier si on utilise vagrant !
DBUSER = "annuaire"
DBNAME = "annuaire"
DBPASSWD = "O1LxCADDA6Px5SiKvifjvdp3DSjfbp"
REDIS_PASSWD = "dummy"
REDIS_PORT = 6379
REDIS_DB = 0
REDIS_HOST = "127.0.0.1"

View file

@ -0,0 +1,24 @@
"""
Settings pour le développement de l'annuaire avec vagrant.
Essaie de rester le plus fidèle possible aux settings de production,
avec des différences les plus minimes possibles.
"""
from .prod import * # noqa
from .prod import TESTING, INSTALLED_APPS, MIDDLEWARE
from .local import show_toolbar
DEBUG = True
MEDIA_ROOT = "/srv/annuaire/media"
STATIC_ROOT = "/srv/annuaire/static"
EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend"
EMAIL_FILE_PATH = "/var/mail/django"
ALLOWED_HOSTS = ["127.0.0.1", "localhost", "0.0.0.0"]
if not TESTING:
INSTALLED_APPS += ["debug_toolbar"]
MIDDLEWARE = ["debug_toolbar.middleware.DebugToolbarMiddleware"] + MIDDLEWARE
DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": show_toolbar}

View file

@ -14,20 +14,23 @@ Including another URLconf
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import include, path
from django.urls import path, include
from django.conf.urls.static import static
from fiches.views import BirthdayView, HomeView
import django_cas_ng.views as cas_views
urlpatterns = [
path("", include("fiches.urls")),
path("admin/", admin.site.urls),
path("authens/", include("authens.urls")),
path("i18n/", include("django.conf.urls.i18n")),
path("fiche/", include("fiches.urls")),
path("", HomeView.as_view(), name="home"),
path("birthday", BirthdayView.as_view(), name="birthday"),
path("accounts/login/", cas_views.LoginView.as_view(), name="cas_ng_login"),
path("logout", cas_views.LogoutView.as_view(), name="cas_ng_logout"),
]
if settings.DEBUG and "debug_toolbar" in settings.INSTALLED_APPS:
from debug_toolbar import urls as debug_urls
import debug_toolbar
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + [
path("__debug__/", include(debug_urls))
]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += [path("__debug__/", include(debug_toolbar.urls))]

View file

@ -11,6 +11,6 @@ import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings')
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'annuaire.settings')
application = get_wsgi_application()

View file

@ -1,183 +0,0 @@
"""
Django settings for the annuaire project
"""
import os
from pathlib import Path
from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _
from loadcredential import Credentials
credentials = Credentials(env_prefix="ANNUAIRE_")
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# WARNING: keep the secret key used in production secret!
SECRET_KEY = credentials["SECRET_KEY"]
# WARNING: don't run with debug turned on in production!
DEBUG = credentials.get_json("DEBUG", False)
ALLOWED_HOSTS = credentials.get_json("ALLOWED_HOSTS", [])
ADMINS = credentials.get_json("ADMINS", [])
###
# List the installed applications
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"authens",
"fiches",
]
###
# List the installed middlewares
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",
]
###
# The main url configuration
ROOT_URLCONF = "app.urls"
###
# Template configuration:
# - Django Templating Language is used
# - Application directories can be used
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
###
# Database configuration
# -> https://docs.djangoproject.com/en/4.2/ref/settings/#databases
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
DATABASES = credentials.get_json(
"DATABASES",
{
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
},
)
CACHES = credentials.get_json(
"CACHES",
default={
"default": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
},
},
)
###
# WSGI application configuration
WSGI_APPLICATION = "app.wsgi.application"
###
# Staticfiles configuration
STATIC_ROOT = credentials["STATIC_ROOT"]
STATIC_URL = "/static/"
MEDIA_ROOT = credentials.get("MEDIA_ROOT", BASE_DIR / "media")
MEDIA_URL = "/media/"
###
# Internationalization configuration
# -> https://docs.djangoproject.com/en/4.2/topics/i18n/
LANGUAGE_CODE = "fr-fr"
TIME_ZONE = "Europe/Paris"
USE_I18N = True
USE_L10N = True
USE_TZ = True
LANGUAGES = [
("fr", _("Français")),
("en", _("Anglais")),
]
LOCALE_PATHS = [BASE_DIR / "locale"]
###
# Authentication configuration
AUTHENS_USE_OLDCAS = False
AUTHENS_USE_PASSWORD = False
LOGIN_URL = reverse_lazy("authens:login")
LOGOUT_REDIRECT_URL = reverse_lazy("home")
AUTHENTICATION_BACKENDS = (
"django.contrib.auth.backends.ModelBackend",
"fiches.backends.BackendFiches",
)
AUTH_PASSWORD_VALIDATORS = map(
lambda v: {"NAME": f"django.contrib.auth.password_validation.{v}"},
[
"UserAttributeSimilarityValidator",
"MinimumLengthValidator",
"CommonPasswordValidator",
"NumericPasswordValidator",
],
)
LDAP = credentials.get_json("LDAP")
ANNUAIRE = credentials.get_json("ANNUAIRE")
# FIXME: Add correct email settings
# Development settings
if DEBUG:
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
AUTH_PASSWORD_VALIDATORS = []
PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]
LOGIN_URL = reverse_lazy("authens:login")
AUTHENS_USE_PASSWORD = True

View file

@ -1,45 +0,0 @@
{
sources ? import ./npins,
pkgs ? import sources.nixpkgs { },
}:
let
nix-pkgs = import sources.nix-pkgs { inherit pkgs; };
python3 = pkgs.python3.override {
packageOverrides = _: _: {
inherit (nix-pkgs) authens loadcredential;
};
};
in
{
devShell = pkgs.mkShell {
name = "annuaire.dev";
packages = [
(python3.withPackages (ps: [
ps.django
ps.pillow
ps.loadcredential
ps.authens
ps.python-dateutil
]))
];
env = {
DJANGO_SETTINGS_MODULE = "app.settings";
CREDENTIALS_DIRECTORY = builtins.toString ./.credentials;
ANNUAIRE_DEBUG = builtins.toJSON true;
ANNUAIRE_STATIC_ROOT = builtins.toString ./.static;
};
shellHook = ''
if [ ! -d .static ]; then
mkdir .static
fi
'';
};
}

View file

@ -1,19 +0,0 @@
from authens.backends import ENSCASBackend, ENSCASError
from authens.utils import parse_entrance_year
from fiches.models import Profile
class BackendFiches(ENSCASBackend):
"""Wrapper around AuthENS's CAS authentication backend.
Ensures the required field promotion year is non-empty"""
def create_user(self, username, attributes):
"""create a CAS user
overwrite to add required promotion"""
user = super().create_user(username, attributes)
entrance_year = parse_entrance_year(attributes.get("homeDirectory"))
if entrance_year is None:
raise ENSCASError("Entrance year not available")
Profile.objects.create(user=user, promotion=entrance_year)
return user

View file

@ -1,24 +1,17 @@
from django import forms
from django.forms.models import inlineformset_factory
from django.utils.translation import gettext_lazy as _
from fiches.models import Address, Department, Mail, Phone, Profile, Social
from fiches.models import Profile, Department, Phone, Social, Mail, Address
class ProfileForm(forms.ModelForm):
birth_date = forms.DateField(
input_formats=["%Y-%m-%d"],
required=False,
)
class Meta:
model = Profile
exclude = ["user"]
class SearchForm(forms.Form):
name = forms.CharField(label=_("Nom/Surnom"), max_length=1023, required=False)
year = forms.IntegerField(label=_("Promotion"), required=False)
name = forms.CharField(label="Nom/Surnom", max_length=1023, required=False)
year = forms.IntegerField(label="Promotion", required=False)
department = forms.ModelMultipleChoiceField(
queryset=Department.objects.all(), required=False
)
@ -30,13 +23,13 @@ class SearchForm(forms.Form):
and not cleaned_data["year"]
and not cleaned_data["department"]
):
raise forms.ValidationError(_("Tous les champs sont vides"), code="invalid")
raise forms.ValidationError(("Tous les champs sont vides"), code="invalid")
PhoneFormSet = inlineformset_factory(Profile, Phone, exclude=[], extra=0)
PhoneFormSet = inlineformset_factory(Profile, Phone, exclude=[])
SocialFormSet = inlineformset_factory(Profile, Social, exclude=[], extra=0)
SocialFormSet = inlineformset_factory(Profile, Social, exclude=[])
MailFormSet = inlineformset_factory(Profile, Mail, exclude=[], extra=0)
MailFormSet = inlineformset_factory(Profile, Mail, exclude=[])
AddressFormSet = inlineformset_factory(Profile, Address, exclude=[], extra=0)
AddressFormSet = inlineformset_factory(Profile, Address, exclude=[])

View file

@ -25,9 +25,16 @@ class LDAP:
def __init__(self):
""" Initialize the LDAP object """
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
self.ldap_obj = ldap.initialize(
"{PROTOCOL}://{URL}:{PORT}".format(**self.LDAP_SERVER)
)
self.ldap_obj.set_option(ldap.OPT_REFERRALS, 0)
self.ldap_obj.set_option(ldap.OPT_PROTOCOL_VERSION, 3)
self.ldap_obj.set_option(ldap.OPT_X_TLS, ldap.OPT_X_TLS_DEMAND)
self.ldap_obj.set_option(ldap.OPT_X_TLS_DEMAND, True)
self.ldap_obj.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
self.ldap_obj.set_option(ldap.OPT_TIMEOUT, 10)
def search(self, filters="(objectClass=*)"):
"""Do a ldap.search_s with the given filters, as specified for
@ -73,11 +80,11 @@ class ClipperLDAP(LDAP):
raise ValueError("Invalid home directory")
# Ça casse en 2100, mais le système de naming de sas aussi...
promo = 2000 + int(promo)
promo = "20" + promo
return promo, self.verbose_depts.get(dept, None)
def get_clipper_list(self, promo_filter=None, verbosity=1, stdout=None):
def get_clipper_list(self, promo_filter=None):
"""Extrait la liste des comptes clipper présents dans le LDAP.
Renvoie une liste de `namedTuple` contenant leur nom, prénom, adresse mail et
département/année d'entrée.
@ -101,11 +108,7 @@ class ClipperLDAP(LDAP):
clipper_list.append(
ClipperAccount(uid, name, email, promo, dept)
)
if verbosity == 3:
stdout.write("Compte clipper trouvé : {}".format(uid))
except ValueError:
if verbosity >= 2:
stdout.write("Entrée malformée trouvée : {}".format(entry))
pass
return clipper_list
@ -118,7 +121,7 @@ class AnnuaireLDAP(LDAP):
search_base = "ou=people,dc=ens,dc=fr"
attr_list = ["uid", "sn", "givenName", "ou"]
def try_match(self, profile, verbosity=1, stderr=None):
def try_match(self, profile):
"""Essaie de trouver une entrée correspondant au profile donné dans
l'annuaire de l'ENS. L'heuristique est la suivante : il est très probable
que le prénom de la personne commence par le premier mot de `profile.full_name`,
@ -133,12 +136,11 @@ class AnnuaireLDAP(LDAP):
if len(search_name) > 0:
if len(search_name) > 2:
if verbosity >= 2:
stderr.write(
"Erreur : deux logins CRI trouvés pour {} ({})".format(
profile.user.username, profile.promotion
)
print(
"Erreur : deux résultats trouvés pour {}".format(
profile.user.username
)
)
return None
return self.extract_ldap_info(search_name[0], "uid")

View file

@ -1,7 +1,7 @@
from datetime import date
from django.contrib.auth.models import User
from django.core.management.base import BaseCommand, CommandError
from django.core.management.base import BaseCommand
from fiches.models import Department, Profile
@ -16,7 +16,7 @@ class Command(BaseCommand):
group.add_argument(
"--all", action="store_true", help="Importe l'intégralité des promotions"
)
group.add_argument("--promo", type=int, help="Spécifie la promotion à importer")
group.add_argument("--promo", help="Spécifie la promotion à importer")
def get_current_promo(self):
today = date.today()
@ -24,28 +24,21 @@ class Command(BaseCommand):
if today.month < 9:
year -= 1
return year
return str(year)
def handle(self, *args, **options):
if options["all"]:
promo = None
elif options["promo"] is not None:
promo = options["promo"]
if promo < 100:
promo = 2000 + promo
if len(promo) == 2:
promo = "20" + promo
else:
promo = self.get_current_promo()
if promo < 2000 or promo > 2100:
raise CommandError("Promotion invalide : {}".format(promo))
verbosity = options["verbosity"]
# On récupère la liste des élèves à créer
ldap = ClipperLDAP()
clipper_list = ldap.get_clipper_list(
promo_filter=promo, verbosity=verbosity, stdout=self.stdout
)
clipper_list = ldap.get_clipper_list(promo_filter=promo)
# On vire les élèves déjà existants
existing_users = set(User.objects.values_list("username", flat=True))
@ -74,18 +67,6 @@ class Command(BaseCommand):
)
)
# À décommenter pour utilisation locale (avec SQLite)
# def _manual_ids(cls, to_create):
# pid = getattr(cls.objects.order_by("-id").first(), "id", 1)
# for p in to_create:
# pid += 1
# p.id = pid
# _manual_ids(User, users_to_create)
# _manual_ids(Profile, profiles_to_create)
# _manual_ids(Profile.department.through, dept_m2m_to_create)
User.objects.bulk_create(users_to_create)
for profile in profiles_to_create:
profile.user_id = profile.user.id
@ -94,10 +75,8 @@ class Command(BaseCommand):
dept_m2m.profile_id = dept_m2m.profile.id
Profile.department.through.objects.bulk_create(dept_m2m_to_create)
if verbosity >= 1:
self.stdout.write(
(
"Création de {} utilisateur·ices et de {}"
" profils effectuée avec succès"
).format(len(users_to_create), len(profiles_to_create))
print(
"Création de {} utilisateur·ices et de {} profils effectuée avec succès".format(
len(users_to_create), len(profiles_to_create)
)
)

View file

@ -13,14 +13,14 @@ from ._ldap import AnnuaireLDAP
class Command(BaseCommand):
help = "Si possible, importe les photos des conscrit·e·s dans l'annuaire."
help = "Si possible, import les photos des conscrit·e·s dans l'annuaire."
def add_arguments(self, parser):
group = parser.add_mutually_exclusive_group()
group.add_argument(
"--all", action="store_true", help="Importe l'intégralité des promotions"
)
group.add_argument("--promo", type=int, help="Spécifie la promotion à importer")
group.add_argument("--promo", help="Spécifie la promotion à importer")
def get_current_promo(self):
today = date.today()
@ -35,13 +35,11 @@ class Command(BaseCommand):
promo = None
elif options["promo"] is not None:
promo = options["promo"]
if promo < 100:
promo = 2000 + promo
if len(promo) == 2:
promo = "20" + promo
else:
promo = self.get_current_promo()
verbosity = options["verbosity"]
no_images = Profile.objects.select_related("user").filter(picture="")
if promo is not None:
@ -53,9 +51,7 @@ class Command(BaseCommand):
success = 0
for profile in no_images:
cri_login = cri_ldap.try_match(
profile, verbosity=verbosity, stderr=self.stderr
)
cri_login = cri_ldap.try_match(profile)
if cri_login is not None:
img_url = "{}/photos/{}.jpg".format(base_annuaire, cri_login)
try:
@ -65,32 +61,13 @@ class Command(BaseCommand):
content=File(BytesIO(istream.read())),
)
success += 1
if verbosity == 3:
self.stdout.write(
"Photo trouvée pour {} ({})".format(
profile.user.username, profile.promotion
)
)
except HTTPError:
# Parfois, même si on trouve un login CRI, il y a une erreur 404.
# Dans ce cas, pas de photo : on échoue gracieusement.
if verbosity >= 2:
self.stdout.write(
"Login CRI trouvé mais pas de photo pour {} ({})".format(
profile.user.username, profile.promotion
)
)
pass
elif verbosity >= 2:
self.stdout.write(
"Pas de login CRI trouvé pour {} ({})".format(
profile.user.username, profile.promotion
)
)
if verbosity >= 1:
self.stdout.write(
"{} profils traités ; {} images importées.".format(
no_images.count(), success
)
print(
"{} profils traités ; {} images importées.".format(
no_images.count(), success
)
)

View file

@ -1,18 +0,0 @@
# Generated by Django 2.2.17 on 2021-01-28 00:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fiches', '0008_auto_20201113_1038'),
]
operations = [
migrations.AlterField(
model_name='address',
name='content',
field=models.TextField(verbose_name='adresse'),
),
]

View file

@ -1,23 +0,0 @@
# Generated by Django 2.2.17 on 2021-01-28 10:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fiches', '0009_multiline_address'),
]
operations = [
migrations.AddField(
model_name='profile',
name='experiences',
field=models.TextField(blank=True, verbose_name='expériences'),
),
migrations.AddField(
model_name='profile',
name='past_studies',
field=models.TextField(blank=True, verbose_name='études passées'),
),
]

View file

@ -1,11 +1,7 @@
from datetime import date
from typing import Optional
from dateutil.relativedelta import relativedelta
from django.contrib.auth.models import User
from django.core.validators import MinValueValidator
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.core.validators import MinValueValidator
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
class Profile(models.Model):
@ -30,8 +26,6 @@ class Profile(models.Model):
birth_date = models.DateField(
blank=True, null=True, verbose_name=_("date de naissance")
)
past_studies = models.TextField(blank=True, verbose_name=_("études passées"))
experiences = models.TextField(blank=True, verbose_name=_("expériences"))
thurne = models.CharField(blank=True, max_length=100, verbose_name=_("thurne"))
text_field = models.TextField(blank=True, verbose_name=_("champ libre"))
printing = models.BooleanField(
@ -41,18 +35,10 @@ class Profile(models.Model):
default=False, verbose_name=_("conserver la fiche annuaire ?")
)
def __str__(self) -> str:
def __str__(self):
return self.full_name
@property
def age(self) -> Optional[int]:
if self.birth_date is not None:
return relativedelta(date.today(), self.birth_date).year
def birthday(self) -> str:
if self.birth_date is None:
return "Unknown"
def birthday(self):
return self.birth_date.strftime("%d%m")
@ -61,7 +47,7 @@ class Department(models.Model):
max_length=255, verbose_name=_("nom du département"), unique=True
)
def __str__(self) -> str:
def __str__(self):
return self.name
@ -72,7 +58,7 @@ class Phone(models.Model):
name = models.CharField(max_length=255, verbose_name=_("type"))
number = models.CharField(max_length=1023, verbose_name=_("numéro"))
def __str__(self) -> str:
def __str__(self):
return "{} : {}".format(self.name, self.number)
@ -94,7 +80,7 @@ class Mail(models.Model):
name = models.CharField(max_length=255, verbose_name=_("type"))
mail = models.CharField(max_length=1023, verbose_name=_("adresse mail"))
def __str__(self) -> str:
def __str__(self):
return "{} : {}".format(self.name, self.mail)
@ -103,7 +89,7 @@ class Address(models.Model):
Profile, on_delete=models.CASCADE, verbose_name=_("profil")
)
name = models.CharField(max_length=255, verbose_name=_("type"))
content = models.TextField(verbose_name=_("adresse"))
content = models.CharField(max_length=1023, verbose_name=_("adresse"))
def __str__(self) -> str:
def __str__(self):
return "{} : {}".format(self.name, self.content)

View file

@ -28,38 +28,73 @@
font-weight: 400;
src: local("Source Code Pro Regular"), local("SourceCodePro-Regular"), url("../fonts/source-code-pro/source-code-pro-v12-latin-regular.woff2") format("woff2"), url("../fonts/source-code-pro/source-code-pro-v12-latin-regular.woff") format("woff");
}
a, #main-menu a a, #account-area a {
#main-menu a a, #account-area a, a {
text-decoration: none;
color: #FFDC00;
}
a:hover, #main-menu a a:hover, #account-area a:hover,
a:active,
#main-menu a a:hover, #account-area a:hover, a:hover,
#main-menu a a:active,
#account-area a:active,
a:focus,
a:active,
#main-menu a a:focus,
#account-area a:focus {
#account-area a:focus,
a:focus {
color: #ffbb00;
}
* {
box-sizing: border-box;
}
body, html {
height: 100%;
}
body {
display: grid;
grid-template-columns: auto auto;
grid-template-areas: "aside main";
background-color: #301827;
color: #FFFFFF;
font-family: "Fira Sans", "Roboto", sans-serif;
}
#aside {
grid-area: aside;
padding: 0 0 0 10vw;
background-color: #1f0e19;
box-shadow: 4px 0 0 rgba(31, 14, 25, 0.3);
z-index: 1000;
}
#main {
grid-area: main;
padding: 0 10vw 0 0;
display: flex;
flex-direction: column;
justify-content: space-between;
background-color: #301827;
z-index: 500;
}
@media only screen and (max-width: 1200px) {
#header {
margin: 0;
}
#main {
margin: 0;
}
}
#menu {
display: grid;
float: right;
width: 320px;
grid-template-columns: auto;
grid-template-rows: auto auto auto auto auto 15px;
grid-template-rows: auto auto auto auto 120px;
grid-template-areas: "title" "language" "search" "menu" "account";
}
#menu #hamburger {
display: none;
grid-area: hamburger;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV9TS1VaHOwg4pChOlkoKuKoVShChVIrtOpgcukXNGlIUlwcBdeCgx+LVQcXZ10dXAVB8APEzc1J0UVK/F9SaBHjwXE/3t173L0DhGaVqWZPHFA1y8gkE2IuvyoGXyEggD6EEZeYqc+l0yl4jq97+Ph6F+NZ3uf+HGGlYDLAJxLPMt2wiDeIpzctnfM+cYSVJYX4nHjcoAsSP3JddvmNc8lhgWdGjGxmnjhCLJa6WO5iVjZU4iniqKJqlC/kXFY4b3FWq3XWvid/YaigrSxzneYIkljEEtIQIaOOCqqwEKNVI8VEhvYTHv5hx58ml0yuChg5FlCDCsnxg//B727N4uSEmxRKAIEX2/4YBYK7QKth29/Htt06AfzPwJXW8deawMwn6Y2OFj0CBraBi+uOJu8BlzvA0JMuGZIj+WkKxSLwfkbflAcGb4H+Nbe39j5OH4AsdZW6AQ4OgbESZa97vLu3u7d/z7T7+wEsq3KLN3ZkxAAAAAlwSFlzAAAA7AAAAOwBeShxvQAAADNJREFUWMPt1EERAEAIA7Ee/h0hjpPBg0RBp49NrnvTmc0Btf2AARVAipVQikGKpdgArvsZZAhH7c55ywAAAABJRU5ErkJggg==");
height: 30px;
width: 30px;
float: right;
margin-top: 20px;
}
#title {
grid-area: title;
@ -101,7 +136,6 @@ a:focus,
background-position: center;
border: none;
transition: 25ms ease-in-out;
cursor: pointer;
}
#search-area button:hover {
background-color: #FFDC00;
@ -113,16 +147,11 @@ a:focus,
text-align: center;
}
#language_switch .language {
margin: 0;
padding: 0;
display: inline-block;
background: none;
border: none;
color: #DFDFDF;
}
#language_switch .language:hover {
color: #FFDC00;
cursor: pointer;
}
#language_switch .language::after,
#language_switch .current-language::after {
@ -171,7 +200,7 @@ a:focus,
#account-area {
grid-area: account;
display: block;
padding-top: 20px;
padding: 20px;
text-align: center;
}
#account-area .clipper {
@ -188,7 +217,6 @@ a:focus,
color: #FFFFFF;
box-shadow: 2px 2px 0 rgba(31, 14, 25, 0.3);
transition: 25ms ease-in-out;
cursor: pointer;
}
.content button:hover,
@ -229,7 +257,7 @@ a:focus,
.content {
max-width: 780px;
width: 70vw;
width: 50vw;
margin: 20px;
}
.content h2 {
@ -246,10 +274,10 @@ a:focus,
}
#content-home form {
display: grid;
width: min(400px, 100%);
width: 400px;
margin: 0 auto;
grid-template-columns: 30% 65%;
gap: 5%;
grid-template-columns: 150px 250px;
gap: 20px;
}
#content-home form label {
grid-column: 1;
@ -275,7 +303,7 @@ a:focus,
#content-view-profile {
display: grid;
grid-template-rows: min(150px, 45%) auto auto;
grid-template-rows: 150px auto auto;
grid-template-columns: 3fr 1fr;
grid-template-areas: "header header" "infos infos" "free-text free-text";
gap: 20px;
@ -334,23 +362,16 @@ a:focus,
}
#content-view-profile .infos > *.multi-entry ul.value li {
padding: 0;
display: grid;
grid-template-areas: "type value";
}
#content-view-profile .infos > *.multi-entry ul.value li .type {
display: inline-block;
margin: 0 1em 0 0;
color: #DFDFDF;
font-style: italic;
grid-area: type;
}
#content-view-profile .infos > *.multi-entry ul.value li .value {
display: inline-block;
grid-area: value;
text-align: right;
}
#content-view-profile .infos > *.multi-entry ul.value li:not(:last-child) {
margin-bottom: 10px;
float: right;
}
#content-view-profile .free-text {
grid-area: free-text;
@ -365,7 +386,7 @@ a:focus,
}
#content-edit-profile form {
width: max(600px, 80%);
width: 400px;
margin: 0 auto;
}
#content-edit-profile form .form-entry {
@ -400,27 +421,19 @@ a:focus,
grid-template-areas: "errors errors errors" "type-input value-input remove-button";
column-gap: 10px;
margin: 0 0 10px 0;
align-items: center;
}
#content-edit-profile form .form-sub-entry.hidden {
display: none;
}
#content-edit-profile form .form-sub-entry input:nth-child(1) {
grid-area: type-input;
max-height: 30px;
}
#content-edit-profile form .form-sub-entry input:nth-child(2) {
grid-area: value-input;
}
#content-edit-profile form .form-sub-entry textarea:nth-child(2) {
grid-area: value-input;
max-height: 35px;
resize: none;
}
#content-edit-profile form .form-sub-entry .remove-button {
grid-area: remove-button;
min-height: 30px;
max-height: 30px;
min-width: 30px;
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white" width="18px" height="18px"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>');
background-size: 80%;
@ -471,9 +484,7 @@ a:focus,
}
#content-edit-profile #free-text-edit-form textarea {
width: 100%;
height: 100px;
padding: 5px;
resize: none;
}
#content-edit-profile #photo-edit-form .current-photo {
margin: 0 0 20px 0;
@ -512,103 +523,3 @@ a:focus,
display: block;
text-align: center;
}
* {
box-sizing: border-box;
}
body, html {
height: 100%;
}
body {
display: grid;
grid-template-areas: "aside main";
background-color: #301827;
color: #FFFFFF;
font-family: "Fira Sans", "Roboto", sans-serif;
overflow-y: scroll;
border: none;
}
#aside {
grid-area: aside;
padding: 0 0 0 10vw;
background-color: #1f0e19;
box-shadow: 4px 0 0 rgba(31, 14, 25, 0.3);
z-index: 10;
}
#main {
grid-area: main;
padding: 0 10vw 0 0;
display: flex;
flex-direction: column;
justify-content: space-between;
background-color: #301827;
z-index: 5;
}
@media screen and (max-width: 900px) {
body {
grid-template-rows: max-content auto;
grid-template-areas: "aside" "main";
}
h1 {
font-size: 1.5em;
}
#aside,
#main {
padding: 0;
box-shadow: unset;
width: 100vw;
}
#content-home,
#content-birthdays,
#content-view-profile,
#content-edit-profile {
width: auto;
}
#content-home form input[type=submit] {
grid-column: 1/3;
}
#title {
font-size: 1.75rem;
}
.content h2 {
font-size: 1.5rem;
}
#footer,
.content {
max-width: 100%;
}
#menu {
width: auto;
float: none;
grid-template-columns: auto 50px;
grid-template-areas: "title hamburger" "language language" "search search" "menu menu" "account account";
}
#menu #hamburger {
display: grid;
}
#menu #main-menu,
#menu #account-area {
display: none;
}
#search-area {
justify-content: center;
margin: auto;
width: 80%;
}
}
/*# sourceMappingURL=annuaire.css.map */

View file

@ -1 +0,0 @@
{"version":3,"sourceRoot":"","sources":["../scss/_fonts.scss","../scss/_links.scss","../scss/_colors.scss","../scss/_header.scss","../scss/_buttons.scss","../scss/_errors.scss","../scss/_content.scss","../scss/_footer.scss","../scss/_common.scss"],"names":[],"mappings":";AASA;AACA;EACI;EACA;EACA;EACA;;AAKJ;AACA;EACI;EACA;EACA;EACA;;AAKJ;AACA;EACI;EACA;EACA;EACA;;AAUJ;AAUA;AACA;EACI;EACA;EACA;EACA;;ACzDJ;EACI;EACA,OCOQ;;;ADJZ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAGI,OCEc;;;ACJlB;EACI;EACA;EACA;EAEA;EACA;EACA,qBACI;;AAMJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAIN;EACI;EAGA;EACA;EACA;EACA;;AAIA;EACI;;;AAIR;EACI;EACA;EACA;EACA,QAhDgB;EAiDhB;EACA;;AAGA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI,kBDzEI;;;AC6EZ;EACI;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;EAEA,OD5Fc;;AC+FlB;EACI,OD7FI;EC8FJ;;AAGJ;AAAA;EAEI;EACA;EACA;EACA,ODzGc;;AC4GlB;EACI;;AAGJ;EACI;;;AAKJ;EAEI;EACA;EACA;EACA;EAEA,OD1HI;EC2HJ;;AAGJ;EAEI,OD/HU;;;ACmIlB;EAEI;EAEA;EACA,ODjIa;ECkIb;;AAEA;EAEI;EACA;EACA;EACA;EAEA,ODnJI;ECoJJ;;AAGJ;EAEI,ODxJU;;;AC4JlB;EACI;EACA;EACA;EACA;;AAIA;EACI,aH7GU;;;AIlElB;AAAA;AAAA;EACI;EACA,kBFUqB;EETrB;EACA;EACA,OFSe;EERf;EACA;EACA;;;AAGJ;AAAA;AAAA;EACI,kBFC2B;;;AGf/B;EACI;EACA;;;AAGJ;EACI;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;;;ACrBR;EACI,OJqBW;;;AIlBf;EACI;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAWJ;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;;AAMJ;EACI;;AAGJ;EACI;;AAGJ;EACI;EACA;;AAGJ;EACI;;AAIA;EACI;;;AAYhB;EACI;EACA;EACA;EACA,qBACI;EAGJ;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;;AAEA;EACI;EACA,OJnGU;EIoGV;EACA;EACA;;AAIR;EACI;EACA;EACA;EACA;;AAGJ;EACI;EAEA;EACA,OJpHS;EIqHT;;AAGJ;EACI;;AAGJ;EACI;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA,kBJtHe;EIuHf;;AAEA;EACI;EACA;EACA;;AAEA;EACI;EACA;EACA;;AAEA;EACI;EACA;EACA,OJvJF;EIwJE;EACA;;AAGJ;EACI;EACA;EACA;;AAGJ;EACE;;AASlB;EACI;EACA;;AAEA;EACI;;AAGJ;EACI;EACA;;;AAMR;EACI;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA,kBJpLe;EIqLf;;AAEA;EACI;;AAGJ;EACI;EACA,OJ7MC;EI8MD;EACA;EACA;;AAGJ;EACI;;AAGJ;EAGI;EACA;;AAIR;EACI;EACA;EACA;EACA,qBACI;EAEJ;EACA;EACA;;AAEA;EACI;;AAMJ;EACI;EACA;;AAEJ;EAAqB;;AACrB;EACI;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;AAAA;EAEI;;AAGJ;EACI;;AAUA;EAAqB;;AACrB;EAAqB;;AAGrB;AAAA;EAEI;;AAIR;EAEI;;AAIR;EACI;;AAGJ;EACI;;AAIA;EACI;EACA;;AAIR;EACI;EACA;;AAIA;EACI;;AAGJ;EACI;EACA;EACA;;AAMR;EACI;EACA;EACA;EACA;;AAKJ;EACI;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;;AAKJ;EACI;;AAGJ;EACI;;AAGJ;EACI;EACA,OJ3WC;EI4WD;;AASJ;EACI;;;AC7XhB;EACI;EACA;EACA,OLGkB;EKFlB;;AAEA;EACI;EACA;;;ACNR;EACI;;;AAMJ;EACI;;;AAGJ;EACI;EACA,qBACI;EAEJ,kBNnBc;EMoBd,ONdQ;EMeR,aR6CY;EQ5CZ;EACA;;;AAGJ;EACI;EACA;EACA,kBN5Be;EM6Bf;EACA;;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA,kBNxCc;EMyCd;;;AAIJ;EACE;IACE;IACA,qBACI;;;EAIN;IACE;;;EAGF;AAAA;IAEE;IACA;IACA;;;EAGF;AAAA;AAAA;AAAA;IAIE;;;EAKE;IACE;;;EAKN;IACE;;;EAIA;IACE;;;EAIJ;AAAA;IAEE;;;EAGF;IACE;IACA;IACA;IACA,qBACE;;EAMF;IACE;;EAGF;AAAA;IAEE;;;EAIJ;IACE;IACA;IACA","file":"annuaire.css"}

View file

@ -1,258 +0,0 @@
/*
MIT License
Copyright (c) 2019 Hidenao Miyamoto
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
.datepicker {
display: none;
}
.datepicker.active {
display: block;
}
.datepicker-dropdown {
position: absolute;
top: 0;
left: 0;
z-index: 20;
padding-top: 4px;
}
.datepicker-dropdown.datepicker-orient-top {
padding-top: 0;
padding-bottom: 4px;
}
.datepicker-picker {
display: inline-block;
border-radius: 0px;
background-color: #301827;
}
.datepicker-dropdown .datepicker-picker {
box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1);
}
.datepicker-picker span {
display: block;
flex: 1;
border: 0;
border-radius: 0px;
cursor: default;
text-align: center;
-webkit-touch-callout: none;
user-select: none;
}
.datepicker-main {
padding: 2px;
}
.datepicker-footer {
box-shadow: inset 0 1px 1px rgba(10, 10, 10, 0.1);
background-color: whitesmoke;
}
.datepicker-grid, .datepicker-view .days-of-week, .datepicker-view, .datepicker-controls {
display: flex;
}
.datepicker-grid {
flex-wrap: wrap;
}
.datepicker-view .days .datepicker-cell, .datepicker-view .dow {
flex-basis: 14.2857142857%;
}
.datepicker-view.datepicker-grid .datepicker-cell {
flex-basis: 25%;
}
.datepicker-cell, .datepicker-view .week {
height: 2.25rem;
line-height: 2.25rem;
}
.datepicker-title {
box-shadow: inset 0 -1px 1px rgba(10, 10, 10, 0.1);
background-color: #1f0e19;
padding: 0.375rem 0.75rem;
text-align: center;
font-weight: 700;
}
.datepicker-header .datepicker-controls {
padding: 2px 2px 0;
}
.datepicker-controls .button {
display: inline-flex;
position: relative;
align-items: center;
justify-content: center;
margin: 0;
border: 1px solid #dbdbdb;
border-radius: 0px;
box-shadow: none;
background-color: white;
cursor: pointer;
padding: calc(0.375em - 1px) 0.75em;
height: 2.25em;
vertical-align: top;
text-align: center;
line-height: 1.5;
white-space: nowrap;
color: #363636;
font-size: 1rem;
}
.datepicker-controls .button:focus, .datepicker-controls .button:active {
outline: none;
}
.datepicker-controls .button:hover {
border-color: #b5b5b5;
color: #363636;
}
.datepicker-controls .button:focus {
border-color: #3273dc;
color: #363636;
}
.datepicker-controls .button:focus:not(:active) {
box-shadow: 0 0 0 0.125em rgba(50, 115, 220, 0.25);
}
.datepicker-controls .button:active {
border-color: #4a4a4a;
color: #363636;
}
.datepicker-controls .button[disabled] {
cursor: not-allowed;
}
.datepicker-header .datepicker-controls .button {
border-color: transparent;
font-weight: bold;
}
.datepicker-header .datepicker-controls .button:hover {
background-color: #f9f9f9;
}
.datepicker-header .datepicker-controls .button:focus:not(:active) {
box-shadow: 0 0 0 0.125em rgba(255, 255, 255, 0.25);
}
.datepicker-header .datepicker-controls .button:active {
background-color: #f2f2f2;
}
.datepicker-header .datepicker-controls .button[disabled] {
box-shadow: none;
}
.datepicker-footer .datepicker-controls .button {
margin: calc(0.375rem - 1px) 0.375rem;
border-radius: 0px;
width: 100%;
font-size: 0.75rem;
}
.datepicker-controls .view-switch {
flex: auto;
}
.datepicker-controls .prev-btn,
.datepicker-controls .next-btn {
padding-right: 0.375rem;
padding-left: 0.375rem;
width: 2.25rem;
}
.datepicker-controls .prev-btn.disabled,
.datepicker-controls .next-btn.disabled {
visibility: hidden;
}
.datepicker-view .dow {
height: 1.5rem;
line-height: 1.5rem;
font-size: 0.875rem;
font-weight: 700;
}
.datepicker-view .week {
width: 2.25rem;
color: #b5b5b5;
font-size: 0.75rem;
}
@media (max-width: 22.5rem) {
.datepicker-view .week {
width: 1.96875rem;
}
}
.datepicker-grid {
width: 15.75rem;
}
@media (max-width: 22.5rem) {
.calendar-weeks + .days .datepicker-grid {
width: 13.78125rem;
}
}
.datepicker-cell:not(.disabled):hover {
background-color: #281420;
cursor: pointer;
}
.datepicker-cell.focused:not(.selected) {
background-color: #1f1019;
}
.datepicker-cell.selected, .datepicker-cell.selected:hover {
background-color: #3b1e31;
color: #ffdc00;
font-weight: 600;
}
.datepicker-cell.disabled {
color: #dbdbdb;
}
.datepicker-cell.prev:not(.disabled), .datepicker-cell.next:not(.disabled) {
color: #7a7a7a;
}
.datepicker-cell.prev.selected, .datepicker-cell.next.selected {
color: #ccb000;
}
.datepicker-cell.highlighted:not(.selected):not(.today) {
border-radius: 0;
background-color: #3b1e31;
}
.datepicker-cell.highlighted:not(.selected):not(.today):not(.disabled):hover {
background-color: #331a2a;
}
.datepicker-cell.highlighted:not(.selected):not(.today).focused {
background-color: #1f1019;
}
.datepicker-cell.today:not(.selected) {
background-color: #3b1e31;
}
.datepicker-cell.today:not(.selected):not(.disabled) {
color: #fff;
}
.datepicker-cell.today.focused:not(.selected) {
background-color: #331a2a;
}
.datepicker-view.datepicker-grid .datepicker-cell {
height: 4.5rem;
line-height: 4.5rem;
}
.datepicker-input.in-edit {
border-color: #2366d1;
}
.datepicker-input.in-edit:focus, .datepicker-input.in-edit:active {
box-shadow: 0 0 0.25em 0.25em rgba(35, 102, 209, 0.2);
}
/*# sourceMappingURL=datepicker.css.map */

View file

@ -1 +0,0 @@
{"version":3,"sourceRoot":"","sources":["../scss/datepicker.scss"],"names":[],"mappings":"AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA0FA;EACE;;AAEA;EACE;;;AAIJ;EACE;EACA;EACA;EACA,SAvDc;EAwDd,aA1DmB;;AA4DnB;EACE;EACA,gBA9DiB;;;AAkErB;EACE;EACA,eA3EiB;EA4EjB,kBA9EoB;;AAgFpB;EACE,YAvEiB;;AA0EnB;EACE;EACA;EACA;EACA,eAtFe;EAuFf;EACA;EACA;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA,kBA/GM;;;AAkHR;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE,QA3GkB;EA4GlB,aA5GkB;;;AA+GpB;EACE;EACA,kBAnH0B;EAoH1B;EACA;EACA,aA3HoB;;;AAiIpB;EACE;;AAMA;EAqBE;EACA;EACA;EACA;EACA;EACA;EACA,eAzKa;EA0Kb;EACA,kBAvLE;EAwLF;EACA;EACA;EACA;EACA;EACA,aA/KgB;EAgLhB;EACA,OA7LC;EA8LD,WAjLgB;;AAmLhB;EAEE;;AAGF;EACE,cAlMK;EAmML,OAvMD;;AA0MD;EACE,cA1MD;EA2MC,OA5MD;;AA8MC;EACE;;AAIJ;EACE,cAjNI;EAkNJ,OArND;;AAwND;EACE;;AAGF;EA1KJ;EACA;;AA4KM;EACE;;AAIA;EACE;;AAIJ;EACE;;AAGF;EACE;;AAIJ;EA3LJ;EACA,eA7CuB;EA8CvB;EACA,WA5CmB;;AA0OnB;EACE;;AAGF;AAAA;EAEE;EACA;EACA,OAzOgB;;AA2OhB;AAAA;EACE;;;AAYJ;EAEE;EACA;EACA;EACA,aApQkB;;AAuQpB;EAEE,OAlQgB;EAmQhB,OAtRS;EAuRT,WA7QiB;;AA+QjB;EANF;IAOI,OAhPmB;;;;AAqPzB;EAIE;;AAEA;EACE;IACE;;;;AAQJ;EACE;EACA;;AAGF;EACE,kBAjS6B;;AAqS7B;EAEE,kBApS8B;EAqS9B,OApSmB;EAqSnB,aApSyB;;AAwS7B;EACE,OAjUW;;AAsUX;EACE,OAnTmB;;AAsTrB;EACE;;AAIJ;EACE;EACA,kBAtTmC;;AAwTnC;EACE;;AAGF;EACE,kBArU2B;;AA0U7B;EACE,kBArU2B;;AAuU3B;EACE,OAvUc;;AA2UlB;EACE;;AAQJ;EAEE;EACA;;;AAIJ;EACE,cAlV8B;;AAoV9B;EAEE","file":"datepicker.css"}

File diff suppressed because one or more lines are too long

View file

@ -53,10 +53,31 @@ class SubEntry {
this.display();
}
reindex(newIndex) {
// Replace -<old index>- by -<new index>- in the values of the
// id and name attributes of every child of this sub-entry's element
const attributesToUpdate = ["id", "name"];
for (let childElement of this.subEntryElement.childNodes) {
for (let attribute of attributesToUpdate) {
if (!childElement.hasAttribute(attribute)) {
continue;
}
const newValue = childElement
.getAttribute(attribute)
.replace(`-${this.index}-`, `-${newIndex}-`);
childElement.setAttribute(attribute, newValue);
}
}
this.index = newIndex;
}
startHandlingSubEntryRemoveButtonClicks() {
this.removeButtonElement.addEventListener("click", event => {
event.preventDefault();
this.markForDeletionAndHide();;
this.parentFormEntry.removeSubEntry(this);
});
}
@ -104,10 +125,6 @@ class MultiEntryFormEntry {
}
get nbSubEntries() {
return this.subEntries.length;
}
get nbActiveSubEntries() {
return this.subEntries
.filter(subEntry =>
!subEntry.subEntryElement.classList.contains("hidden")
@ -115,6 +132,13 @@ class MultiEntryFormEntry {
.length;
}
get hasHiddenSubEntries() {
return this.subEntries
.findIndex(subEntry =>
subEntry.subEntryElement.classList.contains("hidden")
) >= 0;
}
createInitialSubEntries() {
const subEntryElements = this.formEntryElement.querySelectorAll(".form-sub-entry");
this.initialNbSubEntries = subEntryElements.length;
@ -129,31 +153,64 @@ class MultiEntryFormEntry {
}
createNewSubEntry() {
if (this.nbActiveSubEntries === this.maxNbSubEntries) {
if (this.nbSubEntries === this.maxNbSubEntries) {
console.log(`The max. number of sub-entries has been reached (${this.maxNbSubEntries}).`);
return;
}
// If there are hidden sub-entries,
// it means one of the initial sub-entry elements should be reset and displayed
if (this.hasHiddenSubEntries) {
// Reset and display the first hidden sub-entry
const existingSubEntry = this.subEntries.find(
subEntry => subEntry.subEntryElement.classList.contains("hidden")
);
existingSubEntry.resetAndDisplay();
}
// Otherwise, it means a new sub-entry (element) must be created
// and appended to the form entry element
else {
const newSubEntryIndex = this.nbSubEntries;
const newSubEntry = SubEntry.fromTemplate(
this.subEntryTemplateElement,
newSubEntryIndex,
this
);
this.subEntryTemplateElement,
newSubEntryIndex,
this
);
this.subEntries.push(newSubEntry);
this.formEntryElement.insertBefore(
newSubEntry.subEntryElement,
this.newSubEntryButtonElement
);
this.subEntries.push(newSubEntry);
this.formEntryElement.insertBefore(
newSubEntry.subEntryElement,
this.newSubEntryButtonElement
);
// Increment Django's TOTAL_FORMS hidden form input
this.totalFormsElement.value = (parseInt(this.totalFormsElement.value) + 1).toString();
// Increment Django's TOTAL_FORMS hidden form input
this.totalFormsElement.value = (parseInt(this.totalFormsElement.value) + 1).toString();
}
}
removeSubEntry(subEntry) {
// If the index of the sub-entry to remove is below the initial number of sub-entries,
// it means one of the initial sub entry elements should be marked for deletion and hidden
const removedSubEntryIndex = subEntry.index;
if (removedSubEntryIndex < this.initialNbSubEntries) {
subEntry.markForDeletionAndHide();
}
// Otherwise, delete the sub-entry (and remove its element from the DOM)
// and reindex other user-created sub-entries if need be
else {
subEntry.subEntryElement.remove();
this.subEntries.splice(subEntry.index, 1);
for (let index = this.removeSubEntryIndex; index < this.subEntries.length; index++) {
this.subEntries[index].reindex(index);
}
// Decrement Django's TOTAL_FORMS hidden form input
this.totalFormsElement.value = (parseInt(this.totalFormsElement.value) - 1).toString();
}
}
startHandlingNewSubEntryButtonClicks() {

View file

@ -7,10 +7,9 @@
font-size: 1.2rem;
color: colors.$page-button-text;
box-shadow: 2px 2px 0 colors.$shadow;
transition: 25ms ease-in-out;
cursor: pointer;
transition: 25ms ease-in-out;
}
%button:hover {
background-color: colors.$page-button-background-hover;
}
}

View file

@ -15,14 +15,13 @@ body, html {
body {
display: grid;
grid-template-columns: auto auto;
grid-template-areas:
"aside main";
background-color: colors.$page-background;
color: colors.$page-text;
font-family: fonts.$regular-fonts;
overflow-y: scroll;
border:none;
}
#aside {
@ -30,7 +29,7 @@ body {
padding: 0 0 0 10vw;
background-color: colors.$aside-background;
box-shadow: 4px 0 0 colors.$shadow;
z-index: 10;
z-index: 1000;
}
#main {
@ -40,84 +39,16 @@ body {
flex-direction: column;
justify-content: space-between;
background-color: colors.$main-background;
z-index: 5;
z-index: 500;
}
// Pour les vues mobile
@media screen and (max-width: 900px) {
body {
grid-template-rows: max-content auto;
grid-template-areas:
"aside"
"main";
}
h1 {
font-size: 1.5em;
}
#aside,
#main {
padding: 0;
box-shadow: unset;
width: 100vw;
}
#content-home,
#content-birthdays,
#content-view-profile,
#content-edit-profile {
width: auto;
}
#content-home {
form {
input[type="submit"] {
grid-column: 1/3;
}
@media only screen and (max-width: 1200px) {
#header {
margin: 0;
}
}
#title {
font-size: 1.75rem;
}
.content {
h2 {
font-size: 1.5rem;
#main {
margin: 0;
}
}
#footer,
.content {
max-width: 100%;
}
#menu {
width: auto;
float: none;
grid-template-columns: auto 50px;
grid-template-areas:
"title hamburger"
"language language"
"search search"
"menu menu"
"account account";
#hamburger {
display: grid;
}
#main-menu,
#account-area {
display: none;
}
}
#search-area {
justify-content: center;
margin: auto;
width: 80%;
}
}

View file

@ -3,12 +3,12 @@
@use "errors";
#content-area {
color: colors.$content-text;
color: colors.$content-text;
}
.content {
max-width: 780px;
width: 70vw;
width: 50vw;
margin: 20px;
h2 {
@ -21,7 +21,7 @@
input {
min-height: 30px;
padding: 5px;
border: none;
border: none;
border-radius: 0;
}
@ -35,11 +35,11 @@
#content-home {
form {
display: grid;
width: min(400px, 100%);
width: 400px;
margin: 0 auto;
grid-template-columns: 30% 65%;
gap: 5%;
grid-template-columns: 150px 250px;
gap: 20px;
label {
grid-column: 1;
margin: 5px 0 0 0;
@ -74,7 +74,7 @@
// input[type="checkbox"] {
// display: inline;
// width: 1rem;
// float: right;
// float: right;
// }
}
}
@ -82,7 +82,7 @@
#content-view-profile {
display: grid;
grid-template-rows: min(150px, 45%) auto auto;
grid-template-rows: 150px auto auto;
grid-template-columns: 3fr 1fr;
grid-template-areas:
"header header"
@ -111,7 +111,7 @@
text-align: center;
}
}
.photo {
height: 150px;
margin: 0 20px 0 0;
@ -143,7 +143,7 @@
padding: 10px;
background-color: colors.$content-frame-background;
box-shadow: 2px 2px 0 colors.$shadow;
&.multi-entry ul.value {
margin: 0;
padding: 0;
@ -151,29 +151,19 @@
li {
padding: 0;
display: grid;
grid-template-areas: "type value";
.type {
display: inline-block;
margin: 0 1em 0 0;
color: colors.$page-text-secondary;
font-style: italic;
grid-area: type;
}
.value {
display: inline-block;
grid-area: value;
text-align: right;
float: right;
}
&:not(:last-child) {
margin-bottom: 10px;
}
}
}
}
}
@ -195,7 +185,7 @@
#content-edit-profile {
form {
width: max(600px, 80%);
width: 400px;
margin: 0 auto;
.form-entry {
@ -205,11 +195,11 @@
padding: 10px;
background-color: colors.$content-frame-background;
box-shadow: 2px 2px 0 colors.$shadow;
> * {
display: block;
}
label {
margin: 0 0 5px 0;
color: colors.$page-text-emph;
@ -217,7 +207,7 @@
font-style: italic;
text-align: left;
}
input, select {
width: 100%;
}
@ -239,7 +229,6 @@
"type-input value-input remove-button";
column-gap: 10px;
margin: 0 0 10px 0;
align-items: center;
&.hidden {
display: none;
@ -248,21 +237,12 @@
// Since the different labels and inputs are not obvious to identifiate using CSS selectors,
// they are selected one after the other using their natural order in the DOM
// TODO: make this more robust by giving proper class names to each sub-entry element
input:nth-child(1) {
grid-area: type-input;
max-height: 30px;
}
input:nth-child(1) { grid-area: type-input; }
input:nth-child(2) { grid-area: value-input; }
textarea:nth-child(2) {
grid-area: value-input;
max-height: 35px;
resize: none;
}
.remove-button {
grid-area: remove-button;
min-height: 30px;
max-height: 30px;
min-width: 30px;
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white" width="18px" height="18px"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>');
background-size: 80%;
@ -281,14 +261,14 @@
&.erroneous {
@extend %error-list-container;
// TODO: this is not robust and should be replaced by better selectors
// In case of error, increment the child indices
// In case of error, increment the child indices
// to take the additional list of errors (.errorlist)
// prepended by Django to the sub-entry
input:nth-child(2) { grid-area: type-input; }
input:nth-child(3) { grid-area: value-input; }
// Use a darker red color when the sub-entry background is already red
.remove-button:hover,
.remove-button:active {
@ -301,7 +281,7 @@
grid-area: errors;
}
}
.form-sub-entry-template {
display: none;
}
@ -309,7 +289,7 @@
.add-sub-entry-button {
margin: 0 auto 0 auto;
}
.form-entry.checkbox {
> * {
display: inline;
@ -330,7 +310,7 @@
input[type="checkbox"] {
display: block;
width: auto;
float: right;
float: right;
}
}
}
@ -338,9 +318,7 @@
#free-text-edit-form {
textarea {
width: 100%;
height: 100px;
padding: 5px;
resize: none;
}
}
@ -377,7 +355,7 @@
// input[type="checkbox"] {
// display: inline-block;
// width: 1rem;
// float: right;
// float: right;
// }
input[type="file"] {
@ -385,4 +363,4 @@
}
}
}
}
}

View file

@ -5,7 +5,7 @@
padding: 20px;
color: colors.$page-text-secondary;
text-align: center;
.thanks {
display: block;
text-align: center;

View file

@ -12,23 +12,13 @@ $account-area-height: 120px;
width: 320px;
// height: 100%;
grid-template-columns: auto;
grid-template-rows: auto auto auto auto auto 15px;
grid-template-rows: auto auto auto auto $account-area-height;
grid-template-areas:
"title"
"language"
"search"
"menu"
"account";
#hamburger {
display: none;
grid-area: hamburger;
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV9TS1VaHOwg4pChOlkoKuKoVShChVIrtOpgcukXNGlIUlwcBdeCgx+LVQcXZ10dXAVB8APEzc1J0UVK/F9SaBHjwXE/3t173L0DhGaVqWZPHFA1y8gkE2IuvyoGXyEggD6EEZeYqc+l0yl4jq97+Ph6F+NZ3uf+HGGlYDLAJxLPMt2wiDeIpzctnfM+cYSVJYX4nHjcoAsSP3JddvmNc8lhgWdGjGxmnjhCLJa6WO5iVjZU4iniqKJqlC/kXFY4b3FWq3XWvid/YaigrSxzneYIkljEEtIQIaOOCqqwEKNVI8VEhvYTHv5hx58ml0yuChg5FlCDCsnxg//B727N4uSEmxRKAIEX2/4YBYK7QKth29/Htt06AfzPwJXW8deawMwn6Y2OFj0CBraBi+uOJu8BlzvA0JMuGZIj+WkKxSLwfkbflAcGb4H+Nbe39j5OH4AsdZW6AQ4OgbESZa97vLu3u7d/z7T7+wEsq3KLN3ZkxAAAAAlwSFlzAAAA7AAAAOwBeShxvQAAADNJREFUWMPt1EERAEAIA7Ee/h0hjpPBg0RBp49NrnvTmc0Btf2AARVAipVQikGKpdgArvsZZAhH7c55ywAAAABJRU5ErkJggg==');
height: 30px;
width: 30px;
float: right;
margin-top: 20px;
}
}
#title {
@ -78,7 +68,6 @@ $account-area-height: 120px;
background-position: center;
border: none;
transition: 25ms ease-in-out;
cursor: pointer;
}
button:hover {
@ -92,18 +81,13 @@ $account-area-height: 120px;
text-align: center;
.language {
margin: 0;
padding: 0;
display: inline-block;
background: none;
border: none;
// text-decoration: underline;
color: colors.$page-text-secondary;
}
.language:hover {
color: colors.$main-menu-link;
cursor: pointer;
}
.language::after,
@ -169,7 +153,7 @@ $account-area-height: 120px;
#account-area {
grid-area: account;
display: block;
padding-top: 20px;
padding: 20px;
text-align: center;
@extend %menu-link;
@ -177,4 +161,4 @@ $account-area-height: 120px;
.clipper {
font-family: fonts.$monospace-fonts;
}
}
}

View file

@ -1,4 +1,4 @@
@use "common";
@use "header";
@use "content";
@use "footer";
@use "common";
@use "footer";

View file

@ -1,414 +0,0 @@
/*
MIT License
Copyright (c) 2019 Hidenao Miyamoto
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
//== foundational variables ==//
$black: hsl(0, 0%, 4%) !default;
$white: hsl(0, 0%, 100%) !default;
$light: hsl(0, 0%, 96%) !default; // white-ter
$dark: hsl(0, 0%, 21%) !default; // grey-darker
$link: hsl(217, 71%, 53%) !default; // blue
$grey-dark: lighten($black, 25%) !default;
$grey-light: darken($light, 25%) !default;
$grey-lighter: darken($light, 10%) !default;
//== datepicker variables ==//
$dp-background-color: #301827 !default;
$dp-border-color: #1f0e19 !default;
$dp-border-radius: 0px !default;
$dp-border-radius-small: 0px !default;
$dp-line-height-base: 1.5 !default;
$dp-font-size-normal: 1rem !default;
$dp-font-size-small: 0.75rem !default;
$dp-font-weight-semibold: 600 !default;
$dp-font-weight-bold: 700 !default;
$dp-dropdown-offset: 4px !default;
$dp-dropdown-shadow: 0 2px 3px rgba($black, 0.1), 0 0 0 1px rgba($black, 0.1) !default;
$dp-dropdown-z: 20 !default;
$dp-title-background-color: #1f0e19 !default;
$dp-cell-size-base: 2.25rem !default;
$dp-cell-focus-background-color: darken(#301827, 5%) !default;
$dp-cell-prevnext-color: hsl(0, 0%, 48%) !default; // grey
$dp-cell-disabled-color: $grey-lighter !default;
$dp-cell-selected-background-color: #3b1e31 !default;
$dp-cell-selected-color: #ffdc00 !default; // link(blue)-invert
$dp-cell-selected-font-weight: 600 !default;
$dp-cell-today-background-color: #3b1e31 !default; // turquoise (primary)
$dp-cell-today-color: #fff !default; // turquoise-invert
$dp-cell-highlighted-background-color: #3b1e31 !default;
$dp-range-start-end-background-color: $grey-light !default;
$dp-range-start-end-color: $dp-cell-selected-color !default;
$dp-range-background-color: $grey-lighter !default;
$dp-range-today-background-color: $dp-cell-today-background-color !default;
$dp-week-color: $grey-light !default;
$dp-footer-background-color: $light !default;
$dp-input-in-edit-border-color: darken($link, 5%) !default;
$dp-input-in-edit-focus-box-shadow-size: 0 0 0.25em 0.25em !default;
//== non-configurable variables ==//
$dp-cell-shrink-threshold: $dp-cell-size-base * 10; // = 8 * 1.25
$dp-cell-shrinked-width: $dp-cell-size-base * 7 / 8;
//== mixins ==//
@mixin dp-header-button-common {
border-color: transparent;
font-weight: bold;
}
@mixin dp-footer-button-common {
margin: calc(0.375rem - 1px) 0.375rem;
border-radius: $dp-border-radius-small;
width: 100%;
font-size: $dp-font-size-small;
}
//== styles ==//
.datepicker {
display: none;
&.active {
display: block;
}
}
.datepicker-dropdown {
position: absolute;
top: 0;
left: 0;
z-index: $dp-dropdown-z;
padding-top: $dp-dropdown-offset;
&.datepicker-orient-top {
padding-top: 0;
padding-bottom: $dp-dropdown-offset;
}
}
.datepicker-picker {
display: inline-block;
border-radius: $dp-border-radius;
background-color: $dp-background-color;
.datepicker-dropdown & {
box-shadow: $dp-dropdown-shadow;
}
span {
display: block;
flex: 1;
border: 0;
border-radius: $dp-border-radius;
cursor: default;
text-align: center;
-webkit-touch-callout: none;
user-select: none;
}
}
.datepicker-main {
padding: 2px;
}
.datepicker-footer {
box-shadow: inset 0 1px 1px rgba($black, 0.1);
background-color: $dp-footer-background-color;
}
%flex-container {
display: flex;
}
%flex-wrap {
flex-wrap: wrap;
}
%flex-basis-day {
flex-basis: percentage(1 / 7);
}
%flex-basis-month-year {
flex-basis: 25%;
}
%datepicker-cell-height {
height: $dp-cell-size-base;
line-height: $dp-cell-size-base;
}
.datepicker-title {
box-shadow: inset 0 -1px 1px rgba($black, 0.1);
background-color: $dp-title-background-color;
padding: 0.375rem 0.75rem;
text-align: center;
font-weight: $dp-font-weight-bold;
}
.datepicker-controls {
@extend %flex-container;
.datepicker-header & {
padding: 2px 2px 0;
}
@if mixin-exists(dp-button) {
@include dp-button;
} @else {
.button {
$button-color: $dark;
$button-background-color: $white;
$button-border-color: $grey-lighter;
$button-border-width: 1px;
$button-padding-vertical: calc(0.375em - #{$button-border-width});
$button-padding-horizontal: 0.75em;
$button-hover-color: $dark; // link-hover
$button-hover-border-color: $grey-light; // link-hover-border
$button-focus-color: $dark; // link-focus
$button-focus-border-color: $link; // link-focus-border
$button-focus-box-shadow-size: 0 0 0 0.125em;
$button-focus-box-shadow-color: rgba($link, 0.25);
$button-active-color: $dark; // link-active
$button-active-border-color: $grey-dark; // link-active-border
display: inline-flex;
position: relative;
align-items: center;
justify-content: center;
margin: 0;
border: $button-border-width solid $button-border-color;
border-radius: $dp-border-radius; // control-radius
box-shadow: none;
background-color: $button-background-color;
cursor: pointer;
padding: $button-padding-vertical $button-padding-horizontal;
height: 2.25em; // control-height
vertical-align: top;
text-align: center;
line-height: $dp-line-height-base; // control-line-height
white-space: nowrap;
color: $button-color;
font-size: $dp-font-size-normal; // size-normal
&:focus,
&:active {
outline: none;
}
&:hover {
border-color: $button-hover-border-color;
color: $button-hover-color;
}
&:focus {
border-color: $button-focus-border-color;
color: $button-focus-color;
&:not(:active) {
box-shadow: $button-focus-box-shadow-size $button-focus-box-shadow-color;
}
}
&:active {
border-color: $button-active-border-color;
color: $button-active-color;
}
&[disabled] {
cursor: not-allowed;
}
.datepicker-header & {
@include dp-header-button-common;
&:hover {
background-color: darken($white, 2.5%);
}
&:focus {
&:not(:active) {
box-shadow: 0 0 0 0.125em rgba($white, 0.25);
}
}
&:active {
background-color: darken($white, 5%);
}
&[disabled] {
box-shadow: none;
}
}
.datepicker-footer & {
@include dp-footer-button-common;
}
}
}
.view-switch {
flex: auto;
}
.prev-btn,
.next-btn {
padding-right: 0.375rem;
padding-left: 0.375rem;
width: $dp-cell-size-base;
&.disabled {
visibility: hidden;
}
}
}
.datepicker-view {
@extend %flex-container;
.days-of-week {
@extend %flex-container;
}
.dow {
@extend %flex-basis-day;
height: $dp-font-size-normal * $dp-line-height-base;
line-height: $dp-font-size-normal * $dp-line-height-base;
font-size: ($dp-font-size-small + $dp-font-size-normal) / 2;
font-weight: $dp-font-weight-bold;
}
.week {
@extend %datepicker-cell-height;
width: $dp-cell-size-base;
color: $dp-week-color;
font-size: $dp-font-size-small;
@media (max-width: $dp-cell-shrink-threshold) {
width: $dp-cell-shrinked-width;
}
}
}
.datepicker-grid {
@extend %flex-container;
@extend %flex-wrap;
width: $dp-cell-size-base * 7;
@media (max-width: $dp-cell-shrink-threshold) {
.calendar-weeks + .days & {
width: $dp-cell-shrinked-width * 7;
}
}
}
.datepicker-cell {
@extend %datepicker-cell-height;
&:not(.disabled):hover {
background-color: darken($dp-background-color, 2.5%);
cursor: pointer;
}
&.focused:not(.selected) {
background-color: $dp-cell-focus-background-color;
}
&.selected {
&,
&:hover {
background-color: $dp-cell-selected-background-color;
color: $dp-cell-selected-color;
font-weight: $dp-cell-selected-font-weight;
}
}
&.disabled {
color: $dp-cell-disabled-color;
}
&.prev,
&.next {
&:not(.disabled) {
color: $dp-cell-prevnext-color;
}
&.selected {
color: darken($dp-cell-selected-color, 10%);
}
}
&.highlighted:not(.selected):not(.today) {
border-radius: 0;
background-color: $dp-cell-highlighted-background-color;
&:not(.disabled):hover {
background-color: darken($dp-cell-highlighted-background-color, 2.5%);
}
&.focused {
background-color: $dp-cell-focus-background-color;
}
}
&.today {
&:not(.selected) {
background-color: $dp-cell-today-background-color;
&:not(.disabled) {
color: $dp-cell-today-color;
}
}
&.focused:not(.selected) {
background-color: darken($dp-cell-today-background-color, 2.5%);
}
}
.datepicker-view .days & {
@extend %flex-basis-day;
}
.datepicker-view.datepicker-grid & {
@extend %flex-basis-month-year;
height: $dp-cell-size-base * 2;
line-height: $dp-cell-size-base * 2;
}
}
.datepicker-input.in-edit {
border-color: $dp-input-in-edit-border-color;
&:focus,
&:active {
box-shadow: $dp-input-in-edit-focus-box-shadow-size rgba($dp-input-in-edit-border-color, 0.2);
}
}

View file

@ -1,49 +1,43 @@
{% load i18n %}
{% load static %}
{% load staticfiles %}
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta content="width=device-width, initial-scale=1.0; maximum-scale=1.0; minimum-scale=1.0;" name="viewport">
<meta charset="UTF-8">
<title>
{% block title_onglet %}{% trans "Annuaire des élèves de l'ENS" %}{% endblock %}
</title>
<link rel="stylesheet" type="text/css" href="{% static "fiches/css/normalize.css" %}" />
<link rel="stylesheet" type="text/css" href="{% static "fiches/css/annuaire.css" %}" />
<link rel="stylesheet" type="text/css" href="{% static "fiches/css/datepicker.css" %}" />
</head>
<body>
<div id="aside">
<div id="menu">
<h1 id="title">
{% block title %} <a href='{% url "home" %}'>{% trans "Annuaire des élèves de l'ENS" %}</a>{% endblock %}
</h1>
<a id="hamburger" href="javascript:void(0);" onclick="toggleMenu()"></a>
<div id="language_switch">
<form action="{% url 'set_language' %}" method="post">
{% csrf_token %}
{% get_current_language as lang %}
<input name="next" type="hidden" value="{{ request.get_full_path }}">
<button class="language {% if lang == "fr" %}current-language{% endif %}" name="language" value="fr">Français</button><button class="language {% if lang == "en" %}current-language{% endif %}" name="language" value="en">English</button>
</form>
<a class="language french {% if get_current_language == "fr" %}current-language{% endif %}" href="/fr{{ request.get_full_path|slice:'3:' }}">Français</a>
<a class="language english {% if get_current_language == "en" %}current-language{% endif %}" href="/en{{ request.get_full_path|slice:'3:' }}">English</a>
</div>
<form id="search-area" method="post" action='{% url "home" %}'>
{% csrf_token %}
<input id="id_name" name="name" type="search" placeholder="{% trans "Recherche Rapide" %}">
<input id="id_name" name="name" type="search" placeholder="Recherche Rapide">
<button type="submit">{% trans "Rechercher" %}</button>
</form>
<h1 id="title">
{% block title %} <a href='{% url "home" %}'>{% trans "Annuaire des élèves de l'ENS" %}</a>{% endblock %}
</h1>
<div id="main-menu">
<nav>
<a href='{% url "home" %}'>{% trans "Accueil" %}</a>
<a href='{% url "fiche_modif" %}'>{% trans "Modifier sa fiche d'annuaire" %}</a>
{% if user.is_authenticated %}
<a href='{% url "fiche" request.user.profile.user %}'>{% trans "Consulter sa fiche d'annuaire" %}</a>
<a href='{% url "fiche_modif" %}'>{% trans "Modifier sa fiche d'annuaire" %}</a>
<a href='{% url "fiche_reset" %}'>{% trans "Réinitialiser sa fiche d'annuaire" %}</a>
{% endif %}
<a href='{% url "birthday" %}'>{% trans "Anniversaires à venir" %}</a>
</nav>
@ -52,9 +46,9 @@
<div id="account-area">
{% if user.is_authenticated %}
{% blocktrans %}Connecté en tant que <span class="clipper">{{ user }}</span>{% endblocktrans %}<br />
<a href='{% url "authens:logout" %}'>{% trans "Se déconnecter" %}</a>
<a href='{% url "cas_ng_logout" %}'>{% trans "Se déconnecter" %}</a>
{% else %}
<a href='{% url "authens:login" %}'>{% trans "Se connecter" %}</a>
<a href='{% url "cas_ng_login" %}'>{% trans "Se connecter" %}</a>
{% endif %}
</div>
</div>
@ -79,21 +73,6 @@
{% endblock %}
</div>
</div>
<script>
function toggleMenu() {
var menu = document.getElementById("main-menu");
var account = document.getElementById("account-area");
if (menu.style.display === "none") {
menu.style.display = "grid"
account.style.display = "grid"
} else {
menu.style.display = "none"
account.style.display = "none"
}
}
</script>
{% block extra_js %}{% endblock %}
</body>
{% block extra_js %}{% endblock %}
</html>

View file

@ -1,5 +1,5 @@
{% extends "fiches/base.html" %}
{% load i18n l10n %}
{% load i18n %}
{% block content %}
@ -27,7 +27,6 @@
<span class="value">{{ profile.pronoun }}</span>
</div>
{% endif %}
{% if profile.department.exists %}
<div class="department">
<span class="label">{% trans "Département" %}{{ profile.department.count|pluralize }}</span>
@ -35,17 +34,13 @@
<span class="value">{% for dep in profile.department.all %}{{ dep }}{% if not forloop.last %}, {% endif %}{% endfor %}</span>
</div>
{% endif %}
{% if profile.birth_date %}
<div class="birthdate">
<span class="label">{% trans "Date de naissance" %}</span>
<span class="separator"></span>
{% localize on %}
<span class="value">{{ profile.birth_date }}</span>
{% endlocalize %}
</div>
{% endif %}
{% if profile.thurne %}
<div class="room">
<span class="label">{% trans "Thurne" %}</span>
@ -53,76 +48,56 @@
<span class="value">{{ profile.thurne }}</span>
</div>
{% endif %}
{% if profile.phone_set.exists %}
<div class="phone multi-entry">
<span class="label">{% trans "Téléphone" %}{{ profile.phone_set.count|pluralize }}</span>
<span class="separator"></span>
<ul class="value">
{% for p in profile.phone_set.all %}
<li><span class="type">{{ p.name }}</span><span class="value">{{ p.number }}</span></li>
<li><span class="type">{{ p.name }}</span><span class="value">{{ p.number }}</span></li>
{% endfor %}
</ul>
</div>
{% endif %}
{% if profile.social_set.exists %}
<div class="social multi-entry">
<span class="label">{{ profile.social_set.count|pluralize:_("Réseau social,Réseaux sociaux") }}</span>
<span class="separator"></span>
<ul class="value">
{% for p in profile.social_set.all %}
<li><span class="type">{{ p.name }}</span><span class="value">{{ p.content }}</span></li>
<li><span class="type">{{ p.name }}</span><span class="value">{{ p.content }}</span></li>
{% endfor %}
</ul>
</div>
{% endif %}
{% if profile.mail_set.exists %}
<div class="mail multi-entry">
<span class="label">{{ profile.mail_set.count|pluralize:_("Mail,Mails") }}</span>
<span class="separator"></span>
<ul class="value">
{% for p in profile.mail_set.all %}
<li><span class="type">{{ p.name }}</span><span class="value">{{ p.mail }}</span></li>
<li><span class="type">{{ p.name }}</span><span class="value">{{ p.mail }}</span></li>
{% endfor %}
</ul>
</div>
{% endif %}
{% if profile.address_set.exists %}
<div class="address multi-entry">
<span class="label">{{ profile.address_set.count|pluralize:_("Adresse,Adresses") }}</span>
<span class="separator"></span>
<ul class="value">
{% for p in profile.address_set.all %}
<li><span class="type">{{ p.name }}</span><span class="value">{{ p.content|linebreaksbr }}</span></li>
<li><span class="type">{{ p.name }}</span><span class="value">{{ p.content }}</span></li>
{% endfor %}
</ul>
</div>
{% endif %}
{% if profile.past_studies %}
<div class="studies">
<span class="label">{% trans "Études passées" %}</span>
<span class="separator"></span>
<span class="value">{{ profile.past_studies|linebreaksbr }}</span>
</div>
{% endif %}
{% if profile.experiences %}
<div class="studies">
<span class="label">{% trans "Expériences passées" %}</span>
<span class="separator"></span>
<span class="value">{{ profile.experiences|linebreaksbr }}</span>
</div>
{% endif %}
</div>
{% if profile.text_field %}
<div class="free-text">
<span class="label">{% trans "Champ libre" %}</span>
<p class="value">{{ profile.text_field|linebreaksbr }}</p>
<p class="value">{{ profile.text_field }}</p>
</div>
{% endif %}

View file

@ -1,5 +1,6 @@
{% extends "fiches/base.html" %}
{% load i18n static %}
{% load i18n %}
{% load staticfiles %}
{% block content %}
@ -20,10 +21,6 @@
<label for="id_pronoun">{% trans "Pronom(s) utilisé(s) :" %}</label>
{{ form.pronoun }}
</div>
<div class="form-entry">
<label for="id_birth_date">{% trans "Date de naissance :" %}</label>
{{ form.birth_date }}
</div>
<div class="form-entry">
<label for="id_picture">{% trans "Photo :" %}</label>
<div id="photo-edit-form" class="wide-form-entry">
@ -32,6 +29,8 @@
<a href="{{ form.instance.picture.url }}">
<img src="{{ form.instance.picture.url }}">
</a>
{% else %}
<img>
{% endif %}
</div>
<div class="photo-edit-controls">
@ -57,38 +56,46 @@
{{ form.promotion }}
</div>
<div class="form-entry">
<label for="id_past_studies">{% trans "Études passées :" %}</label>
<div id="free-text-edit-form" class="wide-form-entry">
{{ form.past_studies }}
</div>
</div>
<div class="form-entry">
<label for="id_experiences">{% trans "Expériences :" %}</label>
<div id="free-text-edit-form" class="wide-form-entry">
{{ form.experiences }}
</div>
<label for="id_birth_date">{% trans "Date de naissance :" %}</label>
{{ form.birth_date }}
</div>
<div class="form-entry">
<label for="id_thurne">{% trans "Thurne :" %}</label>
{{ form.thurne }}
</div>
<div class="form-entry multi-entry" data-type-placeholder="{% trans "Personnel" %}" data-value-placeholder="{% trans "0612345678" %}">
<label for="id_phone">{% trans "Numéro(s) de téléphone :" %}</label>
<div
class="form-entry multi-entry"
data-type-placeholder="{% trans "Personnel" %}"
data-value-placeholder="{% trans "0612345678" %}"
>
<label for="id_phone">{% trans "Numéro(s) de téléphone :" %}</label>
{% trans "Ajouter un numéro" as add_number %}
{% include "fiches/multientry.html" with formset=phone_form new_entry_text=add_number %}
</div>
<div class="form-entry multi-entry" data-type-placeholder="{% trans "InstaTok" %}" data-value-placeholder="{% trans "mon_profil_instatok" %}">
<div
class="form-entry multi-entry"
data-type-placeholder="{% trans "InstaTok" %}"
data-value-placeholder="{% trans "mon_profil_instatok" %}"
>
<label for="id_social">{% trans "Réseaux sociaux :" %}</label>
{% trans "Ajouter un réseau social" as add_social %}
{% include "fiches/multientry.html" with formset=social_form new_entry_text=add_social %}
</div>
<div class="form-entry multi-entry" data-type-placeholder="{% trans "Professionelle" %}" data-value-placeholder="{% trans "moi@ens.fr" %}">
<label for="id_mail">{% trans "Mail(s) :" %}</label>
<div
class="form-entry multi-entry"
data-type-placeholder="{% trans "Professionelle" %}"
data-value-placeholder="{% trans "moi@ens.fr" %}"
>
<label for="id_mail">{% trans "Mail(s):" %}</label>
{% trans "Ajouter un email" as add_mail %}
{% include "fiches/multientry.html" with formset=mail_form new_entry_text=add_mail %}
</div>
<div class="form-entry multi-entry" data-type-placeholder="{% trans "Bureau" %}" data-value-placeholder="{% trans "45 rue d'Ulm" %}">
<label for="id_address">{% trans "Adresse(s) :" %}</label>
<div
class="form-entry multi-entry"
data-type-placeholder="{% trans "Bureau" %}"
data-value-placeholder="{% trans "45 rue d'Ulm" %}"
>
<label for="id_address">{% trans "Adresse(s):" %}</label>
{% trans "Ajouter une adresse" as add_address %}
{% include "fiches/multientry.html" with formset=address_form new_entry_text=add_address %}
</div>
@ -113,14 +120,5 @@
{% endblock %}
{% block extra_js %}
<script src="{% static "fiches/js/forms.js" %}"></script>
<script src="{% static "fiches/js/datepicker.min.js" %}"></script>
<script>
const elem = document.querySelector('input[name="birth_date"]');
const datepicker = new Datepicker(elem, {
format: "yyyy-mm-dd",
defaultViewDate: "{{ default_birth_date }}",
});
</script>
<script type="text/javascript" src="{% static "fiches/js/forms.js" %}"></script>
{% endblock %}

View file

@ -14,7 +14,7 @@
<label for="id_year">{% trans "Promotion :" %}</label>
{{ form.year }}
<label for="id_department">{% trans "Départment :" %}</label>
<label for="id_department">{% trans "Department :" %}</label>
{{ form.department }}
<input type="submit" value="{% trans "Recherche" %}">
</form>

View file

@ -1,4 +1,4 @@
{{ formset.non_field_errors }}
{{ formset.non_field_errors }}
{{ formset.management_form }}
{% for form in formset %}
{{ form.non_field_errors }}
@ -10,16 +10,16 @@
{% for field in form.hidden_fields %}
{{field}}
{% endfor %}
<button type=button class="remove-button"></button>
<button type=button" class="remove-button"></button>
</div>
{% endfor %}
<div class="form-sub-entry-template">
{% for field in formset.empty_form.visible_fields %}
{{field}}
{% endfor %}
{% for field in formset.empty_form.hidden_fields %}
{{field}}
{% endfor %}
{% for field in form.visible_fields %}
{{field}}
{% endfor %}
{% for field in form.hidden_fields %}
{{field}}
{% endfor %}
<button type="button" class="remove-button"></button>
</div>
<button type="button" class="add-sub-entry-button">{{ new_entry_text }}</button>

View file

@ -1,19 +0,0 @@
{% extends "fiches/base.html" %}
{% load i18n %}
{% block content %}
<div class="content" id="content-reset">
<h2>{% trans "Réinitialiser ma fiche annuaire" %}</h2>
<p id="warning">{% trans "Votre fiche annuaire sera réinitialisée. Votre nom, département et promotion seront récupérés de l'annuaire de l'administration." %}</p>
<form method="post" action="">
{% csrf_token %}
<input type="submit" name="reset" id="reset" value="{% trans "Réinitialiser" %}">
<input type="submit" name="abort" id="abort" value="{% trans "Annuler" %}">
</form>
</div>
{% endblock %}

View file

@ -1,11 +1,7 @@
from django.urls import path
from . import views
urlpatterns = [
path("", views.HomeView.as_view(), name="home"),
path("fiche/_edit", views.EditView.as_view(), name="fiche_modif"),
path("fiche/_reset", views.ResetView.as_view(), name="fiche_reset"),
path("fiche/<slug:slug>", views.FicheView.as_view(), name="fiche"),
path("birthday", views.BirthdayView.as_view(), name="birthday"),
path("edit", views.EditView.as_view(), name="fiche_modif"),
path("<user>", views.FicheView.as_view(), name="fiche"),
]

View file

@ -1,23 +0,0 @@
from .management.commands._ldap import ClipperLDAP
def get_ldap_infos(clipper_login):
ldap = ClipperLDAP()
try:
res = ldap.search("(uid={})".format(clipper_login))
except Exception:
return None
if not res:
return None
if len(res) != 1:
raise RuntimeError("LDAP returned too many results: {}".format(res))
(res,) = res
promo, dept = ldap.parse_dept(ldap.extract_ldap_info(res, "homeDirectory"))
return {
"name": ldap.extract_ldap_info(res, "cn"),
"promo": promo,
"dept": dept,
}

View file

@ -1,34 +1,30 @@
from datetime import date, timedelta
from django.shortcuts import render
from django.shortcuts import get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from django.core.mail import send_mail
from django.db.models import DateTimeField, Q, Value
from django.http import HttpResponseRedirect
from django.template.loader import render_to_string
from django.urls import reverse, reverse_lazy
from fiches.models import Profile, Phone, Social, Mail, Address
from fiches.forms import ProfileForm, SearchForm, PhoneFormSet, SocialFormSet, MailFormSet, AddressFormSet
from django.forms import formset_factory
from django.forms.models import model_to_dict
from django.urls import reverse
from django.db.models import Q
from django.utils import timezone
from django.utils.decorators import method_decorator
from datetime import timedelta
from django.core.mail import send_mail
from django.template.loader import render_to_string
from django.views.generic.detail import DetailView
from django.views.generic.edit import FormView, UpdateView
from django.views.generic.list import ListView
from fiches.forms import (
AddressFormSet,
MailFormSet,
PhoneFormSet,
ProfileForm,
SearchForm,
SocialFormSet,
)
from fiches.models import Department, Profile
from fiches.utils import get_ldap_infos
from django.views.generic.edit import FormView, UpdateView
from django.http import HttpResponseRedirect
@method_decorator(login_required, name="dispatch")
class FicheView(DetailView):
model = Profile
template_name = "fiches/fiche.html"
slug_field = "user__username"
def get_object(self):
return get_object_or_404(Profile, user__username=self.kwargs.get("user"))
@method_decorator(login_required, name="dispatch")
@ -46,14 +42,11 @@ class EditView(UpdateView):
mail_form = MailFormSet(instance=self.object)
address_form = AddressFormSet(instance=self.object)
return self.render_to_response(
self.get_context_data(
form=form,
phone_form=phone_form,
social_form=social_form,
mail_form=mail_form,
address_form=address_form,
)
)
self.get_context_data(form=form,
phone_form=phone_form,
social_form=social_form,
mail_form=mail_form,
address_form=address_form))
def post(self, request, *args, **kwargs):
self.object = self.get_object()
@ -63,21 +56,12 @@ class EditView(UpdateView):
social_form = SocialFormSet(self.request.POST, instance=self.object)
mail_form = MailFormSet(self.request.POST, instance=self.object)
address_form = AddressFormSet(self.request.POST, instance=self.object)
if (
form.is_valid()
and phone_form.is_valid()
and social_form.is_valid()
and mail_form.is_valid()
and address_form.is_valid()
):
return self.form_valid(
form, phone_form, social_form, mail_form, address_form
)
if (form.is_valid() and phone_form.is_valid() and social_form.is_valid()
and mail_form.is_valid() and address_form.is_valid()):
return self.form_valid(form, phone_form, social_form, mail_form, address_form)
else:
return self.form_invalid(
form, phone_form, social_form, mail_form, address_form
)
return self.form_invalid(form, phone_form, social_form, mail_form, address_form)
def form_valid(self, form, phone_form, social_form, mail_form, address_form):
self.object = form.save()
phone_form.save()
@ -85,34 +69,21 @@ class EditView(UpdateView):
mail_form.save()
address_form.save()
send_mail(
"Fiche annuaire modifée",
render_to_string(
"fiches/mail/mail_modif.txt", {"profile": self.get_object()}
),
"klub-dev@ens.psl.eu",
["{}@clipper.ens.psl.eu".format(self.get_object().user.username)],
fail_silently=False,
)
"Fiche annuaire modifée",
render_to_string("fiches/mail/mail_modif.txt", {"profile": self.get_object()}),
"klub-dev@ens.psl.eu",
["{}@clipper.ens.psl.eu".format(self.get_object().user.username)],
fail_silently=False,
)
return HttpResponseRedirect(self.get_success_url())
def form_invalid(self, form, phone_form, social_form, mail_form, address_form):
return self.render_to_response(
self.get_context_data(
form=form,
phone_form=phone_form,
social_form=social_form,
mail_form=mail_form,
address_form=address_form,
)
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
today = date.today()
context["default_birth_date"] = today.replace(
year=(today.year - 20)
).isoformat()
return context
self.get_context_data(form=form,
phone_form=phone_form,
social_form=social_form,
mail_form=mail_form,
address_form=address_form))
def get_object(self):
return self.request.user.profile
@ -128,21 +99,10 @@ class HomeView(FormView):
form_class = SearchForm
def form_valid(self, form):
name = form.cleaned_data["name"]
promotion = form.cleaned_data["year"]
depts = form.cleaned_data["department"]
result = Profile.objects.filter(
Q(full_name__icontains=name)
| Q(nickname__icontains=name)
| Q(user__username__icontains=name)
)
if depts:
result = result.filter(department__in=depts)
if promotion:
result = result.filter(promotion=promotion)
Q(full_name__icontains=form.cleaned_data["name"])
| Q(nickname__icontains=form.cleaned_data["name"])
)
return self.render_to_response(self.get_context_data(result=result))
@ -151,60 +111,19 @@ class BirthdayView(ListView):
model = Profile
template_name = "fiches/birthday.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
today = timezone.now()
context["result"] = list(
Profile.objects.filter(
birth_date__day=today.day, birth_date__month=today.month
).annotate(day=Value(today, output_field=DateTimeField()))
context['result'] = list(
Profile.objects.filter(birth_date__day=today.day, birth_date__month=today.month)
)
for _ in range(1, 7):
for i in range(1, 7):
today = today + timedelta(days=1)
context["result"] += list(
context['result'] += list(
Profile.objects.filter(
birth_date__day=today.day, birth_date__month=today.month
).annotate(day=Value(today, output_field=DateTimeField()))
)
)
return context
@method_decorator(login_required, name="dispatch")
class ResetView(UpdateView):
model = Profile
template_name = "fiches/reset.html"
fields = []
success_url = reverse_lazy("fiche_modif")
def get_object(self):
return self.request.user.profile
def post(self, request, *args, **kwargs):
if "reset" in request.POST:
# On réinitialise le profil
profile = self.get_object()
base_infos = get_ldap_infos(profile.user.cas_account.cas_login)
# On supprime les trucs inutiles
profile.phone_set.all().delete()
profile.social_set.all().delete()
profile.mail_set.all().delete()
profile.address_set.all().delete()
profile.nickname = ""
profile.pronoun = ""
profile.birth_date = None
profile.past_studies = ""
profile.experiences = ""
profile.thurne = ""
profile.text_field = ""
profile.picture.delete()
# On réinitialise avec les infos du LDAP
if base_infos is not None:
profile.full_name = base_infos["name"]
profile.department.clear()
profile.department.add(Department.objects.get(name=base_infos["dept"]))
profile.promotion = base_infos["promo"]
profile.save()
return HttpResponseRedirect(self.success_url)
return context

Binary file not shown.

View file

@ -1,398 +0,0 @@
# Annuaire translations
# Copyright (C) 2021 Klub Dev ENS
# This file is distributed under the same license as the annuaire package.
# Klub Dev ENS <klub-dev@ens.fr>, 2021.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-02-06 12:02+0000\n"
"PO-Revision-Date: 2021-10-08 09:36+0200\n"
"Last-Translator: Test Translator <test@translator>\n"
"Language-Team: \n"
"Language: en\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.0\n"
#: fiches/forms.py:20
msgid "Nom/Surnom"
msgstr "Name/Nickname"
#: fiches/forms.py:21
msgid "Promotion"
msgstr "Entrance year"
#: fiches/forms.py:33
msgid "Tous les champs sont vides"
msgstr "All fields are empty"
#: fiches/models.py:11
msgid "utilisateur"
msgstr "user"
#: fiches/models.py:14
msgid "nom"
msgstr "name"
#: fiches/models.py:15
msgid "surnom"
msgstr "nickname"
#: fiches/models.py:17
msgid "pronom(s) utilisé(s)"
msgstr "pronoun(s)"
#: fiches/models.py:20
msgid "photo"
msgstr "photo"
#: fiches/models.py:22
msgid "département"
msgstr "department"
#: fiches/models.py:24
msgid "promotion"
msgstr "entrance year"
#: fiches/models.py:27
msgid "date de naissance"
msgstr "birth date"
#: fiches/models.py:29
msgid "études passées"
msgstr "past studies"
#: fiches/models.py:30
msgid "expériences"
msgstr "experiences"
#: fiches/models.py:31
msgid "thurne"
msgstr "room"
#: fiches/models.py:32
msgid "champ libre"
msgstr "free space"
#: fiches/models.py:34
msgid "apparaître sur l'annuaire papier ?"
msgstr "appear on the paper directory?"
#: fiches/models.py:37
msgid "conserver la fiche annuaire ?"
msgstr "keep the directory record?"
#: fiches/models.py:49
msgid "nom du département"
msgstr "department name"
#: fiches/models.py:58 fiches/models.py:69 fiches/models.py:80
#: fiches/models.py:91
msgid "profil"
msgstr "profile"
#: fiches/models.py:60 fiches/models.py:71 fiches/models.py:82
#: fiches/models.py:93
msgid "type"
msgstr "type"
#: fiches/models.py:61
msgid "numéro"
msgstr "number"
#: fiches/models.py:72
msgid "contenu"
msgstr "content"
#: fiches/models.py:83
msgid "adresse mail"
msgstr "e-mail"
#: fiches/models.py:94
msgid "adresse"
msgstr "address"
#: fiches/templates/fiches/base.html:10 fiches/templates/fiches/base.html:21
msgid "Annuaire des élèves de l'ENS"
msgstr "ENS student directory"
#: fiches/templates/fiches/base.html:36
msgid "Recherche Rapide"
msgstr "Quick Search"
#: fiches/templates/fiches/base.html:37
msgid "Rechercher"
msgstr "Search"
#: fiches/templates/fiches/base.html:42
msgid "Accueil"
msgstr "Home"
#: fiches/templates/fiches/base.html:44
msgid "Consulter sa fiche d'annuaire"
msgstr "View your directory record"
#: fiches/templates/fiches/base.html:45
msgid "Modifier sa fiche d'annuaire"
msgstr "Edit your directory record"
#: fiches/templates/fiches/base.html:46
msgid "Réinitialiser sa fiche d'annuaire"
msgstr "Reset your directory record"
#: fiches/templates/fiches/base.html:48
msgid "Anniversaires à venir"
msgstr "Upcoming birthdays"
#: fiches/templates/fiches/base.html:54
#, python-format
msgid "Connecté en tant que <span class=\"clipper\">%(user)s</span>"
msgstr "Connected as <span class=\"clipper\">%(user)s</span>"
#: fiches/templates/fiches/base.html:55
msgid "Se déconnecter"
msgstr "Log out"
#: fiches/templates/fiches/base.html:57
msgid "Se connecter"
msgstr "Log in"
#: fiches/templates/fiches/base.html:73
msgid "Crée par KDENS &middot; Propulsé par Django"
msgstr "Created by KDENS &middot; Powered by Django"
#: fiches/templates/fiches/base.html:77
msgid "Page des élèves"
msgstr "Students' page"
#: fiches/templates/fiches/base.html:78
msgid "Contacter l'équipe annuaire"
msgstr "Contact the directory team"
#: fiches/templates/fiches/birthday.html:8
msgid "Anniversaires"
msgstr "Birthdays"
#: fiches/templates/fiches/birthday.html:15
msgid "ans"
msgstr "years"
#: fiches/templates/fiches/fiche.html:25
msgid "Pronom(s) utilisé(s)"
msgstr "Pronoun(s)"
#: fiches/templates/fiches/fiche.html:33
msgid "Département"
msgstr "Department"
#: fiches/templates/fiches/fiche.html:41
msgid "Date de naissance"
msgstr "Birth date"
#: fiches/templates/fiches/fiche.html:51
msgid "Thurne"
msgstr "Room"
#: fiches/templates/fiches/fiche.html:59
msgid "Téléphone"
msgstr "Phone number"
#: fiches/templates/fiches/fiche.html:71
msgid "Réseau social,Réseaux sociaux"
msgstr "Social network,Social networks"
#: fiches/templates/fiches/fiche.html:83
msgid "Mail,Mails"
msgstr "E-mail,E-mails"
#: fiches/templates/fiches/fiche.html:95
msgid "Adresse,Adresses"
msgstr "Address,Addresses"
#: fiches/templates/fiches/fiche.html:107
msgid "Études passées"
msgstr "Past studies"
#: fiches/templates/fiches/fiche.html:115
msgid "Expériences passées"
msgstr "Experiences"
#: fiches/templates/fiches/fiche.html:124
msgid "Champ libre"
msgstr "Free space"
#: fiches/templates/fiches/fiches_modif.html:7
msgid "Modifier ma page d'annuaire"
msgstr "Edit my directory record"
#: fiches/templates/fiches/fiches_modif.html:12
msgid "Nom :"
msgstr "Name:"
#: fiches/templates/fiches/fiches_modif.html:16
msgid "Surnom :"
msgstr "Nickname:"
#: fiches/templates/fiches/fiches_modif.html:20
msgid "Pronom(s) utilisé(s) :"
msgstr "Pronoun(s):"
#: fiches/templates/fiches/fiches_modif.html:24
msgid "Date de naissance :"
msgstr "Birth date:"
#: fiches/templates/fiches/fiches_modif.html:28
msgid "Photo :"
msgstr "Photo:"
#: fiches/templates/fiches/fiches_modif.html:40
msgid "Effacer (cochez la case) :"
msgstr "Delete (check the box):"
#: fiches/templates/fiches/fiches_modif.html:45
msgid "Nouvelle photo :"
msgstr "New photo:"
#: fiches/templates/fiches/fiches_modif.html:52
msgid "Département :"
msgstr "Department:"
#: fiches/templates/fiches/fiches_modif.html:56
#: fiches/templates/fiches/home.html:14
msgid "Promotion :"
msgstr "Entrance year:"
#: fiches/templates/fiches/fiches_modif.html:60
msgid "Études passées :"
msgstr "Past studies:"
#: fiches/templates/fiches/fiches_modif.html:66
msgid "Expériences :"
msgstr "Experiences:"
#: fiches/templates/fiches/fiches_modif.html:72
msgid "Thurne :"
msgstr "Room :"
#: fiches/templates/fiches/fiches_modif.html:75
msgid "Personnel"
msgstr "Private"
#: fiches/templates/fiches/fiches_modif.html:75
msgid "0612345678"
msgstr "0612345678"
#: fiches/templates/fiches/fiches_modif.html:76
msgid "Numéro(s) de téléphone :"
msgstr "Phone number(s):"
#: fiches/templates/fiches/fiches_modif.html:77
msgid "Ajouter un numéro"
msgstr "Add a phone number"
#: fiches/templates/fiches/fiches_modif.html:80
msgid "InstaTok"
msgstr "InstaTok"
#: fiches/templates/fiches/fiches_modif.html:80
msgid "mon_profil_instatok"
msgstr "my_instatok_profile"
#: fiches/templates/fiches/fiches_modif.html:81
msgid "Réseaux sociaux :"
msgstr "Social networks:"
#: fiches/templates/fiches/fiches_modif.html:82
msgid "Ajouter un réseau social"
msgstr "Add a social network"
#: fiches/templates/fiches/fiches_modif.html:85
msgid "Professionelle"
msgstr "Professional"
#: fiches/templates/fiches/fiches_modif.html:85
msgid "moi@ens.fr"
msgstr "me@ens.fr"
#: fiches/templates/fiches/fiches_modif.html:86
msgid "Mail(s) :"
msgstr "E-mail(s):"
#: fiches/templates/fiches/fiches_modif.html:87
msgid "Ajouter un email"
msgstr "Add an e-mail"
#: fiches/templates/fiches/fiches_modif.html:90
msgid "Bureau"
msgstr "Office"
#: fiches/templates/fiches/fiches_modif.html:90
msgid "45 rue d'Ulm"
msgstr "45 rue d'Ulm"
#: fiches/templates/fiches/fiches_modif.html:91
msgid "Adresse(s) :"
msgstr "Address(es):"
#: fiches/templates/fiches/fiches_modif.html:92
msgid "Ajouter une adresse"
msgstr "Add an address"
#: fiches/templates/fiches/fiches_modif.html:96
msgid "Champ libre :"
msgstr "Free space:"
#: fiches/templates/fiches/fiches_modif.html:102
msgid "Apparaître sur l'annuaire papier ?"
msgstr "Appear on the paper directory?"
#: fiches/templates/fiches/fiches_modif.html:106
msgid "Conserver la fiche annuaire ?"
msgstr "Keep the directory record?"
#: fiches/templates/fiches/fiches_modif.html:109
msgid "Enregistrer"
msgstr "Save"
#: fiches/templates/fiches/home.html:8
msgid "Chercher quelqu'un·e dans l'annuaire"
msgstr "Search for someone in the directory"
#: fiches/templates/fiches/home.html:11
msgid "Nom/Surnom :"
msgstr "Name/Nickname:"
#: fiches/templates/fiches/home.html:17
msgid "Départment :"
msgstr "Department:"
#: fiches/templates/fiches/home.html:19
msgid "Recherche"
msgstr "Search"
#: fiches/templates/fiches/reset.html:8
msgid "Réinitialiser ma fiche annuaire"
msgstr "Reset your directory record"
#: fiches/templates/fiches/reset.html:9
msgid ""
"Votre fiche annuaire sera réinitialisée. Votre nom, département et promotion "
"seront récupérés de l'annuaire de l'administration."
msgstr ""
"Your directory record will be reset. Your name, department and promotion "
"will be copied from the administration directory."
#: fiches/templates/fiches/reset.html:13
msgid "Réinitialiser"
msgstr "Reset"
#: fiches/templates/fiches/reset.html:14
msgid "Annuler"
msgstr "Cancel"
#~ msgid "Department :"
#~ msgstr "Department:"

View file

@ -5,7 +5,7 @@ import sys
def main():
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "annuaire.settings.local")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:

View file

@ -1,80 +0,0 @@
# Generated by npins. Do not modify; will be overwritten regularly
let
data = builtins.fromJSON (builtins.readFile ./sources.json);
version = data.version;
mkSource =
spec:
assert spec ? type;
let
path =
if spec.type == "Git" then
mkGitSource spec
else if spec.type == "GitRelease" then
mkGitSource spec
else if spec.type == "PyPi" then
mkPyPiSource spec
else if spec.type == "Channel" then
mkChannelSource spec
else
builtins.throw "Unknown source type ${spec.type}";
in
spec // { outPath = path; };
mkGitSource =
{
repository,
revision,
url ? null,
hash,
branch ? null,
...
}:
assert repository ? type;
# At the moment, either it is a plain git repository (which has an url), or it is a GitHub/GitLab repository
# In the latter case, there we will always be an url to the tarball
if url != null then
(builtins.fetchTarball {
inherit url;
sha256 = hash; # FIXME: check nix version & use SRI hashes
})
else
assert repository.type == "Git";
let
urlToName =
url: rev:
let
matched = builtins.match "^.*/([^/]*)(\\.git)?$" repository.url;
short = builtins.substring 0 7 rev;
appendShort = if (builtins.match "[a-f0-9]*" rev) != null then "-${short}" else "";
in
"${if matched == null then "source" else builtins.head matched}${appendShort}";
name = urlToName repository.url revision;
in
builtins.fetchGit {
url = repository.url;
rev = revision;
inherit name;
# hash = hash;
};
mkPyPiSource =
{ url, hash, ... }:
builtins.fetchurl {
inherit url;
sha256 = hash;
};
mkChannelSource =
{ url, hash, ... }:
builtins.fetchTarball {
inherit url;
sha256 = hash;
};
in
if version == 3 then
builtins.mapAttrs (_: mkSource) data.pins
else
throw "Unsupported format version ${toString version} in sources.json. Try running `npins upgrade`"

View file

@ -1,22 +0,0 @@
{
"pins": {
"nix-pkgs": {
"type": "Git",
"repository": {
"type": "Git",
"url": "https://git.hubrecht.ovh/hubrecht/nix-pkgs"
},
"branch": "main",
"revision": "3e731378f3984313ef902c5e5a49e002e6e2c27e",
"url": null,
"hash": "1vy2dj9fyy653w6idvi1r73s0nd2a332a1xkppddjip6rk0i030p"
},
"nixpkgs": {
"type": "Channel",
"name": "nixpkgs-unstable",
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-24.11pre691017.b69de56fac8c/nixexprs.tar.xz",
"hash": "0z32pj0lh5ng2a6cn0qfmka8cynnygckn5615mkaxq2aplkvgzx3"
}
},
"version": 3
}

1141
poetry.lock generated

File diff suppressed because it is too large Load diff

86
provisioning/bootstrap.sh Normal file
View file

@ -0,0 +1,86 @@
#!/bin/sh
# Stop if an error is encountered
set -e
PROJECTNAME=$(basename $1)
SETTINGS_MODULE="$PROJECTNAME.settings.vagrant"
# Configuration de la base de données. Le mot de passe est constant car c'est
# pour une installation de dév locale qui ne sera accessible que depuis la
# machine virtuelle.
DBUSER=$PROJECTNAME
DBNAME=$PROJECTNAME
DBPASSWD="O1LxCADDA6Px5SiKvifjvdp3DSjfbp"
# Installation de paquets utiles.
# Installe les paquets mentionnés dans `package.list`, en excluant les lignes
# commençant par #.
apt-get update && apt-get upgrade -y
apt-get install -y $(awk '! /^ *#/' /vagrant/provisioning/package.list)
# Postgresql
# On teste si la db existe déjà pour ne pas essayer de la recréer
DB_EXISTS=$(sudo -u postgres psql -lqt | cut -d \| -f 1 | grep -cw $DBNAME || true)
if [ $DB_EXISTS -eq 0 ]
then
sudo -u postgres createdb $DBNAME
sudo -u postgres createuser -SdR $DBUSER
sudo -u postgres psql -c "ALTER USER $DBUSER WITH PASSWORD '$DBPASSWD';"
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE $DBNAME TO $DBUSER;"
fi
# Redis
REDIS_PASSWD="dummy"
redis-cli CONFIG SET requirepass $REDIS_PASSWD
redis-cli -a $REDIS_PASSWD CONFIG REWRITE
# Contenu statique
mkdir -p /srv/$PROJECTNAME/static
ln -sf /vagrant/media /srv/$PROJECTNAME/media
chown -R vagrant:www-data /srv/$PROJECTNAME
# Nginx
rm -f /etc/nginx/sites-enabled/default
sed "s/\_\_PROJECTNAME__/$PROJECTNAME/g" /vagrant/provisioning/nginx.conf > /etc/nginx/sites-enabled/$PROJECTNAME.conf
systemctl reload nginx
# Environnement virtuel python
sudo -H -u vagrant python3 -m venv ~vagrant/venv
sudo -H -u vagrant ~vagrant/venv/bin/pip install -U pip
sudo -H -u vagrant ~vagrant/venv/bin/pip install -r /vagrant/requirements-prod.txt -r /vagrant/requirements-dev.txt
# Préparation de Django
cd /vagrant
sudo -H -u vagrant \
DJANGO_SETTINGS_MODULE=$SETTINGS_MODULE \
bash -c ". ~/venv/bin/activate && bash provisioning/prepare_django.sh"
sudo -H -u vagrant /home/vagrant/venv/bin/python manage.py collectstatic --noinput --settings $SETTINGS_MODULE
# Mails
mkdir -p /var/mail/django
chown -R vagrant:www-data /var/mail/django
# Service files
for file in /vagrant/provisioning/*.service
do
# failsafe si aucun fichier .service n'existe
[ -f $file ] || break
SERVICE=$(basename $file)
# On copie en remplaçant si nécessaire le template
sed "s/\_\_PROJECTNAME__/$PROJECTNAME/g" $file > /etc/systemd/system/$SERVICE
systemctl enable $SERVICE
systemctl start $SERVICE
done
# Mise en place du .bash_profile pour tout configurer lors du `vagrant ssh`
cat >> ~vagrant/.bashrc <<EOF
export DJANGO_SETTINGS_MODULE=$SETTINGS_MODULE
# Charge le virtualenv
source ~/venv/bin/activate
cd /vagrant
EOF

View file

@ -0,0 +1,15 @@
Description="Gunicorn"
After=syslog.target
After=network.target
[Service]
Type=simple
User=vagrant
Group=vagrant
TimeoutSec=300
WorkingDirectory=/vagrant
Environment="DJANGO_SETTINGS_MODULE=__PROJECTNAME__.settings.vagrant"
ExecStart=/home/vagrant/venv/bin/gunicorn --bind=unix:/tmp/gunicorn.sock __PROJECTNAME__.wsgi:application
[Install]
WantedBy=multi-user.target

49
provisioning/nginx.conf Normal file
View file

@ -0,0 +1,49 @@
upstream app_server {
# fail_timeout=0 means we always retry an upstream even if it failed
# to return a good HTTP response
# for UNIX domain socket setups
server unix:/tmp/gunicorn.sock fail_timeout=0;
# for a TCP configuration
# server 192.168.0.7:8000 fail_timeout=0;
}
server {
# use 'listen 80 deferred;' for Linux
# use 'listen 80 accept_filter=httpready;' for FreeBSD
listen 80 deferred;
client_max_body_size 4G;
# set the correct host(s) for your site
server_name localhost;
keepalive_timeout 5;
# path for static files
root /srv/__PROJECTNAME__;
# Static files
location /static/ {
access_log off;
}
# Uploaded media
location /media/ {
access_log off;
}
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-SSL-Client-Serial $ssl_client_serial;
proxy_set_header X-SSL-Client-Verify $ssl_client_verify;
proxy_set_header X-SSL-Client-S-DN $ssl_client_s_dn;
# we don't want nginx trying to do something clever with
# redirects, we set the Host: header above already.
proxy_redirect off;
proxy_pass http://app_server;
}
}

View file

@ -0,0 +1,6 @@
python3-pip python3-dev python3-venv
libpq-dev postgresql postgresql-contrib libjpeg-dev
build-essential nginx git redis-server
# Needed for python-ldap
libldap2-dev libsasl2-dev ldap-utils lcov

View file

@ -0,0 +1,6 @@
#!/bin/bash
# Stop if an error is encountered.
set -e
python manage.py migrate

View file

@ -1,34 +0,0 @@
[tool.poetry]
name = "annuaire"
version = "0.1.0"
description = ""
authors = ["Klub-Dev ENS <klub-dev@ens.fr>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.9"
Django = "^3.2.0"
Pillow = "^9.4.0"
authens = "^0.1b4"
python-dateutil = "^2.8.2"
[tool.poetry.group.dev.dependencies]
ipython = "^8.9.0"
isort = "^5.12.0"
flake8 = "^6.0.0"
black = "^22.12.0"
django-types = "^0.16.0"
django-debug-toolbar = "^3.8.1"
[tool.poetry.group.prod.dependencies]
python-ldap = "^3.4.3"
psycopg2 = "^2.9.5"
gunicorn = "^20.1.0"
django-redis = "^5.2.0"
[tool.isort]
profile = "black"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

View file

@ -1,394 +1,6 @@
appnope==0.1.3 ; python_version >= "3.9" and python_version < "4.0" and sys_platform == "darwin" \
--hash=sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24 \
--hash=sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e
asgiref==3.6.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:71e68008da809b957b7ee4b43dbccff33d1b23519fb8344e33f049897077afac \
--hash=sha256:9567dfe7bd8d3c8c892227827c41cce860b368104c3431da67a0c5a65a949506
asttokens==2.2.1 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:4622110b2a6f30b77e1473affaa97e711bc2f07d3f10848420ff1898edbe94f3 \
--hash=sha256:6b0ac9e93fb0335014d382b8fa9b3afa7df546984258005da0b9e7095b3deb1c
authens==0.1b4 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:3430c85d0c11c7ca78cba484d5dfd18e9ba3370a0f2abbdd14aafb8feb8807a5
backcall==0.2.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e \
--hash=sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255
black==22.12.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320 \
--hash=sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351 \
--hash=sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350 \
--hash=sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f \
--hash=sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf \
--hash=sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148 \
--hash=sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4 \
--hash=sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d \
--hash=sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc \
--hash=sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d \
--hash=sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2 \
--hash=sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f
certifi==2022.12.7 ; python_version >= "3.9" and python_version < "4" \
--hash=sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3 \
--hash=sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18
charset-normalizer==3.0.1 ; python_version >= "3.9" and python_version < "4" \
--hash=sha256:00d3ffdaafe92a5dc603cb9bd5111aaa36dfa187c8285c543be562e61b755f6b \
--hash=sha256:024e606be3ed92216e2b6952ed859d86b4cfa52cd5bc5f050e7dc28f9b43ec42 \
--hash=sha256:0298eafff88c99982a4cf66ba2efa1128e4ddaca0b05eec4c456bbc7db691d8d \
--hash=sha256:02a51034802cbf38db3f89c66fb5d2ec57e6fe7ef2f4a44d070a593c3688667b \
--hash=sha256:083c8d17153ecb403e5e1eb76a7ef4babfc2c48d58899c98fcaa04833e7a2f9a \
--hash=sha256:0a11e971ed097d24c534c037d298ad32c6ce81a45736d31e0ff0ad37ab437d59 \
--hash=sha256:0bf2dae5291758b6f84cf923bfaa285632816007db0330002fa1de38bfcb7154 \
--hash=sha256:0c0a590235ccd933d9892c627dec5bc7511ce6ad6c1011fdf5b11363022746c1 \
--hash=sha256:0f438ae3532723fb6ead77e7c604be7c8374094ef4ee2c5e03a3a17f1fca256c \
--hash=sha256:109487860ef6a328f3eec66f2bf78b0b72400280d8f8ea05f69c51644ba6521a \
--hash=sha256:11b53acf2411c3b09e6af37e4b9005cba376c872503c8f28218c7243582df45d \
--hash=sha256:12db3b2c533c23ab812c2b25934f60383361f8a376ae272665f8e48b88e8e1c6 \
--hash=sha256:14e76c0f23218b8f46c4d87018ca2e441535aed3632ca134b10239dfb6dadd6b \
--hash=sha256:16a8663d6e281208d78806dbe14ee9903715361cf81f6d4309944e4d1e59ac5b \
--hash=sha256:292d5e8ba896bbfd6334b096e34bffb56161c81408d6d036a7dfa6929cff8783 \
--hash=sha256:2c03cc56021a4bd59be889c2b9257dae13bf55041a3372d3295416f86b295fb5 \
--hash=sha256:2e396d70bc4ef5325b72b593a72c8979999aa52fb8bcf03f701c1b03e1166918 \
--hash=sha256:2edb64ee7bf1ed524a1da60cdcd2e1f6e2b4f66ef7c077680739f1641f62f555 \
--hash=sha256:31a9ddf4718d10ae04d9b18801bd776693487cbb57d74cc3458a7673f6f34639 \
--hash=sha256:356541bf4381fa35856dafa6a965916e54bed415ad8a24ee6de6e37deccf2786 \
--hash=sha256:358a7c4cb8ba9b46c453b1dd8d9e431452d5249072e4f56cfda3149f6ab1405e \
--hash=sha256:37f8febc8ec50c14f3ec9637505f28e58d4f66752207ea177c1d67df25da5aed \
--hash=sha256:39049da0ffb96c8cbb65cbf5c5f3ca3168990adf3551bd1dee10c48fce8ae820 \
--hash=sha256:39cf9ed17fe3b1bc81f33c9ceb6ce67683ee7526e65fde1447c772afc54a1bb8 \
--hash=sha256:3ae1de54a77dc0d6d5fcf623290af4266412a7c4be0b1ff7444394f03f5c54e3 \
--hash=sha256:3b590df687e3c5ee0deef9fc8c547d81986d9a1b56073d82de008744452d6541 \
--hash=sha256:3e45867f1f2ab0711d60c6c71746ac53537f1684baa699f4f668d4c6f6ce8e14 \
--hash=sha256:3fc1c4a2ffd64890aebdb3f97e1278b0cc72579a08ca4de8cd2c04799a3a22be \
--hash=sha256:4457ea6774b5611f4bed5eaa5df55f70abde42364d498c5134b7ef4c6958e20e \
--hash=sha256:44ba614de5361b3e5278e1241fda3dc1838deed864b50a10d7ce92983797fa76 \
--hash=sha256:4a8fcf28c05c1f6d7e177a9a46a1c52798bfe2ad80681d275b10dcf317deaf0b \
--hash=sha256:4b0d02d7102dd0f997580b51edc4cebcf2ab6397a7edf89f1c73b586c614272c \
--hash=sha256:502218f52498a36d6bf5ea77081844017bf7982cdbe521ad85e64cabee1b608b \
--hash=sha256:503e65837c71b875ecdd733877d852adbc465bd82c768a067badd953bf1bc5a3 \
--hash=sha256:5995f0164fa7df59db4746112fec3f49c461dd6b31b841873443bdb077c13cfc \
--hash=sha256:59e5686dd847347e55dffcc191a96622f016bc0ad89105e24c14e0d6305acbc6 \
--hash=sha256:601f36512f9e28f029d9481bdaf8e89e5148ac5d89cffd3b05cd533eeb423b59 \
--hash=sha256:608862a7bf6957f2333fc54ab4399e405baad0163dc9f8d99cb236816db169d4 \
--hash=sha256:62595ab75873d50d57323a91dd03e6966eb79c41fa834b7a1661ed043b2d404d \
--hash=sha256:70990b9c51340e4044cfc394a81f614f3f90d41397104d226f21e66de668730d \
--hash=sha256:71140351489970dfe5e60fc621ada3e0f41104a5eddaca47a7acb3c1b851d6d3 \
--hash=sha256:72966d1b297c741541ca8cf1223ff262a6febe52481af742036a0b296e35fa5a \
--hash=sha256:74292fc76c905c0ef095fe11e188a32ebd03bc38f3f3e9bcb85e4e6db177b7ea \
--hash=sha256:761e8904c07ad053d285670f36dd94e1b6ab7f16ce62b9805c475b7aa1cffde6 \
--hash=sha256:772b87914ff1152b92a197ef4ea40efe27a378606c39446ded52c8f80f79702e \
--hash=sha256:79909e27e8e4fcc9db4addea88aa63f6423ebb171db091fb4373e3312cb6d603 \
--hash=sha256:7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24 \
--hash=sha256:7eb33a30d75562222b64f569c642ff3dc6689e09adda43a082208397f016c39a \
--hash=sha256:81d6741ab457d14fdedc215516665050f3822d3e56508921cc7239f8c8e66a58 \
--hash=sha256:8499ca8f4502af841f68135133d8258f7b32a53a1d594aa98cc52013fff55678 \
--hash=sha256:84c3990934bae40ea69a82034912ffe5a62c60bbf6ec5bc9691419641d7d5c9a \
--hash=sha256:87701167f2a5c930b403e9756fab1d31d4d4da52856143b609e30a1ce7160f3c \
--hash=sha256:88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6 \
--hash=sha256:8ac7b6a045b814cf0c47f3623d21ebd88b3e8cf216a14790b455ea7ff0135d18 \
--hash=sha256:8b8af03d2e37866d023ad0ddea594edefc31e827fee64f8de5611a1dbc373174 \
--hash=sha256:8c7fe7afa480e3e82eed58e0ca89f751cd14d767638e2550c77a92a9e749c317 \
--hash=sha256:8eade758719add78ec36dc13201483f8e9b5d940329285edcd5f70c0a9edbd7f \
--hash=sha256:911d8a40b2bef5b8bbae2e36a0b103f142ac53557ab421dc16ac4aafee6f53dc \
--hash=sha256:93ad6d87ac18e2a90b0fe89df7c65263b9a99a0eb98f0a3d2e079f12a0735837 \
--hash=sha256:95dea361dd73757c6f1c0a1480ac499952c16ac83f7f5f4f84f0658a01b8ef41 \
--hash=sha256:9ab77acb98eba3fd2a85cd160851816bfce6871d944d885febf012713f06659c \
--hash=sha256:9cb3032517f1627cc012dbc80a8ec976ae76d93ea2b5feaa9d2a5b8882597579 \
--hash=sha256:9cf4e8ad252f7c38dd1f676b46514f92dc0ebeb0db5552f5f403509705e24753 \
--hash=sha256:9d9153257a3f70d5f69edf2325357251ed20f772b12e593f3b3377b5f78e7ef8 \
--hash=sha256:a152f5f33d64a6be73f1d30c9cc82dfc73cec6477ec268e7c6e4c7d23c2d2291 \
--hash=sha256:a16418ecf1329f71df119e8a65f3aa68004a3f9383821edcb20f0702934d8087 \
--hash=sha256:a60332922359f920193b1d4826953c507a877b523b2395ad7bc716ddd386d866 \
--hash=sha256:a8d0fc946c784ff7f7c3742310cc8a57c5c6dc31631269876a88b809dbeff3d3 \
--hash=sha256:ab5de034a886f616a5668aa5d098af2b5385ed70142090e2a31bcbd0af0fdb3d \
--hash=sha256:c22d3fe05ce11d3671297dc8973267daa0f938b93ec716e12e0f6dee81591dc1 \
--hash=sha256:c2ac1b08635a8cd4e0cbeaf6f5e922085908d48eb05d44c5ae9eabab148512ca \
--hash=sha256:c512accbd6ff0270939b9ac214b84fb5ada5f0409c44298361b2f5e13f9aed9e \
--hash=sha256:c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db \
--hash=sha256:c95a03c79bbe30eec3ec2b7f076074f4281526724c8685a42872974ef4d36b72 \
--hash=sha256:cadaeaba78750d58d3cc6ac4d1fd867da6fc73c88156b7a3212a3cd4819d679d \
--hash=sha256:cd6056167405314a4dc3c173943f11249fa0f1b204f8b51ed4bde1a9cd1834dc \
--hash=sha256:db72b07027db150f468fbada4d85b3b2729a3db39178abf5c543b784c1254539 \
--hash=sha256:df2c707231459e8a4028eabcd3cfc827befd635b3ef72eada84ab13b52e1574d \
--hash=sha256:e62164b50f84e20601c1ff8eb55620d2ad25fb81b59e3cd776a1902527a788af \
--hash=sha256:e696f0dd336161fca9adbb846875d40752e6eba585843c768935ba5c9960722b \
--hash=sha256:eaa379fcd227ca235d04152ca6704c7cb55564116f8bc52545ff357628e10602 \
--hash=sha256:ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f \
--hash=sha256:f4c39b0e3eac288fedc2b43055cfc2ca7a60362d0e5e87a637beac5d801ef478 \
--hash=sha256:f5057856d21e7586765171eac8b9fc3f7d44ef39425f85dbcccb13b3ebea806c \
--hash=sha256:f6f45710b4459401609ebebdbcfb34515da4fc2aa886f95107f556ac69a9147e \
--hash=sha256:f97e83fa6c25693c7a35de154681fcc257c1c41b38beb0304b9c4d2d9e164479 \
--hash=sha256:f9d0c5c045a3ca9bedfc35dca8526798eb91a07aa7a2c0fee134c6c6f321cbd7 \
--hash=sha256:ff6f3db31555657f3163b15a6b7c6938d08df7adbfc9dd13d9d19edad678f1e8
click==8.1.3 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e \
--hash=sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48
colorama==0.4.6 ; python_version >= "3.9" and python_version < "4.0" and sys_platform == "win32" or python_version >= "3.9" and python_version < "4.0" and platform_system == "Windows" \
--hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
decorator==5.1.1 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330 \
--hash=sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186
django-debug-toolbar==3.8.1 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:24ef1a7d44d25e60d7951e378454c6509bf536dce7e7d9d36e7c387db499bc27 \
--hash=sha256:879f8a4672d41621c06a4d322dcffa630fc4df056cada6e417ed01db0e5e0478
django-types==0.16.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:29d23af8b88fa1579b47418e92ffc1d89bfbdf11eab64d2ae006fa09e54fed9a \
--hash=sha256:9df4e1936f304f309a59f4cee19a607e151282fc0d97238833a382fafc5052c8
django==3.2.16 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:18ba8efa36b69cfcd4b670d0fa187c6fe7506596f0ababe580e16909bcdec121 \
--hash=sha256:3adc285124244724a394fa9b9839cc8cd116faf7d159554c43ecdaa8cdf0b94d
executing==1.2.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:0314a69e37426e3608aada02473b4161d4caf5a4b244d1d0c48072b8fee7bacc \
--hash=sha256:19da64c18d2d851112f09c287f8d3dbbdf725ab0e569077efb6cdcbd3497c107
flake8==6.0.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:3833794e27ff64ea4e9cf5d410082a8b97ff1a06c16aa3d2027339cd0f1195c7 \
--hash=sha256:c61007e76655af75e6785a931f452915b371dc48f56efd765247c8fe68f2b181
idna==3.4 ; python_version >= "3.9" and python_version < "4" \
--hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \
--hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2
ipython==8.9.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:71618e82e6d59487bea059626e7c79fb4a5b760d1510d02fab1160db6fdfa1f7 \
--hash=sha256:9c207b0ef2d276d1bfcfeb9a62804336abbe4b170574ea061500952319b1d78c
isort==5.12.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504 \
--hash=sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6
jedi==0.18.2 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:203c1fd9d969ab8f2119ec0a3342e0b49910045abe6af0a3ae83a5764d54639e \
--hash=sha256:bae794c30d07f6d910d32a7048af09b5a39ed740918da923c6b780790ebac612
lxml==4.9.2 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:01d36c05f4afb8f7c20fd9ed5badca32a2029b93b1750f571ccc0b142531caf7 \
--hash=sha256:04876580c050a8c5341d706dd464ff04fd597095cc8c023252566a8826505726 \
--hash=sha256:05ca3f6abf5cf78fe053da9b1166e062ade3fa5d4f92b4ed688127ea7d7b1d03 \
--hash=sha256:090c6543d3696cbe15b4ac6e175e576bcc3f1ccfbba970061b7300b0c15a2140 \
--hash=sha256:0dc313ef231edf866912e9d8f5a042ddab56c752619e92dfd3a2c277e6a7299a \
--hash=sha256:0f2b1e0d79180f344ff9f321327b005ca043a50ece8713de61d1cb383fb8ac05 \
--hash=sha256:13598ecfbd2e86ea7ae45ec28a2a54fb87ee9b9fdb0f6d343297d8e548392c03 \
--hash=sha256:16efd54337136e8cd72fb9485c368d91d77a47ee2d42b057564aae201257d419 \
--hash=sha256:1ab8f1f932e8f82355e75dda5413a57612c6ea448069d4fb2e217e9a4bed13d4 \
--hash=sha256:223f4232855ade399bd409331e6ca70fb5578efef22cf4069a6090acc0f53c0e \
--hash=sha256:2455cfaeb7ac70338b3257f41e21f0724f4b5b0c0e7702da67ee6c3640835b67 \
--hash=sha256:2899456259589aa38bfb018c364d6ae7b53c5c22d8e27d0ec7609c2a1ff78b50 \
--hash=sha256:2a29ba94d065945944016b6b74e538bdb1751a1db6ffb80c9d3c2e40d6fa9894 \
--hash=sha256:2a87fa548561d2f4643c99cd13131acb607ddabb70682dcf1dff5f71f781a4bf \
--hash=sha256:2e430cd2824f05f2d4f687701144556646bae8f249fd60aa1e4c768ba7018947 \
--hash=sha256:36c3c175d34652a35475a73762b545f4527aec044910a651d2bf50de9c3352b1 \
--hash=sha256:3818b8e2c4b5148567e1b09ce739006acfaa44ce3156f8cbbc11062994b8e8dd \
--hash=sha256:3ab9fa9d6dc2a7f29d7affdf3edebf6ece6fb28a6d80b14c3b2fb9d39b9322c3 \
--hash=sha256:3efea981d956a6f7173b4659849f55081867cf897e719f57383698af6f618a92 \
--hash=sha256:4c8f293f14abc8fd3e8e01c5bd86e6ed0b6ef71936ded5bf10fe7a5efefbaca3 \
--hash=sha256:5344a43228767f53a9df6e5b253f8cdca7dfc7b7aeae52551958192f56d98457 \
--hash=sha256:58bfa3aa19ca4c0f28c5dde0ff56c520fbac6f0daf4fac66ed4c8d2fb7f22e74 \
--hash=sha256:5b4545b8a40478183ac06c073e81a5ce4cf01bf1734962577cf2bb569a5b3bbf \
--hash=sha256:5f50a1c177e2fa3ee0667a5ab79fdc6b23086bc8b589d90b93b4bd17eb0e64d1 \
--hash=sha256:63da2ccc0857c311d764e7d3d90f429c252e83b52d1f8f1d1fe55be26827d1f4 \
--hash=sha256:6749649eecd6a9871cae297bffa4ee76f90b4504a2a2ab528d9ebe912b101975 \
--hash=sha256:6804daeb7ef69e7b36f76caddb85cccd63d0c56dedb47555d2fc969e2af6a1a5 \
--hash=sha256:689bb688a1db722485e4610a503e3e9210dcc20c520b45ac8f7533c837be76fe \
--hash=sha256:699a9af7dffaf67deeae27b2112aa06b41c370d5e7633e0ee0aea2e0b6c211f7 \
--hash=sha256:6b418afe5df18233fc6b6093deb82a32895b6bb0b1155c2cdb05203f583053f1 \
--hash=sha256:76cf573e5a365e790396a5cc2b909812633409306c6531a6877c59061e42c4f2 \
--hash=sha256:7b515674acfdcadb0eb5d00d8a709868173acece5cb0be3dd165950cbfdf5409 \
--hash=sha256:7b770ed79542ed52c519119473898198761d78beb24b107acf3ad65deae61f1f \
--hash=sha256:7d2278d59425777cfcb19735018d897ca8303abe67cc735f9f97177ceff8027f \
--hash=sha256:7e91ee82f4199af8c43d8158024cbdff3d931df350252288f0d4ce656df7f3b5 \
--hash=sha256:821b7f59b99551c69c85a6039c65b75f5683bdc63270fec660f75da67469ca24 \
--hash=sha256:822068f85e12a6e292803e112ab876bc03ed1f03dddb80154c395f891ca6b31e \
--hash=sha256:8340225bd5e7a701c0fa98284c849c9b9fc9238abf53a0ebd90900f25d39a4e4 \
--hash=sha256:85cabf64adec449132e55616e7ca3e1000ab449d1d0f9d7f83146ed5bdcb6d8a \
--hash=sha256:880bbbcbe2fca64e2f4d8e04db47bcdf504936fa2b33933efd945e1b429bea8c \
--hash=sha256:8d0b4612b66ff5d62d03bcaa043bb018f74dfea51184e53f067e6fdcba4bd8de \
--hash=sha256:8e20cb5a47247e383cf4ff523205060991021233ebd6f924bca927fcf25cf86f \
--hash=sha256:925073b2fe14ab9b87e73f9a5fde6ce6392da430f3004d8b72cc86f746f5163b \
--hash=sha256:998c7c41910666d2976928c38ea96a70d1aa43be6fe502f21a651e17483a43c5 \
--hash=sha256:9b22c5c66f67ae00c0199f6055705bc3eb3fcb08d03d2ec4059a2b1b25ed48d7 \
--hash=sha256:9f102706d0ca011de571de32c3247c6476b55bb6bc65a20f682f000b07a4852a \
--hash=sha256:a08cff61517ee26cb56f1e949cca38caabe9ea9fbb4b1e10a805dc39844b7d5c \
--hash=sha256:a0a336d6d3e8b234a3aae3c674873d8f0e720b76bc1d9416866c41cd9500ffb9 \
--hash=sha256:a35f8b7fa99f90dd2f5dc5a9fa12332642f087a7641289ca6c40d6e1a2637d8e \
--hash=sha256:a38486985ca49cfa574a507e7a2215c0c780fd1778bb6290c21193b7211702ab \
--hash=sha256:a5da296eb617d18e497bcf0a5c528f5d3b18dadb3619fbdadf4ed2356ef8d941 \
--hash=sha256:a6e441a86553c310258aca15d1c05903aaf4965b23f3bc2d55f200804e005ee5 \
--hash=sha256:a82d05da00a58b8e4c0008edbc8a4b6ec5a4bc1e2ee0fb6ed157cf634ed7fa45 \
--hash=sha256:ab323679b8b3030000f2be63e22cdeea5b47ee0abd2d6a1dc0c8103ddaa56cd7 \
--hash=sha256:b1f42b6921d0e81b1bcb5e395bc091a70f41c4d4e55ba99c6da2b31626c44892 \
--hash=sha256:b23e19989c355ca854276178a0463951a653309fb8e57ce674497f2d9f208746 \
--hash=sha256:b264171e3143d842ded311b7dccd46ff9ef34247129ff5bf5066123c55c2431c \
--hash=sha256:b26a29f0b7fc6f0897f043ca366142d2b609dc60756ee6e4e90b5f762c6adc53 \
--hash=sha256:b64d891da92e232c36976c80ed7ebb383e3f148489796d8d31a5b6a677825efe \
--hash=sha256:b9cc34af337a97d470040f99ba4282f6e6bac88407d021688a5d585e44a23184 \
--hash=sha256:bc718cd47b765e790eecb74d044cc8d37d58562f6c314ee9484df26276d36a38 \
--hash=sha256:be7292c55101e22f2a3d4d8913944cbea71eea90792bf914add27454a13905df \
--hash=sha256:c83203addf554215463b59f6399835201999b5e48019dc17f182ed5ad87205c9 \
--hash=sha256:c9ec3eaf616d67db0764b3bb983962b4f385a1f08304fd30c7283954e6a7869b \
--hash=sha256:ca34efc80a29351897e18888c71c6aca4a359247c87e0b1c7ada14f0ab0c0fb2 \
--hash=sha256:ca989b91cf3a3ba28930a9fc1e9aeafc2a395448641df1f387a2d394638943b0 \
--hash=sha256:d02a5399126a53492415d4906ab0ad0375a5456cc05c3fc0fc4ca11771745cda \
--hash=sha256:d17bc7c2ccf49c478c5bdd447594e82692c74222698cfc9b5daae7ae7e90743b \
--hash=sha256:d5bf6545cd27aaa8a13033ce56354ed9e25ab0e4ac3b5392b763d8d04b08e0c5 \
--hash=sha256:d6b430a9938a5a5d85fc107d852262ddcd48602c120e3dbb02137c83d212b380 \
--hash=sha256:da248f93f0418a9e9d94b0080d7ebc407a9a5e6d0b57bb30db9b5cc28de1ad33 \
--hash=sha256:da4dd7c9c50c059aba52b3524f84d7de956f7fef88f0bafcf4ad7dde94a064e8 \
--hash=sha256:df0623dcf9668ad0445e0558a21211d4e9a149ea8f5666917c8eeec515f0a6d1 \
--hash=sha256:e5168986b90a8d1f2f9dc1b841467c74221bd752537b99761a93d2d981e04889 \
--hash=sha256:efa29c2fe6b4fdd32e8ef81c1528506895eca86e1d8c4657fda04c9b3786ddf9 \
--hash=sha256:f1496ea22ca2c830cbcbd473de8f114a320da308438ae65abad6bab7867fe38f \
--hash=sha256:f49e52d174375a7def9915c9f06ec4e569d235ad428f70751765f48d5926678c
matplotlib-inline==0.1.6 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311 \
--hash=sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304
mccabe==0.7.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325 \
--hash=sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e
mypy-extensions==0.4.3 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d \
--hash=sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8
parso==0.8.3 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0 \
--hash=sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75
pathspec==0.11.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:3a66eb970cbac598f9e5ccb5b2cf58930cd8e3ed86d393d541eaf2d8b1705229 \
--hash=sha256:64d338d4e0914e91c1792321e6907b5a593f1ab1851de7fc269557a21b30ebbc
pexpect==4.8.0 ; python_version >= "3.9" and python_version < "4.0" and sys_platform != "win32" \
--hash=sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937 \
--hash=sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c
pickleshare==0.7.5 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca \
--hash=sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56
pillow==9.4.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:013016af6b3a12a2f40b704677f8b51f72cb007dac785a9933d5c86a72a7fe33 \
--hash=sha256:0845adc64fe9886db00f5ab68c4a8cd933ab749a87747555cec1c95acea64b0b \
--hash=sha256:0884ba7b515163a1a05440a138adeb722b8a6ae2c2b33aea93ea3118dd3a899e \
--hash=sha256:09b89ddc95c248ee788328528e6a2996e09eaccddeeb82a5356e92645733be35 \
--hash=sha256:0dd4c681b82214b36273c18ca7ee87065a50e013112eea7d78c7a1b89a739153 \
--hash=sha256:0e51f608da093e5d9038c592b5b575cadc12fd748af1479b5e858045fff955a9 \
--hash=sha256:0f3269304c1a7ce82f1759c12ce731ef9b6e95b6df829dccd9fe42912cc48569 \
--hash=sha256:16a8df99701f9095bea8a6c4b3197da105df6f74e6176c5b410bc2df2fd29a57 \
--hash=sha256:19005a8e58b7c1796bc0167862b1f54a64d3b44ee5d48152b06bb861458bc0f8 \
--hash=sha256:1b4b4e9dda4f4e4c4e6896f93e84a8f0bcca3b059de9ddf67dac3c334b1195e1 \
--hash=sha256:28676836c7796805914b76b1837a40f76827ee0d5398f72f7dcc634bae7c6264 \
--hash=sha256:2968c58feca624bb6c8502f9564dd187d0e1389964898f5e9e1fbc8533169157 \
--hash=sha256:3f4cc516e0b264c8d4ccd6b6cbc69a07c6d582d8337df79be1e15a5056b258c9 \
--hash=sha256:3fa1284762aacca6dc97474ee9c16f83990b8eeb6697f2ba17140d54b453e133 \
--hash=sha256:43521ce2c4b865d385e78579a082b6ad1166ebed2b1a2293c3be1d68dd7ca3b9 \
--hash=sha256:451f10ef963918e65b8869e17d67db5e2f4ab40e716ee6ce7129b0cde2876eab \
--hash=sha256:46c259e87199041583658457372a183636ae8cd56dbf3f0755e0f376a7f9d0e6 \
--hash=sha256:46f39cab8bbf4a384ba7cb0bc8bae7b7062b6a11cfac1ca4bc144dea90d4a9f5 \
--hash=sha256:519e14e2c49fcf7616d6d2cfc5c70adae95682ae20f0395e9280db85e8d6c4df \
--hash=sha256:53dcb50fbdc3fb2c55431a9b30caeb2f7027fcd2aeb501459464f0214200a503 \
--hash=sha256:54614444887e0d3043557d9dbc697dbb16cfb5a35d672b7a0fcc1ed0cf1c600b \
--hash=sha256:575d8912dca808edd9acd6f7795199332696d3469665ef26163cd090fa1f8bfa \
--hash=sha256:5dd5a9c3091a0f414a963d427f920368e2b6a4c2f7527fdd82cde8ef0bc7a327 \
--hash=sha256:5f532a2ad4d174eb73494e7397988e22bf427f91acc8e6ebf5bb10597b49c493 \
--hash=sha256:60e7da3a3ad1812c128750fc1bc14a7ceeb8d29f77e0a2356a8fb2aa8925287d \
--hash=sha256:653d7fb2df65efefbcbf81ef5fe5e5be931f1ee4332c2893ca638c9b11a409c4 \
--hash=sha256:6663977496d616b618b6cfa43ec86e479ee62b942e1da76a2c3daa1c75933ef4 \
--hash=sha256:6abfb51a82e919e3933eb137e17c4ae9c0475a25508ea88993bb59faf82f3b35 \
--hash=sha256:6c6b1389ed66cdd174d040105123a5a1bc91d0aa7059c7261d20e583b6d8cbd2 \
--hash=sha256:6d9dfb9959a3b0039ee06c1a1a90dc23bac3b430842dcb97908ddde05870601c \
--hash=sha256:765cb54c0b8724a7c12c55146ae4647e0274a839fb6de7bcba841e04298e1011 \
--hash=sha256:7a21222644ab69ddd9967cfe6f2bb420b460dae4289c9d40ff9a4896e7c35c9a \
--hash=sha256:7ac7594397698f77bce84382929747130765f66406dc2cd8b4ab4da68ade4c6e \
--hash=sha256:7cfc287da09f9d2a7ec146ee4d72d6ea1342e770d975e49a8621bf54eaa8f30f \
--hash=sha256:83125753a60cfc8c412de5896d10a0a405e0bd88d0470ad82e0869ddf0cb3848 \
--hash=sha256:847b114580c5cc9ebaf216dd8c8dbc6b00a3b7ab0131e173d7120e6deade1f57 \
--hash=sha256:87708d78a14d56a990fbf4f9cb350b7d89ee8988705e58e39bdf4d82c149210f \
--hash=sha256:8a2b5874d17e72dfb80d917213abd55d7e1ed2479f38f001f264f7ce7bae757c \
--hash=sha256:8f127e7b028900421cad64f51f75c051b628db17fb00e099eb148761eed598c9 \
--hash=sha256:94cdff45173b1919350601f82d61365e792895e3c3a3443cf99819e6fbf717a5 \
--hash=sha256:99d92d148dd03fd19d16175b6d355cc1b01faf80dae93c6c3eb4163709edc0a9 \
--hash=sha256:9a3049a10261d7f2b6514d35bbb7a4dfc3ece4c4de14ef5876c4b7a23a0e566d \
--hash=sha256:9d9a62576b68cd90f7075876f4e8444487db5eeea0e4df3ba298ee38a8d067b0 \
--hash=sha256:9e5f94742033898bfe84c93c831a6f552bb629448d4072dd312306bab3bd96f1 \
--hash=sha256:a1c2d7780448eb93fbcc3789bf3916aa5720d942e37945f4056680317f1cd23e \
--hash=sha256:a2e0f87144fcbbe54297cae708c5e7f9da21a4646523456b00cc956bd4c65815 \
--hash=sha256:a4dfdae195335abb4e89cc9762b2edc524f3c6e80d647a9a81bf81e17e3fb6f0 \
--hash=sha256:a96e6e23f2b79433390273eaf8cc94fec9c6370842e577ab10dabdcc7ea0a66b \
--hash=sha256:aabdab8ec1e7ca7f1434d042bf8b1e92056245fb179790dc97ed040361f16bfd \
--hash=sha256:b222090c455d6d1a64e6b7bb5f4035c4dff479e22455c9eaa1bdd4c75b52c80c \
--hash=sha256:b52ff4f4e002f828ea6483faf4c4e8deea8d743cf801b74910243c58acc6eda3 \
--hash=sha256:b70756ec9417c34e097f987b4d8c510975216ad26ba6e57ccb53bc758f490dab \
--hash=sha256:b8c2f6eb0df979ee99433d8b3f6d193d9590f735cf12274c108bd954e30ca858 \
--hash=sha256:b9b752ab91e78234941e44abdecc07f1f0d8f51fb62941d32995b8161f68cfe5 \
--hash=sha256:ba6612b6548220ff5e9df85261bddc811a057b0b465a1226b39bfb8550616aee \
--hash=sha256:bd752c5ff1b4a870b7661234694f24b1d2b9076b8bf337321a814c612665f343 \
--hash=sha256:c3c4ed2ff6760e98d262e0cc9c9a7f7b8a9f61aa4d47c58835cdaf7b0b8811bb \
--hash=sha256:c5c1362c14aee73f50143d74389b2c158707b4abce2cb055b7ad37ce60738d47 \
--hash=sha256:cb362e3b0976dc994857391b776ddaa8c13c28a16f80ac6522c23d5257156bed \
--hash=sha256:d197df5489004db87d90b918033edbeee0bd6df3848a204bca3ff0a903bef837 \
--hash=sha256:d3b56206244dc8711f7e8b7d6cad4663917cd5b2d950799425076681e8766286 \
--hash=sha256:d5b2f8a31bd43e0f18172d8ac82347c8f37ef3e0b414431157718aa234991b28 \
--hash=sha256:d7081c084ceb58278dd3cf81f836bc818978c0ccc770cbbb202125ddabec6628 \
--hash=sha256:db74f5562c09953b2c5f8ec4b7dfd3f5421f31811e97d1dbc0a7c93d6e3a24df \
--hash=sha256:df41112ccce5d47770a0c13651479fbcd8793f34232a2dd9faeccb75eb5d0d0d \
--hash=sha256:e1339790c083c5a4de48f688b4841f18df839eb3c9584a770cbd818b33e26d5d \
--hash=sha256:e621b0246192d3b9cb1dc62c78cfa4c6f6d2ddc0ec207d43c0dedecb914f152a \
--hash=sha256:e8c5cf126889a4de385c02a2c3d3aba4b00f70234bfddae82a5eaa3ee6d5e3e6 \
--hash=sha256:e9d7747847c53a16a729b6ee5e737cf170f7a16611c143d95aa60a109a59c336 \
--hash=sha256:eaef5d2de3c7e9b21f1e762f289d17b726c2239a42b11e25446abf82b26ac132 \
--hash=sha256:ed3e4b4e1e6de75fdc16d3259098de7c6571b1a6cc863b1a49e7d3d53e036070 \
--hash=sha256:ef21af928e807f10bf4141cad4746eee692a0dd3ff56cfb25fce076ec3cc8abe \
--hash=sha256:f09598b416ba39a8f489c124447b007fe865f786a89dbfa48bb5cf395693132a \
--hash=sha256:f0caf4a5dcf610d96c3bd32932bfac8aee61c96e60481c2a0ea58da435e25acd \
--hash=sha256:f6e78171be3fb7941f9910ea15b4b14ec27725865a73c15277bc39f5ca4f8391 \
--hash=sha256:f715c32e774a60a337b2bb8ad9839b4abf75b267a0f18806f6f4f5f1688c4b5a \
--hash=sha256:fb5c1ad6bad98c57482236a21bf985ab0ef42bd51f7ad4e4538e89a997624e12
platformdirs==2.6.2 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490 \
--hash=sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2
prompt-toolkit==3.0.36 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:3e163f254bef5a03b146397d7c1963bd3e2812f0964bb9a24e6ec761fd28db63 \
--hash=sha256:aa64ad242a462c5ff0363a7b9cfe696c20d55d9fc60c11fd8e632d064804d305
ptyprocess==0.7.0 ; python_version >= "3.9" and python_version < "4.0" and sys_platform != "win32" \
--hash=sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35 \
--hash=sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220
pure-eval==0.2.2 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350 \
--hash=sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3
pyasn1-modules==0.2.8 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e \
--hash=sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74
pyasn1==0.4.8 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d \
--hash=sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba
pycodestyle==2.10.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053 \
--hash=sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610
pyflakes==3.0.1 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:ec55bf7fe21fff7f1ad2f7da62363d749e2a470500eab1b555334b67aa1ef8cf \
--hash=sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd
pygments==2.14.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297 \
--hash=sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717
python-cas==1.6.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:2abc0dae93c3b14097999fb7062f23cd09bc9f4e33d93f03c0cc040bd71ed50e \
--hash=sha256:b8f1dcb1b6c56b3ff4f86bbef47bcdfcf932061ccd4812ae35e3f63954dfdb28
python-ldap==3.4.3 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:ab26c519a0ef2a443a2a10391fa3c5cb52d7871323399db949ebfaa9f25ee2a0
pytz==2022.7.1 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0 \
--hash=sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a
requests==2.28.2 ; python_version >= "3.9" and python_version < "4" \
--hash=sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa \
--hash=sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf
six==1.16.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
--hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
sqlparse==0.4.3 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34 \
--hash=sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268
stack-data==0.6.2 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:32d2dd0376772d01b6cb9fc996f3c8b57a357089dec328ed4b6553d037eaf815 \
--hash=sha256:cbb2a53eb64e5785878201a97ed7c7b94883f48b87bfb0bbe8b623c74679e4a8
tomli==2.0.1 ; python_version >= "3.9" and python_full_version < "3.11.0a7" \
--hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
--hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
traitlets==5.9.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:9e6ec080259b9a5940c797d58b613b5e31441c2257b87c2e795c5228ae80d2d8 \
--hash=sha256:f6cde21a9c68cf756af02035f72d5a723bf607e862e7be33ece505abf4a3bad9
typing-extensions==4.4.0 ; python_version >= "3.9" and python_version < "3.10" \
--hash=sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa \
--hash=sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e
urllib3==1.26.14 ; python_version >= "3.9" and python_version < "4" \
--hash=sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72 \
--hash=sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1
wcwidth==0.2.6 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e \
--hash=sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0
-r requirements.txt
django-debug-toolbar
ipython
black
flake8
isort

View file

@ -1,314 +1,5 @@
asgiref==3.6.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:71e68008da809b957b7ee4b43dbccff33d1b23519fb8344e33f049897077afac \
--hash=sha256:9567dfe7bd8d3c8c892227827c41cce860b368104c3431da67a0c5a65a949506
async-timeout==4.0.2 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15 \
--hash=sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c
authens==0.1b4 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:3430c85d0c11c7ca78cba484d5dfd18e9ba3370a0f2abbdd14aafb8feb8807a5
certifi==2022.12.7 ; python_version >= "3.9" and python_version < "4" \
--hash=sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3 \
--hash=sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18
charset-normalizer==3.0.1 ; python_version >= "3.9" and python_version < "4" \
--hash=sha256:00d3ffdaafe92a5dc603cb9bd5111aaa36dfa187c8285c543be562e61b755f6b \
--hash=sha256:024e606be3ed92216e2b6952ed859d86b4cfa52cd5bc5f050e7dc28f9b43ec42 \
--hash=sha256:0298eafff88c99982a4cf66ba2efa1128e4ddaca0b05eec4c456bbc7db691d8d \
--hash=sha256:02a51034802cbf38db3f89c66fb5d2ec57e6fe7ef2f4a44d070a593c3688667b \
--hash=sha256:083c8d17153ecb403e5e1eb76a7ef4babfc2c48d58899c98fcaa04833e7a2f9a \
--hash=sha256:0a11e971ed097d24c534c037d298ad32c6ce81a45736d31e0ff0ad37ab437d59 \
--hash=sha256:0bf2dae5291758b6f84cf923bfaa285632816007db0330002fa1de38bfcb7154 \
--hash=sha256:0c0a590235ccd933d9892c627dec5bc7511ce6ad6c1011fdf5b11363022746c1 \
--hash=sha256:0f438ae3532723fb6ead77e7c604be7c8374094ef4ee2c5e03a3a17f1fca256c \
--hash=sha256:109487860ef6a328f3eec66f2bf78b0b72400280d8f8ea05f69c51644ba6521a \
--hash=sha256:11b53acf2411c3b09e6af37e4b9005cba376c872503c8f28218c7243582df45d \
--hash=sha256:12db3b2c533c23ab812c2b25934f60383361f8a376ae272665f8e48b88e8e1c6 \
--hash=sha256:14e76c0f23218b8f46c4d87018ca2e441535aed3632ca134b10239dfb6dadd6b \
--hash=sha256:16a8663d6e281208d78806dbe14ee9903715361cf81f6d4309944e4d1e59ac5b \
--hash=sha256:292d5e8ba896bbfd6334b096e34bffb56161c81408d6d036a7dfa6929cff8783 \
--hash=sha256:2c03cc56021a4bd59be889c2b9257dae13bf55041a3372d3295416f86b295fb5 \
--hash=sha256:2e396d70bc4ef5325b72b593a72c8979999aa52fb8bcf03f701c1b03e1166918 \
--hash=sha256:2edb64ee7bf1ed524a1da60cdcd2e1f6e2b4f66ef7c077680739f1641f62f555 \
--hash=sha256:31a9ddf4718d10ae04d9b18801bd776693487cbb57d74cc3458a7673f6f34639 \
--hash=sha256:356541bf4381fa35856dafa6a965916e54bed415ad8a24ee6de6e37deccf2786 \
--hash=sha256:358a7c4cb8ba9b46c453b1dd8d9e431452d5249072e4f56cfda3149f6ab1405e \
--hash=sha256:37f8febc8ec50c14f3ec9637505f28e58d4f66752207ea177c1d67df25da5aed \
--hash=sha256:39049da0ffb96c8cbb65cbf5c5f3ca3168990adf3551bd1dee10c48fce8ae820 \
--hash=sha256:39cf9ed17fe3b1bc81f33c9ceb6ce67683ee7526e65fde1447c772afc54a1bb8 \
--hash=sha256:3ae1de54a77dc0d6d5fcf623290af4266412a7c4be0b1ff7444394f03f5c54e3 \
--hash=sha256:3b590df687e3c5ee0deef9fc8c547d81986d9a1b56073d82de008744452d6541 \
--hash=sha256:3e45867f1f2ab0711d60c6c71746ac53537f1684baa699f4f668d4c6f6ce8e14 \
--hash=sha256:3fc1c4a2ffd64890aebdb3f97e1278b0cc72579a08ca4de8cd2c04799a3a22be \
--hash=sha256:4457ea6774b5611f4bed5eaa5df55f70abde42364d498c5134b7ef4c6958e20e \
--hash=sha256:44ba614de5361b3e5278e1241fda3dc1838deed864b50a10d7ce92983797fa76 \
--hash=sha256:4a8fcf28c05c1f6d7e177a9a46a1c52798bfe2ad80681d275b10dcf317deaf0b \
--hash=sha256:4b0d02d7102dd0f997580b51edc4cebcf2ab6397a7edf89f1c73b586c614272c \
--hash=sha256:502218f52498a36d6bf5ea77081844017bf7982cdbe521ad85e64cabee1b608b \
--hash=sha256:503e65837c71b875ecdd733877d852adbc465bd82c768a067badd953bf1bc5a3 \
--hash=sha256:5995f0164fa7df59db4746112fec3f49c461dd6b31b841873443bdb077c13cfc \
--hash=sha256:59e5686dd847347e55dffcc191a96622f016bc0ad89105e24c14e0d6305acbc6 \
--hash=sha256:601f36512f9e28f029d9481bdaf8e89e5148ac5d89cffd3b05cd533eeb423b59 \
--hash=sha256:608862a7bf6957f2333fc54ab4399e405baad0163dc9f8d99cb236816db169d4 \
--hash=sha256:62595ab75873d50d57323a91dd03e6966eb79c41fa834b7a1661ed043b2d404d \
--hash=sha256:70990b9c51340e4044cfc394a81f614f3f90d41397104d226f21e66de668730d \
--hash=sha256:71140351489970dfe5e60fc621ada3e0f41104a5eddaca47a7acb3c1b851d6d3 \
--hash=sha256:72966d1b297c741541ca8cf1223ff262a6febe52481af742036a0b296e35fa5a \
--hash=sha256:74292fc76c905c0ef095fe11e188a32ebd03bc38f3f3e9bcb85e4e6db177b7ea \
--hash=sha256:761e8904c07ad053d285670f36dd94e1b6ab7f16ce62b9805c475b7aa1cffde6 \
--hash=sha256:772b87914ff1152b92a197ef4ea40efe27a378606c39446ded52c8f80f79702e \
--hash=sha256:79909e27e8e4fcc9db4addea88aa63f6423ebb171db091fb4373e3312cb6d603 \
--hash=sha256:7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24 \
--hash=sha256:7eb33a30d75562222b64f569c642ff3dc6689e09adda43a082208397f016c39a \
--hash=sha256:81d6741ab457d14fdedc215516665050f3822d3e56508921cc7239f8c8e66a58 \
--hash=sha256:8499ca8f4502af841f68135133d8258f7b32a53a1d594aa98cc52013fff55678 \
--hash=sha256:84c3990934bae40ea69a82034912ffe5a62c60bbf6ec5bc9691419641d7d5c9a \
--hash=sha256:87701167f2a5c930b403e9756fab1d31d4d4da52856143b609e30a1ce7160f3c \
--hash=sha256:88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6 \
--hash=sha256:8ac7b6a045b814cf0c47f3623d21ebd88b3e8cf216a14790b455ea7ff0135d18 \
--hash=sha256:8b8af03d2e37866d023ad0ddea594edefc31e827fee64f8de5611a1dbc373174 \
--hash=sha256:8c7fe7afa480e3e82eed58e0ca89f751cd14d767638e2550c77a92a9e749c317 \
--hash=sha256:8eade758719add78ec36dc13201483f8e9b5d940329285edcd5f70c0a9edbd7f \
--hash=sha256:911d8a40b2bef5b8bbae2e36a0b103f142ac53557ab421dc16ac4aafee6f53dc \
--hash=sha256:93ad6d87ac18e2a90b0fe89df7c65263b9a99a0eb98f0a3d2e079f12a0735837 \
--hash=sha256:95dea361dd73757c6f1c0a1480ac499952c16ac83f7f5f4f84f0658a01b8ef41 \
--hash=sha256:9ab77acb98eba3fd2a85cd160851816bfce6871d944d885febf012713f06659c \
--hash=sha256:9cb3032517f1627cc012dbc80a8ec976ae76d93ea2b5feaa9d2a5b8882597579 \
--hash=sha256:9cf4e8ad252f7c38dd1f676b46514f92dc0ebeb0db5552f5f403509705e24753 \
--hash=sha256:9d9153257a3f70d5f69edf2325357251ed20f772b12e593f3b3377b5f78e7ef8 \
--hash=sha256:a152f5f33d64a6be73f1d30c9cc82dfc73cec6477ec268e7c6e4c7d23c2d2291 \
--hash=sha256:a16418ecf1329f71df119e8a65f3aa68004a3f9383821edcb20f0702934d8087 \
--hash=sha256:a60332922359f920193b1d4826953c507a877b523b2395ad7bc716ddd386d866 \
--hash=sha256:a8d0fc946c784ff7f7c3742310cc8a57c5c6dc31631269876a88b809dbeff3d3 \
--hash=sha256:ab5de034a886f616a5668aa5d098af2b5385ed70142090e2a31bcbd0af0fdb3d \
--hash=sha256:c22d3fe05ce11d3671297dc8973267daa0f938b93ec716e12e0f6dee81591dc1 \
--hash=sha256:c2ac1b08635a8cd4e0cbeaf6f5e922085908d48eb05d44c5ae9eabab148512ca \
--hash=sha256:c512accbd6ff0270939b9ac214b84fb5ada5f0409c44298361b2f5e13f9aed9e \
--hash=sha256:c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db \
--hash=sha256:c95a03c79bbe30eec3ec2b7f076074f4281526724c8685a42872974ef4d36b72 \
--hash=sha256:cadaeaba78750d58d3cc6ac4d1fd867da6fc73c88156b7a3212a3cd4819d679d \
--hash=sha256:cd6056167405314a4dc3c173943f11249fa0f1b204f8b51ed4bde1a9cd1834dc \
--hash=sha256:db72b07027db150f468fbada4d85b3b2729a3db39178abf5c543b784c1254539 \
--hash=sha256:df2c707231459e8a4028eabcd3cfc827befd635b3ef72eada84ab13b52e1574d \
--hash=sha256:e62164b50f84e20601c1ff8eb55620d2ad25fb81b59e3cd776a1902527a788af \
--hash=sha256:e696f0dd336161fca9adbb846875d40752e6eba585843c768935ba5c9960722b \
--hash=sha256:eaa379fcd227ca235d04152ca6704c7cb55564116f8bc52545ff357628e10602 \
--hash=sha256:ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f \
--hash=sha256:f4c39b0e3eac288fedc2b43055cfc2ca7a60362d0e5e87a637beac5d801ef478 \
--hash=sha256:f5057856d21e7586765171eac8b9fc3f7d44ef39425f85dbcccb13b3ebea806c \
--hash=sha256:f6f45710b4459401609ebebdbcfb34515da4fc2aa886f95107f556ac69a9147e \
--hash=sha256:f97e83fa6c25693c7a35de154681fcc257c1c41b38beb0304b9c4d2d9e164479 \
--hash=sha256:f9d0c5c045a3ca9bedfc35dca8526798eb91a07aa7a2c0fee134c6c6f321cbd7 \
--hash=sha256:ff6f3db31555657f3163b15a6b7c6938d08df7adbfc9dd13d9d19edad678f1e8
django-redis==5.2.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:1d037dc02b11ad7aa11f655d26dac3fb1af32630f61ef4428860a2e29ff92026 \
--hash=sha256:8a99e5582c79f894168f5865c52bd921213253b7fd64d16733ae4591564465de
django==3.2.16 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:18ba8efa36b69cfcd4b670d0fa187c6fe7506596f0ababe580e16909bcdec121 \
--hash=sha256:3adc285124244724a394fa9b9839cc8cd116faf7d159554c43ecdaa8cdf0b94d
gunicorn==20.1.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e \
--hash=sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8
idna==3.4 ; python_version >= "3.9" and python_version < "4" \
--hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \
--hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2
lxml==4.9.2 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:01d36c05f4afb8f7c20fd9ed5badca32a2029b93b1750f571ccc0b142531caf7 \
--hash=sha256:04876580c050a8c5341d706dd464ff04fd597095cc8c023252566a8826505726 \
--hash=sha256:05ca3f6abf5cf78fe053da9b1166e062ade3fa5d4f92b4ed688127ea7d7b1d03 \
--hash=sha256:090c6543d3696cbe15b4ac6e175e576bcc3f1ccfbba970061b7300b0c15a2140 \
--hash=sha256:0dc313ef231edf866912e9d8f5a042ddab56c752619e92dfd3a2c277e6a7299a \
--hash=sha256:0f2b1e0d79180f344ff9f321327b005ca043a50ece8713de61d1cb383fb8ac05 \
--hash=sha256:13598ecfbd2e86ea7ae45ec28a2a54fb87ee9b9fdb0f6d343297d8e548392c03 \
--hash=sha256:16efd54337136e8cd72fb9485c368d91d77a47ee2d42b057564aae201257d419 \
--hash=sha256:1ab8f1f932e8f82355e75dda5413a57612c6ea448069d4fb2e217e9a4bed13d4 \
--hash=sha256:223f4232855ade399bd409331e6ca70fb5578efef22cf4069a6090acc0f53c0e \
--hash=sha256:2455cfaeb7ac70338b3257f41e21f0724f4b5b0c0e7702da67ee6c3640835b67 \
--hash=sha256:2899456259589aa38bfb018c364d6ae7b53c5c22d8e27d0ec7609c2a1ff78b50 \
--hash=sha256:2a29ba94d065945944016b6b74e538bdb1751a1db6ffb80c9d3c2e40d6fa9894 \
--hash=sha256:2a87fa548561d2f4643c99cd13131acb607ddabb70682dcf1dff5f71f781a4bf \
--hash=sha256:2e430cd2824f05f2d4f687701144556646bae8f249fd60aa1e4c768ba7018947 \
--hash=sha256:36c3c175d34652a35475a73762b545f4527aec044910a651d2bf50de9c3352b1 \
--hash=sha256:3818b8e2c4b5148567e1b09ce739006acfaa44ce3156f8cbbc11062994b8e8dd \
--hash=sha256:3ab9fa9d6dc2a7f29d7affdf3edebf6ece6fb28a6d80b14c3b2fb9d39b9322c3 \
--hash=sha256:3efea981d956a6f7173b4659849f55081867cf897e719f57383698af6f618a92 \
--hash=sha256:4c8f293f14abc8fd3e8e01c5bd86e6ed0b6ef71936ded5bf10fe7a5efefbaca3 \
--hash=sha256:5344a43228767f53a9df6e5b253f8cdca7dfc7b7aeae52551958192f56d98457 \
--hash=sha256:58bfa3aa19ca4c0f28c5dde0ff56c520fbac6f0daf4fac66ed4c8d2fb7f22e74 \
--hash=sha256:5b4545b8a40478183ac06c073e81a5ce4cf01bf1734962577cf2bb569a5b3bbf \
--hash=sha256:5f50a1c177e2fa3ee0667a5ab79fdc6b23086bc8b589d90b93b4bd17eb0e64d1 \
--hash=sha256:63da2ccc0857c311d764e7d3d90f429c252e83b52d1f8f1d1fe55be26827d1f4 \
--hash=sha256:6749649eecd6a9871cae297bffa4ee76f90b4504a2a2ab528d9ebe912b101975 \
--hash=sha256:6804daeb7ef69e7b36f76caddb85cccd63d0c56dedb47555d2fc969e2af6a1a5 \
--hash=sha256:689bb688a1db722485e4610a503e3e9210dcc20c520b45ac8f7533c837be76fe \
--hash=sha256:699a9af7dffaf67deeae27b2112aa06b41c370d5e7633e0ee0aea2e0b6c211f7 \
--hash=sha256:6b418afe5df18233fc6b6093deb82a32895b6bb0b1155c2cdb05203f583053f1 \
--hash=sha256:76cf573e5a365e790396a5cc2b909812633409306c6531a6877c59061e42c4f2 \
--hash=sha256:7b515674acfdcadb0eb5d00d8a709868173acece5cb0be3dd165950cbfdf5409 \
--hash=sha256:7b770ed79542ed52c519119473898198761d78beb24b107acf3ad65deae61f1f \
--hash=sha256:7d2278d59425777cfcb19735018d897ca8303abe67cc735f9f97177ceff8027f \
--hash=sha256:7e91ee82f4199af8c43d8158024cbdff3d931df350252288f0d4ce656df7f3b5 \
--hash=sha256:821b7f59b99551c69c85a6039c65b75f5683bdc63270fec660f75da67469ca24 \
--hash=sha256:822068f85e12a6e292803e112ab876bc03ed1f03dddb80154c395f891ca6b31e \
--hash=sha256:8340225bd5e7a701c0fa98284c849c9b9fc9238abf53a0ebd90900f25d39a4e4 \
--hash=sha256:85cabf64adec449132e55616e7ca3e1000ab449d1d0f9d7f83146ed5bdcb6d8a \
--hash=sha256:880bbbcbe2fca64e2f4d8e04db47bcdf504936fa2b33933efd945e1b429bea8c \
--hash=sha256:8d0b4612b66ff5d62d03bcaa043bb018f74dfea51184e53f067e6fdcba4bd8de \
--hash=sha256:8e20cb5a47247e383cf4ff523205060991021233ebd6f924bca927fcf25cf86f \
--hash=sha256:925073b2fe14ab9b87e73f9a5fde6ce6392da430f3004d8b72cc86f746f5163b \
--hash=sha256:998c7c41910666d2976928c38ea96a70d1aa43be6fe502f21a651e17483a43c5 \
--hash=sha256:9b22c5c66f67ae00c0199f6055705bc3eb3fcb08d03d2ec4059a2b1b25ed48d7 \
--hash=sha256:9f102706d0ca011de571de32c3247c6476b55bb6bc65a20f682f000b07a4852a \
--hash=sha256:a08cff61517ee26cb56f1e949cca38caabe9ea9fbb4b1e10a805dc39844b7d5c \
--hash=sha256:a0a336d6d3e8b234a3aae3c674873d8f0e720b76bc1d9416866c41cd9500ffb9 \
--hash=sha256:a35f8b7fa99f90dd2f5dc5a9fa12332642f087a7641289ca6c40d6e1a2637d8e \
--hash=sha256:a38486985ca49cfa574a507e7a2215c0c780fd1778bb6290c21193b7211702ab \
--hash=sha256:a5da296eb617d18e497bcf0a5c528f5d3b18dadb3619fbdadf4ed2356ef8d941 \
--hash=sha256:a6e441a86553c310258aca15d1c05903aaf4965b23f3bc2d55f200804e005ee5 \
--hash=sha256:a82d05da00a58b8e4c0008edbc8a4b6ec5a4bc1e2ee0fb6ed157cf634ed7fa45 \
--hash=sha256:ab323679b8b3030000f2be63e22cdeea5b47ee0abd2d6a1dc0c8103ddaa56cd7 \
--hash=sha256:b1f42b6921d0e81b1bcb5e395bc091a70f41c4d4e55ba99c6da2b31626c44892 \
--hash=sha256:b23e19989c355ca854276178a0463951a653309fb8e57ce674497f2d9f208746 \
--hash=sha256:b264171e3143d842ded311b7dccd46ff9ef34247129ff5bf5066123c55c2431c \
--hash=sha256:b26a29f0b7fc6f0897f043ca366142d2b609dc60756ee6e4e90b5f762c6adc53 \
--hash=sha256:b64d891da92e232c36976c80ed7ebb383e3f148489796d8d31a5b6a677825efe \
--hash=sha256:b9cc34af337a97d470040f99ba4282f6e6bac88407d021688a5d585e44a23184 \
--hash=sha256:bc718cd47b765e790eecb74d044cc8d37d58562f6c314ee9484df26276d36a38 \
--hash=sha256:be7292c55101e22f2a3d4d8913944cbea71eea90792bf914add27454a13905df \
--hash=sha256:c83203addf554215463b59f6399835201999b5e48019dc17f182ed5ad87205c9 \
--hash=sha256:c9ec3eaf616d67db0764b3bb983962b4f385a1f08304fd30c7283954e6a7869b \
--hash=sha256:ca34efc80a29351897e18888c71c6aca4a359247c87e0b1c7ada14f0ab0c0fb2 \
--hash=sha256:ca989b91cf3a3ba28930a9fc1e9aeafc2a395448641df1f387a2d394638943b0 \
--hash=sha256:d02a5399126a53492415d4906ab0ad0375a5456cc05c3fc0fc4ca11771745cda \
--hash=sha256:d17bc7c2ccf49c478c5bdd447594e82692c74222698cfc9b5daae7ae7e90743b \
--hash=sha256:d5bf6545cd27aaa8a13033ce56354ed9e25ab0e4ac3b5392b763d8d04b08e0c5 \
--hash=sha256:d6b430a9938a5a5d85fc107d852262ddcd48602c120e3dbb02137c83d212b380 \
--hash=sha256:da248f93f0418a9e9d94b0080d7ebc407a9a5e6d0b57bb30db9b5cc28de1ad33 \
--hash=sha256:da4dd7c9c50c059aba52b3524f84d7de956f7fef88f0bafcf4ad7dde94a064e8 \
--hash=sha256:df0623dcf9668ad0445e0558a21211d4e9a149ea8f5666917c8eeec515f0a6d1 \
--hash=sha256:e5168986b90a8d1f2f9dc1b841467c74221bd752537b99761a93d2d981e04889 \
--hash=sha256:efa29c2fe6b4fdd32e8ef81c1528506895eca86e1d8c4657fda04c9b3786ddf9 \
--hash=sha256:f1496ea22ca2c830cbcbd473de8f114a320da308438ae65abad6bab7867fe38f \
--hash=sha256:f49e52d174375a7def9915c9f06ec4e569d235ad428f70751765f48d5926678c
pillow==9.4.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:013016af6b3a12a2f40b704677f8b51f72cb007dac785a9933d5c86a72a7fe33 \
--hash=sha256:0845adc64fe9886db00f5ab68c4a8cd933ab749a87747555cec1c95acea64b0b \
--hash=sha256:0884ba7b515163a1a05440a138adeb722b8a6ae2c2b33aea93ea3118dd3a899e \
--hash=sha256:09b89ddc95c248ee788328528e6a2996e09eaccddeeb82a5356e92645733be35 \
--hash=sha256:0dd4c681b82214b36273c18ca7ee87065a50e013112eea7d78c7a1b89a739153 \
--hash=sha256:0e51f608da093e5d9038c592b5b575cadc12fd748af1479b5e858045fff955a9 \
--hash=sha256:0f3269304c1a7ce82f1759c12ce731ef9b6e95b6df829dccd9fe42912cc48569 \
--hash=sha256:16a8df99701f9095bea8a6c4b3197da105df6f74e6176c5b410bc2df2fd29a57 \
--hash=sha256:19005a8e58b7c1796bc0167862b1f54a64d3b44ee5d48152b06bb861458bc0f8 \
--hash=sha256:1b4b4e9dda4f4e4c4e6896f93e84a8f0bcca3b059de9ddf67dac3c334b1195e1 \
--hash=sha256:28676836c7796805914b76b1837a40f76827ee0d5398f72f7dcc634bae7c6264 \
--hash=sha256:2968c58feca624bb6c8502f9564dd187d0e1389964898f5e9e1fbc8533169157 \
--hash=sha256:3f4cc516e0b264c8d4ccd6b6cbc69a07c6d582d8337df79be1e15a5056b258c9 \
--hash=sha256:3fa1284762aacca6dc97474ee9c16f83990b8eeb6697f2ba17140d54b453e133 \
--hash=sha256:43521ce2c4b865d385e78579a082b6ad1166ebed2b1a2293c3be1d68dd7ca3b9 \
--hash=sha256:451f10ef963918e65b8869e17d67db5e2f4ab40e716ee6ce7129b0cde2876eab \
--hash=sha256:46c259e87199041583658457372a183636ae8cd56dbf3f0755e0f376a7f9d0e6 \
--hash=sha256:46f39cab8bbf4a384ba7cb0bc8bae7b7062b6a11cfac1ca4bc144dea90d4a9f5 \
--hash=sha256:519e14e2c49fcf7616d6d2cfc5c70adae95682ae20f0395e9280db85e8d6c4df \
--hash=sha256:53dcb50fbdc3fb2c55431a9b30caeb2f7027fcd2aeb501459464f0214200a503 \
--hash=sha256:54614444887e0d3043557d9dbc697dbb16cfb5a35d672b7a0fcc1ed0cf1c600b \
--hash=sha256:575d8912dca808edd9acd6f7795199332696d3469665ef26163cd090fa1f8bfa \
--hash=sha256:5dd5a9c3091a0f414a963d427f920368e2b6a4c2f7527fdd82cde8ef0bc7a327 \
--hash=sha256:5f532a2ad4d174eb73494e7397988e22bf427f91acc8e6ebf5bb10597b49c493 \
--hash=sha256:60e7da3a3ad1812c128750fc1bc14a7ceeb8d29f77e0a2356a8fb2aa8925287d \
--hash=sha256:653d7fb2df65efefbcbf81ef5fe5e5be931f1ee4332c2893ca638c9b11a409c4 \
--hash=sha256:6663977496d616b618b6cfa43ec86e479ee62b942e1da76a2c3daa1c75933ef4 \
--hash=sha256:6abfb51a82e919e3933eb137e17c4ae9c0475a25508ea88993bb59faf82f3b35 \
--hash=sha256:6c6b1389ed66cdd174d040105123a5a1bc91d0aa7059c7261d20e583b6d8cbd2 \
--hash=sha256:6d9dfb9959a3b0039ee06c1a1a90dc23bac3b430842dcb97908ddde05870601c \
--hash=sha256:765cb54c0b8724a7c12c55146ae4647e0274a839fb6de7bcba841e04298e1011 \
--hash=sha256:7a21222644ab69ddd9967cfe6f2bb420b460dae4289c9d40ff9a4896e7c35c9a \
--hash=sha256:7ac7594397698f77bce84382929747130765f66406dc2cd8b4ab4da68ade4c6e \
--hash=sha256:7cfc287da09f9d2a7ec146ee4d72d6ea1342e770d975e49a8621bf54eaa8f30f \
--hash=sha256:83125753a60cfc8c412de5896d10a0a405e0bd88d0470ad82e0869ddf0cb3848 \
--hash=sha256:847b114580c5cc9ebaf216dd8c8dbc6b00a3b7ab0131e173d7120e6deade1f57 \
--hash=sha256:87708d78a14d56a990fbf4f9cb350b7d89ee8988705e58e39bdf4d82c149210f \
--hash=sha256:8a2b5874d17e72dfb80d917213abd55d7e1ed2479f38f001f264f7ce7bae757c \
--hash=sha256:8f127e7b028900421cad64f51f75c051b628db17fb00e099eb148761eed598c9 \
--hash=sha256:94cdff45173b1919350601f82d61365e792895e3c3a3443cf99819e6fbf717a5 \
--hash=sha256:99d92d148dd03fd19d16175b6d355cc1b01faf80dae93c6c3eb4163709edc0a9 \
--hash=sha256:9a3049a10261d7f2b6514d35bbb7a4dfc3ece4c4de14ef5876c4b7a23a0e566d \
--hash=sha256:9d9a62576b68cd90f7075876f4e8444487db5eeea0e4df3ba298ee38a8d067b0 \
--hash=sha256:9e5f94742033898bfe84c93c831a6f552bb629448d4072dd312306bab3bd96f1 \
--hash=sha256:a1c2d7780448eb93fbcc3789bf3916aa5720d942e37945f4056680317f1cd23e \
--hash=sha256:a2e0f87144fcbbe54297cae708c5e7f9da21a4646523456b00cc956bd4c65815 \
--hash=sha256:a4dfdae195335abb4e89cc9762b2edc524f3c6e80d647a9a81bf81e17e3fb6f0 \
--hash=sha256:a96e6e23f2b79433390273eaf8cc94fec9c6370842e577ab10dabdcc7ea0a66b \
--hash=sha256:aabdab8ec1e7ca7f1434d042bf8b1e92056245fb179790dc97ed040361f16bfd \
--hash=sha256:b222090c455d6d1a64e6b7bb5f4035c4dff479e22455c9eaa1bdd4c75b52c80c \
--hash=sha256:b52ff4f4e002f828ea6483faf4c4e8deea8d743cf801b74910243c58acc6eda3 \
--hash=sha256:b70756ec9417c34e097f987b4d8c510975216ad26ba6e57ccb53bc758f490dab \
--hash=sha256:b8c2f6eb0df979ee99433d8b3f6d193d9590f735cf12274c108bd954e30ca858 \
--hash=sha256:b9b752ab91e78234941e44abdecc07f1f0d8f51fb62941d32995b8161f68cfe5 \
--hash=sha256:ba6612b6548220ff5e9df85261bddc811a057b0b465a1226b39bfb8550616aee \
--hash=sha256:bd752c5ff1b4a870b7661234694f24b1d2b9076b8bf337321a814c612665f343 \
--hash=sha256:c3c4ed2ff6760e98d262e0cc9c9a7f7b8a9f61aa4d47c58835cdaf7b0b8811bb \
--hash=sha256:c5c1362c14aee73f50143d74389b2c158707b4abce2cb055b7ad37ce60738d47 \
--hash=sha256:cb362e3b0976dc994857391b776ddaa8c13c28a16f80ac6522c23d5257156bed \
--hash=sha256:d197df5489004db87d90b918033edbeee0bd6df3848a204bca3ff0a903bef837 \
--hash=sha256:d3b56206244dc8711f7e8b7d6cad4663917cd5b2d950799425076681e8766286 \
--hash=sha256:d5b2f8a31bd43e0f18172d8ac82347c8f37ef3e0b414431157718aa234991b28 \
--hash=sha256:d7081c084ceb58278dd3cf81f836bc818978c0ccc770cbbb202125ddabec6628 \
--hash=sha256:db74f5562c09953b2c5f8ec4b7dfd3f5421f31811e97d1dbc0a7c93d6e3a24df \
--hash=sha256:df41112ccce5d47770a0c13651479fbcd8793f34232a2dd9faeccb75eb5d0d0d \
--hash=sha256:e1339790c083c5a4de48f688b4841f18df839eb3c9584a770cbd818b33e26d5d \
--hash=sha256:e621b0246192d3b9cb1dc62c78cfa4c6f6d2ddc0ec207d43c0dedecb914f152a \
--hash=sha256:e8c5cf126889a4de385c02a2c3d3aba4b00f70234bfddae82a5eaa3ee6d5e3e6 \
--hash=sha256:e9d7747847c53a16a729b6ee5e737cf170f7a16611c143d95aa60a109a59c336 \
--hash=sha256:eaef5d2de3c7e9b21f1e762f289d17b726c2239a42b11e25446abf82b26ac132 \
--hash=sha256:ed3e4b4e1e6de75fdc16d3259098de7c6571b1a6cc863b1a49e7d3d53e036070 \
--hash=sha256:ef21af928e807f10bf4141cad4746eee692a0dd3ff56cfb25fce076ec3cc8abe \
--hash=sha256:f09598b416ba39a8f489c124447b007fe865f786a89dbfa48bb5cf395693132a \
--hash=sha256:f0caf4a5dcf610d96c3bd32932bfac8aee61c96e60481c2a0ea58da435e25acd \
--hash=sha256:f6e78171be3fb7941f9910ea15b4b14ec27725865a73c15277bc39f5ca4f8391 \
--hash=sha256:f715c32e774a60a337b2bb8ad9839b4abf75b267a0f18806f6f4f5f1688c4b5a \
--hash=sha256:fb5c1ad6bad98c57482236a21bf985ab0ef42bd51f7ad4e4538e89a997624e12
psycopg2==2.9.5 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:093e3894d2d3c592ab0945d9eba9d139c139664dcf83a1c440b8a7aa9bb21955 \
--hash=sha256:190d51e8c1b25a47484e52a79638a8182451d6f6dff99f26ad9bd81e5359a0fa \
--hash=sha256:1a5c7d7d577e0eabfcf15eb87d1e19314c8c4f0e722a301f98e0e3a65e238b4e \
--hash=sha256:1e5a38aa85bd660c53947bd28aeaafb6a97d70423606f1ccb044a03a1203fe4a \
--hash=sha256:322fd5fca0b1113677089d4ebd5222c964b1760e361f151cbb2706c4912112c5 \
--hash=sha256:4cb9936316d88bfab614666eb9e32995e794ed0f8f6b3b718666c22819c1d7ee \
--hash=sha256:920bf418000dd17669d2904472efeab2b20546efd0548139618f8fa305d1d7ad \
--hash=sha256:922cc5f0b98a5f2b1ff481f5551b95cd04580fd6f0c72d9b22e6c0145a4840e0 \
--hash=sha256:a5246d2e683a972e2187a8714b5c2cf8156c064629f9a9b1a873c1730d9e245a \
--hash=sha256:b9ac1b0d8ecc49e05e4e182694f418d27f3aedcfca854ebd6c05bb1cffa10d6d \
--hash=sha256:d3ef67e630b0de0779c42912fe2cbae3805ebaba30cda27fea2a3de650a9414f \
--hash=sha256:f5b6320dbc3cf6cfb9f25308286f9f7ab464e65cfb105b64cc9c52831748ced2 \
--hash=sha256:fc04dd5189b90d825509caa510f20d1d504761e78b8dfb95a0ede180f71d50e5
pyasn1-modules==0.2.8 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e \
--hash=sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74
pyasn1==0.4.8 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d \
--hash=sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba
python-cas==1.6.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:2abc0dae93c3b14097999fb7062f23cd09bc9f4e33d93f03c0cc040bd71ed50e \
--hash=sha256:b8f1dcb1b6c56b3ff4f86bbef47bcdfcf932061ccd4812ae35e3f63954dfdb28
python-ldap==3.4.3 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:ab26c519a0ef2a443a2a10391fa3c5cb52d7871323399db949ebfaa9f25ee2a0
pytz==2022.7.1 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0 \
--hash=sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a
redis==4.4.2 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:a010f6cb7378065040a02839c3f75c7e0fb37a87116fb4a95be82a95552776c7 \
--hash=sha256:e6206448e2f8a432871d07d432c13ed6c2abcf6b74edb436c99752b1371be387
requests==2.28.2 ; python_version >= "3.9" and python_version < "4" \
--hash=sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa \
--hash=sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf
setuptools==67.0.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:883131c5b6efa70b9101c7ef30b2b7b780a4283d5fc1616383cdf22c83cbefe6 \
--hash=sha256:9d790961ba6219e9ff7d9557622d2fe136816a264dd01d5997cfc057d804853d
six==1.16.0 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
--hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
sqlparse==0.4.3 ; python_version >= "3.9" and python_version < "4.0" \
--hash=sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34 \
--hash=sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268
urllib3==1.26.14 ; python_version >= "3.9" and python_version < "4" \
--hash=sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72 \
--hash=sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1
-r requirements.txt
psycopg2
python-ldap
gunicorn

3
requirements.txt Normal file
View file

@ -0,0 +1,3 @@
django==2.2.*
Pillow
django_cas_ng

View file

@ -1,2 +0,0 @@
[flake8]
max-line-length = 99

View file

@ -1,2 +0,0 @@
(import ./. { }).devShell

View file

@ -1,242 +0,0 @@
import json
import os
import sys
from datetime import datetime
import django
from django.contrib.auth import get_user_model
print("\nTransfert des fiches annuaires :")
# Configuration
print("Paramétrage de Django...", end=" ")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "annuaire.settings.prod")
django.setup()
User = get_user_model()
from authens.models import CASAccount # noqa
from fiches.management.commands._ldap import ClipperLDAP # noqa
from fiches.models import Address, Department, Mail, Phone, Profile # noqa
print("[ok]")
# Utilitaires
def parse_date(s):
try:
return datetime.strptime(s, "%Y-%m-%d").date()
except (ValueError, TypeError):
return None
def get_text_field(data):
text = ""
if data["interests"]:
text += "Mes intérêts :\n" + data["interests"] + "\n\n"
if data["websites"]:
text += "Mes pages web :\n\n" + data["websites"] + "\n\n"
if data["comments"]:
text += data["comments"] + "\n\n"
return text
def get_address(data):
content = data["lines"] + "\n"
if data["zip"] and data["city"]:
content += data["zip"] + ", " + data["city"]
elif data["zip"] or data["city"]:
content += data["zip"] + data["city"]
if data["country"]:
content += ", " + data["country"]
return content
def parse_list(s):
s = s or ""
return [p for p in s.replace(",", ";").replace("\n", ";").split(";") if p]
def create_phones(s, profile, numeros, name="Téléphone"):
for p in parse_list(s):
numeros.append(Phone(profile=profile, name=name, number=p))
def create_mails(s, profile, mails, name="E-mail"):
for m in parse_list(s):
mails.append(Mail(profile=profile, name=name, mail=m))
print("Récupération des comptes clipper...", end=" ")
ldap = ClipperLDAP()
clippers = {c.uid: c for c in ldap.get_clipper_list(stdout=sys.stdout)}
print("[ok]")
depts = {
dept: Department.objects.get_or_create(name=dept)[0].id
for dept in ldap.verbose_depts.values()
}
users_to_create = []
users = {}
profiles_to_create = []
dept_m2m_to_create = []
print("Création des comptes...", end=" ")
for clipper in clippers.values():
user = User(username=clipper.uid, email=clipper.email)
users_to_create.append(user)
User.objects.bulk_create(users_to_create)
users = {u.username: (u, clippers[u.username]) for u in User.objects.all()}
print("[ok]")
print("Création des comptes Authens...", end=" ")
cas_accounts = []
for (u, c) in users.values():
cas_accounts.append(CASAccount(user=u, cas_login=c.uid, entrance_year=c.year))
CASAccount.objects.bulk_create(cas_accounts)
print("[ok]")
fiches = {}
references = {}
adresses = []
devises = {}
numeros = []
mails = []
fiches_pk = {}
print("Récupération des anciennes fiches :")
# On recrée les profils
with open("old_fiches.json") as json_file:
data = json.load(json_file)
for obj in data:
if obj["model"] == "annuaire.fiches2021":
obj_data = obj["fields"]
try:
user, clipper = users[obj_data["login"]]
fiches_pk[obj["pk"]] = user.username
fiches[obj_data["login"]] = Profile(
user=user,
full_name=" ".join((obj_data["firstname"], obj_data["lastname"])),
promotion=clipper.year,
birth_date=parse_date((obj_data["birthdate"] or "")),
past_studies=(
(obj_data["past_studies"] or "")
+ "\n"
+ (obj_data["studies"] or "")
).strip(),
experiences=(obj_data["experiences"] or ""),
thurne=(obj_data["room"] or ""),
text_field=get_text_field(obj_data),
)
create_phones(obj_data["phones"], fiches[obj_data["login"]], numeros)
create_phones(
obj_data["mobiles"],
fiches[obj_data["login"]],
numeros,
name="Portable",
)
create_mails(obj_data["emails"], fiches[obj_data["login"]], mails)
dept_m2m_to_create.append(
Profile.department.through(
profile=fiches[obj_data["login"]],
department_id=depts[clipper.dept],
)
)
except KeyError:
print(f"\tLogin inconnu : {obj_data['login']}")
elif obj["model"] == "annuaire.lesreferences2021":
try:
username = fiches_pk[obj["pk"]]
surnom = obj["fields"]["nickname"]
if surnom:
fiche = fiches[username]
if fiche.nickname:
fiche.nickname += ", "
fiche.nickname += surnom
except KeyError:
pass
elif obj["model"] == "annuaire.adresses2021":
try:
username = fiches_pk[obj["pk"]]
obj_data = obj["fields"]
fiche = fiches[username]
a = get_address(obj_data).strip()
if a:
adresses.append(Address(profile=fiche, name="Adresse", content=a))
except KeyError:
pass
elif obj["model"] == "annuaire.devises2021":
try:
username = fiches_pk[obj["pk"]]
obj_data = obj["fields"]
fiche = fiches[username]
if obj_data["quote"]:
fiche.text_field += f"\n{obj_data['quote']}"
if obj_data["source"]:
fiche.text_field += f" ({obj_data['source']})"
if obj_data["author"]:
fiche.text_field += f", ({obj_data['author']})"
except KeyError:
pass
print("Création des nouvelles fiches fiches...", end=" ")
Profile.objects.bulk_create(fiches.values())
profils = {p.user.username: p for p in Profile.objects.select_related("user")}
print("[ok]")
print("Rattachement des départements...", end=" ")
for dept_m2m in dept_m2m_to_create:
dept_m2m.profile = profils[dept_m2m.profile.user.username]
Profile.department.through.objects.bulk_create(dept_m2m_to_create)
print("[ok]")
print("Création des numéros de téléphone...", end=" ")
for p in numeros:
p.profile = profils[p.profile.user.username]
Phone.objects.bulk_create(numeros)
print("[ok]")
print("Création des adresses mail...", end=" ")
for m in mails:
m.profile = profils[m.profile.user.username]
Mail.objects.bulk_create(mails)
print("[ok]")
print("Création des adresses...", end=" ")
for a in adresses:
a.profile = profils[a.profile.user.username]
Address.objects.bulk_create(adresses)
print("[ok]")