Compare commits
69 commits
Author | SHA1 | Date | |
---|---|---|---|
0209ad53ca | |||
4d75efe7c5 | |||
d7b80ea06a | |||
1491956e30 | |||
|
25f965c750 | ||
|
db1d35fbb6 | ||
02f7b3c8c3 | |||
d9111cc8cb | |||
732a6a08da | |||
32ba0e6111 | |||
b53170feae | |||
5bc518eba6 | |||
7cfc85f1fc | |||
|
26ad68ff69 | ||
|
c40a91fb67 | ||
|
46eacc94da | ||
|
370447d355 | ||
|
22b5016687 | ||
|
3c2f93bccb | ||
|
1956f38176 | ||
|
9c1092cf8f | ||
|
e470a2a268 | ||
|
18d1d53c45 | ||
|
2e92d5aa8a | ||
|
5726ff2692 | ||
|
45b72a9a77 | ||
|
d3f5c3df70 | ||
|
318b1dda78 | ||
|
66c278341c | ||
|
3a65c2a815 | ||
|
b443073921 | ||
|
76c7b1c642 | ||
|
804dc0fb96 | ||
|
092c373f2a | ||
|
c0cbff5070 | ||
|
8f3c02d292 | ||
|
833a8367cf | ||
|
5275e9036a | ||
|
da3ce8f464 | ||
|
5f49ecf270 | ||
|
941294cf93 | ||
|
40b65c7a7b | ||
|
eb2d4bd274 | ||
|
6ffa35948f | ||
|
bc17ff9e7c | ||
|
cb7f9187cf | ||
|
04f56ec0af | ||
|
b9e128cafb | ||
|
70ff24c708 | ||
|
d28e195873 | ||
|
78f7fd5afd | ||
|
2b94a28670 | ||
|
52f574678d | ||
|
9bdc6c277f | ||
|
754034cd57 | ||
|
85de4f0245 | ||
|
09f1cb0c91 | ||
|
32e15134e5 | ||
|
a2e3665f50 | ||
|
ca14bf09fc | ||
|
a3f12a22f8 | ||
|
4dc201e572 | ||
|
78d4d7c624 | ||
|
bdca70964a | ||
|
e9af6f5cfd | ||
|
f9f62bd1b6 | ||
|
4c97c8e420 | ||
|
d8df1064e7 | ||
|
c716862c7d |
94 changed files with 5559 additions and 1852 deletions
1
.credentials/SECRET_KEY
Normal file
1
.credentials/SECRET_KEY
Normal file
|
@ -0,0 +1 @@
|
||||||
|
insecure-secret-key
|
1
.envrc
Normal file
1
.envrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
use nix
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -108,6 +108,6 @@ test.py
|
||||||
.#*
|
.#*
|
||||||
*.sqlite3
|
*.sqlite3
|
||||||
.sass-cache
|
.sass-cache
|
||||||
/static/
|
|
||||||
settings.py
|
|
||||||
secrets.py
|
secrets.py
|
||||||
|
.direnv
|
||||||
|
.pre-commit-config.yaml
|
||||||
|
|
24
README.md
24
README.md
|
@ -3,11 +3,13 @@ ExpériENS : partagez votre stage
|
||||||
|
|
||||||
ExpériENS est un projet visant à faire un "Annuaire de stage", afin de partager vos ressentis concernant les lieux, les personnes, tout ce qui a fait votre séjour.
|
ExpériENS est un projet visant à faire un "Annuaire de stage", afin de partager vos ressentis concernant les lieux, les personnes, tout ce qui a fait votre séjour.
|
||||||
|
|
||||||
|
Il est visible sur https://www.eleves.ens.fr/experiens/
|
||||||
|
|
||||||
## Développer sur son ordinateur
|
## Développer sur son ordinateur
|
||||||
|
|
||||||
Clonez le dépôt. Installez les pré-requis :
|
Clonez le dépôt. Installez les pré-requis :
|
||||||
|
|
||||||
sudo apt-get install libxlst-dev python2.7-dev
|
sudo apt-get install libxlst-dev libsals2-dev libxml2-dev libldap2-dev libssl-dev
|
||||||
|
|
||||||
On a besoin de SpatiaLite pour une base de données GIS. Essayez
|
On a besoin de SpatiaLite pour une base de données GIS. Essayez
|
||||||
|
|
||||||
|
@ -23,15 +25,17 @@ Ensuite, paramétrez les settings :
|
||||||
|
|
||||||
cd experiENS/
|
cd experiENS/
|
||||||
echo 'SECRET_KEY="toto"' > secrets.py
|
echo 'SECRET_KEY="toto"' > secrets.py
|
||||||
|
echo 'GOOGLE_API_KEY="toto"' >> secrets.py
|
||||||
|
echo 'MAPBOX_API_KEY="toto"' >> secrets.py
|
||||||
ln -s settings_dev.py settings.py
|
ln -s settings_dev.py settings.py
|
||||||
cd ../
|
cd ../
|
||||||
|
|
||||||
Enfin, installez les autres dépendances :
|
Enfin, installez les autres dépendances :
|
||||||
|
|
||||||
virtualenv venv
|
python3 -m venv venv
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
pip install -r requirements.txt
|
pip install --update pip
|
||||||
pip install -r requirements_dev.txt
|
pip install -r requirements-dev.txt
|
||||||
python manage.py makemigrations
|
python manage.py makemigrations
|
||||||
python manage.py migrate
|
python manage.py migrate
|
||||||
|
|
||||||
|
@ -39,9 +43,13 @@ Vous pouvez alors lancez le serveur de développement
|
||||||
|
|
||||||
python manage.py runserver
|
python manage.py runserver
|
||||||
|
|
||||||
|
C'est bon, vous pouvez développer sur ExpériENS !
|
||||||
|
|
||||||
## Configuration de la recherche
|
## Configuration de la recherche
|
||||||
|
|
||||||
Il faut installer elasticsearch 5.*. C'est compliqué. Mais en suivant https://www.elastic.co/guide/en/elasticsearch/reference/5.4/deb.html ça va.
|
**Cette partie n'est pas obligatoire pour faire fonctionner un serveur de développement en local.** Elle n'est utile que si vous voulez toucher aux fonctionnalités de recherche.
|
||||||
|
|
||||||
|
Il faut installer elasticsearch 5.*. C'est compliqué. Mais en suivant https://www.elastic.co/guide/en/elasticsearch/reference/5.4/deb.html c'est faisable.
|
||||||
|
|
||||||
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -
|
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -
|
||||||
sudo apt-get install apt-transport-https
|
sudo apt-get install apt-transport-https
|
||||||
|
@ -52,11 +60,13 @@ Il faut installer elasticsearch 5.*. C'est compliqué. Mais en suivant https://w
|
||||||
sudo systemctl enable elasticsearch.service
|
sudo systemctl enable elasticsearch.service
|
||||||
sudo systemctl start elasticsearch.service
|
sudo systemctl start elasticsearch.service
|
||||||
|
|
||||||
Et puis, de retour dans le virtualenv python
|
Vous devez ensuite activer ElasticSearch dans vos paramètres locaux, en changeant `USE_ELASTICSEARCH = True` à la fin du fichier `experiENS/settings_dev.py`.
|
||||||
|
|
||||||
|
Enfin, de retour dans la console et le virtualenv python, vous pouvez faire
|
||||||
|
|
||||||
python manage.py search_index --rebuild
|
python manage.py search_index --rebuild
|
||||||
|
|
||||||
Si des erreurs s'affichent, il y a une cachuète dans le beurre.
|
Si des erreurs s'affichent, demandez de l'aide sur Merle ou par e-mail.
|
||||||
|
|
||||||
## Changer le CSS
|
## Changer le CSS
|
||||||
|
|
||||||
|
|
13
app/auth.py
Normal file
13
app/auth.py
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
from authens.backends import ENSCASBackend as AuthENSBackend
|
||||||
|
from authens.utils import parse_entrance_year
|
||||||
|
|
||||||
|
|
||||||
|
class ENSCASBackend(AuthENSBackend):
|
||||||
|
# Override AuthENS backend user creation to implement the @<promo> logic
|
||||||
|
|
||||||
|
def get_free_username(self, cas_login, attributes):
|
||||||
|
entrance_year = parse_entrance_year(attributes.get("homeDirectory"))
|
||||||
|
if entrance_year is None:
|
||||||
|
return super().get_free_username(cas_login, attributes)
|
||||||
|
entrance_year %= 100
|
||||||
|
return "%s@%02d" % (cas_login, entrance_year)
|
242
app/settings.py
Normal file
242
app/settings.py
Normal file
|
@ -0,0 +1,242 @@
|
||||||
|
"""
|
||||||
|
Django settings for the experiENS project
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from loadcredential import Credentials
|
||||||
|
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
credentials = Credentials(env_prefix="EXPERIENS_")
|
||||||
|
|
||||||
|
# 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", [])
|
||||||
|
|
||||||
|
SITE_ID = 1
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# ElasticSearch configuration
|
||||||
|
|
||||||
|
USE_ELASTICSEARCH = credentials.get_json("USE_ELASTICSEARCH", False)
|
||||||
|
ELASTICSEARCH_DSL = credentials.get_json(
|
||||||
|
"ELASTICSEARCH_DSL", {"default": {"hosts": "127.0.0.1:9200"}}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# Libraries configuration
|
||||||
|
|
||||||
|
GDAL_LIBRARY_PATH = credentials.get("GDAL_LIBRARY_PATH")
|
||||||
|
GEOS_LIBRARY_PATH = credentials.get("GEOS_LIBRARY_PATH")
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# List the installed applications
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
"django.contrib.admin",
|
||||||
|
"django.contrib.auth",
|
||||||
|
"django.contrib.contenttypes",
|
||||||
|
"django.contrib.sessions",
|
||||||
|
"django.contrib.messages",
|
||||||
|
"django.contrib.staticfiles",
|
||||||
|
"django.contrib.gis",
|
||||||
|
"django.contrib.sites",
|
||||||
|
*(["django_elasticsearch_dsl"] if USE_ELASTICSEARCH else []),
|
||||||
|
"simple_email_confirmation",
|
||||||
|
"authens",
|
||||||
|
"tastypie",
|
||||||
|
"braces",
|
||||||
|
"tinymce",
|
||||||
|
"taggit",
|
||||||
|
"taggit_autosuggest",
|
||||||
|
"avisstage",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# 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.contrib.auth.context_processors.auth",
|
||||||
|
"django.template.context_processors.debug",
|
||||||
|
"django.template.context_processors.i18n",
|
||||||
|
"django.template.context_processors.media",
|
||||||
|
"django.template.context_processors.static",
|
||||||
|
"django.template.context_processors.tz",
|
||||||
|
"django.template.context_processors.request",
|
||||||
|
"django.contrib.messages.context_processors.messages",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# 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.contrib.gis.db.backends.spatialite",
|
||||||
|
"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")),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# Authentication configuration
|
||||||
|
|
||||||
|
AUTHENTICATION_BACKENDS = [
|
||||||
|
"django.contrib.auth.backends.ModelBackend",
|
||||||
|
"experiENS.auth.ENSCASBackend",
|
||||||
|
]
|
||||||
|
|
||||||
|
CAS_SERVER_URL = "https://cas.eleves.ens.fr/" # SPI CAS
|
||||||
|
|
||||||
|
AUTHENS_USE_OLDCAS = False
|
||||||
|
|
||||||
|
LOGIN_URL = reverse_lazy("authens:login")
|
||||||
|
LOGOUT_URL = reverse_lazy("authens:logout")
|
||||||
|
LOGIN_REDIRECT_URL = reverse_lazy("avisstage:perso")
|
||||||
|
LOGOUT_REDIRECT_URL = reverse_lazy("avisstage:index")
|
||||||
|
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{"NAME": f"django.contrib.auth.password_validation.{v}"}
|
||||||
|
for v in [
|
||||||
|
"UserAttributeSimilarityValidator",
|
||||||
|
"MinimumLengthValidator",
|
||||||
|
"CommonPasswordValidator",
|
||||||
|
"NumericPasswordValidator",
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# Logging configuration
|
||||||
|
|
||||||
|
LOGGING = {
|
||||||
|
"version": 1,
|
||||||
|
"disable_existing_loggers": False,
|
||||||
|
"handlers": {
|
||||||
|
"file": {
|
||||||
|
"level": "INFO",
|
||||||
|
"class": "logging.FileHandler",
|
||||||
|
"filename": credentials.get(
|
||||||
|
"RECHERCHE_LOG_FILE", BASE_DIR / "recherche.log"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"loggers": {
|
||||||
|
"recherche": {
|
||||||
|
"handlers": ["file"],
|
||||||
|
"level": "INFO",
|
||||||
|
"propagate": True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# LDAP configuration
|
||||||
|
|
||||||
|
CLIPPER_LDAP_SERVER = credentials.get("CLIPPER_LDAP_SERVER", "ldaps://localhost:636")
|
||||||
|
|
||||||
|
|
||||||
|
# Development settings
|
||||||
|
if DEBUG:
|
||||||
|
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||||
|
INSTALLED_APPS += [
|
||||||
|
"debug_toolbar",
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = ["debug_toolbar.middleware.DebugToolbarMiddleware", *MIDDLEWARE]
|
18
app/urls.py
Normal file
18
app/urls.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import include, path
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("", include("avisstage.urls")),
|
||||||
|
path("authens/", include("authens.urls")),
|
||||||
|
path("tinymce/", include("tinymce.urls")),
|
||||||
|
path("taggit_autosuggest/", include("taggit_autosuggest.urls")),
|
||||||
|
path("admin/", admin.site.urls),
|
||||||
|
]
|
||||||
|
|
||||||
|
if settings.DEBUG:
|
||||||
|
import debug_toolbar
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("__debug__/", include(debug_toolbar.urls)),
|
||||||
|
] + urlpatterns
|
|
@ -8,7 +8,9 @@ https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "experiENS.settings")
|
|
||||||
|
|
||||||
from django.core.wsgi import get_wsgi_application
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings")
|
||||||
|
|
||||||
application = get_wsgi_application()
|
application = get_wsgi_application()
|
|
@ -1,34 +1,47 @@
|
||||||
|
import authens.models as authmod
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.auth.admin import UserAdmin
|
from django.contrib.auth.admin import UserAdmin
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from avisstage.models import *
|
|
||||||
|
from avisstage.models import AvisLieu, AvisStage, Lieu, Normalien, Stage, StageMatiere
|
||||||
|
|
||||||
|
|
||||||
class NormalienInline(admin.StackedInline):
|
class NormalienInline(admin.StackedInline):
|
||||||
model = Normalien
|
model = Normalien
|
||||||
inline_classes = ("collapse open",)
|
inline_classes = ("collapse open",)
|
||||||
|
|
||||||
|
|
||||||
class UserAdmin(UserAdmin):
|
class UserAdmin(UserAdmin):
|
||||||
inlines = (NormalienInline, )
|
inlines = (NormalienInline,)
|
||||||
|
|
||||||
|
|
||||||
class AvisLieuInline(admin.StackedInline):
|
class AvisLieuInline(admin.StackedInline):
|
||||||
model = AvisLieu
|
model = AvisLieu
|
||||||
inline_classes = ("collapse open",)
|
inline_classes = ("collapse open",)
|
||||||
extra = 0
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
class AvisStageInline(admin.StackedInline):
|
class AvisStageInline(admin.StackedInline):
|
||||||
model = AvisStage
|
model = AvisStage
|
||||||
inline_classes = ("collapse open",)
|
inline_classes = ("collapse open",)
|
||||||
extra = 0
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
class StageAdmin(admin.ModelAdmin):
|
class StageAdmin(admin.ModelAdmin):
|
||||||
inlines = (AvisLieuInline, AvisStageInline)
|
inlines = (AvisLieuInline, AvisStageInline)
|
||||||
|
|
||||||
|
|
||||||
class StageMatiereAdmin(admin.ModelAdmin):
|
class StageMatiereAdmin(admin.ModelAdmin):
|
||||||
model = StageMatiere
|
model = StageMatiere
|
||||||
prepopulated_fields = {"slug": ('nom',)}
|
prepopulated_fields = {"slug": ("nom",)}
|
||||||
|
|
||||||
|
|
||||||
admin.site.unregister(User)
|
admin.site.unregister(User)
|
||||||
admin.site.register(User, UserAdmin)
|
admin.site.register(User, UserAdmin)
|
||||||
admin.site.register(Lieu)
|
admin.site.register(Lieu)
|
||||||
admin.site.register(StageMatiere, StageMatiereAdmin)
|
admin.site.register(StageMatiere, StageMatiereAdmin)
|
||||||
admin.site.register(Stage, StageAdmin)
|
admin.site.register(Stage, StageAdmin)
|
||||||
|
|
||||||
|
admin.site.register(authmod.CASAccount)
|
||||||
|
admin.site.register(authmod.OldCASAccount)
|
||||||
|
|
|
@ -1,25 +1,32 @@
|
||||||
# coding: utf-8
|
from tastypie import fields
|
||||||
|
|
||||||
from tastypie.resources import ModelResource
|
|
||||||
from tastypie.authentication import SessionAuthentication
|
from tastypie.authentication import SessionAuthentication
|
||||||
from tastypie import fields, utils
|
from tastypie.resources import ModelResource
|
||||||
|
|
||||||
from django.contrib.gis import geos
|
from django.contrib.gis import geos
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from .models import Lieu, Stage, Normalien, StageMatiere
|
from .models import Lieu, Normalien, Stage
|
||||||
|
from .utils import approximate_distance
|
||||||
|
|
||||||
|
|
||||||
|
class EnScolariteAuthentication(SessionAuthentication):
|
||||||
|
def is_authenticated(self, request, **kwargs):
|
||||||
|
if super().is_authenticated(request, **kwargs):
|
||||||
|
return request.user.profil.en_scolarite
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
# API principale pour les lieux
|
# API principale pour les lieux
|
||||||
class LieuResource(ModelResource):
|
class LieuResource(ModelResource):
|
||||||
stages = fields.ToManyField("avisstage.api.StageResource",
|
# stages = fields.ToManyField("avisstage.api.StageResource",
|
||||||
"stages", use_in="detail", full=True)
|
# "stages", use_in="detail", full=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
queryset = Lieu.objects.all()
|
queryset = Lieu.objects.all()
|
||||||
resource_name = "lieu"
|
resource_name = "lieu"
|
||||||
fields = ["nom", "ville", "pays", "coord", "type_lieu", "id"]
|
fields = ["nom", "ville", "pays", "coord", "type_lieu", "id"]
|
||||||
|
|
||||||
#login_required
|
# login_required
|
||||||
authentication = SessionAuthentication()
|
authentication = SessionAuthentication()
|
||||||
|
|
||||||
# Filtres personnalisés
|
# Filtres personnalisés
|
||||||
|
@ -30,15 +37,15 @@ class LieuResource(ModelResource):
|
||||||
|
|
||||||
# Trouver les lieux à proximités d'un point donné
|
# Trouver les lieux à proximités d'un point donné
|
||||||
if "lng" in filters and "lat" in filters:
|
if "lng" in filters and "lat" in filters:
|
||||||
lat = float(filters['lat'])
|
lat = float(filters["lat"])
|
||||||
lng = float(filters['lng'])
|
lng = float(filters["lng"])
|
||||||
pt = geos.Point((lng,lat), srid=4326)
|
pt = geos.Point((lng, lat), srid=4326)
|
||||||
self.reference_point = pt
|
self.reference_point = pt
|
||||||
orm_filters['coord__distance_lte'] = (pt, 10000)
|
orm_filters["coord__distance_lte"] = (pt, 10000)
|
||||||
|
|
||||||
# Filtrer les lieux qui ont déjà des stages
|
# Filtrer les lieux qui ont déjà des stages
|
||||||
if "has_stage" in filters:
|
if "has_stage" in filters:
|
||||||
orm_filters['stages__public'] = True
|
orm_filters["stages__public"] = True
|
||||||
|
|
||||||
return orm_filters
|
return orm_filters
|
||||||
|
|
||||||
|
@ -51,12 +58,13 @@ class LieuResource(ModelResource):
|
||||||
bundle = super(LieuResource, self).dehydrate(bundle)
|
bundle = super(LieuResource, self).dehydrate(bundle)
|
||||||
|
|
||||||
obj = bundle.obj
|
obj = bundle.obj
|
||||||
bundle.data['coord'] = {'lat': float(obj.coord.y),
|
bundle.data["coord"] = {"lat": float(obj.coord.y), "lng": float(obj.coord.x)}
|
||||||
'lng': float(obj.coord.x)}
|
|
||||||
|
|
||||||
# Distance au point recherché (inutile en fait)
|
# Distance au point recherché
|
||||||
#if "lat" in bundle.request.GET and "lng" in bundle.request.GET:
|
if "lat" in bundle.request.GET and "lng" in bundle.request.GET:
|
||||||
# bundle.data['distance'] = self.reference_point.distance(bundle.obj.coord)
|
bundle.data["distance"] = approximate_distance(
|
||||||
|
self.reference_point, bundle.obj.coord
|
||||||
|
)
|
||||||
|
|
||||||
# Autres infos utiles
|
# Autres infos utiles
|
||||||
bundle.data["pays_nom"] = obj.get_pays_display()
|
bundle.data["pays_nom"] = obj.get_pays_display()
|
||||||
|
@ -66,6 +74,7 @@ class LieuResource(ModelResource):
|
||||||
|
|
||||||
return bundle
|
return bundle
|
||||||
|
|
||||||
|
|
||||||
# API sur un stage
|
# API sur un stage
|
||||||
class StageResource(ModelResource):
|
class StageResource(ModelResource):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -73,8 +82,8 @@ class StageResource(ModelResource):
|
||||||
resource_name = "stage"
|
resource_name = "stage"
|
||||||
fields = ["sujet", "date_debut", "date_fin", "matieres", "id"]
|
fields = ["sujet", "date_debut", "date_fin", "matieres", "id"]
|
||||||
|
|
||||||
#login_required
|
# login_required
|
||||||
authentication = SessionAuthentication()
|
authentication = EnScolariteAuthentication()
|
||||||
|
|
||||||
# Filtres personnalisés
|
# Filtres personnalisés
|
||||||
def build_filters(self, filters=None, **kwargs):
|
def build_filters(self, filters=None, **kwargs):
|
||||||
|
@ -84,8 +93,8 @@ class StageResource(ModelResource):
|
||||||
|
|
||||||
# Récupération des stages à un lieu donné
|
# Récupération des stages à un lieu donné
|
||||||
if "lieux" in filters:
|
if "lieux" in filters:
|
||||||
flieux = map(int, filters['lieux'].split(','))
|
flieux = map(int, filters["lieux"].split(","))
|
||||||
orm_filters['lieux__id__in'] = flieux
|
orm_filters["lieux__id__in"] = flieux
|
||||||
|
|
||||||
return orm_filters
|
return orm_filters
|
||||||
|
|
||||||
|
@ -95,23 +104,27 @@ class StageResource(ModelResource):
|
||||||
obj = bundle.obj
|
obj = bundle.obj
|
||||||
|
|
||||||
# Affichage des manytomany en condensé
|
# Affichage des manytomany en condensé
|
||||||
bundle.data['auteur'] = obj.auteur.nom
|
bundle.data["auteur"] = obj.auteur.nom
|
||||||
bundle.data['thematiques'] = list(obj.thematiques.all().values_list("name", flat=True))
|
bundle.data["thematiques"] = list(
|
||||||
bundle.data['matieres'] = list(obj.matieres.all().values_list("nom", flat=True))
|
obj.thematiques.all().values_list("name", flat=True)
|
||||||
|
)
|
||||||
|
bundle.data["matieres"] = list(obj.matieres.all().values_list("nom", flat=True))
|
||||||
|
|
||||||
# Adresse de la fiche de stage
|
# Adresse de la fiche de stage
|
||||||
bundle.data['url'] = reverse("avisstage:stage", kwargs={"pk": obj.id});
|
bundle.data["url"] = reverse("avisstage:stage", kwargs={"pk": obj.id})
|
||||||
return bundle
|
return bundle
|
||||||
|
|
||||||
|
|
||||||
# Auteurs des fiches (TODO supprimer ?)
|
# Auteurs des fiches (TODO supprimer ?)
|
||||||
class AuteurResource(ModelResource):
|
class AuteurResource(ModelResource):
|
||||||
stages = fields.ToManyField("avisstage.api.StageResource",
|
stages = fields.ToManyField(
|
||||||
"stages", use_in="detail")
|
"avisstage.api.StageResource", "stages", use_in="detail"
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
queryset = Normalien.objects.all()
|
queryset = Normalien.objects.all()
|
||||||
resource_name = "profil"
|
resource_name = "profil"
|
||||||
fields = ["id", "nom", "stages"]
|
fields = ["id", "nom", "stages"]
|
||||||
|
|
||||||
#login_required
|
# login_required
|
||||||
authentication = SessionAuthentication()
|
authentication = EnScolariteAuthentication()
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class AvisstageConfig(AppConfig):
|
class AvisstageConfig(AppConfig):
|
||||||
name = 'avisstage'
|
name = "avisstage"
|
||||||
|
|
14
avisstage/decorators.py
Normal file
14
avisstage/decorators.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
|
||||||
|
def en_scolarite_required(view_func):
|
||||||
|
@wraps(view_func)
|
||||||
|
def _wrapped_view(request, *args, **kwargs):
|
||||||
|
if request.user.profil.en_scolarite:
|
||||||
|
return view_func(request, *args, **kwargs)
|
||||||
|
return redirect(reverse("avisstage:403-archicubes"))
|
||||||
|
|
||||||
|
return _wrapped_view
|
|
@ -1,61 +1,69 @@
|
||||||
from django_elasticsearch_dsl import DocType, Index, fields
|
from django_elasticsearch_dsl import Document, Index, fields
|
||||||
from elasticsearch_dsl import analyzer, token_filter, tokenizer
|
from elasticsearch_dsl import analyzer, token_filter
|
||||||
|
|
||||||
from .models import Stage, AvisStage, AvisLieu
|
from .models import Stage
|
||||||
from .statics import PAYS_OPTIONS
|
from .statics import PAYS_OPTIONS
|
||||||
|
|
||||||
PAYS_DICT = dict(PAYS_OPTIONS)
|
PAYS_DICT = dict(PAYS_OPTIONS)
|
||||||
|
|
||||||
stage = Index('stages')
|
stage = Index("stages")
|
||||||
stage.settings(
|
stage.settings(number_of_shards=1, number_of_replicas=0)
|
||||||
number_of_shards=1,
|
|
||||||
number_of_replicas=0
|
|
||||||
)
|
|
||||||
|
|
||||||
text_analyzer = analyzer(
|
text_analyzer = analyzer(
|
||||||
'default',
|
"default",
|
||||||
tokenizer="standard",
|
tokenizer="standard",
|
||||||
filter=['lowercase', 'standard', 'asciifolding',
|
filter=[
|
||||||
|
"lowercase",
|
||||||
|
"asciifolding",
|
||||||
token_filter("frstop", type="stop", stopwords="_french_"),
|
token_filter("frstop", type="stop", stopwords="_french_"),
|
||||||
token_filter("frsnow", type="snowball", language="French")])
|
token_filter("frsnow", type="snowball", language="French"),
|
||||||
|
],
|
||||||
|
)
|
||||||
stage.analyzer(text_analyzer)
|
stage.analyzer(text_analyzer)
|
||||||
|
|
||||||
@stage.doc_type
|
|
||||||
class StageDocument(DocType):
|
|
||||||
lieux = fields.ObjectField(properties={
|
|
||||||
'nom': fields.StringField(),
|
|
||||||
'ville': fields.StringField(),
|
|
||||||
'pays': fields.StringField(),
|
|
||||||
})
|
|
||||||
auteur = fields.ObjectField(properties={
|
|
||||||
'nom': fields.StringField(),
|
|
||||||
})
|
|
||||||
thematiques = fields.StringField()
|
|
||||||
matieres = fields.StringField()
|
|
||||||
|
|
||||||
class Meta:
|
@stage.doc_type
|
||||||
|
class StageDocument(Document):
|
||||||
|
lieux = fields.ObjectField(
|
||||||
|
properties={
|
||||||
|
"nom": fields.TextField(),
|
||||||
|
"ville": fields.TextField(),
|
||||||
|
"pays": fields.TextField(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
auteur = fields.ObjectField(
|
||||||
|
properties={
|
||||||
|
"nom": fields.TextField(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
thematiques = fields.TextField()
|
||||||
|
matieres = fields.TextField()
|
||||||
|
|
||||||
|
class Django:
|
||||||
model = Stage
|
model = Stage
|
||||||
fields = [
|
fields = [
|
||||||
'sujet',
|
"sujet",
|
||||||
'encadrants',
|
"encadrants",
|
||||||
'type_stage',
|
"type_stage",
|
||||||
'niveau_scol',
|
"niveau_scol",
|
||||||
'structure',
|
"structure",
|
||||||
'date_debut',
|
"date_debut",
|
||||||
'date_fin'
|
"date_fin",
|
||||||
]
|
]
|
||||||
|
|
||||||
def prepare_thematiques(self, instance):
|
def prepare_thematiques(self, instance):
|
||||||
return ", ".join(instance.thematiques.all().values_list("name", flat=True))
|
return ", ".join(
|
||||||
|
instance.thematiques.all().values_list("name", flat=True)
|
||||||
|
).lower()
|
||||||
|
|
||||||
def prepare_matieres(self, instance):
|
def prepare_matieres(self, instance):
|
||||||
return ", ".join(instance.matieres.all().values_list("nom", flat=True))
|
return ", ".join(instance.matieres.all().values_list("nom", flat=True)).lower()
|
||||||
|
|
||||||
def prepare_niveau_scol(self, instance):
|
def prepare_niveau_scol(self, instance):
|
||||||
return instance.get_niveau_scol_display()
|
return instance.get_niveau_scol_display().lower()
|
||||||
|
|
||||||
def prepare_type_stage(self, instance):
|
def prepare_type_stage(self, instance):
|
||||||
return instance.type_stage_fancy
|
return instance.type_stage_fancy.lower()
|
||||||
|
|
||||||
def prepare_date_fin(self, instance):
|
def prepare_date_fin(self, instance):
|
||||||
return instance.date_fin.year
|
return instance.date_fin.year
|
||||||
|
@ -63,10 +71,13 @@ class StageDocument(DocType):
|
||||||
def prepare_date_debut(self, instance):
|
def prepare_date_debut(self, instance):
|
||||||
return instance.date_debut.year
|
return instance.date_debut.year
|
||||||
|
|
||||||
|
def prepare_sujet(self, instance):
|
||||||
|
return instance.sujet.lower()
|
||||||
|
|
||||||
# Hook pour l'affichage des noms de pays
|
# Hook pour l'affichage des noms de pays
|
||||||
def prepare(self, instance):
|
def prepare(self, instance):
|
||||||
data = super(StageDocument, self).prepare(instance)
|
data = super(StageDocument, self).prepare(instance)
|
||||||
|
|
||||||
for lieu in data['lieux']:
|
for lieu in data["lieux"]:
|
||||||
lieu['pays'] = PAYS_DICT[lieu['pays']]
|
lieu["pays"] = PAYS_DICT[lieu["pays"]].lower()
|
||||||
return data
|
return data
|
||||||
|
|
|
@ -1,46 +1,71 @@
|
||||||
# coding: utf-8
|
import re
|
||||||
|
import unicodedata
|
||||||
|
|
||||||
|
from simple_email_confirmation.models import EmailAddress
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.contrib.auth.forms import PasswordResetForm
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
import re
|
from .models import AvisLieu, AvisStage, Lieu, Stage, User
|
||||||
|
|
||||||
from .models import Normalien, Stage, Lieu, AvisLieu, AvisStage
|
|
||||||
from .widgets import LatLonField
|
from .widgets import LatLonField
|
||||||
|
|
||||||
|
|
||||||
# Sur-classe utile
|
# Sur-classe utile
|
||||||
class HTMLTrimmerForm(forms.ModelForm):
|
class HTMLTrimmerForm(forms.ModelForm):
|
||||||
def clean(self):
|
def clean(self):
|
||||||
# Suppression des espaces blanc avant et après le texte pour les champs html
|
# Suppression des espaces blanc avant et après le texte pour les champs html
|
||||||
leading_white = re.compile(r"^( \t\n)*(<p>( |[ \n\t]|<br[ /]*>)*</p>( \t\n)*)+?", re.IGNORECASE)
|
leading_white = re.compile(
|
||||||
trailing_white = re.compile(r"(( \t\n)*<p>( |[ \n\t]|<br[ /]*>)*</p>)+?( \t\n)*$", re.IGNORECASE)
|
r"^( \t\n)*(<p>( |[ \n\t]|<br[ /]*>)*</p>( \t\n)*)+?", re.IGNORECASE
|
||||||
|
)
|
||||||
|
trailing_white = re.compile(
|
||||||
|
r"(( \t\n)*<p>( |[ \n\t]|<br[ /]*>)*</p>)+?( \t\n)*$", re.IGNORECASE
|
||||||
|
)
|
||||||
cleaned_data = super(HTMLTrimmerForm, self).clean()
|
cleaned_data = super(HTMLTrimmerForm, self).clean()
|
||||||
|
|
||||||
for (fname, fval) in cleaned_data.items():
|
for (fname, fval) in cleaned_data.items():
|
||||||
# Heuristique : les champs commençant par "avis_" sont des champs html
|
# Heuristique : les champs commençant par "avis_" sont des champs html
|
||||||
if fname[:5] == "avis_":
|
if fname[:5] == "avis_":
|
||||||
cleaned_data[fname] = leading_white.sub("", trailing_white.sub("", fval))
|
cleaned_data[fname] = leading_white.sub(
|
||||||
|
"", trailing_white.sub("", fval)
|
||||||
|
)
|
||||||
|
|
||||||
return cleaned_data
|
return cleaned_data
|
||||||
|
|
||||||
|
|
||||||
# Infos sur un stage
|
# Infos sur un stage
|
||||||
class StageForm(forms.ModelForm):
|
class StageForm(forms.ModelForm):
|
||||||
date_widget = forms.DateInput(attrs={"class":"datepicker",
|
date_widget = forms.DateInput(
|
||||||
"placeholder":"JJ/MM/AAAA"})
|
attrs={"class": "datepicker", "placeholder": "JJ/MM/AAAA"}
|
||||||
date_debut = forms.DateField(label=u"Date de début",
|
)
|
||||||
input_formats=["%d/%m/%Y"], widget=date_widget)
|
date_debut = forms.DateField(
|
||||||
date_fin = forms.DateField(label=u"Date de fin",
|
label="Date de début", input_formats=["%d/%m/%Y"], widget=date_widget
|
||||||
input_formats=["%d/%m/%Y"], widget=date_widget)
|
)
|
||||||
|
date_fin = forms.DateField(
|
||||||
|
label="Date de fin", input_formats=["%d/%m/%Y"], widget=date_widget
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Stage
|
model = Stage
|
||||||
fields = ['sujet', 'date_debut', 'date_fin', 'type_stage', 'niveau_scol', 'thematiques', 'matieres', 'structure', 'encadrants']
|
fields = [
|
||||||
|
"sujet",
|
||||||
|
"date_debut",
|
||||||
|
"date_fin",
|
||||||
|
"type_stage",
|
||||||
|
"niveau_scol",
|
||||||
|
"thematiques",
|
||||||
|
"matieres",
|
||||||
|
"structure",
|
||||||
|
"encadrants",
|
||||||
|
]
|
||||||
help_texts = {
|
help_texts = {
|
||||||
"thematiques": u"Mettez une virgule pour valider votre thématique si la suggestion ne correspond pas ou si elle n'existe pas encore",
|
"thematiques": "Mettez une virgule pour valider votre thématique si la suggestion ne "
|
||||||
"structure": u"Nom de l'équipe, du laboratoire, de la startup... (si le lieu ne suffit pas)"
|
"correspond pas ou si elle n'existe pas encore",
|
||||||
|
"structure": "Nom de l'équipe, du laboratoire, de la startup... (si le lieu ne suffit "
|
||||||
|
"pas)",
|
||||||
}
|
}
|
||||||
labels = {
|
labels = {
|
||||||
"date_debut": u"Date de début",
|
"date_debut": "Date de début",
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
@ -51,7 +76,7 @@ class StageForm(forms.ModelForm):
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
# Lors de la création : attribution à l'utilisateur connecté
|
# Lors de la création : attribution à l'utilisateur connecté
|
||||||
if self.instance.id is None and hasattr(self, 'request'):
|
if self.instance.id is None and hasattr(self, "request"):
|
||||||
self.instance.auteur = self.request.user.profil
|
self.instance.auteur = self.request.user.profil
|
||||||
|
|
||||||
# Date de modification
|
# Date de modification
|
||||||
|
@ -62,36 +87,83 @@ class StageForm(forms.ModelForm):
|
||||||
stage = super(StageForm, self).save(commit=commit)
|
stage = super(StageForm, self).save(commit=commit)
|
||||||
return stage
|
return stage
|
||||||
|
|
||||||
|
|
||||||
# Sous-formulaire des avis sur le stage
|
# Sous-formulaire des avis sur le stage
|
||||||
class AvisStageForm(HTMLTrimmerForm):
|
class AvisStageForm(HTMLTrimmerForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AvisStage
|
model = AvisStage
|
||||||
fields = ['chapo', 'avis_sujet', 'avis_ambiance', 'avis_admin', 'avis_prestage', 'les_plus', 'les_moins']
|
fields = [
|
||||||
|
"chapo",
|
||||||
|
"avis_sujet",
|
||||||
|
"avis_ambiance",
|
||||||
|
"avis_admin",
|
||||||
|
"avis_prestage",
|
||||||
|
"les_plus",
|
||||||
|
"les_moins",
|
||||||
|
]
|
||||||
help_texts = {
|
help_texts = {
|
||||||
"chapo": u"\"Trop long, pas lu\" : une accroche résumant ce que vous avez pensé de ce séjour",
|
"chapo": (
|
||||||
"avis_ambiance": u"Avez-vous passé un bon moment à ce travail ? Étiez-vous assez guidé⋅e ? Aviez-vous un bon contact avec vos encadrant⋅e⋅s ? Y avait-il une bonne ambiance dans l'équipe ?",
|
'"Trop long, pas lu" : une accroche résumant ce que vous avez pensé de ce séjour'
|
||||||
"avis_sujet": u"Quelle était votre mission ? Qu'en avez-vous retiré ? Le travail correspondait-il à vos attentes ? Était-ce à votre niveau, trop dur, trop facile ?",
|
),
|
||||||
"avis_admin": u"Avez-vous commencé votre travail à la date prévue ? Était-ce compliqué d'obtenir les documents nécessaires (visa, contrats, etc) ? L'administration de l'établissement vous a-t-elle aidé⋅e ? Étiez-vous rémunéré⋅e ?",
|
"avis_ambiance": (
|
||||||
"avis_prestage": u"Comment avez-vous trouvé où aller pour cette expérience ? À quel moment avez-vous commencé à chercher ? Avez-vous eu des entretiens pour obtenir votre place ? Avez-vous eu d'autres pistes, pourquoi avez-vous choisi cette option ?",
|
"Avez-vous passé un bon moment à ce travail ? Étiez-vous assez guidé·e ? "
|
||||||
"les_plus": u"Les principaux points positifs de cette expérience",
|
"Aviez-vous un bon contact avec vos encadrant·e·s ? Y avait-il une bonne "
|
||||||
"les_moins": u"Ce qui aurait pu être mieux",
|
"ambiance dans l'équipe ?"
|
||||||
|
),
|
||||||
|
"avis_sujet": (
|
||||||
|
"Quelle était votre mission ? Qu'en avez-vous retiré ? Le travail "
|
||||||
|
"correspondait-il à vos attentes ? Était-ce à votre niveau, trop dur, "
|
||||||
|
"trop facile ?"
|
||||||
|
),
|
||||||
|
"avis_admin": (
|
||||||
|
"Avez-vous commencé votre travail à la date prévue ? Était-ce compliqué "
|
||||||
|
"d'obtenir les documents nécessaires (visa, contrats, etc) ? L'administration "
|
||||||
|
"de l'établissement vous a-t-elle aidé·e ? Étiez-vous rémunéré·e ?"
|
||||||
|
),
|
||||||
|
"avis_prestage": (
|
||||||
|
"Comment avez-vous trouvé où aller pour cette expérience ? À quel moment "
|
||||||
|
"avez-vous commencé à chercher ? Avez-vous eu des entretiens pour obtenir "
|
||||||
|
"votre place ? Avez-vous eu d'autres pistes, pourquoi avez-vous choisi "
|
||||||
|
"cette option ?"
|
||||||
|
),
|
||||||
|
"les_plus": "Les principaux points positifs de cette expérience",
|
||||||
|
"les_moins": "Ce qui aurait pu être mieux",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class AvisLieuForm(HTMLTrimmerForm):
|
class AvisLieuForm(HTMLTrimmerForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AvisLieu
|
model = AvisLieu
|
||||||
fields = ['lieu', 'chapo', 'avis_lieustage', 'avis_pratique', 'avis_tourisme', 'les_plus', 'les_moins']
|
fields = [
|
||||||
|
"lieu",
|
||||||
|
"chapo",
|
||||||
|
"avis_lieustage",
|
||||||
|
"avis_pratique",
|
||||||
|
"avis_tourisme",
|
||||||
|
"les_plus",
|
||||||
|
"les_moins",
|
||||||
|
]
|
||||||
help_texts = {
|
help_texts = {
|
||||||
"chapo": u"\"Trop long, pas lu\" : une accroche résumant ce que vous avez pensé de cet endroit",
|
"chapo": (
|
||||||
"avis_lieustage": u"Qu'avez-vous pensé des lieux où vous travailliez ? Les bâtiments étaient-ils modernes ? Était-il agréable d'y travailler ?",
|
'"Trop long, pas lu" : une accroche résumant ce que vous avez pensé de cet endroit'
|
||||||
"avis_pratique": u"Avez-vous eu du mal à trouver un logement ? Y-a-t-il des choses que vous avez apprises sur place qu'il vous aurait été utile de savoir avant de partir ?",
|
),
|
||||||
"avis_tourisme": u"Y-a-t-il des lieux à visiter dans cette zone ? Avez-vous pratiqué des activités sportives ? Est-il facile de faire des rencontres ?",
|
"avis_lieustage": (
|
||||||
"les_plus": u"Les meilleures raisons de partir à cet endroit",
|
"Qu'avez-vous pensé des lieux où vous travailliez ? Les bâtiments "
|
||||||
"les_moins": u"Ce qui vous a gêné ou manqué là-bas",
|
"étaient-ils modernes ? Était-il agréable d'y travailler ?"
|
||||||
}
|
),
|
||||||
widgets = {
|
"avis_pratique": (
|
||||||
"lieu": forms.HiddenInput(attrs={"class":"lieu-hidden"})
|
"Avez-vous eu du mal à trouver un logement ? Y-a-t-il des choses que vous avez "
|
||||||
|
"apprises sur place qu'il vous aurait été utile de savoir avant de partir ?"
|
||||||
|
),
|
||||||
|
"avis_tourisme": (
|
||||||
|
"Y-a-t-il des lieux à visiter dans cette zone ? Avez-vous pratiqué "
|
||||||
|
"des activités sportives ? Est-il facile de faire des rencontres ?"
|
||||||
|
),
|
||||||
|
"les_plus": "Les meilleures raisons de partir à cet endroit",
|
||||||
|
"les_moins": "Ce qui vous a gêné ou manqué là-bas",
|
||||||
}
|
}
|
||||||
|
widgets = {"lieu": forms.HiddenInput(attrs={"class": "lieu-hidden"})}
|
||||||
|
|
||||||
|
|
||||||
# Création d'un nouveau lieu
|
# Création d'un nouveau lieu
|
||||||
class LieuForm(forms.ModelForm):
|
class LieuForm(forms.ModelForm):
|
||||||
|
@ -100,10 +172,59 @@ class LieuForm(forms.ModelForm):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Lieu
|
model = Lieu
|
||||||
fields = ['id', 'nom', 'type_lieu', 'ville', 'pays', 'coord']
|
fields = ["id", "nom", "type_lieu", "ville", "pays", "coord"]
|
||||||
|
|
||||||
|
|
||||||
# Widget de feedback
|
# Widget de feedback
|
||||||
class FeedbackForm(forms.Form):
|
class FeedbackForm(forms.Form):
|
||||||
objet = forms.CharField(label="Objet", required=True)
|
objet = forms.CharField(label="Objet", required=True)
|
||||||
message = forms.CharField(label="Message", required=True, widget=forms.widgets.Textarea())
|
message = forms.CharField(
|
||||||
|
label="Message", required=True, widget=forms.widgets.Textarea()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Nouvelle adresse mail
|
||||||
|
class AdresseEmailForm(forms.Form):
|
||||||
|
def __init__(self, _user, **kwargs):
|
||||||
|
self._user = _user
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
email = forms.EmailField(
|
||||||
|
widget=forms.widgets.EmailInput(attrs={"placeholder": "Nouvelle adresse"})
|
||||||
|
)
|
||||||
|
|
||||||
|
def clean_email(self):
|
||||||
|
email = self.cleaned_data["email"]
|
||||||
|
if EmailAddress.objects.filter(user=self._user, email=email).exists():
|
||||||
|
raise forms.ValidationError("Cette adresse est déjà associée à ce compte")
|
||||||
|
return email
|
||||||
|
|
||||||
|
|
||||||
|
def _unicode_ci_compare(s1, s2):
|
||||||
|
"""
|
||||||
|
Perform case-insensitive comparison of two identifiers, using the
|
||||||
|
recommended algorithm from Unicode Technical Report 36, section
|
||||||
|
2.11.2(B)(2).
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
unicodedata.normalize("NFKC", s1).casefold()
|
||||||
|
== unicodedata.normalize("NFKC", s2).casefold()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# (Ré)initialisation du mot de passe
|
||||||
|
class ReinitMdpForm(PasswordResetForm):
|
||||||
|
def get_users(self, email):
|
||||||
|
"""Override default method to allow unusable passwords"""
|
||||||
|
email_field_name = User.get_email_field_name()
|
||||||
|
active_users = User._default_manager.filter(
|
||||||
|
**{
|
||||||
|
"%s__iexact" % email_field_name: email,
|
||||||
|
"is_active": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
u
|
||||||
|
for u in active_users
|
||||||
|
if _unicode_ci_compare(email, getattr(u, email_field_name))
|
||||||
|
)
|
||||||
|
|
|
@ -1,43 +1,59 @@
|
||||||
#coding: utf-8
|
from django.core.management.base import BaseCommand
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
|
||||||
from django.db.models import Count
|
from avisstage.models import Lieu
|
||||||
from avisstage.models import Stage, Lieu
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = 'Nettoie les stages à plusieurs lieux identiques'
|
help = "Nettoie les stages à plusieurs lieux identiques"
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
parser.add_argument('min_lieu', nargs='?', default=0, type=int)
|
parser.add_argument("min_lieu", nargs="?", default=0, type=int)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--apply',
|
"--apply",
|
||||||
action='store_true',
|
action="store_true",
|
||||||
default=False,
|
default=False,
|
||||||
help='Applies the modifications',
|
help="Applies the modifications",
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
rundb = False
|
rundb = False
|
||||||
if options.get('apply', False):
|
if options.get("apply", False):
|
||||||
rundb = True
|
rundb = True
|
||||||
else:
|
else:
|
||||||
print u"Les modifications ne seront pas appliquées"
|
print("Les modifications ne seront pas appliquées")
|
||||||
|
|
||||||
min_lieu = options.get('min_lieu', 0)
|
min_lieu = options.get("min_lieu", 0)
|
||||||
|
|
||||||
for lieu in Lieu.objects.filter(id__gte=min_lieu).order_by('-id'):
|
for lieu in Lieu.objects.filter(id__gte=min_lieu).order_by("-id"):
|
||||||
lproches = Lieu.objects.filter(id__lt=lieu.id, coord__distance_lte=(lieu.coord, 5))
|
lproches = Lieu.objects.filter(
|
||||||
|
id__lt=lieu.id, coord__distance_lte=(lieu.coord, 5)
|
||||||
|
)
|
||||||
if len(lproches) == 0:
|
if len(lproches) == 0:
|
||||||
continue
|
continue
|
||||||
print u"Doublons possibles pour %s (id=%d, %d avis) :" % (lieu, lieu.id, lieu.avislieu_set.count())
|
print(
|
||||||
|
"Doublons possibles pour %s (id=%d, %d avis) :"
|
||||||
|
% (lieu, lieu.id, lieu.avislieu_set.count())
|
||||||
|
)
|
||||||
for plieu in lproches:
|
for plieu in lproches:
|
||||||
pprint = u" > %s (id=%d, %d avis)" % (plieu, plieu.id, plieu.avislieu_set.count())
|
pprint = " > %s (id=%d, %d avis)" % (
|
||||||
if plieu.nom == lieu.nom and plieu.ville == lieu.ville and plieu.type_lieu == lieu.type_lieu:
|
plieu,
|
||||||
print u"%s %s" % (pprint, self.style.SUCCESS(u'-> Suppression'))
|
plieu.id,
|
||||||
|
plieu.avislieu_set.count(),
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
plieu.nom == lieu.nom
|
||||||
|
and plieu.ville == lieu.ville
|
||||||
|
and plieu.type_lieu == lieu.type_lieu
|
||||||
|
):
|
||||||
|
print("%s %s" % (pprint, self.style.SUCCESS("-> Suppression")))
|
||||||
if rundb:
|
if rundb:
|
||||||
for avis in plieu.avislieu_set.all():
|
for avis in plieu.avislieu_set.all():
|
||||||
avis.lieu = lieu
|
avis.lieu = lieu
|
||||||
avis.save()
|
avis.save()
|
||||||
plieu.delete()
|
plieu.delete()
|
||||||
else:
|
else:
|
||||||
print u"%s %s" % (pprint, self.style.WARNING(u'-> À supprimer manuellement'))
|
print(
|
||||||
self.stdout.write(self.style.SUCCESS(u'Nettoyage des lieux effectué'))
|
"%s %s"
|
||||||
|
% (pprint, self.style.WARNING("-> À supprimer manuellement"))
|
||||||
|
)
|
||||||
|
self.stdout.write(self.style.SUCCESS("Nettoyage des lieux effectué"))
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
#coding: utf-8
|
from django.core.management.base import BaseCommand
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
from avisstage.models import Stage, Lieu
|
|
||||||
|
from avisstage.models import Stage
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = 'Nettoie les stages à plusieurs lieux identiques'
|
help = "Nettoie les stages à plusieurs lieux identiques"
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
parser.add_argument('min_stage', nargs='?', default=0, type=int)
|
parser.add_argument("min_stage", nargs="?", default=0, type=int)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--apply',
|
"--apply",
|
||||||
action='store_true',
|
action="store_true",
|
||||||
default=False,
|
default=False,
|
||||||
help='Applies the modifications',
|
help="Applies the modifications",
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
|
@ -27,15 +28,16 @@ class Command(BaseCommand):
|
||||||
return length
|
return length
|
||||||
|
|
||||||
rundb = False
|
rundb = False
|
||||||
if options.get('apply', False):
|
if options.get("apply", False):
|
||||||
rundb = True
|
rundb = True
|
||||||
else:
|
else:
|
||||||
print u"Les modifications ne seront pas appliquées"
|
print("Les modifications ne seront pas appliquées")
|
||||||
|
|
||||||
min_stage = options.get('min_stage', 0)
|
min_stage = options.get("min_stage", 0)
|
||||||
|
|
||||||
for stage in Stage.objects.annotate(c=Count("lieux"))\
|
for stage in Stage.objects.annotate(c=Count("lieux")).filter(
|
||||||
.filter(c__gte=2, id__gte=min_stage):
|
c__gte=2, id__gte=min_stage
|
||||||
|
):
|
||||||
lieuset = {}
|
lieuset = {}
|
||||||
todel = []
|
todel = []
|
||||||
problems = []
|
problems = []
|
||||||
|
@ -52,15 +54,19 @@ class Command(BaseCommand):
|
||||||
problems += [(avis, alen), lieuset[aid]]
|
problems += [(avis, alen), lieuset[aid]]
|
||||||
lieuset[aid] = (avis, alen)
|
lieuset[aid] = (avis, alen)
|
||||||
if len(todel) > 0:
|
if len(todel) > 0:
|
||||||
print u"Doublons détectés dans %s" % (stage,)
|
print("Doublons détectés dans %s" % (stage,))
|
||||||
for avis, alen in todel:
|
for avis, alen in todel:
|
||||||
print u" > Suppression de l'avis sur %s de %d mots" % \
|
print(
|
||||||
(avis.lieu, alen)
|
" > Suppression de l'avis sur %s de %d mots" % (avis.lieu, alen)
|
||||||
|
)
|
||||||
if rundb:
|
if rundb:
|
||||||
avis.delete()
|
avis.delete()
|
||||||
if len(problems) > 0:
|
if len(problems) > 0:
|
||||||
self.stdout.write(self.style.WARNING(u"Réparation impossible de %s (id=%d)" % (stage, stage.id)))
|
self.stdout.write(
|
||||||
|
self.style.WARNING(
|
||||||
|
"Réparation impossible de %s (id=%d)" % (stage, stage.id)
|
||||||
|
)
|
||||||
|
)
|
||||||
for avis, alen in problems:
|
for avis, alen in problems:
|
||||||
print u" > Avis sur %s de %d mots" % \
|
print(" > Avis sur %s de %d mots" % (avis.lieu, alen))
|
||||||
(avis.lieu, alen)
|
self.stdout.write(self.style.SUCCESS("Nettoyage des stages effectué"))
|
||||||
self.stdout.write(self.style.SUCCESS(u'Nettoyage des stages effectué'))
|
|
||||||
|
|
|
@ -1,35 +1,41 @@
|
||||||
#coding: utf-8
|
from django.core.management.base import BaseCommand
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
|
||||||
from django.db.models import Count
|
from avisstage.models import Lieu
|
||||||
from avisstage.models import Stage, Lieu
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = 'Nettoie les stages à plusieurs lieux identiques'
|
help = "Nettoie les stages à plusieurs lieux identiques"
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
parser.add_argument('del_lieu', type=int, help='Lieu à supprimer')
|
parser.add_argument("del_lieu", type=int, help="Lieu à supprimer")
|
||||||
parser.add_argument('repl_lieu', type=int, help='Lieu le remplaçant')
|
parser.add_argument("repl_lieu", type=int, help="Lieu le remplaçant")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--apply',
|
"--apply",
|
||||||
action='store_true',
|
action="store_true",
|
||||||
default=False,
|
default=False,
|
||||||
help='Applies the modifications',
|
help="Applies the modifications",
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
rundb = False
|
rundb = False
|
||||||
if options.get('apply', False):
|
if options.get("apply", False):
|
||||||
rundb = True
|
rundb = True
|
||||||
else:
|
else:
|
||||||
print u"Les modifications ne seront pas appliquées"
|
print("Les modifications ne seront pas appliquées")
|
||||||
|
|
||||||
plieu = Lieu.objects.get(id=options['del_lieu'])
|
plieu = Lieu.objects.get(id=options["del_lieu"])
|
||||||
lieu = Lieu.objects.get(id=options['repl_lieu'])
|
lieu = Lieu.objects.get(id=options["repl_lieu"])
|
||||||
print u"Suppression de %s (id=%d, %d avis)" % (plieu, plieu.id, plieu.avislieu_set.count())
|
print(
|
||||||
print u"Remplacement par %s (id=%d, %d avis)" % (lieu, lieu.id, lieu.avislieu_set.count())
|
"Suppression de %s (id=%d, %d avis)"
|
||||||
|
% (plieu, plieu.id, plieu.avislieu_set.count())
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"Remplacement par %s (id=%d, %d avis)"
|
||||||
|
% (lieu, lieu.id, lieu.avislieu_set.count())
|
||||||
|
)
|
||||||
if rundb:
|
if rundb:
|
||||||
for avis in plieu.avislieu_set.all():
|
for avis in plieu.avislieu_set.all():
|
||||||
avis.lieu = lieu
|
avis.lieu = lieu
|
||||||
avis.save()
|
avis.save()
|
||||||
plieu.delete()
|
plieu.delete()
|
||||||
self.stdout.write(self.style.SUCCESS(u'Terminé'))
|
self.stdout.write(self.style.SUCCESS("Terminé"))
|
||||||
|
|
18
avisstage/management/commands/termine_scolarite.py
Normal file
18
avisstage/management/commands/termine_scolarite.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from avisstage.models import Normalien
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Réinitialise les statuts "en scolarité" de tout le monde'
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
return
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
t = timezone.now() - timedelta(days=365)
|
||||||
|
Normalien.objects.all().update(last_cas_connect=t)
|
||||||
|
self.stdout.write(self.style.SUCCESS("Terminé"))
|
File diff suppressed because one or more lines are too long
|
@ -2,30 +2,37 @@
|
||||||
# Generated by Django 1.11.2 on 2017-10-02 20:43
|
# Generated by Django 1.11.2 on 2017-10-02 20:43
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import tinymce.models
|
import tinymce.models
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('avisstage', '0001_initial'),
|
("avisstage", "0001_initial"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='avisstage',
|
model_name="avisstage",
|
||||||
name='avis_prestage',
|
name="avis_prestage",
|
||||||
field=tinymce.models.HTMLField(blank=True, default='', verbose_name='Avant le stage'),
|
field=tinymce.models.HTMLField(
|
||||||
|
blank=True, default="", verbose_name="Avant le stage"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='stage',
|
model_name="stage",
|
||||||
name='len_avis_lieux',
|
name="len_avis_lieux",
|
||||||
field=models.IntegerField(default=0, verbose_name='Longueur des avis de lieu'),
|
field=models.IntegerField(
|
||||||
|
default=0, verbose_name="Longueur des avis de lieu"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='stage',
|
model_name="stage",
|
||||||
name='len_avis_stage',
|
name="len_avis_stage",
|
||||||
field=models.IntegerField(default=0, verbose_name='Longueur des avis de stage'),
|
field=models.IntegerField(
|
||||||
|
default=0, verbose_name="Longueur des avis de stage"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
357
avisstage/migrations/0003_auto_20210117_1208.py
Normal file
357
avisstage/migrations/0003_auto_20210117_1208.py
Normal file
|
@ -0,0 +1,357 @@
|
||||||
|
# Generated by Django 2.2.17 on 2021-01-17 11:08
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("avisstage", "0002_auto_20171002_2243"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="lieu",
|
||||||
|
name="pays",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("AF", "Afghanistan"),
|
||||||
|
("AL", "Albanie"),
|
||||||
|
("AQ", "Antarctique"),
|
||||||
|
("DZ", "Algérie"),
|
||||||
|
("AS", "Samoa Américaines"),
|
||||||
|
("AD", "Andorre"),
|
||||||
|
("AO", "Angola"),
|
||||||
|
("AG", "Antigua-et-Barbuda"),
|
||||||
|
("AZ", "Azerbaïdjan"),
|
||||||
|
("AR", "Argentine"),
|
||||||
|
("AU", "Australie"),
|
||||||
|
("AT", "Autriche"),
|
||||||
|
("BS", "Bahamas"),
|
||||||
|
("BH", "Bahreïn"),
|
||||||
|
("BD", "Bangladesh"),
|
||||||
|
("AM", "Arménie"),
|
||||||
|
("BB", "Barbade"),
|
||||||
|
("BE", "Belgique"),
|
||||||
|
("BM", "Bermudes"),
|
||||||
|
("BT", "Bhoutan"),
|
||||||
|
("BO", "Bolivie"),
|
||||||
|
("BA", "Bosnie-Herzégovine"),
|
||||||
|
("BW", "Botswana"),
|
||||||
|
("BV", "Île Bouvet"),
|
||||||
|
("BR", "Brésil"),
|
||||||
|
("BZ", "Belize"),
|
||||||
|
("IO", "Territoire Britannique de l'Océan Indien"),
|
||||||
|
("SB", "Îles Salomon"),
|
||||||
|
("VG", "Îles Vierges Britanniques"),
|
||||||
|
("BN", "Brunéi Darussalam"),
|
||||||
|
("BG", "Bulgarie"),
|
||||||
|
("MM", "Myanmar"),
|
||||||
|
("BI", "Burundi"),
|
||||||
|
("BY", "Bélarus"),
|
||||||
|
("KH", "Cambodge"),
|
||||||
|
("CM", "Cameroun"),
|
||||||
|
("CA", "Canada"),
|
||||||
|
("CV", "Cap-vert"),
|
||||||
|
("KY", "Îles Caïmanes"),
|
||||||
|
("CF", "République Centrafricaine"),
|
||||||
|
("LK", "Sri Lanka"),
|
||||||
|
("TD", "Tchad"),
|
||||||
|
("CL", "Chili"),
|
||||||
|
("CN", "Chine"),
|
||||||
|
("TW", "Taïwan"),
|
||||||
|
("CX", "Île Christmas"),
|
||||||
|
("CC", "Îles Cocos (Keeling)"),
|
||||||
|
("CO", "Colombie"),
|
||||||
|
("KM", "Comores"),
|
||||||
|
("YT", "Mayotte"),
|
||||||
|
("CG", "République du Congo"),
|
||||||
|
("CD", "République Démocratique du Congo"),
|
||||||
|
("CK", "Îles Cook"),
|
||||||
|
("CR", "Costa Rica"),
|
||||||
|
("HR", "Croatie"),
|
||||||
|
("CU", "Cuba"),
|
||||||
|
("CY", "Chypre"),
|
||||||
|
("CZ", "République Tchèque"),
|
||||||
|
("BJ", "Bénin"),
|
||||||
|
("DK", "Danemark"),
|
||||||
|
("DM", "Dominique"),
|
||||||
|
("DO", "République Dominicaine"),
|
||||||
|
("EC", "Équateur"),
|
||||||
|
("SV", "El Salvador"),
|
||||||
|
("GQ", "Guinée Équatoriale"),
|
||||||
|
("ET", "Éthiopie"),
|
||||||
|
("ER", "Érythrée"),
|
||||||
|
("EE", "Estonie"),
|
||||||
|
("FO", "Îles Féroé"),
|
||||||
|
("FK", "Îles (malvinas) Falkland"),
|
||||||
|
("GS", "Géorgie du Sud et les Îles Sandwich du Sud"),
|
||||||
|
("FJ", "Fidji"),
|
||||||
|
("FI", "Finlande"),
|
||||||
|
("AX", "Îles Åland"),
|
||||||
|
("FR", "France"),
|
||||||
|
("GF", "Guyane Française"),
|
||||||
|
("PF", "Polynésie Française"),
|
||||||
|
("TF", "Terres Australes Françaises"),
|
||||||
|
("DJ", "Djibouti"),
|
||||||
|
("GA", "Gabon"),
|
||||||
|
("GE", "Géorgie"),
|
||||||
|
("GM", "Gambie"),
|
||||||
|
("PS", "Territoire Palestinien Occupé"),
|
||||||
|
("DE", "Allemagne"),
|
||||||
|
("GH", "Ghana"),
|
||||||
|
("GI", "Gibraltar"),
|
||||||
|
("KI", "Kiribati"),
|
||||||
|
("GR", "Grèce"),
|
||||||
|
("GL", "Groenland"),
|
||||||
|
("GD", "Grenade"),
|
||||||
|
("GP", "Guadeloupe"),
|
||||||
|
("GU", "Guam"),
|
||||||
|
("GT", "Guatemala"),
|
||||||
|
("GN", "Guinée"),
|
||||||
|
("GY", "Guyana"),
|
||||||
|
("HT", "Haïti"),
|
||||||
|
("HM", "Îles Heard et Mcdonald"),
|
||||||
|
("VA", "Saint-Siège (état de la Cité du Vatican)"),
|
||||||
|
("HN", "Honduras"),
|
||||||
|
("HK", "Hong-Kong"),
|
||||||
|
("HU", "Hongrie"),
|
||||||
|
("IS", "Islande"),
|
||||||
|
("IN", "Inde"),
|
||||||
|
("ID", "Indonésie"),
|
||||||
|
("IR", "République Islamique d'Iran"),
|
||||||
|
("IQ", "Iraq"),
|
||||||
|
("IE", "Irlande"),
|
||||||
|
("IL", "Israël"),
|
||||||
|
("IT", "Italie"),
|
||||||
|
("CI", "Côte d'Ivoire"),
|
||||||
|
("JM", "Jamaïque"),
|
||||||
|
("JP", "Japon"),
|
||||||
|
("KZ", "Kazakhstan"),
|
||||||
|
("JO", "Jordanie"),
|
||||||
|
("KE", "Kenya"),
|
||||||
|
("KP", "République Populaire Démocratique de Corée"),
|
||||||
|
("KR", "République de Corée"),
|
||||||
|
("KW", "Koweït"),
|
||||||
|
("KG", "Kirghizistan"),
|
||||||
|
("LA", "République Démocratique Populaire Lao"),
|
||||||
|
("LB", "Liban"),
|
||||||
|
("LS", "Lesotho"),
|
||||||
|
("LV", "Lettonie"),
|
||||||
|
("LR", "Libéria"),
|
||||||
|
("LY", "Jamahiriya Arabe Libyenne"),
|
||||||
|
("LI", "Liechtenstein"),
|
||||||
|
("LT", "Lituanie"),
|
||||||
|
("LU", "Luxembourg"),
|
||||||
|
("MO", "Macao"),
|
||||||
|
("MG", "Madagascar"),
|
||||||
|
("MW", "Malawi"),
|
||||||
|
("MY", "Malaisie"),
|
||||||
|
("MV", "Maldives"),
|
||||||
|
("ML", "Mali"),
|
||||||
|
("MT", "Malte"),
|
||||||
|
("MQ", "Martinique"),
|
||||||
|
("MR", "Mauritanie"),
|
||||||
|
("MU", "Maurice"),
|
||||||
|
("MX", "Mexique"),
|
||||||
|
("MC", "Monaco"),
|
||||||
|
("MN", "Mongolie"),
|
||||||
|
("MD", "République de Moldova"),
|
||||||
|
("MS", "Montserrat"),
|
||||||
|
("MA", "Maroc"),
|
||||||
|
("MZ", "Mozambique"),
|
||||||
|
("OM", "Oman"),
|
||||||
|
("NA", "Namibie"),
|
||||||
|
("NR", "Nauru"),
|
||||||
|
("NP", "Népal"),
|
||||||
|
("NL", "Pays-Bas"),
|
||||||
|
("AN", "Antilles Néerlandaises"),
|
||||||
|
("AW", "Aruba"),
|
||||||
|
("NC", "Nouvelle-Calédonie"),
|
||||||
|
("VU", "Vanuatu"),
|
||||||
|
("NZ", "Nouvelle-Zélande"),
|
||||||
|
("NI", "Nicaragua"),
|
||||||
|
("NE", "Niger"),
|
||||||
|
("NG", "Nigéria"),
|
||||||
|
("NU", "Niué"),
|
||||||
|
("NF", "Île Norfolk"),
|
||||||
|
("NO", "Norvège"),
|
||||||
|
("MP", "Îles Mariannes du Nord"),
|
||||||
|
("UM", "Îles Mineures Éloignées des États-Unis"),
|
||||||
|
("FM", "États Fédérés de Micronésie"),
|
||||||
|
("MH", "Îles Marshall"),
|
||||||
|
("PW", "Palaos"),
|
||||||
|
("PK", "Pakistan"),
|
||||||
|
("PA", "Panama"),
|
||||||
|
("PG", "Papouasie-Nouvelle-Guinée"),
|
||||||
|
("PY", "Paraguay"),
|
||||||
|
("PE", "Pérou"),
|
||||||
|
("PH", "Philippines"),
|
||||||
|
("PN", "Pitcairn"),
|
||||||
|
("PL", "Pologne"),
|
||||||
|
("PT", "Portugal"),
|
||||||
|
("GW", "Guinée-Bissau"),
|
||||||
|
("TL", "Timor-Leste"),
|
||||||
|
("PR", "Porto Rico"),
|
||||||
|
("QA", "Qatar"),
|
||||||
|
("RE", "Réunion"),
|
||||||
|
("RO", "Roumanie"),
|
||||||
|
("RU", "Fédération de Russie"),
|
||||||
|
("RW", "Rwanda"),
|
||||||
|
("SH", "Sainte-Hélène"),
|
||||||
|
("KN", "Saint-Kitts-et-Nevis"),
|
||||||
|
("AI", "Anguilla"),
|
||||||
|
("LC", "Sainte-Lucie"),
|
||||||
|
("PM", "Saint-Pierre-et-Miquelon"),
|
||||||
|
("VC", "Saint-Vincent-et-les Grenadines"),
|
||||||
|
("SM", "Saint-Marin"),
|
||||||
|
("ST", "Sao Tomé-et-Principe"),
|
||||||
|
("SA", "Arabie Saoudite"),
|
||||||
|
("SN", "Sénégal"),
|
||||||
|
("SC", "Seychelles"),
|
||||||
|
("SL", "Sierra Leone"),
|
||||||
|
("SG", "Singapour"),
|
||||||
|
("SK", "Slovaquie"),
|
||||||
|
("VN", "Viet Nam"),
|
||||||
|
("SI", "Slovénie"),
|
||||||
|
("SO", "Somalie"),
|
||||||
|
("ZA", "Afrique du Sud"),
|
||||||
|
("ZW", "Zimbabwe"),
|
||||||
|
("ES", "Espagne"),
|
||||||
|
("EH", "Sahara Occidental"),
|
||||||
|
("SD", "Soudan"),
|
||||||
|
("SR", "Suriname"),
|
||||||
|
("SJ", "Svalbard etÎle Jan Mayen"),
|
||||||
|
("SZ", "Swaziland"),
|
||||||
|
("SE", "Suède"),
|
||||||
|
("CH", "Suisse"),
|
||||||
|
("SY", "République Arabe Syrienne"),
|
||||||
|
("TJ", "Tadjikistan"),
|
||||||
|
("TH", "Thaïlande"),
|
||||||
|
("TG", "Togo"),
|
||||||
|
("TK", "Tokelau"),
|
||||||
|
("TO", "Tonga"),
|
||||||
|
("TT", "Trinité-et-Tobago"),
|
||||||
|
("AE", "Émirats Arabes Unis"),
|
||||||
|
("TN", "Tunisie"),
|
||||||
|
("TR", "Turquie"),
|
||||||
|
("TM", "Turkménistan"),
|
||||||
|
("TC", "Îles Turks et Caïques"),
|
||||||
|
("TV", "Tuvalu"),
|
||||||
|
("UG", "Ouganda"),
|
||||||
|
("UA", "Ukraine"),
|
||||||
|
("MK", "L'ex-République Yougoslave de Macédoine"),
|
||||||
|
("EG", "Égypte"),
|
||||||
|
("GB", "Royaume-Uni"),
|
||||||
|
("IM", "Île de Man"),
|
||||||
|
("TZ", "République-Unie de Tanzanie"),
|
||||||
|
("US", "États-Unis"),
|
||||||
|
("VI", "Îles Vierges des États-Unis"),
|
||||||
|
("BF", "Burkina Faso"),
|
||||||
|
("UY", "Uruguay"),
|
||||||
|
("UZ", "Ouzbékistan"),
|
||||||
|
("VE", "Venezuela"),
|
||||||
|
("WF", "Wallis et Futuna"),
|
||||||
|
("WS", "Samoa"),
|
||||||
|
("YE", "Yémen"),
|
||||||
|
("CS", "Serbie-et-Monténégro"),
|
||||||
|
("ZM", "Zambie"),
|
||||||
|
],
|
||||||
|
max_length=2,
|
||||||
|
verbose_name="Pays",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="lieu",
|
||||||
|
name="type_lieu",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("universite", "Université"),
|
||||||
|
("entreprise", "Entreprise"),
|
||||||
|
("centrerecherche", "Centre de recherche"),
|
||||||
|
("administration", "Administration"),
|
||||||
|
("autre", "Autre"),
|
||||||
|
],
|
||||||
|
default="universite",
|
||||||
|
max_length=15,
|
||||||
|
verbose_name="Type de structure d'accueil",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="normalien",
|
||||||
|
name="user",
|
||||||
|
field=models.OneToOneField(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="profil",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="stage",
|
||||||
|
name="auteur",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="stages",
|
||||||
|
to="avisstage.Normalien",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="stage",
|
||||||
|
name="niveau_scol",
|
||||||
|
field=models.CharField(
|
||||||
|
blank=True,
|
||||||
|
choices=[
|
||||||
|
("L3", "Licence 3"),
|
||||||
|
("M1", "Master 1"),
|
||||||
|
("M2", "Master 2"),
|
||||||
|
("DOC", "Pré-doctorat"),
|
||||||
|
("CST", "Césure"),
|
||||||
|
("BLA", "Année blanche"),
|
||||||
|
("VAC", "Vacances scolaires"),
|
||||||
|
("MIT", "Mi-temps en parallèle des études"),
|
||||||
|
("", "Autre"),
|
||||||
|
],
|
||||||
|
default="",
|
||||||
|
max_length=3,
|
||||||
|
verbose_name="Année de scolarité",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="stage",
|
||||||
|
name="type_stage",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
(
|
||||||
|
"Recherche :",
|
||||||
|
(
|
||||||
|
("recherche", "Stage académique"),
|
||||||
|
("recherche_autre", "Stage non-académique"),
|
||||||
|
("sejour_dri", "Séjour de recherche DRI"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Stage sans visée de recherche :",
|
||||||
|
(
|
||||||
|
("pro", "Stage en entreprise"),
|
||||||
|
("admin", "Stage en admin./ONG/orga. internationale"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Enseignement :",
|
||||||
|
(
|
||||||
|
("lectorat", "Lectorat DRI"),
|
||||||
|
("autre_teach", "Autre expérience d'enseignement"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("autre", "Autre"),
|
||||||
|
],
|
||||||
|
default="stage",
|
||||||
|
max_length=31,
|
||||||
|
verbose_name="Type",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
107
avisstage/migrations/0004_allauth_to_authens.py
Normal file
107
avisstage/migrations/0004_allauth_to_authens.py
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
from django.apps import apps as global_apps
|
||||||
|
from django.db import migrations
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
|
||||||
|
def forwards(apps, schema_editor):
|
||||||
|
User = apps.get_model("auth", "User")
|
||||||
|
|
||||||
|
try:
|
||||||
|
CASAccount = apps.get_model("authens", "CASAccount")
|
||||||
|
except LookupError:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
SocialAccount = apps.get_model("socialaccount", "SocialAccount")
|
||||||
|
OldEmailAddress = apps.get_model("account", "EmailAddress")
|
||||||
|
except LookupError:
|
||||||
|
# Allauth not installed
|
||||||
|
# Simply create CAS accounts for every profile
|
||||||
|
# This procedure is not meant to be fast
|
||||||
|
from authens.shortcuts import fetch_cas_account
|
||||||
|
|
||||||
|
def migrate_user(user):
|
||||||
|
ldap_info = fetch_cas_account(user.username)
|
||||||
|
if ldap_info:
|
||||||
|
entrance_year = ldap_info["entrance_year"]
|
||||||
|
CASAccount.objects.create(
|
||||||
|
user=user, cas_login=user.username, entrance_year=entrance_year
|
||||||
|
)
|
||||||
|
|
||||||
|
for user in User.objects.all():
|
||||||
|
migrate_user(user)
|
||||||
|
return
|
||||||
|
|
||||||
|
NewEmailAddress = apps.get_model("simple_email_confirmation", "EmailAddress")
|
||||||
|
from simple_email_confirmation.models import EmailAddressManager
|
||||||
|
|
||||||
|
# Transfer from allauth to authens
|
||||||
|
# Assumes usernames have the format <clipper>@<promo>
|
||||||
|
# Assumes no clashing clipper accounts have ever been found
|
||||||
|
oldusers = User.objects.all().prefetch_related(
|
||||||
|
"emailaddress_set", "socialaccount_set"
|
||||||
|
)
|
||||||
|
|
||||||
|
is_ens_mail = lambda mail: (
|
||||||
|
mail is not None and (mail.endswith("ens.fr") or mail.endswith("ens.psl.eu"))
|
||||||
|
)
|
||||||
|
new_conns = []
|
||||||
|
new_mails = []
|
||||||
|
|
||||||
|
for user in oldusers:
|
||||||
|
# Move EmailAddress to new model
|
||||||
|
addresses = user.emailaddress_set.all()
|
||||||
|
for addr in addresses:
|
||||||
|
newaddr = NewEmailAddress(
|
||||||
|
user=user,
|
||||||
|
email=addr.email,
|
||||||
|
set_at=timezone.now(),
|
||||||
|
confirmed_at=(timezone.now() if addr.verified else None),
|
||||||
|
key=EmailAddressManager().generate_key(),
|
||||||
|
)
|
||||||
|
if addr.primary and user.email != addr.email:
|
||||||
|
print("Adresse principale inconsistante", user.email, addr.email)
|
||||||
|
new_mails.append(newaddr)
|
||||||
|
|
||||||
|
# Create new CASAccount connexion
|
||||||
|
saccounts = user.socialaccount_set.all()
|
||||||
|
if not saccounts:
|
||||||
|
continue
|
||||||
|
if len(saccounts) > 1:
|
||||||
|
print(saccounts)
|
||||||
|
saccount = saccounts[0]
|
||||||
|
clipper = saccount.uid
|
||||||
|
if "@" not in user.username:
|
||||||
|
print(user.username)
|
||||||
|
continue
|
||||||
|
entrance_year = saccount.extra_data.get(
|
||||||
|
"entrance_year", user.username.split("@")[1]
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
entrance_year = 2000 + int(entrance_year)
|
||||||
|
except ValueError:
|
||||||
|
print(entrance_year)
|
||||||
|
continue
|
||||||
|
|
||||||
|
new_conns.append(
|
||||||
|
CASAccount(user=user, cas_login=clipper, entrance_year=int(entrance_year))
|
||||||
|
)
|
||||||
|
|
||||||
|
NewEmailAddress.objects.bulk_create(new_mails)
|
||||||
|
CASAccount.objects.bulk_create(new_conns)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(forwards, migrations.RunPython.noop),
|
||||||
|
]
|
||||||
|
dependencies = [
|
||||||
|
("avisstage", "0003_auto_20210117_1208"),
|
||||||
|
("authens", "0002_old_cas_account"),
|
||||||
|
]
|
||||||
|
|
||||||
|
if global_apps.is_installed("allauth"):
|
||||||
|
dependencies.append(("socialaccount", "0003_extra_data_default_dict"))
|
||||||
|
|
||||||
|
if global_apps.is_installed("simple_email_confirmation"):
|
||||||
|
dependencies.append(("simple_email_confirmation", "0001_initial"))
|
18
avisstage/migrations/0005_normalien_en_scolarite.py
Normal file
18
avisstage/migrations/0005_normalien_en_scolarite.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.17 on 2021-01-17 20:46
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("avisstage", "0004_allauth_to_authens"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="normalien",
|
||||||
|
name="en_scolarite",
|
||||||
|
field=models.BooleanField(blank=True, default=False),
|
||||||
|
),
|
||||||
|
]
|
37
avisstage/migrations/0006_auto_20210131_1954.py
Normal file
37
avisstage/migrations/0006_auto_20210131_1954.py
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
# Generated by Django 2.2.17 on 2021-01-31 18:54
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import avisstage.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("avisstage", "0005_normalien_en_scolarite"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="normalien",
|
||||||
|
name="en_scolarite",
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="normalien",
|
||||||
|
name="mail",
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="normalien",
|
||||||
|
name="last_cas_login",
|
||||||
|
field=models.DateField(default=avisstage.models._default_cas_login),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="normalien",
|
||||||
|
name="contactez_moi",
|
||||||
|
field=models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
help_text="Affiche votre adresse e-mail principale sur votre profil public",
|
||||||
|
verbose_name="Inviter les visiteurs à me contacter",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,126 +1,154 @@
|
||||||
# coding: utf-8
|
from datetime import timedelta
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
from django.db.models.signals import post_save
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.contrib.gis.db import models as geomodels
|
|
||||||
from django.template.defaultfilters import slugify
|
|
||||||
from django.forms.widgets import DateInput
|
|
||||||
from django.urls import reverse
|
|
||||||
from django.utils import timezone
|
|
||||||
from django.utils.html import strip_tags
|
|
||||||
|
|
||||||
|
from authens.signals import post_cas_connect
|
||||||
from taggit_autosuggest.managers import TaggableManager
|
from taggit_autosuggest.managers import TaggableManager
|
||||||
from tinymce.models import HTMLField as RichTextField
|
from tinymce.models import HTMLField as RichTextField
|
||||||
|
|
||||||
from .utils import choices_length
|
from django.contrib.auth.models import User
|
||||||
from .statics import DEPARTEMENTS_DEFAUT, PAYS_OPTIONS, TYPE_LIEU_OPTIONS, TYPE_STAGE_OPTIONS, TYPE_LIEU_DICT, TYPE_STAGE_DICT, NIVEAU_SCOL_OPTIONS, NIVEAU_SCOL_DICT
|
from django.contrib.gis.db import models as geomodels
|
||||||
|
from django.db import models
|
||||||
|
from django.db.models.signals import post_save
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.utils.functional import cached_property
|
||||||
|
|
||||||
|
from .statics import (
|
||||||
|
DEPARTEMENTS_DEFAUT,
|
||||||
|
NIVEAU_SCOL_DICT,
|
||||||
|
NIVEAU_SCOL_OPTIONS,
|
||||||
|
PAYS_OPTIONS,
|
||||||
|
TYPE_LIEU_DICT,
|
||||||
|
TYPE_LIEU_OPTIONS,
|
||||||
|
TYPE_STAGE_DICT,
|
||||||
|
TYPE_STAGE_OPTIONS,
|
||||||
|
)
|
||||||
|
from .utils import choices_length
|
||||||
|
|
||||||
|
|
||||||
|
def _default_cas_login():
|
||||||
|
return (timezone.now() - timedelta(days=365)).date()
|
||||||
|
|
||||||
import ldap
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Profil Normalien (extension du modèle User)
|
# Profil Normalien (extension du modèle User)
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
class Normalien(models.Model):
|
class Normalien(models.Model):
|
||||||
user = models.OneToOneField(User, related_name="profil")
|
user = models.OneToOneField(
|
||||||
|
User, related_name="profil", on_delete=models.SET_NULL, null=True
|
||||||
|
)
|
||||||
|
|
||||||
# Infos spécifiques
|
# Infos spécifiques
|
||||||
nom = models.CharField(u"Nom complet", max_length=255, blank=True)
|
nom = models.CharField("Nom complet", max_length=255, blank=True)
|
||||||
promotion = models.CharField(u"Promotion", max_length=40, blank=True)
|
promotion = models.CharField("Promotion", max_length=40, blank=True)
|
||||||
mail = models.EmailField(u"Adresse e-mail permanente",
|
contactez_moi = models.BooleanField(
|
||||||
max_length=200, blank=True)
|
"Inviter les visiteurs à me contacter",
|
||||||
contactez_moi = models.BooleanField(u"Inviter les visiteurs à me contacter",
|
default=True,
|
||||||
default=True)
|
help_text="Affiche votre adresse e-mail principale sur votre profil public",
|
||||||
bio = models.TextField(u"À propos de moi", blank=True, default="");
|
)
|
||||||
|
bio = models.TextField("À propos de moi", blank=True, default="")
|
||||||
|
last_cas_login = models.DateField(default=_default_cas_login)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = u"Profil élève"
|
verbose_name = "Profil élève"
|
||||||
verbose_name_plural = u"Profils élèves"
|
verbose_name_plural = "Profils élèves"
|
||||||
|
|
||||||
def __unicode__(self):
|
def __str__(self):
|
||||||
return u"%s (%s)" % (self.nom, self.user.username)
|
return "%s (%s)" % (self.nom, self.user.username)
|
||||||
|
|
||||||
# Liste des stages publiés
|
# Liste des stages publiés
|
||||||
def stages_publics(self):
|
def stages_publics(self):
|
||||||
return self.stages.filter(public=True).order_by('-date_debut')
|
return self.stages.filter(public=True).order_by("-date_debut")
|
||||||
|
|
||||||
# Hook à la création d'un nouvel utilisateur : récupération de ses infos par LDAP
|
def has_nonENS_email(self):
|
||||||
def create_user_profile(sender, instance, created, **kwargs):
|
return (
|
||||||
|
self.user.email_address_set.exclude(confirmed_at__isnull=True)
|
||||||
|
.exclude(email__endswith="ens.fr")
|
||||||
|
.exclude(email__endswith="ens.psl.eu")
|
||||||
|
.exists()
|
||||||
|
)
|
||||||
|
|
||||||
|
def nom_complet(self):
|
||||||
|
if self.nom.strip():
|
||||||
|
return self.nom
|
||||||
|
return self.user.username
|
||||||
|
|
||||||
|
@property
|
||||||
|
def en_scolarite(self):
|
||||||
|
return self.last_cas_login > (timezone.now() - timedelta(days=60)).date()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def preferred_email(self):
|
||||||
|
return self.user.email
|
||||||
|
|
||||||
|
|
||||||
|
# Hook à la création d'un nouvel utilisateur : information de base
|
||||||
|
def create_basic_user_profile(sender, instance, created, **kwargs):
|
||||||
if created:
|
if created:
|
||||||
profil, created = Normalien.objects.get_or_create(user=instance)
|
profil, created = Normalien.objects.get_or_create(user=instance)
|
||||||
try:
|
|
||||||
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
|
|
||||||
l = ldap.initialize("ldaps://ldap.spi.ens.fr:636")
|
|
||||||
l.set_option(ldap.OPT_REFERRALS, 0)
|
|
||||||
l.set_option(ldap.OPT_PROTOCOL_VERSION, 3)
|
|
||||||
l.set_option(ldap.OPT_X_TLS,ldap.OPT_X_TLS_DEMAND)
|
|
||||||
l.set_option(ldap.OPT_X_TLS_DEMAND, True)
|
|
||||||
l.set_option(ldap.OPT_DEBUG_LEVEL, 255)
|
|
||||||
l.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
|
|
||||||
l.set_option(ldap.OPT_TIMEOUT, 10)
|
|
||||||
|
|
||||||
info = l.search_s('dc=spi,dc=ens,dc=fr',
|
if not created and profil.promotion != "":
|
||||||
ldap.SCOPE_SUBTREE,
|
return
|
||||||
('(uid=%s)' % (instance.username,)),
|
|
||||||
[str("cn"),
|
|
||||||
str("mailRoutingAddress"),
|
|
||||||
str("homeDirectory")])
|
|
||||||
|
|
||||||
# Si des informations sont disponibles
|
|
||||||
if len(info) > 0:
|
|
||||||
infos = info[0][1]
|
|
||||||
|
|
||||||
# Nom
|
|
||||||
profil.nom = infos.get('cn', [''])[0]
|
|
||||||
|
|
||||||
# Parsing du homeDirectory pour la promotion
|
|
||||||
if 'homeDirectory' in infos:
|
|
||||||
dirs = infos['homeDirectory'][0].split('/')
|
|
||||||
if dirs[1] == 'users':
|
|
||||||
annee = dirs[2]
|
|
||||||
dep = dirs[3]
|
|
||||||
dep = dict(DEPARTEMENTS_DEFAUT).get(dep.lower(), '')
|
|
||||||
profil.promotion = u'%s %s' % (dep, annee)
|
|
||||||
|
|
||||||
# Mail
|
|
||||||
pmail = infos.get('mailRoutingAddress',
|
|
||||||
['%s@clipper.ens.fr'%instance.username])
|
|
||||||
if len(pmail) > 0:
|
|
||||||
profil.mail = pmail[0]
|
|
||||||
|
|
||||||
|
if "@" in instance.username:
|
||||||
|
profil.promotion = instance.username.split("@")[1]
|
||||||
profil.save()
|
profil.save()
|
||||||
except ldap.LDAPError:
|
|
||||||
pass
|
|
||||||
post_save.connect(create_user_profile, sender=User)
|
post_save.connect(create_basic_user_profile, sender=User)
|
||||||
|
|
||||||
|
|
||||||
|
# Hook d'authENS : information du CAS
|
||||||
|
def handle_cas_connection(sender, instance, created, cas_login, attributes, **kwargs):
|
||||||
|
profil, created = Normalien.objects.get_or_create(user=instance)
|
||||||
|
|
||||||
|
profil.last_cas_login = timezone.now().date()
|
||||||
|
if not created:
|
||||||
|
profil.save()
|
||||||
|
return
|
||||||
|
|
||||||
|
dirs = attributes.get("homeDirectory", "").split("/")
|
||||||
|
if len(dirs) < 4:
|
||||||
|
print("HomeDirectory invalide", dirs)
|
||||||
|
return
|
||||||
|
|
||||||
|
year = dirs[2]
|
||||||
|
departement = dirs[3]
|
||||||
|
|
||||||
|
dep = dict(DEPARTEMENTS_DEFAUT).get(departement.lower(), "")
|
||||||
|
|
||||||
|
profil.promotion = "%s %s" % (dep, year)
|
||||||
|
profil.nom = attributes.get("name", "")
|
||||||
|
profil.save()
|
||||||
|
|
||||||
|
|
||||||
|
post_cas_connect.connect(handle_cas_connection, sender=User)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Lieu de stage
|
# Lieu de stage
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
class Lieu(models.Model):
|
class Lieu(models.Model):
|
||||||
# Général
|
# Général
|
||||||
nom = models.CharField(u"Nom de l'institution d'accueil",
|
nom = models.CharField("Nom de l'institution d'accueil", max_length=250)
|
||||||
max_length=250)
|
type_lieu = models.CharField(
|
||||||
type_lieu = models.CharField(u"Type de structure d'accueil",
|
"Type de structure d'accueil",
|
||||||
default="universite",
|
default="universite",
|
||||||
choices=TYPE_LIEU_OPTIONS,
|
choices=TYPE_LIEU_OPTIONS,
|
||||||
max_length=choices_length(TYPE_LIEU_OPTIONS))
|
max_length=choices_length(TYPE_LIEU_OPTIONS),
|
||||||
|
)
|
||||||
|
|
||||||
# Infos géographiques
|
# Infos géographiques
|
||||||
ville = models.CharField(u"Ville",
|
ville = models.CharField("Ville", max_length=200)
|
||||||
max_length=200)
|
pays = models.CharField(
|
||||||
pays = models.CharField(u"Pays",
|
"Pays", choices=PAYS_OPTIONS, max_length=choices_length(PAYS_OPTIONS)
|
||||||
choices=PAYS_OPTIONS,
|
)
|
||||||
max_length=choices_length(PAYS_OPTIONS))
|
|
||||||
|
|
||||||
# Coordonnées
|
# Coordonnées
|
||||||
objects = geomodels.GeoManager() # Requis par GeoDjango
|
# objects = geomodels.GeoManager() # Requis par GeoDjango
|
||||||
coord = geomodels.PointField(u"Coordonnées",
|
coord = geomodels.PointField("Coordonnées", geography=True, srid=4326)
|
||||||
geography=True,
|
|
||||||
srid = 4326)
|
|
||||||
|
|
||||||
# Type du lieu en plus joli
|
# Type du lieu en plus joli
|
||||||
@property
|
@property
|
||||||
|
@ -131,70 +159,83 @@ class Lieu(models.Model):
|
||||||
def type_lieu_fem(self):
|
def type_lieu_fem(self):
|
||||||
return TYPE_LIEU_DICT.get(self.type_lieu, ("lieu", False))[1]
|
return TYPE_LIEU_DICT.get(self.type_lieu, ("lieu", False))[1]
|
||||||
|
|
||||||
def __unicode__(self):
|
def __str__(self):
|
||||||
return u"%s (%s)" % (self.nom, self.ville)
|
return "%s (%s)" % (self.nom, self.ville)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Lieu"
|
verbose_name = "Lieu"
|
||||||
verbose_name_plural = "Lieux"
|
verbose_name_plural = "Lieux"
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Matières des stages
|
# Matières des stages
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
class StageMatiere(models.Model):
|
class StageMatiere(models.Model):
|
||||||
nom = models.CharField(u"Nom", max_length=30)
|
nom = models.CharField("Nom", max_length=30)
|
||||||
slug = models.SlugField()
|
slug = models.SlugField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Matière des stages"
|
verbose_name = "Matière des stages"
|
||||||
verbose_name_plural = "Matières des stages"
|
verbose_name_plural = "Matières des stages"
|
||||||
|
|
||||||
def __unicode__(self):
|
def __str__(self):
|
||||||
return self.nom
|
return self.nom
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Un stage
|
# Un stage
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
class Stage(models.Model):
|
class Stage(models.Model):
|
||||||
# Misc
|
# Misc
|
||||||
auteur = models.ForeignKey(Normalien, related_name="stages")
|
auteur = models.ForeignKey(
|
||||||
public = models.BooleanField(u"Visible publiquement", default=False)
|
Normalien, related_name="stages", on_delete=models.SET_NULL, null=True
|
||||||
date_creation = models.DateTimeField(u"Créé le", default=timezone.now)
|
)
|
||||||
date_maj = models.DateTimeField(u"Mis à jour le", default=timezone.now)
|
public = models.BooleanField("Visible publiquement", default=False)
|
||||||
len_avis_stage = models.IntegerField(u"Longueur des avis de stage", default=0)
|
date_creation = models.DateTimeField("Créé le", default=timezone.now)
|
||||||
len_avis_lieux = models.IntegerField(u"Longueur des avis de lieu", default=0)
|
date_maj = models.DateTimeField("Mis à jour le", default=timezone.now)
|
||||||
|
len_avis_stage = models.IntegerField("Longueur des avis de stage", default=0)
|
||||||
|
len_avis_lieux = models.IntegerField("Longueur des avis de lieu", default=0)
|
||||||
|
|
||||||
# Caractéristiques du stage
|
# Caractéristiques du stage
|
||||||
sujet = models.CharField(u"Sujet", max_length=500)
|
sujet = models.CharField("Sujet", max_length=500)
|
||||||
|
|
||||||
date_debut = models.DateField(u"Date de début", null=True)
|
date_debut = models.DateField("Date de début", null=True)
|
||||||
date_fin = models.DateField(u"Date de fin", null=True)
|
date_fin = models.DateField("Date de fin", null=True)
|
||||||
|
|
||||||
type_stage = models.CharField(u"Type",
|
type_stage = models.CharField(
|
||||||
|
"Type",
|
||||||
default="stage",
|
default="stage",
|
||||||
choices=TYPE_STAGE_OPTIONS,
|
choices=TYPE_STAGE_OPTIONS,
|
||||||
max_length=choices_length(TYPE_STAGE_OPTIONS))
|
max_length=choices_length(TYPE_STAGE_OPTIONS),
|
||||||
niveau_scol = models.CharField(u"Année de scolarité",
|
)
|
||||||
|
niveau_scol = models.CharField(
|
||||||
|
"Année de scolarité",
|
||||||
default="",
|
default="",
|
||||||
choices=NIVEAU_SCOL_OPTIONS,
|
choices=NIVEAU_SCOL_OPTIONS,
|
||||||
max_length=choices_length(NIVEAU_SCOL_OPTIONS),
|
max_length=choices_length(NIVEAU_SCOL_OPTIONS),
|
||||||
blank=True)
|
blank=True,
|
||||||
|
)
|
||||||
|
|
||||||
thematiques = TaggableManager(u"Thématiques", blank=True)
|
thematiques = TaggableManager("Thématiques", blank=True)
|
||||||
matieres = models.ManyToManyField(StageMatiere, verbose_name=u"Matière(s)", related_name="stages")
|
matieres = models.ManyToManyField(
|
||||||
encadrants = models.CharField(u"Encadrant⋅e⋅s", max_length=500, blank=True)
|
StageMatiere, verbose_name="Matière(s)", related_name="stages"
|
||||||
structure = models.CharField(u"Structure d'accueil", max_length=500, blank=True)
|
)
|
||||||
|
encadrants = models.CharField("Encadrant⋅e⋅s", max_length=500, blank=True)
|
||||||
|
structure = models.CharField("Structure d'accueil", max_length=500, blank=True)
|
||||||
|
|
||||||
# Avis
|
# Avis
|
||||||
lieux = models.ManyToManyField(Lieu, related_name="stages",
|
lieux = models.ManyToManyField(
|
||||||
through="AvisLieu", blank=True)
|
Lieu, related_name="stages", through="AvisLieu", blank=True
|
||||||
|
)
|
||||||
|
|
||||||
# Affichage des avis ordonnés
|
# Affichage des avis ordonnés
|
||||||
@property
|
@property
|
||||||
def avis_lieux(self):
|
def avis_lieux(self):
|
||||||
return self.avislieu_set.order_by('order')
|
return self.avislieu_set.order_by("order")
|
||||||
|
|
||||||
# Shortcut pour affichage rapide
|
# Shortcut pour affichage rapide
|
||||||
@property
|
@property
|
||||||
|
@ -208,6 +249,7 @@ class Stage(models.Model):
|
||||||
@property
|
@property
|
||||||
def type_stage_fancy(self):
|
def type_stage_fancy(self):
|
||||||
return TYPE_STAGE_DICT.get(self.type_stage, ("stage", False))[0]
|
return TYPE_STAGE_DICT.get(self.type_stage, ("stage", False))[0]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type_stage_fem(self):
|
def type_stage_fem(self):
|
||||||
return TYPE_STAGE_DICT.get(self.type_stage, ("stage", False))[1]
|
return TYPE_STAGE_DICT.get(self.type_stage, ("stage", False))[1]
|
||||||
|
@ -217,12 +259,16 @@ class Stage(models.Model):
|
||||||
def niveau_scol_fancy(self):
|
def niveau_scol_fancy(self):
|
||||||
return NIVEAU_SCOL_DICT.get(self.niveau_scol, "")
|
return NIVEAU_SCOL_DICT.get(self.niveau_scol, "")
|
||||||
|
|
||||||
|
# Optimisation de requêtes
|
||||||
|
@cached_property
|
||||||
|
def all_lieux(self):
|
||||||
|
return self.lieux.all()
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('avisstage:stage', self)
|
return reverse("avisstage:stage", self)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __str__(self):
|
||||||
return u"%s (par %s)" % (self.sujet, self.auteur.user.username)
|
return "%s (par %s)" % (self.sujet, self.auteur.user.username)
|
||||||
|
|
||||||
def update_stats(self, save=True):
|
def update_stats(self, save=True):
|
||||||
def get_len(obj):
|
def get_len(obj):
|
||||||
|
@ -247,57 +293,67 @@ class Stage(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Stage"
|
verbose_name = "Stage"
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Les avis
|
# Les avis
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
class AvisStage(models.Model):
|
class AvisStage(models.Model):
|
||||||
stage = models.OneToOneField(Stage, related_name="avis_stage")
|
stage = models.OneToOneField(
|
||||||
|
Stage, related_name="avis_stage", on_delete=models.CASCADE
|
||||||
|
)
|
||||||
|
|
||||||
chapo = models.TextField(u"En quelques mots", blank=True)
|
chapo = models.TextField("En quelques mots", blank=True)
|
||||||
avis_ambiance = RichTextField(u"L'ambiance de travail", blank=True)
|
avis_ambiance = RichTextField("L'ambiance de travail", blank=True)
|
||||||
avis_sujet = RichTextField(u"La mission", blank=True)
|
avis_sujet = RichTextField("La mission", blank=True)
|
||||||
avis_admin = RichTextField(u"Formalités et administration", blank=True)
|
avis_admin = RichTextField("Formalités et administration", blank=True)
|
||||||
avis_prestage = RichTextField(u"Avant le stage", blank=True, default="")
|
avis_prestage = RichTextField("Avant le stage", blank=True, default="")
|
||||||
|
|
||||||
les_plus = models.TextField(u"Les plus de cette expérience", blank=True)
|
les_plus = models.TextField("Les plus de cette expérience", blank=True)
|
||||||
les_moins = models.TextField(u"Les moins de cette expérience", blank=True)
|
les_moins = models.TextField("Les moins de cette expérience", blank=True)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __str__(self):
|
||||||
return u"Avis sur {%s} par %s" % (self.stage.sujet, self.stage.auteur.user.username)
|
return "Avis sur {%s} par %s" % (
|
||||||
|
self.stage.sujet,
|
||||||
|
self.stage.auteur.user.username,
|
||||||
|
)
|
||||||
|
|
||||||
# Liste des champs d'avis, couplés à leur nom (pour l'affichage)
|
# Liste des champs d'avis, couplés à leur nom (pour l'affichage)
|
||||||
@property
|
@property
|
||||||
def avis_all(self):
|
def avis_all(self):
|
||||||
fields = ['avis_sujet', 'avis_ambiance', 'avis_admin', 'avis_prestage']
|
fields = ["avis_sujet", "avis_ambiance", "avis_admin", "avis_prestage"]
|
||||||
return [(AvisStage._meta.get_field(field).verbose_name,
|
return [
|
||||||
getattr(self, field, '')) for field in fields]
|
(AvisStage._meta.get_field(field).verbose_name, getattr(self, field, ""))
|
||||||
|
for field in fields
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class AvisLieu(models.Model):
|
class AvisLieu(models.Model):
|
||||||
stage = models.ForeignKey(Stage)
|
stage = models.ForeignKey(Stage, on_delete=models.CASCADE)
|
||||||
lieu = models.ForeignKey(Lieu)
|
lieu = models.ForeignKey(Lieu, on_delete=models.CASCADE)
|
||||||
order = models.IntegerField("Ordre", default=0)
|
order = models.IntegerField("Ordre", default=0)
|
||||||
|
|
||||||
chapo = models.TextField(u"En quelques mots", blank=True)
|
chapo = models.TextField("En quelques mots", blank=True)
|
||||||
avis_lieustage = RichTextField(u"Les lieux de travail", blank=True)
|
avis_lieustage = RichTextField("Les lieux de travail", blank=True)
|
||||||
avis_pratique = RichTextField(u"S'installer - conseils pratiques",
|
avis_pratique = RichTextField("S'installer - conseils pratiques", blank=True)
|
||||||
blank=True)
|
avis_tourisme = RichTextField("Dans les parages", blank=True)
|
||||||
avis_tourisme = RichTextField(u"Dans les parages", blank=True)
|
|
||||||
|
|
||||||
les_plus = models.TextField(u"Les plus du lieu", blank=True)
|
les_plus = models.TextField("Les plus du lieu", blank=True)
|
||||||
les_moins = models.TextField(u"Les moins du lieu", blank=True)
|
les_moins = models.TextField("Les moins du lieu", blank=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Avis sur un lieu de stage"
|
verbose_name = "Avis sur un lieu de stage"
|
||||||
verbose_name_plural = "Avis sur un lieu de stage"
|
verbose_name_plural = "Avis sur un lieu de stage"
|
||||||
|
|
||||||
def __unicode__(self):
|
def __str__(self):
|
||||||
return u"Avis sur {%s} par %s" % (self.lieu.nom, self.stage.auteur.user_id)
|
return "Avis sur {%s} par %s" % (self.lieu.nom, self.stage.auteur.user_id)
|
||||||
|
|
||||||
# Liste des champs d'avis, couplés à leur nom (pour l'affichage)
|
# Liste des champs d'avis, couplés à leur nom (pour l'affichage)
|
||||||
@property
|
@property
|
||||||
def avis_all(self):
|
def avis_all(self):
|
||||||
fields = ['avis_lieustage', 'avis_pratique', 'avis_tourisme']
|
fields = ["avis_lieustage", "avis_pratique", "avis_tourisme"]
|
||||||
return [(AvisLieu._meta.get_field(field).verbose_name,
|
return [
|
||||||
getattr(self, field, '')) for field in fields]
|
(AvisLieu._meta.get_field(field).verbose_name, getattr(self, field, ""))
|
||||||
|
for field in fields
|
||||||
|
]
|
||||||
|
|
71
avisstage/sass/_fonts.scss
Normal file
71
avisstage/sass/_fonts.scss
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
@import "_definitions.scss";
|
||||||
|
|
||||||
|
/* alegreya-700 - latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Alegreya';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
src: local('Alegreya Bold'), local('Alegreya-Bold'),
|
||||||
|
url('' + $staticurl + 'fonts/alegreya-v11-latin-700.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||||
|
url('' + $staticurl + 'fonts/alegreya-v11-latin-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* dosis-300 - latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Dosis';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300;
|
||||||
|
src: local('Dosis Light'), local('Dosis-Light'),
|
||||||
|
url('' + $staticurl +'fonts/dosis-v7-latin-300.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||||
|
url('' + $staticurl +'fonts/dosis-v7-latin-300.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* dosis-500 - latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Dosis';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
src: local('Dosis Medium'), local('Dosis-Medium'),
|
||||||
|
url('' + $staticurl +'fonts/dosis-v7-latin-500.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||||
|
url('' + $staticurl +'fonts/dosis-v7-latin-500.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* dosis-700 - latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Dosis';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
src: local('Dosis Bold'), local('Dosis-Bold'),
|
||||||
|
url('' + $staticurl +'fonts/dosis-v7-latin-700.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||||
|
url('' + $staticurl +'fonts/dosis-v7-latin-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* lato-300 - latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Lato';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300;
|
||||||
|
src: local('Lato Light'), local('Lato-Light'),
|
||||||
|
url('' + $staticurl + 'fonts/lato-v14-latin-300.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||||
|
url('' + $staticurl + 'fonts/lato-v14-latin-300.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* lato-300italic - latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Lato';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 300;
|
||||||
|
src: local('Lato Light Italic'), local('Lato-LightItalic'),
|
||||||
|
url('' + $staticurl + 'fonts/lato-v14-latin-300italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||||
|
url('' + $staticurl + 'fonts/lato-v14-latin-300italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* lato-700 - latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Lato';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
src: local('Lato Bold'), local('Lato-Bold'),
|
||||||
|
url('' + $staticurl + 'fonts/lato-v14-latin-700.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||||
|
url('' + $staticurl + 'fonts/lato-v14-latin-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||||
|
}
|
63
avisstage/sass/_miniheader.scss
Normal file
63
avisstage/sass/_miniheader.scss
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
@mixin miniHeader() {
|
||||||
|
header {
|
||||||
|
z-index: 40;
|
||||||
|
position: fixed;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
max-height:100vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
#showmenu {
|
||||||
|
display: block;
|
||||||
|
float: right;
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 35px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nav {
|
||||||
|
clear: both;
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
ul {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: block;
|
||||||
|
a {
|
||||||
|
display: block;
|
||||||
|
padding: 10px 20px;
|
||||||
|
height: auto;
|
||||||
|
text-align:right;
|
||||||
|
|
||||||
|
br {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.username:after {
|
||||||
|
content: " | ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
header.expanded {
|
||||||
|
nav ul {
|
||||||
|
display:block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.6em;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,8 @@
|
||||||
section.content.recherche {
|
@import "_miniheader.scss";
|
||||||
|
|
||||||
|
body.recherche {
|
||||||
|
|
||||||
|
section.content {
|
||||||
form.recherche {
|
form.recherche {
|
||||||
.generale {
|
.generale {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -89,6 +93,7 @@ section.content.recherche {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.vue-hybride #voir_hybride,
|
&.vue-hybride #voir_hybride,
|
||||||
&.vue-carte #voir_carte,
|
&.vue-carte #voir_carte,
|
||||||
|
@ -124,14 +129,14 @@ section.content.recherche {
|
||||||
left: 60px;
|
left: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.vue-hybride, &.vue-carte {
|
&.vue-hybride section.content,
|
||||||
|
&.vue-carte section.content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: unset;
|
min-width: unset;
|
||||||
max-width: unset;
|
max-width: unset;
|
||||||
min-height: unset;
|
min-height: unset;
|
||||||
max-height: unset;
|
max-height: unset;
|
||||||
height: 90vh;
|
height: 100vh;
|
||||||
height: calc(100vh - 30px);
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
|
@ -145,7 +150,8 @@ section.content.recherche {
|
||||||
}
|
}
|
||||||
|
|
||||||
&.vue-liste .recherche-carte,
|
&.vue-liste .recherche-carte,
|
||||||
&.vue-carte .recherche-liste {
|
&.vue-carte .recherche-liste,
|
||||||
|
&.vue-carte header {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,13 +162,23 @@ section.content.recherche {
|
||||||
}
|
}
|
||||||
|
|
||||||
&.vue-hybride {
|
&.vue-hybride {
|
||||||
|
|
||||||
|
@include miniHeader;
|
||||||
|
|
||||||
|
header {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 500px;
|
||||||
|
z-index: 15;
|
||||||
|
}
|
||||||
|
section.content {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
.recherche-liste {
|
.recherche-liste {
|
||||||
width: 100%;
|
padding-top: 60px;
|
||||||
min-width: 400px;
|
width: 500px;
|
||||||
max-width: 500px;
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
.dates {
|
.dates {
|
||||||
display:none;
|
display:none;
|
||||||
|
@ -184,6 +200,7 @@ section.content.recherche {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#carte {
|
#carte {
|
||||||
width:100%;
|
width:100%;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
@import "_miniheader.scss";
|
||||||
|
|
||||||
@media screen and (max-width: 850px) {
|
@media screen and (max-width: 850px) {
|
||||||
header {
|
header {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
|
@ -18,67 +20,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 600px) {
|
@media screen and (max-width: 600px) {
|
||||||
header {
|
|
||||||
z-index: 40;
|
|
||||||
position: fixed;
|
|
||||||
top: 0px;
|
|
||||||
left: 0px;
|
|
||||||
width: 100%;
|
|
||||||
display: block;
|
|
||||||
max-height:100vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
|
|
||||||
h1 {
|
@include miniHeader;
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
#showmenu {
|
|
||||||
display: block;
|
|
||||||
float: right;
|
|
||||||
padding: 10px;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 35px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nav {
|
|
||||||
clear: both;
|
|
||||||
text-align: right;
|
|
||||||
|
|
||||||
ul {
|
|
||||||
display: none;
|
|
||||||
|
|
||||||
li {
|
|
||||||
display: block;
|
|
||||||
a {
|
|
||||||
display: block;
|
|
||||||
padding: 10px 20px;
|
|
||||||
height: auto;
|
|
||||||
text-align:right;
|
|
||||||
|
|
||||||
br {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.username:after {
|
|
||||||
content: " | ";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
header.expanded {
|
|
||||||
nav ul {
|
|
||||||
display:block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-size: 1.6em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#feedback-button {
|
#feedback-button {
|
||||||
transform: unset;
|
transform: unset;
|
||||||
|
@ -234,14 +177,14 @@
|
||||||
display: block;
|
display: block;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
font-size: 0.95em;
|
font-size: 0.95em;
|
||||||
color: $compl * 0.8;
|
color: darken($compl, 20%);
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.help_text {
|
.help_text {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
color: $fond * 0.4;
|
color: darken($fond, 60%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
@import "compass/reset";
|
@import "compass/reset";
|
||||||
@import "_definitions.scss";
|
@import "_definitions.scss";
|
||||||
@import url('https://fonts.googleapis.com/css?family=Dosis:300,500,700|Alegreya:700|Lato:300,300i,700');
|
@import "_fonts.scss";
|
||||||
|
// @import url('https://fonts.googleapis.com/css?family=Dosis:300,500,700|Alegreya:700|Lato:300,300i,700');
|
||||||
|
|
||||||
// Général
|
// Général
|
||||||
|
|
||||||
|
@ -45,7 +46,7 @@ em, i {
|
||||||
|
|
||||||
a {
|
a {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $compl * 0.9;
|
color: darken($compl, 10%);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,7 +107,7 @@ header {
|
||||||
color: lighten($fond, 40%);
|
color: lighten($fond, 40%);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $barre * 0.6;
|
background: darken($barre, 40%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -161,13 +162,25 @@ header {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.warning {
|
||||||
|
background-color: #c20;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #fff;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
margin-top: 15px;
|
||||||
|
font-size: 0.8em;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
// Liste des stages condensée sur le profil
|
// Liste des stages condensée sur le profil
|
||||||
|
|
||||||
.condensed-stages {
|
.condensed-stages {
|
||||||
li {
|
li {
|
||||||
display: table;
|
display: table;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
//border: 1px solid $fond * 1.3;
|
|
||||||
background: #fff;
|
background: #fff;
|
||||||
margin: 12px;
|
margin: 12px;
|
||||||
|
|
||||||
|
@ -256,8 +269,10 @@ header {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
a.hoverlink {
|
a.hoverlink {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: block;
|
display: block;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
@ -265,8 +280,6 @@ header {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.infos {
|
ul.infos {
|
||||||
|
@ -349,6 +362,57 @@ section.profil {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
section.two-cols {
|
||||||
|
display: flex;
|
||||||
|
display: flexbox;
|
||||||
|
align-items: center;
|
||||||
|
& > * {
|
||||||
|
flex: 1;
|
||||||
|
width: 50%;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.mes-emails {
|
||||||
|
li {
|
||||||
|
display: flex;
|
||||||
|
background: #fff;
|
||||||
|
margin: 5px;
|
||||||
|
padding: 10px;
|
||||||
|
min-height: 70px;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adresse {
|
||||||
|
text-align: left;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirmee {
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.supprimer {
|
||||||
|
flex: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
.field {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
// Détail d'un stage
|
// Détail d'un stage
|
||||||
|
@ -418,7 +482,7 @@ input[type="submit"], .btn {
|
||||||
font: $textfontsize $textfont;
|
font: $textfontsize $textfont;
|
||||||
background-color: $fond;
|
background-color: $fond;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border: 1px solid $fond * 0.7;
|
border: 1px solid darken($fond, 30%);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -502,15 +566,20 @@ form {
|
||||||
|
|
||||||
// taggit autosuggest
|
// taggit autosuggest
|
||||||
|
|
||||||
ul.as-selections {
|
ul.as-selections,
|
||||||
|
.selectize-control.multi {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
display:inline-block;
|
display:inline-block;
|
||||||
}
|
}
|
||||||
|
.selectize-input, .selectize-dropdown {
|
||||||
.as-selection-item {
|
font-size: 100%;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
.as-selection-item,
|
||||||
|
.selectize-input > div {
|
||||||
padding: 0 5px;
|
padding: 0 5px;
|
||||||
background: $compl;
|
background: $compl;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
@ -542,6 +611,8 @@ ul.as-selections {
|
||||||
|
|
||||||
div.as-results {
|
div.as-results {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -632,8 +703,16 @@ div.as-results {
|
||||||
// Widget choix et ajout de lieux
|
// Widget choix et ajout de lieux
|
||||||
|
|
||||||
#lieu_widget {
|
#lieu_widget {
|
||||||
|
.window-content {
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
.lieu-ui {
|
.lieu-ui {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 150px;
|
||||||
|
flex: 2;
|
||||||
|
|
||||||
.map {
|
.map {
|
||||||
height: 400px;
|
height: 400px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -646,10 +725,67 @@ div.as-results {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.lieu-choixmodif {
|
.lieu-choixmodif, .lieu-options {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lieu-global {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
&.with-options {
|
||||||
|
.lieu-options {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.modif, &.edit {
|
||||||
|
.lieu-global.with-options .lieu-options {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lieu-options {
|
||||||
|
padding: 7px;
|
||||||
|
max-width: 350px;
|
||||||
|
flex: 3;
|
||||||
|
|
||||||
|
.lieu-suggestions {
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
li {
|
||||||
|
position: relative;
|
||||||
|
background: #fff;
|
||||||
|
margin: 2px;
|
||||||
|
padding: 4px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
&:hover {
|
||||||
|
background: #ccc;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin: 2px 0;
|
||||||
|
}
|
||||||
|
.lieu-nom {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.lieu-infos {
|
||||||
|
font-size: 0.8em;
|
||||||
|
display:flex;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: inline-block;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overlow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.modif {
|
&.modif {
|
||||||
.lieu-choixmodif {
|
.lieu-choixmodif {
|
||||||
display: unset;
|
display: unset;
|
||||||
|
@ -751,6 +887,13 @@ a.lieu-change {
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin: 15px auto;
|
margin: 15px auto;
|
||||||
|
|
||||||
|
.archicubes {
|
||||||
|
border-top: 2px solid $vert;
|
||||||
|
margin-top: 5px;
|
||||||
|
padding-top: 5px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
article.promo {
|
article.promo {
|
||||||
|
@ -857,6 +1000,27 @@ article.promo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Modération
|
||||||
|
|
||||||
|
table.stats {
|
||||||
|
width: 100%;
|
||||||
|
background: #fff;
|
||||||
|
margin: 20px 0;
|
||||||
|
cellspacing: 1px;
|
||||||
|
th {
|
||||||
|
font-weight: bold;
|
||||||
|
border-top: 1px solid #000;
|
||||||
|
border-bottom: 1px solid #999;
|
||||||
|
}
|
||||||
|
td, th {
|
||||||
|
padding: 5px 3px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
// Recherche
|
// Recherche
|
||||||
|
|
File diff suppressed because it is too large
Load diff
BIN
avisstage/static/fonts/alegreya-v11-latin-700.woff
Normal file
BIN
avisstage/static/fonts/alegreya-v11-latin-700.woff
Normal file
Binary file not shown.
BIN
avisstage/static/fonts/alegreya-v11-latin-700.woff2
Normal file
BIN
avisstage/static/fonts/alegreya-v11-latin-700.woff2
Normal file
Binary file not shown.
BIN
avisstage/static/fonts/dosis-v7-latin-300.woff
Normal file
BIN
avisstage/static/fonts/dosis-v7-latin-300.woff
Normal file
Binary file not shown.
BIN
avisstage/static/fonts/dosis-v7-latin-300.woff2
Normal file
BIN
avisstage/static/fonts/dosis-v7-latin-300.woff2
Normal file
Binary file not shown.
BIN
avisstage/static/fonts/dosis-v7-latin-500.woff
Normal file
BIN
avisstage/static/fonts/dosis-v7-latin-500.woff
Normal file
Binary file not shown.
BIN
avisstage/static/fonts/dosis-v7-latin-500.woff2
Normal file
BIN
avisstage/static/fonts/dosis-v7-latin-500.woff2
Normal file
Binary file not shown.
BIN
avisstage/static/fonts/dosis-v7-latin-700.woff
Normal file
BIN
avisstage/static/fonts/dosis-v7-latin-700.woff
Normal file
Binary file not shown.
BIN
avisstage/static/fonts/dosis-v7-latin-700.woff2
Normal file
BIN
avisstage/static/fonts/dosis-v7-latin-700.woff2
Normal file
Binary file not shown.
BIN
avisstage/static/fonts/lato-v14-latin-300.woff
Normal file
BIN
avisstage/static/fonts/lato-v14-latin-300.woff
Normal file
Binary file not shown.
BIN
avisstage/static/fonts/lato-v14-latin-300.woff2
Normal file
BIN
avisstage/static/fonts/lato-v14-latin-300.woff2
Normal file
Binary file not shown.
BIN
avisstage/static/fonts/lato-v14-latin-300italic.woff
Normal file
BIN
avisstage/static/fonts/lato-v14-latin-300italic.woff
Normal file
Binary file not shown.
BIN
avisstage/static/fonts/lato-v14-latin-300italic.woff2
Normal file
BIN
avisstage/static/fonts/lato-v14-latin-300italic.woff2
Normal file
Binary file not shown.
BIN
avisstage/static/fonts/lato-v14-latin-700.woff
Normal file
BIN
avisstage/static/fonts/lato-v14-latin-700.woff
Normal file
Binary file not shown.
BIN
avisstage/static/fonts/lato-v14-latin-700.woff2
Normal file
BIN
avisstage/static/fonts/lato-v14-latin-700.woff2
Normal file
Binary file not shown.
|
@ -1,4 +1,4 @@
|
||||||
function initEditStage(STATIC_URL, API_URL) {
|
function initEditStage(STATIC_URL, API_URL, MAPBOX_API_KEY) {
|
||||||
var has_changes = true;
|
var has_changes = true;
|
||||||
var stage_object_id = $("#stage_object_id").val();
|
var stage_object_id = $("#stage_object_id").val();
|
||||||
$(window).on('beforeunload',
|
$(window).on('beforeunload',
|
||||||
|
@ -39,7 +39,7 @@ function initEditStage(STATIC_URL, API_URL) {
|
||||||
var slts = $("select[multiple]").selectize();
|
var slts = $("select[multiple]").selectize();
|
||||||
|
|
||||||
// CHOIX DU LIEU
|
// CHOIX DU LIEU
|
||||||
var lieu_select = new SelectLieuWidget(STATIC_URL, API_URL,
|
var lieu_select = new SelectLieuWidget(STATIC_URL, API_URL, MAPBOX_API_KEY,
|
||||||
$("#lieu_widget"), lieuChoisi);
|
$("#lieu_widget"), lieuChoisi);
|
||||||
var avis_lieu_template = $("#avis_lieu_vide").remove().html();
|
var avis_lieu_template = $("#avis_lieu_vide").remove().html();
|
||||||
var lieux_liste = $("#lieux-selector");
|
var lieux_liste = $("#lieux-selector");
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
function InterfaceRecherche(STATIC_ROOT, API_LIEU, lieux) {
|
function InterfaceRecherche(STATIC_ROOT, API_LIEU, MAPBOX_API_KEY, ITEMS_URL, lieux) {
|
||||||
var interface_mode, main_container;
|
var interface_mode, main_container;
|
||||||
var lieux_map = {}, lieux_list = [], stages_map = {}, lieux_db = {};
|
var lieux_map = {}, lieux_list = [], stages_map = {}, lieux_db = {};
|
||||||
var stages_data = {};
|
var stages_db = {};
|
||||||
|
var details_liste_data;
|
||||||
var marqueurs = L.markerClusterGroup();
|
var marqueurs = L.markerClusterGroup();
|
||||||
var marqueurs_db = {};
|
var marqueurs_db = {};
|
||||||
var changevue;
|
var changevue;
|
||||||
|
@ -13,8 +14,10 @@ function InterfaceRecherche(STATIC_ROOT, API_LIEU, lieux) {
|
||||||
// TODO se souvenir des préférences d'affichage
|
// TODO se souvenir des préférences d'affichage
|
||||||
|
|
||||||
function initInterface() {
|
function initInterface() {
|
||||||
main_container = $(".content.recherche");
|
main_container = $("body");
|
||||||
if (main_container.hasClass("vue-liste")) {
|
if (sessionStorage && sessionStorage.interface_mode) {
|
||||||
|
interface_mode = sessionStorage.interface_mode;
|
||||||
|
} else if (main_container.hasClass("vue-liste")) {
|
||||||
interface_mode = "liste";
|
interface_mode = "liste";
|
||||||
} else if (main_container.hasClass("vue-carte")) {
|
} else if (main_container.hasClass("vue-carte")) {
|
||||||
interface_mode = "carte";
|
interface_mode = "carte";
|
||||||
|
@ -43,6 +46,17 @@ function InterfaceRecherche(STATIC_ROOT, API_LIEU, lieux) {
|
||||||
});
|
});
|
||||||
|
|
||||||
changeInterface(interface_mode);
|
changeInterface(interface_mode);
|
||||||
|
initLoadMoreAJAX();
|
||||||
|
referenceStageItems($("#resultats").children());
|
||||||
|
}
|
||||||
|
|
||||||
|
function referenceStageItems (stages, hard) {
|
||||||
|
$.each(stages, function(i, item) {
|
||||||
|
if (item.id === undefined) return;
|
||||||
|
var iid = Number(item.id.split("-")[2]);
|
||||||
|
if (stages_db[iid] !== undefined && !hard) return;
|
||||||
|
stages_db[iid] = $(item);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Changement d'affichage : mise à jour des classes et démarrage de la carte si nécessaire
|
// Changement d'affichage : mise à jour des classes et démarrage de la carte si nécessaire
|
||||||
|
@ -53,8 +67,11 @@ function InterfaceRecherche(STATIC_ROOT, API_LIEU, lieux) {
|
||||||
|
|
||||||
function changeInterface(mode) {
|
function changeInterface(mode) {
|
||||||
interface_mode = mode;
|
interface_mode = mode;
|
||||||
$(".content.recherche").removeClass("vue-carte vue-hybride vue-liste")
|
main_container.removeClass("vue-carte vue-hybride vue-liste")
|
||||||
.addClass("vue-"+mode);
|
.addClass("vue-"+mode);
|
||||||
|
if (sessionStorage) {
|
||||||
|
sessionStorage.interface_mode = mode;
|
||||||
|
}
|
||||||
if (mode=="hybride" || mode=="carte") {
|
if (mode=="hybride" || mode=="carte") {
|
||||||
initCarte();
|
initCarte();
|
||||||
map.invalidateSize();
|
map.invalidateSize();
|
||||||
|
@ -66,7 +83,12 @@ function InterfaceRecherche(STATIC_ROOT, API_LIEU, lieux) {
|
||||||
function initCarte() {
|
function initCarte() {
|
||||||
if (map !== undefined) return;
|
if (map !== undefined) return;
|
||||||
map = L.map("carte").panTo([30, 15]).setZoom(1);
|
map = L.map("carte").panTo([30, 15]).setZoom(1);
|
||||||
var layer = new L.TileLayer("https://korona.geog.uni-heidelberg.de/tiles/roads/x={x}&y={y}&z={z}", {attribution: 'Map tiles by <a href="http://korona.geog.uni-heidelberg.de/">GIScience Heidelberg</a>'});
|
var layer = L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', {
|
||||||
|
attribution: 'Map data © <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
|
||||||
|
maxZoom: 18,
|
||||||
|
id: 'mapbox/streets-v11',
|
||||||
|
accessToken: MAPBOX_API_KEY
|
||||||
|
});
|
||||||
map.addLayer(layer);
|
map.addLayer(layer);
|
||||||
|
|
||||||
$.getJSON(API_LIEU + "set/"+lieux_list.join(';')+"/?format=json", onLoadLieux);
|
$.getJSON(API_LIEU + "set/"+lieux_list.join(';')+"/?format=json", onLoadLieux);
|
||||||
|
@ -84,7 +106,7 @@ function InterfaceRecherche(STATIC_ROOT, API_LIEU, lieux) {
|
||||||
var greenIcon = makeIcon('red');
|
var greenIcon = makeIcon('red');
|
||||||
var blueIcon = makeIcon('blue', 1.2);
|
var blueIcon = makeIcon('blue', 1.2);
|
||||||
|
|
||||||
// Chargeùent des infos
|
// Chargement des infos
|
||||||
function onLoadLieux(data){
|
function onLoadLieux(data){
|
||||||
console.log(data);
|
console.log(data);
|
||||||
var lieux = data.objects;
|
var lieux = data.objects;
|
||||||
|
@ -133,7 +155,7 @@ function InterfaceRecherche(STATIC_ROOT, API_LIEU, lieux) {
|
||||||
var html = $("<div>").html(marqueur._popup_header);
|
var html = $("<div>").html(marqueur._popup_header);
|
||||||
var stageliste = $("<ul>");
|
var stageliste = $("<ul>");
|
||||||
$.each(lieux_map[data.id], function(i, item) {
|
$.each(lieux_map[data.id], function(i, item) {
|
||||||
var stage_el = $("#resultat-stage-"+item);
|
var stage_el = stages_db[item];
|
||||||
var url = stage_el.find('a.stage-sujet').attr('href');
|
var url = stage_el.find('a.stage-sujet').attr('href');
|
||||||
var sujet = stage_el.find('a.stage-sujet').text();
|
var sujet = stage_el.find('a.stage-sujet').text();
|
||||||
var auteur = stage_el.find('.auteur').text();
|
var auteur = stage_el.find('.auteur').text();
|
||||||
|
@ -146,19 +168,42 @@ function InterfaceRecherche(STATIC_ROOT, API_LIEU, lieux) {
|
||||||
marqueur.setPopupContent(html[0]);
|
marqueur.setPopupContent(html[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Affichage de la liste hybride au survol + chargement asynchrone
|
||||||
|
|
||||||
function showDetailsListeListener (evt) {
|
function showDetailsListeListener (evt) {
|
||||||
showDetailsListe(this._lieu_data);
|
showDetailsListe(this._lieu_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
function showDetailsListe (data) {
|
function showDetailsListe (data, is_callback) {
|
||||||
main_container.addClass("vue-details");
|
main_container.addClass("vue-details");
|
||||||
|
var to_load = [];
|
||||||
var liste_el = $("#resultats-details");
|
var liste_el = $("#resultats-details");
|
||||||
$.each(liste_el.children(), function(i, item){$(item).remove();});
|
$.each(liste_el.children(), function(i, item){$(item).remove();});
|
||||||
$.each(lieux_map[data.id], function(i, item) {
|
$.each(lieux_map[data.id], function(i, item) {
|
||||||
var stage_el = $("#resultat-stage-"+item);
|
var stage_el = stages_db[item];
|
||||||
|
if (stage_el === undefined) {
|
||||||
|
to_load.push(item);
|
||||||
|
return;
|
||||||
|
}
|
||||||
var new_el = $("<li>", {class:"stage"}).html(stage_el.html());
|
var new_el = $("<li>", {class:"stage"}).html(stage_el.html());
|
||||||
liste_el.append(new_el);
|
liste_el.append(new_el);
|
||||||
});
|
});
|
||||||
|
if (to_load.length > 0 && !is_callback) { // On évite la boucle si erreur
|
||||||
|
loadDetailsListe(to_load, data);
|
||||||
|
liste_el.append($("<li>", {class:"stage"}).html("Chargement..."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadDetailsListe (liste, data) {
|
||||||
|
details_liste_data = data;
|
||||||
|
$.get(ITEMS_URL, {ids: liste.join(";")},
|
||||||
|
function (html) {
|
||||||
|
var temp_el = $("<ul>").html(html);
|
||||||
|
referenceStageItems(temp_el.children());
|
||||||
|
if (details_liste_data == data) {
|
||||||
|
showDetailsListe(data, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function unlockDetailsListe () {
|
function unlockDetailsListe () {
|
||||||
|
@ -167,6 +212,7 @@ function InterfaceRecherche(STATIC_ROOT, API_LIEU, lieux) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideDetailsListeListener () {
|
function hideDetailsListeListener () {
|
||||||
|
details_liste_data = undefined;
|
||||||
if (details_lock === false)
|
if (details_lock === false)
|
||||||
main_container.removeClass("vue-details");
|
main_container.removeClass("vue-details");
|
||||||
else
|
else
|
||||||
|
@ -195,6 +241,33 @@ function InterfaceRecherche(STATIC_ROOT, API_LIEU, lieux) {
|
||||||
$("li.stage.expanded").removeClass("expanded");
|
$("li.stage.expanded").removeClass("expanded");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Pagination et chargement automatique
|
||||||
|
//
|
||||||
|
|
||||||
|
function initLoadMoreAJAX () {
|
||||||
|
var btn = $("#next-page-btn");
|
||||||
|
btn.on("click", loadMoreAJAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadMoreAJAX () {
|
||||||
|
var btn = this;
|
||||||
|
var url = btn.href;
|
||||||
|
if (btn.__is_loading) return false;
|
||||||
|
btn.innerHTML = "Chargement...";
|
||||||
|
$.get(url+"&format=raw", {}, function(html) {
|
||||||
|
$(btn).remove();
|
||||||
|
var new_els = $("<ul>").html(html).children();
|
||||||
|
$("#resultats").append(new_els);
|
||||||
|
referenceStageItems(new_els);
|
||||||
|
new_els.filter(".stage")
|
||||||
|
.on("mouseover touchdown", showLieuxFromStage)
|
||||||
|
.on("mouseout", hideLieuxSurvol);
|
||||||
|
initLoadMoreAJAX();
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// __init__
|
// __init__
|
||||||
|
|
||||||
initInterface();
|
initInterface();
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
function SelectLieuWidget(STATIC_ROOT, API_LIEU, target, callback) {
|
function SelectLieuWidget(STATIC_ROOT, API_LIEU, MAPBOX_API_KEY, target, callback) {
|
||||||
|
|
||||||
//
|
//
|
||||||
// INITIALISATION
|
// INITIALISATION
|
||||||
|
@ -15,6 +15,10 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, target, callback) {
|
||||||
var message_el = $el.find(".lieu-message");
|
var message_el = $el.find(".lieu-message");
|
||||||
var closer = $el.find(".window-closer");
|
var closer = $el.find(".window-closer");
|
||||||
var cmodif_el = $el.find(".lieu-choixmodif");
|
var cmodif_el = $el.find(".lieu-choixmodif");
|
||||||
|
var lieuglobal_el = $el.find(".lieu-global");
|
||||||
|
var lieuoptions_el = $el.find(".lieu-options");
|
||||||
|
var suggestions_el = $el.find(".lieu-suggestions");
|
||||||
|
var newlieubtn_el = $el.find(".new-lieu-btn");
|
||||||
var marqueurs = L.markerClusterGroup();
|
var marqueurs = L.markerClusterGroup();
|
||||||
|
|
||||||
// Variables globales
|
// Variables globales
|
||||||
|
@ -59,7 +63,12 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, target, callback) {
|
||||||
|
|
||||||
// Affiche la carte
|
// Affiche la carte
|
||||||
map = L.map(map_el[0]).setView([48.8422411,2.3430553], 15);
|
map = L.map(map_el[0]).setView([48.8422411,2.3430553], 15);
|
||||||
var layer = new L.TileLayer("https://korona.geog.uni-heidelberg.de/tiles/roads/x={x}&y={y}&z={z}", {attribution: 'Map tiles by <a href="http://korona.geog.uni-heidelberg.de/">GIScience Heidelberg</a>'});
|
var layer = L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', {
|
||||||
|
attribution: 'Map data © <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
|
||||||
|
maxZoom: 18,
|
||||||
|
id: 'mapbox/streets-v11',
|
||||||
|
accessToken: MAPBOX_API_KEY
|
||||||
|
});
|
||||||
map.addLayer(layer);
|
map.addLayer(layer);
|
||||||
map.addLayer(marqueurs);
|
map.addLayer(marqueurs);
|
||||||
|
|
||||||
|
@ -149,8 +158,10 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, target, callback) {
|
||||||
|
|
||||||
lieux_db.suggestion = data;
|
lieux_db.suggestion = data;
|
||||||
|
|
||||||
map.panTo(data.coord);
|
panMapTo(data.coord);
|
||||||
lieuSurCarte(data, redIcon);
|
lieuSurCarte(data, redIcon);
|
||||||
|
newlieubtn_el.prop("_lieustage_data", data)
|
||||||
|
.on("click", choixLieuStage);
|
||||||
|
|
||||||
// Affichage des suggestions
|
// Affichage des suggestions
|
||||||
askForSuggestions(place.geometry.location);
|
askForSuggestions(place.geometry.location);
|
||||||
|
@ -166,17 +177,52 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, target, callback) {
|
||||||
|
|
||||||
// Callback suggestions
|
// Callback suggestions
|
||||||
function showPropositions(data) {
|
function showPropositions(data) {
|
||||||
showMessage("Cliquez sur un des lieux déjà existants (en bleu) pour le choisir, ou sur votre recherche (en rouge) pour créer un nouveau lieu");
|
showMessage("Cliquez sur un des lieux déjà existants (en bleu) " +
|
||||||
|
"pour le choisir, ou sur votre recherche (en rouge) " +
|
||||||
|
"pour créer un nouveau lieu");
|
||||||
|
suggestions_el.html("");
|
||||||
|
lieuglobal_el.addClass("with-options");
|
||||||
|
map.invalidateSize();
|
||||||
|
data.objects.sort(function(a,b){ return a.distance-b.distance; });
|
||||||
|
|
||||||
|
function showdistance (dist) {
|
||||||
|
if(dist<1000) return Math.round(dist) + "m";
|
||||||
|
else return (Math.round(dist/100)/10) + "km";
|
||||||
|
}
|
||||||
// Affichage sur la carte
|
// Affichage sur la carte
|
||||||
$.each(data.objects, function(i, item) {
|
$.each(data.objects, function(i, item) {
|
||||||
var plieu = lieux_db[item.id];
|
var plieu = lieux_db[item.id];
|
||||||
if(plieu !== undefined)
|
if(plieu !== undefined) {
|
||||||
|
plieu.distance = item.distance;
|
||||||
item = plieu;
|
item = plieu;
|
||||||
else
|
} else
|
||||||
lieux_db[item.id] = item;
|
lieux_db[item.id] = item;
|
||||||
|
var option_el = $("<li>", {class: "lieu-option"})
|
||||||
|
.append($("<p>", {class: "lieu-nom"}).text(item.nom))
|
||||||
|
.append($("<p>", {class: "lieu-infos"})
|
||||||
|
.append($("<span>", {class: "numstages"})
|
||||||
|
.text((item.num_stages || 0) + " stage"
|
||||||
|
+ (item.num_stages>1 ? "s" : "") + " ici"))
|
||||||
|
.append($("<span>", {class: "ville"})
|
||||||
|
.text(item.ville+", "+item.pays_nom))
|
||||||
|
.append($("<span>", {class: "distance"})
|
||||||
|
.text("à "+showdistance(item.distance))))
|
||||||
|
.append($("<a>", {class: "hoverlink",
|
||||||
|
href: "javascript:void(0)"}))
|
||||||
|
.prop("_lieustage_data", item)
|
||||||
|
.on("click", choixLieuStage);
|
||||||
|
suggestions_el.append(option_el);
|
||||||
lieuSurCarte(item, blueIcon);
|
lieuSurCarte(item, blueIcon);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (data.objects.length == 0) {
|
||||||
|
var option_el = $("<li>", {class: "lieu-option"})
|
||||||
|
.append($("<p>", {class: "lieu-infos"})
|
||||||
|
.text("Aucun lieu déjà connu à proximité"));
|
||||||
|
suggestions_el.append(option_el);
|
||||||
|
}
|
||||||
|
|
||||||
|
panMapTo(lieux_db.suggestion.coord);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Affiche un lieu sur la carte
|
// Affiche un lieu sur la carte
|
||||||
|
@ -201,10 +247,14 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, target, callback) {
|
||||||
data.marqueur = marqueur;
|
data.marqueur = marqueur;
|
||||||
|
|
||||||
// Création de la description
|
// Création de la description
|
||||||
var desc = $("<div>").append($("<h3>").text(data.nom))
|
var desc = $("<div>").append($("<h4>").text(data.nom))
|
||||||
.append($("<p>").text(data.ville+", "+data.pays_nom));
|
.append($("<p>")
|
||||||
|
.text(data.ville+", "+data.pays_nom));
|
||||||
if (data.num_stages !== undefined)
|
if (data.num_stages !== undefined)
|
||||||
desc.append($("<p>").text(data.num_stages + (data.num_stages > 1 ? " stages" : " stage") + " à cet endroit"));
|
desc.append($("<p>")
|
||||||
|
.text(data.num_stages
|
||||||
|
+ (data.num_stages > 1 ? " stages" : " stage")
|
||||||
|
+ " à cet endroit"));
|
||||||
|
|
||||||
var activeBtn = $("<a>", {href:"javascript:void(0);"})
|
var activeBtn = $("<a>", {href:"javascript:void(0);"})
|
||||||
.prop("_lieustage_data", data)
|
.prop("_lieustage_data", data)
|
||||||
|
@ -229,7 +279,7 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, target, callback) {
|
||||||
function resetOrigLieu() {
|
function resetOrigLieu() {
|
||||||
var data = this._lieustage_data;
|
var data = this._lieustage_data;
|
||||||
data.marqueur.setLatLng(data.orig_coord);
|
data.marqueur.setLatLng(data.orig_coord);
|
||||||
map.panTo(data.orig_coord);
|
panMapTo(data.orig_coord);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -248,12 +298,15 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, target, callback) {
|
||||||
if (modification == true) {
|
if (modification == true) {
|
||||||
setUIMode("ajout");
|
setUIMode("ajout");
|
||||||
form_el.find("#id_id").val(choix.id);
|
form_el.find("#id_id").val(choix.id);
|
||||||
form_el.find("h3").text("Modifier un lieu");
|
form_el.find(".form-title").text("Modifier un lieu");
|
||||||
} else {
|
} else {
|
||||||
setUIMode("edit");
|
setUIMode("edit");
|
||||||
form_el.find("#id_id").val('');
|
form_el.find("#id_id").val('');
|
||||||
form_el.find("h3").text("Créer un lieu");
|
form_el.find(".form-title").text("Créer un lieu");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
map.invalidateSize();
|
||||||
|
panMapTo(choix.coord);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Envoi du formulaire
|
// Envoi du formulaire
|
||||||
|
@ -306,7 +359,7 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, target, callback) {
|
||||||
var lieu = lieux_db[modiflieu_id];
|
var lieu = lieux_db[modiflieu_id];
|
||||||
|
|
||||||
lieu.fromSuggestion = true;
|
lieu.fromSuggestion = true;
|
||||||
map.panTo(lieu.coord);
|
panMapTo(lieu.coord);
|
||||||
lieuSurCarte(lieu, greenIcon);
|
lieuSurCarte(lieu, greenIcon);
|
||||||
|
|
||||||
showForm(lieu, true);
|
showForm(lieu, true);
|
||||||
|
@ -318,9 +371,13 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, target, callback) {
|
||||||
var lieu = lieux_db[modiflieu_id];
|
var lieu = lieux_db[modiflieu_id];
|
||||||
|
|
||||||
lieu.fromSuggestion = false;
|
lieu.fromSuggestion = false;
|
||||||
map.panTo(lieu.coord);
|
|
||||||
lieuSurCarte(lieu, greenIcon);
|
|
||||||
|
|
||||||
|
map.invalidateSize();
|
||||||
|
panMapTo(lieu.coord);
|
||||||
|
lieuSurCarte(lieu, greenIcon);
|
||||||
|
newlieubtn_el.prop("_lieustage_data", lieu)
|
||||||
|
.on("click", choixLieuStage);
|
||||||
|
lieux_db.suggestion = lieu;
|
||||||
askForSuggestions(lieu.coord);
|
askForSuggestions(lieu.coord);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -332,6 +389,7 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, target, callback) {
|
||||||
initUI();
|
initUI();
|
||||||
|
|
||||||
input.val('');
|
input.val('');
|
||||||
|
lieuglobal_el.removeClass("with-options");
|
||||||
|
|
||||||
// Nettoyage
|
// Nettoyage
|
||||||
hideMessage();
|
hideMessage();
|
||||||
|
@ -344,14 +402,14 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, target, callback) {
|
||||||
if (lieu_id === undefined) {
|
if (lieu_id === undefined) {
|
||||||
// Choix d'un nouveau lieu : pas grand-chose à faire
|
// Choix d'un nouveau lieu : pas grand-chose à faire
|
||||||
ui_mode_creation = true;
|
ui_mode_creation = true;
|
||||||
$el.find("h3").text("Ajouter un lieu");
|
$el.find(".window-title").text("Ajouter un lieu");
|
||||||
map_el.addClass("masked");
|
map_el.addClass("masked");
|
||||||
} else {
|
} else {
|
||||||
// Lieu déjà existant
|
// Lieu déjà existant
|
||||||
lieu_id = lieu_id * 1;
|
lieu_id = lieu_id * 1;
|
||||||
ui_mode_creation = false;
|
ui_mode_creation = false;
|
||||||
modiflieu_id = lieu_id;
|
modiflieu_id = lieu_id;
|
||||||
$el.find("h3").text("Modifier le lieu");
|
$el.find(".window-title").text("Modifier le lieu");
|
||||||
|
|
||||||
// Chargement des infos
|
// Chargement des infos
|
||||||
if(lieux_db[lieu_id] === undefined) {
|
if(lieux_db[lieu_id] === undefined) {
|
||||||
|
@ -364,6 +422,10 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, target, callback) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function panMapTo(coord) {
|
||||||
|
map.panTo(coord);
|
||||||
|
}
|
||||||
|
|
||||||
// Fermeture du widget
|
// Fermeture du widget
|
||||||
function closeWidget () {
|
function closeWidget () {
|
||||||
$el.removeClass("visible");
|
$el.removeClass("visible");
|
||||||
|
@ -373,7 +435,7 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, target, callback) {
|
||||||
// Le lieu est choisi, on appelle le callback
|
// Le lieu est choisi, on appelle le callback
|
||||||
function choixLieuStage() {
|
function choixLieuStage() {
|
||||||
var choix = this._lieustage_data;
|
var choix = this._lieustage_data;
|
||||||
if(!choix.fromSuggestion)
|
if(!choix.fromSuggestion && this !== newlieubtn_el[0])
|
||||||
callback(choix);
|
callback(choix);
|
||||||
else
|
else
|
||||||
showForm(choix);
|
showForm(choix);
|
||||||
|
|
|
@ -1,330 +1,337 @@
|
||||||
# coding: utf-8
|
|
||||||
|
|
||||||
DEPARTEMENTS_DEFAUT = (
|
DEPARTEMENTS_DEFAUT = (
|
||||||
('phy', u'Physique'),
|
("phy", "Physique"),
|
||||||
('maths', u'Maths'),
|
("maths", "Maths"),
|
||||||
('bio', u'Biologie'),
|
("bio", "Biologie"),
|
||||||
('chimie', u'Chimie'),
|
("chimie", "Chimie"),
|
||||||
('geol', u'Géosciences'),
|
("geol", "Géosciences"),
|
||||||
('dec', u'DEC'),
|
("dec", "DEC"),
|
||||||
('info', u'Informatique'),
|
("info", "Informatique"),
|
||||||
('litt', u'Littéraire'),
|
("litt", "Littéraire"),
|
||||||
('guests', u'Pensionnaires étrangers'),
|
("guests", "Pensionnaires étrangers"),
|
||||||
('pei', u'PEI'),
|
("pei", "PEI"),
|
||||||
)
|
)
|
||||||
|
|
||||||
TYPE_STAGE_OPTIONS = (
|
TYPE_STAGE_OPTIONS = (
|
||||||
(u'Recherche :', (
|
(
|
||||||
('recherche', u"Stage académique"),
|
"Recherche :",
|
||||||
('recherche_autre', u"Stage non-académique"),
|
(
|
||||||
('sejour_dri', u"Séjour de recherche DRI"),
|
("recherche", "Stage académique"),
|
||||||
)),
|
("recherche_autre", "Stage non-académique"),
|
||||||
(u'Stage sans visée de recherche :', (
|
("sejour_dri", "Séjour de recherche DRI"),
|
||||||
('pro', u"Stage en entreprise"),
|
),
|
||||||
('admin', u"Stage en admin./ONG/orga. internationale"),
|
),
|
||||||
)),
|
(
|
||||||
(u'Enseignement :', (
|
"Stage sans visée de recherche :",
|
||||||
('lectorat', u"Lectorat DRI"),
|
(
|
||||||
('autre_teach', u"Autre expérience d'enseignement"),
|
("pro", "Stage en entreprise"),
|
||||||
)),
|
("admin", "Stage en admin./ONG/orga. internationale"),
|
||||||
('autre', u"Autre"),
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Enseignement :",
|
||||||
|
(
|
||||||
|
("lectorat", "Lectorat DRI"),
|
||||||
|
("autre_teach", "Autre expérience d'enseignement"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("autre", "Autre"),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Dictionnaire des type de stage (et de leur genre, True=féminin)
|
# Dictionnaire des type de stage (et de leur genre, True=féminin)
|
||||||
TYPE_STAGE_DICT = {
|
TYPE_STAGE_DICT = {
|
||||||
'recherche': (u"stage de recherche académique", False),
|
"recherche": ("stage de recherche académique", False),
|
||||||
'recherche_autre': (u"stage de recherche non-académique", False),
|
"recherche_autre": ("stage de recherche non-académique", False),
|
||||||
'sejour_dri': (u"séjour de recherche DRI", False),
|
"sejour_dri": ("séjour de recherche DRI", False),
|
||||||
'pro': (u"stage en entreprise sans visée de recherche", False),
|
"pro": ("stage en entreprise sans visée de recherche", False),
|
||||||
'admin': (u"stage en administration, ONG ou organisation internationale", False),
|
"admin": ("stage en administration, ONG ou organisation internationale", False),
|
||||||
'lectorat': (u"lectorat DRI", False),
|
"lectorat": ("lectorat DRI", False),
|
||||||
'autre_teach': (u"expérience de recherche", True),
|
"autre_teach": ("expérience de recherche", True),
|
||||||
'autre': (u"expérience", True),
|
"autre": ("expérience", True),
|
||||||
}
|
}
|
||||||
|
|
||||||
TYPE_LIEU_OPTIONS = (
|
TYPE_LIEU_OPTIONS = (
|
||||||
('universite', u"Université"),
|
("universite", "Université"),
|
||||||
('entreprise', u"Entreprise"),
|
("entreprise", "Entreprise"),
|
||||||
('centrerecherche', u"Centre de recherche"),
|
("centrerecherche", "Centre de recherche"),
|
||||||
('administration', u"Administration"),
|
("administration", "Administration"),
|
||||||
('autre', u"Autre"),
|
("autre", "Autre"),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Place du stage dans le cursus
|
# Place du stage dans le cursus
|
||||||
|
|
||||||
NIVEAU_SCOL_OPTIONS = (
|
NIVEAU_SCOL_OPTIONS = (
|
||||||
('L3', u"Licence 3"),
|
("L3", "Licence 3"),
|
||||||
('M1', u"Master 1"),
|
("M1", "Master 1"),
|
||||||
('M2', u"Master 2"),
|
("M2", "Master 2"),
|
||||||
('DOC', u"Pré-doctorat"),
|
("DOC", "Pré-doctorat"),
|
||||||
('CST', u"Césure"),
|
("CST", "Césure"),
|
||||||
('BLA', u"Année blanche"),
|
("BLA", "Année blanche"),
|
||||||
('VAC', u"Vacances scolaires"),
|
("VAC", "Vacances scolaires"),
|
||||||
('MIT', u"Mi-temps en parallèle des études"),
|
("MIT", "Mi-temps en parallèle des études"),
|
||||||
('', u"Autre"),
|
("", "Autre"),
|
||||||
)
|
)
|
||||||
|
|
||||||
NIVEAU_SCOL_DICT = {
|
NIVEAU_SCOL_DICT = {
|
||||||
"L3": u"pendant sa troisième année de Licence",
|
"L3": "pendant sa troisième année de Licence",
|
||||||
"M1": u"pendant sa première année de Master",
|
"M1": "pendant sa première année de Master",
|
||||||
"M2": u"pendant sa deuxième année de Master",
|
"M2": "pendant sa deuxième année de Master",
|
||||||
"DOC": u"pendant son année de pré-doctorat",
|
"DOC": "pendant son année de pré-doctorat",
|
||||||
"CST": u"pendant une année de césure",
|
"CST": "pendant une année de césure",
|
||||||
"BLA": u"pendant une année blanche",
|
"BLA": "pendant une année blanche",
|
||||||
"VAC": u"pendant des vacances scolaires",
|
"VAC": "pendant des vacances scolaires",
|
||||||
"MIT": u"à mi-temps en parallèle des études",
|
"MIT": "à mi-temps en parallèle des études",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# Dictionnaire des noms de lieux (et de leur genre, True=féminin)
|
# Dictionnaire des noms de lieux (et de leur genre, True=féminin)
|
||||||
TYPE_LIEU_DICT = {
|
TYPE_LIEU_DICT = {
|
||||||
'universite': (u"université", True),
|
"universite": ("université", True),
|
||||||
'entreprise': (u"entreprise", True),
|
"entreprise": ("entreprise", True),
|
||||||
'centrerecherche': (u"centre de recherche", False),
|
"centrerecherche": ("centre de recherche", False),
|
||||||
'administration': (u"administration", True),
|
"administration": ("administration", True),
|
||||||
'autre': (u"lieu", False),
|
"autre": ("lieu", False),
|
||||||
}
|
}
|
||||||
|
|
||||||
PAYS_OPTIONS = (
|
PAYS_OPTIONS = (
|
||||||
("AF", u"Afghanistan"),
|
("AF", "Afghanistan"),
|
||||||
("AL", u"Albanie"),
|
("AL", "Albanie"),
|
||||||
("AQ", u"Antarctique"),
|
("AQ", "Antarctique"),
|
||||||
("DZ", u"Algérie"),
|
("DZ", "Algérie"),
|
||||||
("AS", u"Samoa Américaines"),
|
("AS", "Samoa Américaines"),
|
||||||
("AD", u"Andorre"),
|
("AD", "Andorre"),
|
||||||
("AO", u"Angola"),
|
("AO", "Angola"),
|
||||||
("AG", u"Antigua-et-Barbuda"),
|
("AG", "Antigua-et-Barbuda"),
|
||||||
("AZ", u"Azerbaïdjan"),
|
("AZ", "Azerbaïdjan"),
|
||||||
("AR", u"Argentine"),
|
("AR", "Argentine"),
|
||||||
("AU", u"Australie"),
|
("AU", "Australie"),
|
||||||
("AT", u"Autriche"),
|
("AT", "Autriche"),
|
||||||
("BS", u"Bahamas"),
|
("BS", "Bahamas"),
|
||||||
("BH", u"Bahreïn"),
|
("BH", "Bahreïn"),
|
||||||
("BD", u"Bangladesh"),
|
("BD", "Bangladesh"),
|
||||||
("AM", u"Arménie"),
|
("AM", "Arménie"),
|
||||||
("BB", u"Barbade"),
|
("BB", "Barbade"),
|
||||||
("BE", u"Belgique"),
|
("BE", "Belgique"),
|
||||||
("BM", u"Bermudes"),
|
("BM", "Bermudes"),
|
||||||
("BT", u"Bhoutan"),
|
("BT", "Bhoutan"),
|
||||||
("BO", u"Bolivie"),
|
("BO", "Bolivie"),
|
||||||
("BA", u"Bosnie-Herzégovine"),
|
("BA", "Bosnie-Herzégovine"),
|
||||||
("BW", u"Botswana"),
|
("BW", "Botswana"),
|
||||||
("BV", u"Île Bouvet"),
|
("BV", "Île Bouvet"),
|
||||||
("BR", u"Brésil"),
|
("BR", "Brésil"),
|
||||||
("BZ", u"Belize"),
|
("BZ", "Belize"),
|
||||||
("IO", u"Territoire Britannique de l'Océan Indien"),
|
("IO", "Territoire Britannique de l'Océan Indien"),
|
||||||
("SB", u"Îles Salomon"),
|
("SB", "Îles Salomon"),
|
||||||
("VG", u"Îles Vierges Britanniques"),
|
("VG", "Îles Vierges Britanniques"),
|
||||||
("BN", u"Brunéi Darussalam"),
|
("BN", "Brunéi Darussalam"),
|
||||||
("BG", u"Bulgarie"),
|
("BG", "Bulgarie"),
|
||||||
("MM", u"Myanmar"),
|
("MM", "Myanmar"),
|
||||||
("BI", u"Burundi"),
|
("BI", "Burundi"),
|
||||||
("BY", u"Bélarus"),
|
("BY", "Bélarus"),
|
||||||
("KH", u"Cambodge"),
|
("KH", "Cambodge"),
|
||||||
("CM", u"Cameroun"),
|
("CM", "Cameroun"),
|
||||||
("CA", u"Canada"),
|
("CA", "Canada"),
|
||||||
("CV", u"Cap-vert"),
|
("CV", "Cap-vert"),
|
||||||
("KY", u"Îles Caïmanes"),
|
("KY", "Îles Caïmanes"),
|
||||||
("CF", u"République Centrafricaine"),
|
("CF", "République Centrafricaine"),
|
||||||
("LK", u"Sri Lanka"),
|
("LK", "Sri Lanka"),
|
||||||
("TD", u"Tchad"),
|
("TD", "Tchad"),
|
||||||
("CL", u"Chili"),
|
("CL", "Chili"),
|
||||||
("CN", u"Chine"),
|
("CN", "Chine"),
|
||||||
("TW", u"Taïwan"),
|
("TW", "Taïwan"),
|
||||||
("CX", u"Île Christmas"),
|
("CX", "Île Christmas"),
|
||||||
("CC", u"Îles Cocos (Keeling)"),
|
("CC", "Îles Cocos (Keeling)"),
|
||||||
("CO", u"Colombie"),
|
("CO", "Colombie"),
|
||||||
("KM", u"Comores"),
|
("KM", "Comores"),
|
||||||
("YT", u"Mayotte"),
|
("YT", "Mayotte"),
|
||||||
("CG", u"République du Congo"),
|
("CG", "République du Congo"),
|
||||||
("CD", u"République Démocratique du Congo"),
|
("CD", "République Démocratique du Congo"),
|
||||||
("CK", u"Îles Cook"),
|
("CK", "Îles Cook"),
|
||||||
("CR", u"Costa Rica"),
|
("CR", "Costa Rica"),
|
||||||
("HR", u"Croatie"),
|
("HR", "Croatie"),
|
||||||
("CU", u"Cuba"),
|
("CU", "Cuba"),
|
||||||
("CY", u"Chypre"),
|
("CY", "Chypre"),
|
||||||
("CZ", u"République Tchèque"),
|
("CZ", "République Tchèque"),
|
||||||
("BJ", u"Bénin"),
|
("BJ", "Bénin"),
|
||||||
("DK", u"Danemark"),
|
("DK", "Danemark"),
|
||||||
("DM", u"Dominique"),
|
("DM", "Dominique"),
|
||||||
("DO", u"République Dominicaine"),
|
("DO", "République Dominicaine"),
|
||||||
("EC", u"Équateur"),
|
("EC", "Équateur"),
|
||||||
("SV", u"El Salvador"),
|
("SV", "El Salvador"),
|
||||||
("GQ", u"Guinée Équatoriale"),
|
("GQ", "Guinée Équatoriale"),
|
||||||
("ET", u"Éthiopie"),
|
("ET", "Éthiopie"),
|
||||||
("ER", u"Érythrée"),
|
("ER", "Érythrée"),
|
||||||
("EE", u"Estonie"),
|
("EE", "Estonie"),
|
||||||
("FO", u"Îles Féroé"),
|
("FO", "Îles Féroé"),
|
||||||
("FK", u"Îles (malvinas) Falkland"),
|
("FK", "Îles (malvinas) Falkland"),
|
||||||
("GS", u"Géorgie du Sud et les Îles Sandwich du Sud"),
|
("GS", "Géorgie du Sud et les Îles Sandwich du Sud"),
|
||||||
("FJ", u"Fidji"),
|
("FJ", "Fidji"),
|
||||||
("FI", u"Finlande"),
|
("FI", "Finlande"),
|
||||||
("AX", u"Îles Åland"),
|
("AX", "Îles Åland"),
|
||||||
("FR", u"France"),
|
("FR", "France"),
|
||||||
("GF", u"Guyane Française"),
|
("GF", "Guyane Française"),
|
||||||
("PF", u"Polynésie Française"),
|
("PF", "Polynésie Française"),
|
||||||
("TF", u"Terres Australes Françaises"),
|
("TF", "Terres Australes Françaises"),
|
||||||
("DJ", u"Djibouti"),
|
("DJ", "Djibouti"),
|
||||||
("GA", u"Gabon"),
|
("GA", "Gabon"),
|
||||||
("GE", u"Géorgie"),
|
("GE", "Géorgie"),
|
||||||
("GM", u"Gambie"),
|
("GM", "Gambie"),
|
||||||
("PS", u"Territoire Palestinien Occupé"),
|
("PS", "Territoire Palestinien Occupé"),
|
||||||
("DE", u"Allemagne"),
|
("DE", "Allemagne"),
|
||||||
("GH", u"Ghana"),
|
("GH", "Ghana"),
|
||||||
("GI", u"Gibraltar"),
|
("GI", "Gibraltar"),
|
||||||
("KI", u"Kiribati"),
|
("KI", "Kiribati"),
|
||||||
("GR", u"Grèce"),
|
("GR", "Grèce"),
|
||||||
("GL", u"Groenland"),
|
("GL", "Groenland"),
|
||||||
("GD", u"Grenade"),
|
("GD", "Grenade"),
|
||||||
("GP", u"Guadeloupe"),
|
("GP", "Guadeloupe"),
|
||||||
("GU", u"Guam"),
|
("GU", "Guam"),
|
||||||
("GT", u"Guatemala"),
|
("GT", "Guatemala"),
|
||||||
("GN", u"Guinée"),
|
("GN", "Guinée"),
|
||||||
("GY", u"Guyana"),
|
("GY", "Guyana"),
|
||||||
("HT", u"Haïti"),
|
("HT", "Haïti"),
|
||||||
("HM", u"Îles Heard et Mcdonald"),
|
("HM", "Îles Heard et Mcdonald"),
|
||||||
("VA", u"Saint-Siège (état de la Cité du Vatican)"),
|
("VA", "Saint-Siège (état de la Cité du Vatican)"),
|
||||||
("HN", u"Honduras"),
|
("HN", "Honduras"),
|
||||||
("HK", u"Hong-Kong"),
|
("HK", "Hong-Kong"),
|
||||||
("HU", u"Hongrie"),
|
("HU", "Hongrie"),
|
||||||
("IS", u"Islande"),
|
("IS", "Islande"),
|
||||||
("IN", u"Inde"),
|
("IN", "Inde"),
|
||||||
("ID", u"Indonésie"),
|
("ID", "Indonésie"),
|
||||||
("IR", u"République Islamique d'Iran"),
|
("IR", "République Islamique d'Iran"),
|
||||||
("IQ", u"Iraq"),
|
("IQ", "Iraq"),
|
||||||
("IE", u"Irlande"),
|
("IE", "Irlande"),
|
||||||
("IL", u"Israël"),
|
("IL", "Israël"),
|
||||||
("IT", u"Italie"),
|
("IT", "Italie"),
|
||||||
("CI", u"Côte d'Ivoire"),
|
("CI", "Côte d'Ivoire"),
|
||||||
("JM", u"Jamaïque"),
|
("JM", "Jamaïque"),
|
||||||
("JP", u"Japon"),
|
("JP", "Japon"),
|
||||||
("KZ", u"Kazakhstan"),
|
("KZ", "Kazakhstan"),
|
||||||
("JO", u"Jordanie"),
|
("JO", "Jordanie"),
|
||||||
("KE", u"Kenya"),
|
("KE", "Kenya"),
|
||||||
("KP", u"République Populaire Démocratique de Corée"),
|
("KP", "République Populaire Démocratique de Corée"),
|
||||||
("KR", u"République de Corée"),
|
("KR", "République de Corée"),
|
||||||
("KW", u"Koweït"),
|
("KW", "Koweït"),
|
||||||
("KG", u"Kirghizistan"),
|
("KG", "Kirghizistan"),
|
||||||
("LA", u"République Démocratique Populaire Lao"),
|
("LA", "République Démocratique Populaire Lao"),
|
||||||
("LB", u"Liban"),
|
("LB", "Liban"),
|
||||||
("LS", u"Lesotho"),
|
("LS", "Lesotho"),
|
||||||
("LV", u"Lettonie"),
|
("LV", "Lettonie"),
|
||||||
("LR", u"Libéria"),
|
("LR", "Libéria"),
|
||||||
("LY", u"Jamahiriya Arabe Libyenne"),
|
("LY", "Jamahiriya Arabe Libyenne"),
|
||||||
("LI", u"Liechtenstein"),
|
("LI", "Liechtenstein"),
|
||||||
("LT", u"Lituanie"),
|
("LT", "Lituanie"),
|
||||||
("LU", u"Luxembourg"),
|
("LU", "Luxembourg"),
|
||||||
("MO", u"Macao"),
|
("MO", "Macao"),
|
||||||
("MG", u"Madagascar"),
|
("MG", "Madagascar"),
|
||||||
("MW", u"Malawi"),
|
("MW", "Malawi"),
|
||||||
("MY", u"Malaisie"),
|
("MY", "Malaisie"),
|
||||||
("MV", u"Maldives"),
|
("MV", "Maldives"),
|
||||||
("ML", u"Mali"),
|
("ML", "Mali"),
|
||||||
("MT", u"Malte"),
|
("MT", "Malte"),
|
||||||
("MQ", u"Martinique"),
|
("MQ", "Martinique"),
|
||||||
("MR", u"Mauritanie"),
|
("MR", "Mauritanie"),
|
||||||
("MU", u"Maurice"),
|
("MU", "Maurice"),
|
||||||
("MX", u"Mexique"),
|
("MX", "Mexique"),
|
||||||
("MC", u"Monaco"),
|
("MC", "Monaco"),
|
||||||
("MN", u"Mongolie"),
|
("MN", "Mongolie"),
|
||||||
("MD", u"République de Moldova"),
|
("MD", "République de Moldova"),
|
||||||
("MS", u"Montserrat"),
|
("MS", "Montserrat"),
|
||||||
("MA", u"Maroc"),
|
("MA", "Maroc"),
|
||||||
("MZ", u"Mozambique"),
|
("MZ", "Mozambique"),
|
||||||
("OM", u"Oman"),
|
("OM", "Oman"),
|
||||||
("NA", u"Namibie"),
|
("NA", "Namibie"),
|
||||||
("NR", u"Nauru"),
|
("NR", "Nauru"),
|
||||||
("NP", u"Népal"),
|
("NP", "Népal"),
|
||||||
("NL", u"Pays-Bas"),
|
("NL", "Pays-Bas"),
|
||||||
("AN", u"Antilles Néerlandaises"),
|
("AN", "Antilles Néerlandaises"),
|
||||||
("AW", u"Aruba"),
|
("AW", "Aruba"),
|
||||||
("NC", u"Nouvelle-Calédonie"),
|
("NC", "Nouvelle-Calédonie"),
|
||||||
("VU", u"Vanuatu"),
|
("VU", "Vanuatu"),
|
||||||
("NZ", u"Nouvelle-Zélande"),
|
("NZ", "Nouvelle-Zélande"),
|
||||||
("NI", u"Nicaragua"),
|
("NI", "Nicaragua"),
|
||||||
("NE", u"Niger"),
|
("NE", "Niger"),
|
||||||
("NG", u"Nigéria"),
|
("NG", "Nigéria"),
|
||||||
("NU", u"Niué"),
|
("NU", "Niué"),
|
||||||
("NF", u"Île Norfolk"),
|
("NF", "Île Norfolk"),
|
||||||
("NO", u"Norvège"),
|
("NO", "Norvège"),
|
||||||
("MP", u"Îles Mariannes du Nord"),
|
("MP", "Îles Mariannes du Nord"),
|
||||||
("UM", u"Îles Mineures Éloignées des États-Unis"),
|
("UM", "Îles Mineures Éloignées des États-Unis"),
|
||||||
("FM", u"États Fédérés de Micronésie"),
|
("FM", "États Fédérés de Micronésie"),
|
||||||
("MH", u"Îles Marshall"),
|
("MH", "Îles Marshall"),
|
||||||
("PW", u"Palaos"),
|
("PW", "Palaos"),
|
||||||
("PK", u"Pakistan"),
|
("PK", "Pakistan"),
|
||||||
("PA", u"Panama"),
|
("PA", "Panama"),
|
||||||
("PG", u"Papouasie-Nouvelle-Guinée"),
|
("PG", "Papouasie-Nouvelle-Guinée"),
|
||||||
("PY", u"Paraguay"),
|
("PY", "Paraguay"),
|
||||||
("PE", u"Pérou"),
|
("PE", "Pérou"),
|
||||||
("PH", u"Philippines"),
|
("PH", "Philippines"),
|
||||||
("PN", u"Pitcairn"),
|
("PN", "Pitcairn"),
|
||||||
("PL", u"Pologne"),
|
("PL", "Pologne"),
|
||||||
("PT", u"Portugal"),
|
("PT", "Portugal"),
|
||||||
("GW", u"Guinée-Bissau"),
|
("GW", "Guinée-Bissau"),
|
||||||
("TL", u"Timor-Leste"),
|
("TL", "Timor-Leste"),
|
||||||
("PR", u"Porto Rico"),
|
("PR", "Porto Rico"),
|
||||||
("QA", u"Qatar"),
|
("QA", "Qatar"),
|
||||||
("RE", u"Réunion"),
|
("RE", "Réunion"),
|
||||||
("RO", u"Roumanie"),
|
("RO", "Roumanie"),
|
||||||
("RU", u"Fédération de Russie"),
|
("RU", "Fédération de Russie"),
|
||||||
("RW", u"Rwanda"),
|
("RW", "Rwanda"),
|
||||||
("SH", u"Sainte-Hélène"),
|
("SH", "Sainte-Hélène"),
|
||||||
("KN", u"Saint-Kitts-et-Nevis"),
|
("KN", "Saint-Kitts-et-Nevis"),
|
||||||
("AI", u"Anguilla"),
|
("AI", "Anguilla"),
|
||||||
("LC", u"Sainte-Lucie"),
|
("LC", "Sainte-Lucie"),
|
||||||
("PM", u"Saint-Pierre-et-Miquelon"),
|
("PM", "Saint-Pierre-et-Miquelon"),
|
||||||
("VC", u"Saint-Vincent-et-les Grenadines"),
|
("VC", "Saint-Vincent-et-les Grenadines"),
|
||||||
("SM", u"Saint-Marin"),
|
("SM", "Saint-Marin"),
|
||||||
("ST", u"Sao Tomé-et-Principe"),
|
("ST", "Sao Tomé-et-Principe"),
|
||||||
("SA", u"Arabie Saoudite"),
|
("SA", "Arabie Saoudite"),
|
||||||
("SN", u"Sénégal"),
|
("SN", "Sénégal"),
|
||||||
("SC", u"Seychelles"),
|
("SC", "Seychelles"),
|
||||||
("SL", u"Sierra Leone"),
|
("SL", "Sierra Leone"),
|
||||||
("SG", u"Singapour"),
|
("SG", "Singapour"),
|
||||||
("SK", u"Slovaquie"),
|
("SK", "Slovaquie"),
|
||||||
("VN", u"Viet Nam"),
|
("VN", "Viet Nam"),
|
||||||
("SI", u"Slovénie"),
|
("SI", "Slovénie"),
|
||||||
("SO", u"Somalie"),
|
("SO", "Somalie"),
|
||||||
("ZA", u"Afrique du Sud"),
|
("ZA", "Afrique du Sud"),
|
||||||
("ZW", u"Zimbabwe"),
|
("ZW", "Zimbabwe"),
|
||||||
("ES", u"Espagne"),
|
("ES", "Espagne"),
|
||||||
("EH", u"Sahara Occidental"),
|
("EH", "Sahara Occidental"),
|
||||||
("SD", u"Soudan"),
|
("SD", "Soudan"),
|
||||||
("SR", u"Suriname"),
|
("SR", "Suriname"),
|
||||||
("SJ", u"Svalbard etÎle Jan Mayen"),
|
("SJ", "Svalbard etÎle Jan Mayen"),
|
||||||
("SZ", u"Swaziland"),
|
("SZ", "Swaziland"),
|
||||||
("SE", u"Suède"),
|
("SE", "Suède"),
|
||||||
("CH", u"Suisse"),
|
("CH", "Suisse"),
|
||||||
("SY", u"République Arabe Syrienne"),
|
("SY", "République Arabe Syrienne"),
|
||||||
("TJ", u"Tadjikistan"),
|
("TJ", "Tadjikistan"),
|
||||||
("TH", u"Thaïlande"),
|
("TH", "Thaïlande"),
|
||||||
("TG", u"Togo"),
|
("TG", "Togo"),
|
||||||
("TK", u"Tokelau"),
|
("TK", "Tokelau"),
|
||||||
("TO", u"Tonga"),
|
("TO", "Tonga"),
|
||||||
("TT", u"Trinité-et-Tobago"),
|
("TT", "Trinité-et-Tobago"),
|
||||||
("AE", u"Émirats Arabes Unis"),
|
("AE", "Émirats Arabes Unis"),
|
||||||
("TN", u"Tunisie"),
|
("TN", "Tunisie"),
|
||||||
("TR", u"Turquie"),
|
("TR", "Turquie"),
|
||||||
("TM", u"Turkménistan"),
|
("TM", "Turkménistan"),
|
||||||
("TC", u"Îles Turks et Caïques"),
|
("TC", "Îles Turks et Caïques"),
|
||||||
("TV", u"Tuvalu"),
|
("TV", "Tuvalu"),
|
||||||
("UG", u"Ouganda"),
|
("UG", "Ouganda"),
|
||||||
("UA", u"Ukraine"),
|
("UA", "Ukraine"),
|
||||||
("MK", u"L'ex-République Yougoslave de Macédoine"),
|
("MK", "L'ex-République Yougoslave de Macédoine"),
|
||||||
("EG", u"Égypte"),
|
("EG", "Égypte"),
|
||||||
("GB", u"Royaume-Uni"),
|
("GB", "Royaume-Uni"),
|
||||||
("IM", u"Île de Man"),
|
("IM", "Île de Man"),
|
||||||
("TZ", u"République-Unie de Tanzanie"),
|
("TZ", "République-Unie de Tanzanie"),
|
||||||
("US", u"États-Unis"),
|
("US", "États-Unis"),
|
||||||
("VI", u"Îles Vierges des États-Unis"),
|
("VI", "Îles Vierges des États-Unis"),
|
||||||
("BF", u"Burkina Faso"),
|
("BF", "Burkina Faso"),
|
||||||
("UY", u"Uruguay"),
|
("UY", "Uruguay"),
|
||||||
("UZ", u"Ouzbékistan"),
|
("UZ", "Ouzbékistan"),
|
||||||
("VE", u"Venezuela"),
|
("VE", "Venezuela"),
|
||||||
("WF", u"Wallis et Futuna"),
|
("WF", "Wallis et Futuna"),
|
||||||
("WS", u"Samoa"),
|
("WS", "Samoa"),
|
||||||
("YE", u"Yémen"),
|
("YE", "Yémen"),
|
||||||
("CS", u"Serbie-et-Monténégro"),
|
("CS", "Serbie-et-Monténégro"),
|
||||||
("ZM", u"Zambie"),
|
("ZM", "Zambie"),
|
||||||
)
|
)
|
||||||
|
|
13
avisstage/templates/404.html
Normal file
13
avisstage/templates/404.html
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{% extends "avisstage/base.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
|
||||||
|
{% block title %}Page non trouvée{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<article>
|
||||||
|
<section>
|
||||||
|
<h1>Page non trouvée</h1>
|
||||||
|
<p>Cette page n'existe pas, ou peut-être que vous n'y avez pas accès.</p>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
{% endblock %}
|
14
avisstage/templates/avisstage/403-archicubes.html
Normal file
14
avisstage/templates/avisstage/403-archicubes.html
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{% extends "avisstage/base.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
|
||||||
|
{% block title %}Accès interdit{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<article>
|
||||||
|
<section>
|
||||||
|
<h1>Accès réservé aux personnes en scolarité</h1>
|
||||||
|
<p>Vous pouvez pas consulter cette page : après la fin de votre scolarité, vous ne pouvez accéder qu'à votre profil et vos fiches de stages pour les tenir à jour.</p>
|
||||||
|
<p><a href="{% url "avisstage:perso" %}">Aller à mon tableau de bord</a></p>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
{% endblock %}
|
|
@ -5,8 +5,6 @@
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>{% block title %}ExperiENS{% endblock %}</title>
|
<title>{% block title %}ExperiENS{% endblock %}</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0; maximum-scale=1.0; minimum-scale=1.0;">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0; maximum-scale=1.0; minimum-scale=1.0;">
|
||||||
<link type="text/css" rel="stylesheet" href="{% static 'css/jquery-ui.min.css' %}" />
|
|
||||||
<link type="text/css" rel="stylesheet" href="{% static 'css/screen.css' %}" />
|
|
||||||
<script type="text/javascript" src="{% static "js/jquery-3.2.0.min.js" %}"></script>
|
<script type="text/javascript" src="{% static "js/jquery-3.2.0.min.js" %}"></script>
|
||||||
<script type="text/javascript" src="{% static "js/jquery-ui.min.js" %}"></script>
|
<script type="text/javascript" src="{% static "js/jquery-ui.min.js" %}"></script>
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
|
@ -16,9 +14,12 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% block extra_head %}{% endblock %}
|
{% block extra_head %}{% endblock %}
|
||||||
|
|
||||||
|
<link type="text/css" rel="stylesheet" href="{% static 'css/jquery-ui.min.css' %}" />
|
||||||
|
<link type="text/css" rel="stylesheet" href="{% static 'css/screen.css' %}" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body class="{% block bodyclass %}{% endblock %}">
|
||||||
<header>
|
<header>
|
||||||
<h1><a href="{% url 'avisstage:index' %}">ExperiENS{# <span class='beta'>beta</span>#}</a></h1>
|
<h1><a href="{% url 'avisstage:index' %}">ExperiENS{# <span class='beta'>beta</span>#}</a></h1>
|
||||||
|
|
||||||
|
@ -27,16 +28,18 @@
|
||||||
<ul id="menu">
|
<ul id="menu">
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<li><a href="{% url 'avisstage:perso' %}">Mon expérience</a></li>
|
<li><a href="{% url 'avisstage:perso' %}">Mon expérience</a></li>
|
||||||
|
{% if user.profil.en_scolarite %}
|
||||||
<li><a href="{% url 'avisstage:recherche' %}">Recherche</a></li>
|
<li><a href="{% url 'avisstage:recherche' %}">Recherche</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
<li><a href="{% url 'avisstage:faq' %}">FAQ</a></li>
|
<li><a href="{% url 'avisstage:faq' %}">FAQ</a></li>
|
||||||
{% if user.is_staff %}
|
{% if user.is_staff %}
|
||||||
<li><a href="{% url 'avisstage:moderation' %}">Modo</a></li>
|
<li><a href="{% url 'avisstage:moderation' %}">Modo</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<li><a href="{% url 'logout' %}"><span class="username">{{ user.username }}</span><br/> Déconnexion</a></li>
|
<li><a href="{% url "authens:logout" %}"><span class="username">{{ user.username }}</span><br/> Déconnexion</a></li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li><a href="{% url 'login' %}">Connexion</a></li>
|
<li><a href="{% url "authens:login" %}">Connexion</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
19
avisstage/templates/avisstage/compte/aconfirmer.html
Normal file
19
avisstage/templates/avisstage/compte/aconfirmer.html
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{% extends "avisstage/base.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
|
||||||
|
{% block title %}Confirmation requise - ExperiENS{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>Confirmation requise</h1>
|
||||||
|
|
||||||
|
<article>
|
||||||
|
{% if object.confirmed_at %}
|
||||||
|
<p>L'adresse {{ object.email }} a déjà été confirmée.</p>
|
||||||
|
{% else %}
|
||||||
|
<p>Un mail de confirmation vous a été envoyé à l'adresse {{ object.email }} pour la vérifier.</p>
|
||||||
|
<p>Merci de cliquer sur le lien inclus pour confirmer qu'elle est correcte.</p>
|
||||||
|
<p>Si vous ne recevez rien, vérifier dans vos indésirables.</p>
|
||||||
|
{% endif %}
|
||||||
|
<p><a href="{% url "avisstage:parametres" %}">Retour</a></p>
|
||||||
|
</article>
|
||||||
|
{% endblock %}
|
29
avisstage/templates/avisstage/compte/edit_mdp.html
Normal file
29
avisstage/templates/avisstage/compte/edit_mdp.html
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
{% extends "avisstage/base.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>Définir un mot de passe</h1>
|
||||||
|
<form action="" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.non_field_errors }}
|
||||||
|
<div class="field">
|
||||||
|
<label>Nom d'utilisateur</label>
|
||||||
|
<div class="input">
|
||||||
|
{{ user.username }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% for field in form %}
|
||||||
|
{{ field.errors }}
|
||||||
|
<div class="field">
|
||||||
|
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
|
||||||
|
<div class="input">
|
||||||
|
{{ field }}
|
||||||
|
{% if field.help_text %}
|
||||||
|
<p class="help_text">{{ field.help_text }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
<input type="submit" value="Enregistrer" />
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
18
avisstage/templates/avisstage/compte/email_supprime.html
Normal file
18
avisstage/templates/avisstage/compte/email_supprime.html
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{% extends "avisstage/base.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
|
||||||
|
{% block title %}Supprimer une adresse mail - ExperiENS{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>Supprimer une adresse mail</h1>
|
||||||
|
|
||||||
|
<article>
|
||||||
|
<section class="profil">
|
||||||
|
<form action="" method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
<p>Êtes-vous sûr⋅e de vouloir supprimer l'adresse mail {{ object.email }} ?</p>
|
||||||
|
<p><a href="{% url "avisstage:parametres" %}">Retour</a> <input type="submit" value="Confirmer la suppression"></p>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
{% endblock %}
|
76
avisstage/templates/avisstage/compte/parametres.html
Normal file
76
avisstage/templates/avisstage/compte/parametres.html
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
{% extends "avisstage/base.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
|
||||||
|
{% block title %}Mes paramètres - ExperiENS{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>Mes paramètres</h1>
|
||||||
|
|
||||||
|
<article>
|
||||||
|
<h2>Adresses e-mail</h2>
|
||||||
|
<ul class="mes-emails">
|
||||||
|
{% for email in request.user.email_address_set.all %}
|
||||||
|
<li>
|
||||||
|
<span class="adresse">{{ email.email }}
|
||||||
|
<span class="confirmee">{{ email.confirmed_at|yesno:"✓,✗"|safe }}</span></span>
|
||||||
|
{% if email.confirmed_at %}
|
||||||
|
<span class="principale">
|
||||||
|
{% if email.email == user.email %}
|
||||||
|
Principale
|
||||||
|
{% else %}
|
||||||
|
<form action="{% url "avisstage:emails_principal" email.email %}" method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="submit" value="Rendre principale">
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
<span class="confirmer">
|
||||||
|
<form action="{% url "avisstage:emails_reconfirme" email.email %}" method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="submit" value="Renvoyer le lien de confirmation">
|
||||||
|
</form>
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
<span class="supprimer">
|
||||||
|
{% if not email.email == user.email %}
|
||||||
|
<a href="{% url "avisstage:emails_supprime" email.email %}">Supprimer</a>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
<li>
|
||||||
|
<form action="" method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.non_field_errors }}
|
||||||
|
{% for field in form %}
|
||||||
|
{{ field.errors }}
|
||||||
|
<div class="field">
|
||||||
|
<div class="input">
|
||||||
|
{{ field }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
<input type="submit" value="Ajouter l'adresse">
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article>
|
||||||
|
<h2>Mot de passe</h2>
|
||||||
|
<section class="profil">
|
||||||
|
{% if request.user.password and request.user.has_usable_password %}
|
||||||
|
<p>Un mot de passe interne est déjà défini pour ce compte.</p>
|
||||||
|
{% else %}
|
||||||
|
<p>Aucun mot de passe n'est défini pour ce compte. Créez-en un pour pouvoir vous connecter après la fin de votre scolarité à l'ENS.</p>
|
||||||
|
{% endif %}
|
||||||
|
<form action="{% url "avisstage:mdp_demande" %}" method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="submit" value="Définir un nouveau mot de passe" />
|
||||||
|
</form>
|
||||||
|
<p>En cliquant sur ce bouton, un lien unique vous sera envoyé à votre adresse e-mail principale ({{ request.user.email }}) qui vous donnera accès au formulaire d'édition du mot de passe.</p>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
{% endblock %}
|
|
@ -5,10 +5,10 @@
|
||||||
<script type="text/javascript" src="{% static "js/render.js" %}"></script>
|
<script type="text/javascript" src="{% static "js/render.js" %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block title %}Profil de {{ object.nom }} - ExperiENS{% endblock %}
|
{% block title %}Profil de {{ object.nom_complet }} - ExperiENS{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Profil de {{ object.nom }}
|
<h1>Profil de {{ object.nom_complet }}
|
||||||
{% if object.user == user %}
|
{% if object.user == user %}
|
||||||
<a href="{% url "avisstage:profil_edit" %}" class="btn edit-btn">Modifier mes infos</a>
|
<a href="{% url "avisstage:profil_edit" %}" class="btn edit-btn">Modifier mes infos</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -18,7 +18,7 @@
|
||||||
<p class="promo">Promotion : <b>{{ object.promotion }}</b></p>
|
<p class="promo">Promotion : <b>{{ object.promotion }}</b></p>
|
||||||
<p class="contact">
|
<p class="contact">
|
||||||
{% if object.contactez_moi %}
|
{% if object.contactez_moi %}
|
||||||
Contact : <a href="mailto:{{ object.mail }}">{{ object.mail }}</a>
|
Contact : <a href="mailto:{{ object.preferred_email }}">{{ object.preferred_email }}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
{% block extra_head %}
|
{% block extra_head %}
|
||||||
<script src="{% static 'js/toc.min.js' %}" type="text/javascript"></script>
|
<script src="{% static 'js/toc.min.js' %}" type="text/javascript"></script>
|
||||||
<script type="text/javascript" src="{% static "js/leaflet.js" %}"></script>
|
<script type="text/javascript" src="{% static "js/leaflet.js" %}"></script>
|
||||||
|
<script src='https://api.mapbox.com/mapbox-gl-js/v2.0.0/mapbox-gl.js'></script>
|
||||||
|
<link href='https://api.mapbox.com/mapbox-gl-js/v2.0.0/mapbox-gl.css' rel='stylesheet' />
|
||||||
|
<script src="https://cdn.maptiler.com/mapbox-gl-leaflet/latest/leaflet-mapbox-gl.js"></script>
|
||||||
<link rel="stylesheet" type="text/css" href="{% static "css/leaflet.css" %}" />
|
<link rel="stylesheet" type="text/css" href="{% static "css/leaflet.css" %}" />
|
||||||
<script type="text/javascript" src="{% static "js/tile.stamen.js" %}"></script>
|
<script type="text/javascript" src="{% static "js/tile.stamen.js" %}"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
@ -13,7 +16,12 @@
|
||||||
var STATIC_ROOT = "{{ STATIC_URL|escapejs }}";
|
var STATIC_ROOT = "{{ STATIC_URL|escapejs }}";
|
||||||
function initStageMap(lieux) {
|
function initStageMap(lieux) {
|
||||||
var map = L.map("stage-map");
|
var map = L.map("stage-map");
|
||||||
var layer = new L.TileLayer("https://korona.geog.uni-heidelberg.de/tiles/roads/x={x}&y={y}&z={z}", {attribution: 'Map tiles by <a href="http://korona.geog.uni-heidelberg.de/">GIScience Heidelberg</a>'});
|
var layer = L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', {
|
||||||
|
attribution: 'Map data © <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
|
||||||
|
maxZoom: 18,
|
||||||
|
id: 'mapbox/streets-v11',
|
||||||
|
accessToken: "{{ MAPBOX_API_KEY|escapejs }}"
|
||||||
|
});
|
||||||
map.addLayer(layer);
|
map.addLayer(layer);
|
||||||
|
|
||||||
function makeIcon(couleur){
|
function makeIcon(couleur){
|
||||||
|
@ -51,22 +59,25 @@
|
||||||
<p>Cette page n'est qu'un brouillon, vous seul pouvez le voir. <input type="submit" value="Publier ce stage" name="publier" /></p>
|
<p>Cette page n'est qu'un brouillon, vous seul pouvez le voir. <input type="submit" value="Publier ce stage" name="publier" /></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
|
{% if not object.all_lieux %}
|
||||||
|
<p class="warning">Vous n'avez pas indiqué de lieu pour cette expérience. Pensez à en <a href="{% url "avisstage:stage_edit" object.id %}">ajouter un</a> pour pouvoir donner un avis sur cet endroit, et que votre expérience apparaisse sur la carte.</p>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<article class="stage">
|
<article class="stage">
|
||||||
<section class="misc">
|
<section class="misc">
|
||||||
<div class="misc-content {% if object.lieux.all %}withmap{% endif %}">
|
<div class="misc-content {% if object.all_lieux %}withmap{% endif %}">
|
||||||
<div class="desc">
|
<div class="desc">
|
||||||
<div class="misc-hdr">
|
<div class="misc-hdr">
|
||||||
<h1>{{ object.sujet }}</h1>
|
<h1>{{ object.sujet }}</h1>
|
||||||
<p class="dates"><span class="year">{{ object.date_debut|date:"Y" }}</span><span class="debut">{{ object.date_debut|date:"d/m" }}</span><span class="fin">{{ object.date_fin|date:"d/m" }}</span></p>
|
<p class="dates"><span class="year">{{ object.date_debut|date:"Y" }}</span><span class="debut">{{ object.date_debut|date:"d/m" }}</span><span class="fin">{{ object.date_fin|date:"d/m" }}</span></p>
|
||||||
</div>
|
</div>
|
||||||
<p><a href="{% url "avisstage:profil" object.auteur.user.username %}">{{ object.auteur.nom }}</a>
|
<p><a href="{% url "avisstage:profil" object.auteur.user.username %}">{{ object.auteur.nom_complet }}</a>
|
||||||
a fait {{ object.type_stage_fem|yesno:"cette,ce" }} <b>{{ object.type_stage_fancy }}</b>
|
a fait {{ object.type_stage_fem|yesno:"cette,ce" }} <b>{{ object.type_stage_fancy }}</b>
|
||||||
{% if object.niveau_scol %}{{ object.niveau_scol_fancy }},{% endif %}
|
{% if object.niveau_scol %}{{ object.niveau_scol_fancy }},{% endif %}
|
||||||
{% if object.structure %}au sein de {{ object.structure }}{% endif %}{% if object.encadrants %}, supervisé par {{ object.encadrants }}{% endif %}.</p>
|
{% if object.structure %}au sein de {{ object.structure }}{% endif %}{% if object.encadrants %}, supervisé par {{ object.encadrants }}{% endif %}.</p>
|
||||||
{% if object.lieux.all %}<p>Cela s'est passé à :
|
{% if object.all_lieux %}<p>Cela s'est passé à :
|
||||||
{% for lieu in object.lieux.all %}{{ lieu.nom }} ({{ lieu.ville }}){% if not forloop.last %}, {% endif %}{% endfor %}.</p>
|
{% for lieu in object.all_lieux %}{{ lieu.nom }} ({{ lieu.ville }}){% if not forloop.last %}, {% endif %}{% endfor %}.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<ul class="infos">
|
<ul class="infos">
|
||||||
|
@ -79,12 +90,12 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if object.lieux.all %}
|
{% if object.all_lieux %}
|
||||||
<div class="map">
|
<div class="map">
|
||||||
<div id="stage-map"></div>
|
<div id="stage-map"></div>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var lieux = [
|
var lieux = [
|
||||||
{% for lieu in object.lieux.all %}
|
{% for lieu in object.all_lieux %}
|
||||||
{
|
{
|
||||||
coord: {lat: "{{ lieu.coord.y|escapejs }}", lon: "{{ lieu.coord.x|escapejs }}" },
|
coord: {lat: "{{ lieu.coord.y|escapejs }}", lon: "{{ lieu.coord.x|escapejs }}" },
|
||||||
popup: "<h3>{{ lieu.nom|escapejs }}</h3>" +
|
popup: "<h3>{{ lieu.nom|escapejs }}</h3>" +
|
||||||
|
|
|
@ -74,7 +74,8 @@
|
||||||
|
|
||||||
<h3>Je n'ai plus de compte clipper, puis-je accéder à ce site ?</h3>
|
<h3>Je n'ai plus de compte clipper, puis-je accéder à ce site ?</h3>
|
||||||
<p>Pour conserver l'accès à ce site limité, et garantir une certaine liberté de parole, seuls les normalien⋅ne⋅s en scolarité ont accès au site entier.</p>
|
<p>Pour conserver l'accès à ce site limité, et garantir une certaine liberté de parole, seuls les normalien⋅ne⋅s en scolarité ont accès au site entier.</p>
|
||||||
<p>En revanche, si vous écrivez des avis ici, vous pourrez toujours les modifier ou les supprimer. Une procédure de connexion spécifique est prévue, mais pas encore implémentée.</p>
|
<p>En revanche, si vous écrivez des avis ici, vous pourrez toujours les modifier ou les supprimer. Il suffira d'utiliser l'accès archicubes, avec des identifiants spécifiques à ce site.</p>
|
||||||
|
<p>Si vous aviez un compte dont vous avez perdu l'accès, vous pouvez contacter le <a href="https://www.eleves.ens.fr/home/klub-dev/">Klub Dev ENS</a> pour qu'on vous donne des accès.
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article>
|
<article>
|
||||||
|
@ -88,7 +89,7 @@
|
||||||
<p>Faites-en part en cliquant sur le bouton feedback, on est preneur !</p>
|
<p>Faites-en part en cliquant sur le bouton feedback, on est preneur !</p>
|
||||||
|
|
||||||
<h3>Qui est derrière ?</h3>
|
<h3>Qui est derrière ?</h3>
|
||||||
<p>Cette plateforme a été réalisée par <a href="http://www.robin-champenois.fr">Robin Champenois</a> (Info 2012) en django, sur une idée originale de Damien Moulin (Physique 2013). Le code source est disponible <a href="https://git.eleves.ens.fr/champeno/experiENS">ici</a>. Le site est hébergé sur le serveur des élèves.</p>
|
<p>Cette plateforme a été lancée en 2017 par <a href="http://www.robin-champenois.fr">Robin Champenois</a> (Info 2012) en django, sur une idée originale de Damien Moulin (Physique 2013). Il est désormais maintenu et développé au sein du <a href="https://www.eleves.ens.fr/home/klub-dev/">Klub Dev ENS</a>. Le code source est disponible <a href="https://git.eleves.ens.fr/klub-dev-ens/experiENS">ici</a>. Le site est hébergé sur le serveur des élèves.</p>
|
||||||
|
|
||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -18,6 +18,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<input type="submit" />
|
<div class="field">
|
||||||
|
<label>Adresse e-mail</label>
|
||||||
|
<div class="input">
|
||||||
|
{{ request.user.email }}
|
||||||
|
<p class="help_text">Allez dans <a href="{% url "avisstage:parametres" %}">les paramètres de connexion</a> pour modifier votre adresse principale</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input type="submit" value="Enregistrer" />
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
{% block title %}{% if creation %}Nouvelle expérience{% else %}Modification d'une expérience{% endif %}{% endblock %}
|
{% block title %}{% if creation %}Nouvelle expérience{% else %}Modification d'une expérience{% endif %}{% endblock %}
|
||||||
|
|
||||||
{% block extra_head %}
|
{% block extra_head %}
|
||||||
<script type="text/javascript" src="//maps.googleapis.com/maps/api/js?libraries=places&key=AIzaSyDd4innPShfHcW8KDJB833vZHZSsqt-ACw"></script>
|
<script type="text/javascript" src="//maps.googleapis.com/maps/api/js?libraries=places&key={{ GOOGLE_API_KEY }}"></script>
|
||||||
<script type="text/javascript" src="{% static "js/leaflet.js" %}"></script>
|
<script type="text/javascript" src="{% static "js/leaflet.js" %}"></script>
|
||||||
<script type="text/javascript" src="{% static "js/leaflet-gplaces-autocomplete.js" %}"></script>
|
<script type="text/javascript" src="{% static "js/leaflet-gplaces-autocomplete.js" %}"></script>
|
||||||
<script type="text/javascript" src="{% static "js/leaflet.markercluster.js" %}"></script>
|
<script type="text/javascript" src="{% static "js/leaflet.markercluster.js" %}"></script>
|
||||||
|
@ -16,14 +16,19 @@
|
||||||
<link rel="stylesheet" type="text/css" href="{% static "css/MarkerCluster.Default.css" %}" />
|
<link rel="stylesheet" type="text/css" href="{% static "css/MarkerCluster.Default.css" %}" />
|
||||||
<script type="text/javascript" src="{% static "js/selectize.min.js" %}"></script>
|
<script type="text/javascript" src="{% static "js/selectize.min.js" %}"></script>
|
||||||
<link rel="stylesheet" type="text/css" href="{% static "css/selectize.css" %}" />
|
<link rel="stylesheet" type="text/css" href="{% static "css/selectize.css" %}" />
|
||||||
<script type="text/javascript" src="{% static "js/editstage.js" %}"></script>
|
<link href="{% static "jquery-autosuggest/css/autoSuggest-upshot.css" %}" type="text/css" media="all" rel="stylesheet" />
|
||||||
|
<script type="text/javascript" src="{% static "js/editstage.js" %}?v2"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$(function(){
|
$(function(){
|
||||||
initEditStage(
|
initEditStage(
|
||||||
"{{ STATIC_URL|escapejs }}",
|
"{{ STATIC_URL|escapejs }}",
|
||||||
"{% url 'avisstage:api_dispatch_list' resource_name="lieu" api_name="v1" %}");
|
"{% url 'avisstage:api_dispatch_list' resource_name="lieu" api_name="v1" %}",
|
||||||
|
"{{ MAPBOX_API_KEY|escapejs }}");
|
||||||
});
|
});
|
||||||
|
var django = {};
|
||||||
|
django.jQuery = $;
|
||||||
</script>
|
</script>
|
||||||
|
<script type="text/javascript" src="{% static "jquery-autosuggest/js/jquery.autoSuggest.minified.js" %}"> </script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
|
@ -11,8 +11,9 @@
|
||||||
|
|
||||||
{% if not user.is_authenticated %}
|
{% if not user.is_authenticated %}
|
||||||
<div class="entrer">
|
<div class="entrer">
|
||||||
<p><a href="{% url 'login' %}" class="btn">Connexion</a></p>
|
<p><a href="{% url "authens:login.cas" %}" class="btn">Connexion</a></p>
|
||||||
<p class="helptext">Connexion via le serveur central d'authentification ENS (identifiants clipper)</p>
|
<p class="helptext">Connexion via le serveur central d'authentification ENS <br />(identifiants clipper)</p>
|
||||||
|
<p class="archicubes"><a href="{% url "authens:login.pwd" %}">Accès archicubes</a> <br /><i>Pour continuer à tenir à jour ses fiches, sans voir celles des autres</i></p>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
@ -23,7 +24,13 @@
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p>Ne partez plus en stage en terre inconnue : nourrissez-vous des {{ num_stages }} expériences de séjours effectués par la communauté normalienne, repérez les bons plans, et ne faites pas les mêmes erreurs !</p>
|
<p>Ne partez plus en stage en terre inconnue : nourrissez-vous des {{ num_stages }} expériences de séjours effectués par la communauté normalienne, repérez les bons plans, et ne faites pas les mêmes erreurs !</p>
|
||||||
{% if user.is_authenticated %}<p><a href="{% url 'avisstage:recherche' %}" class="btn">Rechercher des stages</a></p>{% endif %}
|
{% if user.is_authenticated %}
|
||||||
|
{% if user.profil.en_scolarite %}
|
||||||
|
<p><a href="{% url 'avisstage:recherche' %}" class="btn">Rechercher des stages</a></p>
|
||||||
|
{% else %}
|
||||||
|
<p><i>Accès restreint aux personnes en scolarité</i></p>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="explications">
|
<div class="explications">
|
||||||
|
@ -36,8 +43,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="betacadre">
|
<div class="footer">
|
||||||
Ce site est en développement actif, et fait l'objet de mises à jours régulières. N'hésitez pas à donner votre avis en utilisant le bouton "feedback".
|
Un projet du <a href="https://www.eleves.ens.fr/home/klub-dev/">Klub Dev ENS</a>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
8
avisstage/templates/avisstage/mails/reinit_mdp.html
Normal file
8
avisstage/templates/avisstage/mails/reinit_mdp.html
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
Bonjour,
|
||||||
|
|
||||||
|
Pour créer ou modifier le mot de passe associé à votre compte {{ user.get_username }}, merci de cliquer sur le lien suivant ou de le copier dans votre navigateur :
|
||||||
|
|
||||||
|
{{ protocol }}://{{ domain }}{% url 'avisstage:mdp_edit' uidb64=uid token=token %}
|
||||||
|
|
||||||
|
Cordialement,
|
||||||
|
L'équipe ExperiENS
|
1
avisstage/templates/avisstage/mails/reinit_mdp.txt
Normal file
1
avisstage/templates/avisstage/mails/reinit_mdp.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
[ExperiENS] Définition du mot de passe
|
|
@ -11,10 +11,22 @@
|
||||||
<article>
|
<article>
|
||||||
<h2>Stages</h2>
|
<h2>Stages</h2>
|
||||||
<p>{{ num_stages }} stages créés, {{ num_stages_pub }} stages publiés</p>
|
<p>{{ num_stages }} stages créés, {{ num_stages_pub }} stages publiés</p>
|
||||||
<p>{% for npm in num_par_matiere %}
|
<table class="stats">
|
||||||
{{ npm.scount }} en {{ npm.matieres__nom }},
|
<tbody>
|
||||||
|
<tr><th>Matière</th><th>Fiches publiques</th><th>Brouillons</th></tr>
|
||||||
|
{% for npm in num_par_matiere %}
|
||||||
|
<tr><td>{{ npm.matiere }}</td><td>{{ npm.publics }}</td><td>{{ npm.drafts }}</td></tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</p>
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<table class="stats">
|
||||||
|
<tbody>
|
||||||
|
<tr><th>Longueur des avis</th><th>Sur le stage</th><th>Sur les lieux</th></tr>
|
||||||
|
{% for longueur, nlstages, nllieux in num_par_longueur %}
|
||||||
|
<tr><td>{{ longueur }}</td><td>{{ nlstages }}</td><td>{{ nllieux }}</td></tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
<h2>Utilisateurs</h2>
|
<h2>Utilisateurs</h2>
|
||||||
<p>{{ num_users }} utilisateurs connectés au moins une fois, {{ num_auteurs }} ont écrit une fiche</p>
|
<p>{{ num_users }} utilisateurs connectés au moins une fois, {{ num_auteurs }} ont écrit une fiche</p>
|
||||||
<p>{% for nsta, naut in num_par_auteur %}
|
<p>{% for nsta, naut in num_par_auteur %}
|
||||||
|
|
|
@ -4,7 +4,60 @@
|
||||||
{% block title %}Espace personnel - ExperiENS{% endblock %}
|
{% block title %}Espace personnel - ExperiENS{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Bonjour {{ user.profil.nom }} !</h1>
|
<h1>Bonjour {{ user.profil.nom_complet }} !</h1>
|
||||||
|
|
||||||
|
<article>
|
||||||
|
<h2>Mon compte</h2>
|
||||||
|
<section class="two-cols">
|
||||||
|
<section class="profil">
|
||||||
|
{% if user.profil.en_scolarite %}
|
||||||
|
<h3 class="scolarite">Statut : En scolarité</h3>
|
||||||
|
<p>Vous pouvez accéder à l'ensemble du site, et aux fiches de stages.</p>
|
||||||
|
<p>Quand vous n'aurez plus de compte clipper (après votre scolarité), votre accès sera restreint à vos propres expériences, que vous pourrez ajouter, modifier, supprimer.</p>
|
||||||
|
<p>Pensez à renseigner une adresse e-mail non-ENS pour conserver cet accès, et permettre aux futur⋅e⋅s normalien⋅ne⋅s de toujours vous contacter !</p>
|
||||||
|
{% else %}
|
||||||
|
<h3 class="scolarite">Statut : Archicube</h3>
|
||||||
|
<p>Vous ne pouvez plus accéder qu'à vos propres expériences pour les modifier, et tenir à jour votre profil.</p>
|
||||||
|
<p>Si vous êtes encore en scolarité, merci de vous <a href="{% url "authens:login.cas" %}">reconnecter en passant par le serveur d'authentification de l'ENS</a> pour mettre à jour votre statut.</p>
|
||||||
|
{% endif %}
|
||||||
|
<hr />
|
||||||
|
<p><i>Le statut est mis à jour automatiquement tous les deux mois selon le mode de connexion que vous utilisez.</i></p>
|
||||||
|
</section>
|
||||||
|
<section class="profil">
|
||||||
|
<h3>Connexion</h3>
|
||||||
|
<p><b>Adresse e-mail principale :</b><br/> {{ user.email }}</p>
|
||||||
|
{% if not user.profil.has_nonENS_email %}<p align="center" class="warning">Vous n'avez pas renseigné d'adresse mail autre que celle de l'ENS. Pensez à le faire, pour que les générations futures puissent toujours vous contacter !</p>{% endif %}
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<p><b>Mot de passe interne :</b> {% if user.password and user.has_usable_password %}Défini{% else %}Non défini{% endif %}</p>
|
||||||
|
{% if not user.password or not user.has_usable_password %}<p class="warning" align="center">Pensez à définir un mot de passe propre à ExperiENS pour garder l'accès au site quand vous n'aurez plus de compte clipper !</p>{% endif %}
|
||||||
|
{% if user.profil.en_scolarite %}<p>En scolarité, utilisez le serveur central d'authentification pour vous connecter. Quand vous n'aurez plus de compte clipper, vous devrez vous connecter directement via l'accès archicubes, avec votre login <b>{{ user.username }}</b> et le mot de passe spécifique à ExperiENS que vous aurez défini.</p>{% endif %}
|
||||||
|
<hr/>
|
||||||
|
<p><a href="{% url "avisstage:parametres" %}">Gérer mes paramètres de connexion</a></p>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article>
|
||||||
|
<h2>Mon profil public <a href="{% url "avisstage:profil" user.username %}" class="btn">Voir</a> <a href="{% url "avisstage:profil_edit" %}" class="edit-btn btn">Modifier mes infos</a></h2>
|
||||||
|
{% with object=user.profil %}
|
||||||
|
<section class="profil">
|
||||||
|
<div class="infos">
|
||||||
|
<p class="promo">Promotion : <b>{{ object.promotion }}</b></p>
|
||||||
|
<p class="contact">
|
||||||
|
{% if object.contactez_moi %}
|
||||||
|
Contact : {{ object.preferred_email }}
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% if object.bio %}
|
||||||
|
<div class="bio">{{ object.bio|linebreaks }}</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="bio"><p><i>Vous n'avez rien mis ici. <a href="{% url "avisstage:profil_edit" %}">Écrivez un peu à propos de vous !</a></i></p></div>
|
||||||
|
{% endif %}
|
||||||
|
</section>
|
||||||
|
{% endwith %}
|
||||||
|
</article>
|
||||||
|
|
||||||
<article>
|
<article>
|
||||||
<h2>Mes stages</h2>
|
<h2>Mes stages</h2>
|
||||||
|
@ -23,25 +76,4 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article>
|
|
||||||
<h2><a href="{% url "avisstage:profil" user.username %}">Mon profil public</a> <a href="{% url "avisstage:profil_edit" %}" class="edit-btn btn">Modifier mes infos</a></h2>
|
|
||||||
{% with object=user.profil %}
|
|
||||||
<section class="profil">
|
|
||||||
<div class="infos">
|
|
||||||
<p class="promo">Promotion : <b>{{ object.promotion }}</b></p>
|
|
||||||
<p class="contact">
|
|
||||||
{% if object.contactez_moi %}
|
|
||||||
Contact : {{ object.mail }}
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{% if object.bio %}
|
|
||||||
<div class="bio">{{ object.bio|linebreaks }}</div>
|
|
||||||
{% else %}
|
|
||||||
<div class="bio"><p><i>Vous n'avez rien mis ici. <a href="{% url "avisstage:profil_edit" %}">Écrivez un peu à propos de vous !</a></i></p></div>
|
|
||||||
{% endif %}
|
|
||||||
</section>
|
|
||||||
{% endwith %}
|
|
||||||
</article>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<p class="generale">
|
<p class="generale">
|
||||||
<span>
|
<span>
|
||||||
{{ form.generique }}
|
{{ form.generique }}
|
||||||
<input type="submit" action="submit" value="Chercher un stage"/>
|
<input type="submit" action="submit" value="Chercher" class="submitSearch" />
|
||||||
</span>
|
</span>
|
||||||
<a class="toggle_avancee" href="#" onclick="$('#recherche_avancee').toggleClass('expanded'); return false;">Recherche avancée</a>
|
<a class="toggle_avancee" href="#" onclick="$('#recherche_avancee').toggleClass('expanded'); return false;">Recherche avancée</a>
|
||||||
</p>
|
</p>
|
||||||
|
@ -20,7 +20,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<li class="btnsubmit">
|
<li class="btnsubmit">
|
||||||
<input type="submit" action="submit" value="Chercher un stage"/>
|
<input type="submit" action="submit" value="Chercher" class="submitSearch"/>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
{% block title %}Chercher un stage - ExperiENS{% endblock %}
|
{% block title %}Chercher un stage - ExperiENS{% endblock %}
|
||||||
|
|
||||||
{% block extra_content_class %}recherche{% endblock %}
|
{% block bodyclass %}recherche{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Chercher un stage</h1>
|
<h1>Chercher un stage</h1>
|
||||||
|
|
|
@ -6,14 +6,17 @@
|
||||||
{% block extra_head %}
|
{% block extra_head %}
|
||||||
<script type="text/javascript" src="{% static 'js/leaflet.js' %}"></script>
|
<script type="text/javascript" src="{% static 'js/leaflet.js' %}"></script>
|
||||||
<script type="text/javascript" src="{% static 'js/leaflet.markercluster.js' %}"></script>
|
<script type="text/javascript" src="{% static 'js/leaflet.markercluster.js' %}"></script>
|
||||||
<script type="text/javascript" src="{% static 'js/recherche.js' %}"></script>
|
<script type="text/javascript" src="{% static 'js/recherche.js' %}?v2"></script>
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'css/leaflet.css' %}" />
|
<link rel="stylesheet" type="text/css" href="{% static 'css/leaflet.css' %}" />
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'css/MarkerCluster.css' %}" />
|
<link rel="stylesheet" type="text/css" href="{% static 'css/MarkerCluster.css' %}" />
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'css/MarkerCluster.Default.css' %}" />
|
<link rel="stylesheet" type="text/css" href="{% static 'css/MarkerCluster.Default.css' %}" />
|
||||||
|
<script src='https://api.mapbox.com/mapbox-gl-js/v2.0.0/mapbox-gl.js'></script>
|
||||||
|
<link href='https://api.mapbox.com/mapbox-gl-js/v2.0.0/mapbox-gl.css' rel='stylesheet' />
|
||||||
|
<script src="https://cdn.maptiler.com/mapbox-gl-leaflet/latest/leaflet-mapbox-gl.js"></script>
|
||||||
<script type="text/javascript" src="{% static "js/render.js" %}"></script>
|
<script type="text/javascript" src="{% static "js/render.js" %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_content_class %}recherche {{ vue }}{% endblock %}
|
{% block bodyclass %}recherche {{ vue }}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="recherche-liste" id="recherche-liste">
|
<section class="recherche-liste" id="recherche-liste">
|
||||||
|
@ -30,42 +33,11 @@
|
||||||
<li><a href="javascript:void(0);" id="voir_carte">Carte</a></li>
|
<li><a href="javascript:void(0);" id="voir_carte">Carte</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<p class="numresults">{{ stages|length }} expérience{{ stages|length|pluralize }} trouvée{{ stages|length|pluralize }}</p>
|
<p class="numresults">{{ paginator.paginator.count }} expérience{{ paginator.paginator.count|pluralize }} trouvée{{ paginator.paginator.count|pluralize }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<ul class="stage-liste" id="resultats">
|
<ul class="stage-liste" id="resultats">
|
||||||
{% for stage in stages %}
|
{% include "avisstage/recherche/stage_items.html" %}
|
||||||
{% if tri == '-date_maj' %}
|
|
||||||
{% ifchanged stage.date_maj.date %}<li class="date-maj">Mis à jour le {{ stage.date_maj.date }}</li>{% endifchanged %}
|
|
||||||
{% endif %}
|
|
||||||
<li class="stage" id="resultat-stage-{{ stage.id }}">
|
|
||||||
<div class="misc-hdr">
|
|
||||||
<h3><a href="{% url "avisstage:stage" stage.id %}" class="stage-sujet">{{ stage.sujet }}</a><span class="auteur"> par <span class="stage-auteur">{{ stage.auteur.nom }}</span></span></h3>
|
|
||||||
<p class="dates" c-radius="30"><span class="detail"><span class="debut">{{ stage.date_debut|date:"d/m" }}</span><span class="fin">{{ stage.date_fin|date:"d/m" }}</span></span><span class="year">{{ stage.date_debut|date:"Y" }}</span></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<ul class="infos">
|
|
||||||
<li class="type">{{ stage.get_type_stage_display }}</li>
|
|
||||||
{% if stage.structure %}<li class="structure">{{ stage.structure }}</li>{% endif %}
|
|
||||||
{% for lieu in stage.lieux.all %}<li class="lieu">{{ lieu.nom }}</li>{% endfor %}
|
|
||||||
{% for matiere in stage.matieres.all %}
|
|
||||||
<li class="matiere">{{ matiere.nom }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
{% for thematique in stage.thematiques.all %}
|
|
||||||
<li class="thematique">{{ thematique.name }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
<li class="year">{{ stage.date_debut|date:"Y" }}</li>
|
|
||||||
<li class="avis-len avis-{{ stage.len_avis_stage|avis_len }}">Avis stage {{ stage.len_avis_stage|avis_len }}</li>
|
|
||||||
<li class="avis-len avis-{{ stage.len_avis_lieux|avis_len }}">Avis lieux {{ stage.len_avis_lieux|avis_len }}</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<a href="{% url "avisstage:stage" stage.id %}" class="hoverlink"> </a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
|
|
||||||
{% empty %}
|
|
||||||
<li class="stage">Aucun stage ne correspond à votre recherche et vos critères</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</article>
|
</article>
|
||||||
|
@ -82,12 +54,17 @@
|
||||||
<div id="carte"></div>
|
<div id="carte"></div>
|
||||||
<div id="vue-options2" class="vue-options">
|
<div id="vue-options2" class="vue-options">
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="javascript:void(0);" id="voir_hybride">Afficher la liste</a></li>
|
<li><a href="javascript:void(0);" id="voir_hybride">Afficher la liste et les menus</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var lieux = [{{ lieux|join:',' }}];
|
var lieux = [{{ lieux|join:',' }}];
|
||||||
var interfaceRecherche = new InterfaceRecherche("{{ STATIC_URL|escapejs }}", "{% url 'avisstage:api_dispatch_list' resource_name="lieu" api_name="v1" %}", lieux);
|
var interfaceRecherche = new InterfaceRecherche(
|
||||||
|
"{{ STATIC_URL|escapejs }}",
|
||||||
|
"{% url 'avisstage:api_dispatch_list' resource_name="lieu" api_name="v1" %}",
|
||||||
|
"{{ MAPBOX_API_KEY }}",
|
||||||
|
"{% url 'avisstage:stage_items' %}",
|
||||||
|
lieux);
|
||||||
</script>
|
</script>
|
||||||
</section>
|
</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
38
avisstage/templates/avisstage/recherche/stage_items.html
Normal file
38
avisstage/templates/avisstage/recherche/stage_items.html
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
{% load avisstage_tags %}
|
||||||
|
{% for stage in stages %}
|
||||||
|
{% if tri == '-date_maj' %}
|
||||||
|
{% ifchanged stage.date_maj.date %}<li class="date-maj">Mis à jour le {{ stage.date_maj.date }}</li>{% endifchanged %}
|
||||||
|
{% endif %}
|
||||||
|
<li class="stage" id="resultat-stage-{{ stage.id }}">
|
||||||
|
<div class="misc-hdr">
|
||||||
|
<h3><a href="{% url "avisstage:stage" stage.id %}" class="stage-sujet">{{ stage.sujet }}</a><span class="auteur"> par <span class="stage-auteur">{{ stage.auteur.nom }}</span></span></h3>
|
||||||
|
<p class="dates" c-radius="30"><span class="detail"><span class="debut">{{ stage.date_debut|date:"d/m" }}</span><span class="fin">{{ stage.date_fin|date:"d/m" }}</span></span><span class="year">{{ stage.date_debut|date:"Y" }}</span></p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<ul class="infos">
|
||||||
|
<li class="type">{{ stage.get_type_stage_display }}</li>
|
||||||
|
{% if stage.structure %}<li class="structure">{{ stage.structure }}</li>{% endif %}
|
||||||
|
{% for lieu in stage.lieux.all %}<li class="lieu">{{ lieu.nom }}</li>{% endfor %}
|
||||||
|
{% for matiere in stage.matieres.all %}
|
||||||
|
<li class="matiere">{{ matiere.nom }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
{% for thematique in stage.thematiques.all %}
|
||||||
|
<li class="thematique">{{ thematique.name }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
<li class="year">{{ stage.date_debut|date:"Y" }}</li>
|
||||||
|
<li class="avis-len avis-{{ stage.len_avis_stage|avis_len }}">Avis stage {{ stage.len_avis_stage|avis_len }}</li>
|
||||||
|
<li class="avis-len avis-{{ stage.len_avis_lieux|avis_len }}">Avis lieux {{ stage.len_avis_lieux|avis_len }}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<a href="{% url "avisstage:stage" stage.id %}" class="hoverlink"> </a>
|
||||||
|
</li>
|
||||||
|
{% empty %}
|
||||||
|
<li class="stage">Aucun stage ne correspond à votre recherche et vos critères</li>
|
||||||
|
{% endfor %}
|
||||||
|
{% if paginator %}
|
||||||
|
<li class="pagination">
|
||||||
|
{% if paginator.has_next %}
|
||||||
|
<a href="?{% url_replace request 'page' paginator.next_page_number %}" id="next-page-btn" class="btn">Plus de résultats</a>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
|
@ -4,16 +4,27 @@
|
||||||
<div class="window-bg"></div>
|
<div class="window-bg"></div>
|
||||||
<div class="window-content">
|
<div class="window-content">
|
||||||
<a class="window-closer" href="javascript:void(0);"></a>
|
<a class="window-closer" href="javascript:void(0);"></a>
|
||||||
<h2>Choisir un lieu</h2>
|
<h2 class="window-title">Choisir un lieu</h2>
|
||||||
|
|
||||||
<p>Restez général dans le lieu : choisissez l'université plutôt que le laboratoire, l'incubateur plutôt que la startup...</p>
|
<p>Restez général dans le lieu : choisissez l'université plutôt que le laboratoire, l'incubateur plutôt que la startup...</p>
|
||||||
|
|
||||||
<div class="message"></div>
|
<div class="message"></div>
|
||||||
|
|
||||||
{# UI avec carte et autocomplete #}
|
{# UI avec carte et autocomplete #}
|
||||||
|
<div class="lieu-global">
|
||||||
<div class="lieu-ui">
|
<div class="lieu-ui">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="lieu-options">
|
||||||
|
<h3>Choisir un lieu existant</h3>
|
||||||
|
<ul class="lieu-suggestions">
|
||||||
|
</ul>
|
||||||
|
<h3>Aucune suggestion correcte ?</h3>
|
||||||
|
<a href="javascript:void(0)" class="btn new-lieu-btn">
|
||||||
|
Créer un nouveau lieu ici</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{# En cas de modification #}
|
{# En cas de modification #}
|
||||||
<div class="lieu-choixmodif">
|
<div class="lieu-choixmodif">
|
||||||
<h4>Que voulez-vous faire pour le lieu : <span class="lieu-choixrappel"></span> ?</h4>
|
<h4>Que voulez-vous faire pour le lieu : <span class="lieu-choixrappel"></span> ?</h4>
|
||||||
|
@ -26,7 +37,7 @@
|
||||||
{# Formulaire de création/modification #}
|
{# Formulaire de création/modification #}
|
||||||
<div class="lieu-form">{% load staticfiles %}
|
<div class="lieu-form">{% load staticfiles %}
|
||||||
<form action="{% url 'avisstage:lieu_ajout' %}" method="post" id="lieu_ajout">
|
<form action="{% url 'avisstage:lieu_ajout' %}" method="post" id="lieu_ajout">
|
||||||
<h2>Ajouter un lieu</h2>
|
<h2 class="form-title">Ajouter un lieu</h2>
|
||||||
<p class="help_text">Vous pouvez déplacer le curseur pour indiquer précisément la bonne position</p>
|
<p class="help_text">Vous pouvez déplacer le curseur pour indiquer précisément la bonne position</p>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% for field in form.hidden_fields %}
|
{% for field in form.hidden_fields %}
|
||||||
|
|
|
@ -1,30 +1,33 @@
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
import re
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
|
|
||||||
from avisstage.forms import LieuForm, FeedbackForm
|
from avisstage.forms import FeedbackForm, LieuForm
|
||||||
import re
|
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
@register.inclusion_tag('avisstage/templatetags/widget_lieu.html')
|
|
||||||
|
@register.inclusion_tag("avisstage/templatetags/widget_lieu.html")
|
||||||
def lieu_widget():
|
def lieu_widget():
|
||||||
form = LieuForm()
|
form = LieuForm()
|
||||||
return {"form": form}
|
return {"form": form}
|
||||||
|
|
||||||
@register.inclusion_tag('avisstage/templatetags/widget_feedback.html')
|
|
||||||
|
@register.inclusion_tag("avisstage/templatetags/widget_feedback.html")
|
||||||
def feedback_widget():
|
def feedback_widget():
|
||||||
form = FeedbackForm()
|
form = FeedbackForm()
|
||||||
return {"form": form}
|
return {"form": form}
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def typonazisme(value):
|
def typonazisme(value):
|
||||||
#print value
|
value = re.sub(r"(\w)\s*([?!:])", "\\1 \\2", value)
|
||||||
#return value
|
value = re.sub(r"(\w)\s*([,.])", "\\1\\2", value)
|
||||||
value = re.sub(r'(\w)\s*([?!:])', u'\\1 \\2', value)
|
value = re.sub(r"([?!:,.])(\w)", "\\1 \\2", value)
|
||||||
value = re.sub(r'(\w)\s*([,.])', u'\\1\\2', value)
|
|
||||||
value = re.sub(r'([?!:,.])(\w)', u'\\1 \\2', value)
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def avis_len(value):
|
def avis_len(value):
|
||||||
if value < 5:
|
if value < 5:
|
||||||
|
@ -33,3 +36,10 @@ def avis_len(value):
|
||||||
return "court"
|
return "court"
|
||||||
else:
|
else:
|
||||||
return "long"
|
return "long"
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def url_replace(request, field, value):
|
||||||
|
dict_ = request.GET.copy()
|
||||||
|
dict_[field] = value
|
||||||
|
return dict_.urlencode()
|
||||||
|
|
|
@ -1,3 +1,565 @@
|
||||||
from django.test import TestCase
|
from datetime import date, timedelta
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
# Create your tests here.
|
from authens.models import CASAccount, OldCASAccount
|
||||||
|
from authens.tests.cas_utils import FakeCASClient
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from .models import AvisLieu, Lieu, Stage, StageMatiere, User
|
||||||
|
|
||||||
|
|
||||||
|
class ExperiENSTestCase(TestCase):
|
||||||
|
|
||||||
|
# Dummy database
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.u_conscrit = User.objects.create_user(
|
||||||
|
"conscrit", "conscrit@ens.fr", "conscrit"
|
||||||
|
)
|
||||||
|
self.p_conscrit = self.u_conscrit.profil
|
||||||
|
self.p_conscrit.nom = "Petit conscrit"
|
||||||
|
self.p_conscrit.promotion = "Serpentard 2020"
|
||||||
|
self.p_conscrit.bio = "Je suis un petit conscrit"
|
||||||
|
self.p_conscrit.save()
|
||||||
|
|
||||||
|
self.sa_conscrit = CASAccount(
|
||||||
|
user=self.u_conscrit,
|
||||||
|
cas_login="conscrit",
|
||||||
|
entrance_year=2020,
|
||||||
|
)
|
||||||
|
self.sa_conscrit.save()
|
||||||
|
|
||||||
|
self.u_archi = User.objects.create_user(
|
||||||
|
"archicube", "archicube@ens.fr", "archicube"
|
||||||
|
)
|
||||||
|
self.p_archi = self.u_archi.profil
|
||||||
|
self.p_archi.nom = "Vieil archicube"
|
||||||
|
self.p_archi.promotion = "Gryffondor 2014"
|
||||||
|
self.p_archi.bio = "Je suis un vieil archicube"
|
||||||
|
|
||||||
|
self.lieu1 = Lieu(
|
||||||
|
nom="Beaux-Bâtons",
|
||||||
|
type_lieu="universite",
|
||||||
|
ville="Brocéliande",
|
||||||
|
pays="FR",
|
||||||
|
coord="POINT(-1.63971 48.116382)",
|
||||||
|
)
|
||||||
|
self.lieu1.save()
|
||||||
|
self.lieu2 = Lieu(
|
||||||
|
nom="Durmstrang",
|
||||||
|
type_lieu="universite",
|
||||||
|
ville="Edimbourg",
|
||||||
|
pays="GB",
|
||||||
|
coord="POINT(56.32153 -1.259715)",
|
||||||
|
)
|
||||||
|
self.lieu2.save()
|
||||||
|
|
||||||
|
self.matiere1 = StageMatiere(nom="Arithmancie", slug="arithmancie")
|
||||||
|
self.matiere1.save()
|
||||||
|
self.matiere2 = StageMatiere(nom="Sortilège", slug="sortilege")
|
||||||
|
self.matiere2.save()
|
||||||
|
|
||||||
|
self.cstage1 = Stage(
|
||||||
|
auteur=self.p_conscrit,
|
||||||
|
sujet="Wingardium Leviosa",
|
||||||
|
date_debut=date(2020, 5, 10),
|
||||||
|
date_fin=date(2020, 8, 26),
|
||||||
|
type_stage="recherche",
|
||||||
|
niveau_scol="M1",
|
||||||
|
public=True,
|
||||||
|
)
|
||||||
|
self.cstage1.save()
|
||||||
|
self.cstage1.matieres.add(self.matiere1)
|
||||||
|
alieu1 = AvisLieu(stage=self.cstage1, lieu=self.lieu1, chapo="Trop bien")
|
||||||
|
alieu1.save()
|
||||||
|
|
||||||
|
self.cstage2 = Stage(
|
||||||
|
auteur=self.p_conscrit,
|
||||||
|
sujet="Avada Kedavra",
|
||||||
|
date_debut=date(2021, 5, 10),
|
||||||
|
date_fin=date(2021, 8, 26),
|
||||||
|
type_stage="sejour_dri",
|
||||||
|
niveau_scol="M2",
|
||||||
|
public=False,
|
||||||
|
)
|
||||||
|
self.cstage2.save()
|
||||||
|
self.cstage2.matieres.add(self.matiere2)
|
||||||
|
alieu2 = AvisLieu(stage=self.cstage2, lieu=self.lieu2, chapo="Trop nul")
|
||||||
|
alieu2.save()
|
||||||
|
|
||||||
|
self.astage1 = Stage(
|
||||||
|
auteur=self.p_archi,
|
||||||
|
sujet="Alohomora",
|
||||||
|
date_debut=date(2014, 5, 10),
|
||||||
|
date_fin=date(2014, 8, 26),
|
||||||
|
type_stage="recherche",
|
||||||
|
niveau_scol="M2",
|
||||||
|
public=True,
|
||||||
|
)
|
||||||
|
self.astage1.save()
|
||||||
|
self.astage1.matieres.add(self.matiere2)
|
||||||
|
alieu3 = AvisLieu(stage=self.astage1, lieu=self.lieu1, chapo="Trop moyen")
|
||||||
|
alieu3.save()
|
||||||
|
|
||||||
|
def assertRedirectToLogin(self, testurl):
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
return self.assertRedirects(r, settings.LOGIN_URL + "?next=" + testurl)
|
||||||
|
|
||||||
|
def assertPageNotFound(self, testurl):
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
self.assertEqual(r.status_code, 404)
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
ACCÈS PUBLIC
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class PublicViewsTest(ExperiENSTestCase):
|
||||||
|
"""
|
||||||
|
Vérifie que les fiches de stages ne sont pas visibles hors connexion
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_stage_visibility_public(self):
|
||||||
|
self.assertRedirectToLogin(
|
||||||
|
reverse("avisstage:stage", kwargs={"pk": self.cstage1.id})
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertRedirectToLogin(
|
||||||
|
reverse("avisstage:stage", kwargs={"pk": self.cstage2.id})
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertRedirectToLogin(
|
||||||
|
reverse("avisstage:stage", kwargs={"pk": self.astage1.id})
|
||||||
|
)
|
||||||
|
|
||||||
|
"""
|
||||||
|
Vérifie que les profils de normaliens ne sont pas visibles hors connexion
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_profil_visibility_public(self):
|
||||||
|
self.assertRedirectToLogin(
|
||||||
|
reverse("avisstage:profil", kwargs={"username": self.u_conscrit.username})
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertRedirectToLogin(
|
||||||
|
reverse("avisstage:profil", kwargs={"username": self.u_archi.username})
|
||||||
|
)
|
||||||
|
|
||||||
|
"""
|
||||||
|
Vérifie que la recherche n'est pas accessible hors connexion
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_pages_visibility_public(self):
|
||||||
|
self.assertRedirectToLogin(reverse("avisstage:recherche"))
|
||||||
|
|
||||||
|
self.assertRedirectToLogin(reverse("avisstage:recherche_resultats"))
|
||||||
|
|
||||||
|
self.assertRedirectToLogin(reverse("avisstage:stage_items"))
|
||||||
|
|
||||||
|
self.assertRedirectToLogin(reverse("avisstage:feedback"))
|
||||||
|
|
||||||
|
self.assertRedirectToLogin(reverse("avisstage:moderation"))
|
||||||
|
|
||||||
|
"""
|
||||||
|
Vérifie que l'API n'est pas accessible hors connexion
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_api_visibility_public(self):
|
||||||
|
testurl = reverse(
|
||||||
|
"avisstage:api_dispatch_list",
|
||||||
|
kwargs={"resource_name": "lieu", "api_name": "v1"},
|
||||||
|
)
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
self.assertEqual(r.status_code, 401)
|
||||||
|
|
||||||
|
testurl = reverse(
|
||||||
|
"avisstage:api_dispatch_list",
|
||||||
|
kwargs={"resource_name": "stage", "api_name": "v1"},
|
||||||
|
)
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
self.assertEqual(r.status_code, 401)
|
||||||
|
|
||||||
|
testurl = reverse(
|
||||||
|
"avisstage:api_dispatch_list",
|
||||||
|
kwargs={"resource_name": "profil", "api_name": "v1"},
|
||||||
|
)
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
self.assertEqual(r.status_code, 401)
|
||||||
|
|
||||||
|
"""
|
||||||
|
Vérifie que les pages d'édition ne sont pas accessible hors connexion
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_edit_visibility_public(self):
|
||||||
|
self.assertRedirectToLogin(
|
||||||
|
reverse("avisstage:stage_edit", kwargs={"pk": self.cstage1.id})
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertRedirectToLogin(
|
||||||
|
reverse("avisstage:stage_edit", kwargs={"pk": self.astage1.id})
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertRedirectToLogin(
|
||||||
|
reverse("avisstage:stage_publication", kwargs={"pk": self.cstage1.id})
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertRedirectToLogin(
|
||||||
|
reverse("avisstage:stage_publication", kwargs={"pk": self.astage1.id})
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertRedirectToLogin(reverse("avisstage:stage_ajout"))
|
||||||
|
|
||||||
|
self.assertRedirectToLogin(reverse("avisstage:profil_edit"))
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
ACCÈS ARCHICUBE
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class ArchicubeViewsTest(ExperiENSTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
# Connexion with password
|
||||||
|
self.client.login(username="archicube", password="archicube")
|
||||||
|
|
||||||
|
def assert403Archicubes(self, testurl):
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
return self.assertRedirects(r, reverse("avisstage:403-archicubes"))
|
||||||
|
|
||||||
|
"""
|
||||||
|
Vérifie que les seules fiches de stages visibles sont les siennes
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_stage_visibility_archi(self):
|
||||||
|
self.assertPageNotFound(
|
||||||
|
reverse("avisstage:stage", kwargs={"pk": self.cstage1.id})
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertPageNotFound(
|
||||||
|
reverse("avisstage:stage", kwargs={"pk": self.cstage2.id})
|
||||||
|
)
|
||||||
|
|
||||||
|
testurl = reverse("avisstage:stage", kwargs={"pk": self.astage1.id})
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
"""
|
||||||
|
Vérifie que le seul profil visible est le sien
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_profil_visibility_archi(self):
|
||||||
|
self.assertPageNotFound(
|
||||||
|
reverse("avisstage:profil", kwargs={"username": self.u_conscrit.username})
|
||||||
|
)
|
||||||
|
|
||||||
|
testurl = reverse(
|
||||||
|
"avisstage:profil", kwargs={"username": self.u_archi.username}
|
||||||
|
)
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
"""
|
||||||
|
Vérifie que la recherche n'est pas accessible
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_pages_visibility_archi(self):
|
||||||
|
self.assert403Archicubes(reverse("avisstage:recherche"))
|
||||||
|
|
||||||
|
self.assert403Archicubes(reverse("avisstage:recherche_resultats"))
|
||||||
|
|
||||||
|
self.assert403Archicubes(reverse("avisstage:stage_items"))
|
||||||
|
|
||||||
|
testurl = reverse("avisstage:feedback")
|
||||||
|
r = self.client.post(
|
||||||
|
testurl, {"objet": "Contact", "message": "Ceci est un texte"}
|
||||||
|
)
|
||||||
|
self.assertRedirects(r, reverse("avisstage:index"))
|
||||||
|
|
||||||
|
testurl = reverse("avisstage:moderation")
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
self.assertRedirects(r, reverse("admin:login") + "?next=" + testurl)
|
||||||
|
|
||||||
|
"""
|
||||||
|
Vérifie que la seule API accessible est celle des lieux
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_api_visibility_archi(self):
|
||||||
|
testurl = reverse(
|
||||||
|
"avisstage:api_dispatch_list",
|
||||||
|
kwargs={"resource_name": "lieu", "api_name": "v1"},
|
||||||
|
)
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
testurl = reverse(
|
||||||
|
"avisstage:api_dispatch_list",
|
||||||
|
kwargs={"resource_name": "stage", "api_name": "v1"},
|
||||||
|
)
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
self.assertEqual(r.status_code, 401)
|
||||||
|
|
||||||
|
testurl = reverse(
|
||||||
|
"avisstage:api_dispatch_list",
|
||||||
|
kwargs={"resource_name": "profil", "api_name": "v1"},
|
||||||
|
)
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
self.assertEqual(r.status_code, 401)
|
||||||
|
|
||||||
|
"""
|
||||||
|
Vérifie que le seul stage modifiable est le sien
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_edit_visibility_archi(self):
|
||||||
|
testurl = reverse("avisstage:stage_edit", kwargs={"pk": self.cstage1.id})
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
self.assertEqual(r.status_code, 403)
|
||||||
|
|
||||||
|
testurl = reverse("avisstage:stage_edit", kwargs={"pk": self.astage1.id})
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
testurl = reverse("avisstage:stage_publication", kwargs={"pk": self.cstage1.id})
|
||||||
|
r = self.client.post(testurl, {"publier": True})
|
||||||
|
self.assertEqual(r.status_code, 403)
|
||||||
|
|
||||||
|
testurl = reverse("avisstage:stage_publication", kwargs={"pk": self.astage1.id})
|
||||||
|
r = self.client.post(testurl, {"publier": True})
|
||||||
|
self.assertRedirects(
|
||||||
|
r, reverse("avisstage:stage", kwargs={"pk": self.astage1.id})
|
||||||
|
)
|
||||||
|
|
||||||
|
testurl = reverse("avisstage:stage_ajout")
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
testurl = reverse("avisstage:profil_edit")
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
# TODO : test post()
|
||||||
|
|
||||||
|
|
||||||
|
class DeprecatedArchicubeViewsTest(ArchicubeViewsTest):
|
||||||
|
@mock.patch("authens.backends.get_cas_client")
|
||||||
|
def setUp(self, mock_cas_client):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
fake_cas_client = FakeCASClient(cas_login="archicube", entrance_year=2012)
|
||||||
|
mock_cas_client.return_value = fake_cas_client
|
||||||
|
|
||||||
|
self.sa_archi = OldCASAccount(
|
||||||
|
user=self.u_archi,
|
||||||
|
cas_login="archicube",
|
||||||
|
entrance_year=2012,
|
||||||
|
)
|
||||||
|
self.sa_archi.save()
|
||||||
|
|
||||||
|
# First connexion through CAS
|
||||||
|
self.client.login(ticket="dummy")
|
||||||
|
self.client.logout()
|
||||||
|
|
||||||
|
# Time flies
|
||||||
|
self.p_archi.last_cas_login = (timezone.now() - timedelta(days=365)).date()
|
||||||
|
self.p_archi.save()
|
||||||
|
|
||||||
|
# New connexion with password
|
||||||
|
self.client.login(username="archicube", password="archicube")
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
ACCÈS EN SCOLARITE
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class ScolariteViewsTest(ExperiENSTestCase):
|
||||||
|
@mock.patch("authens.backends.get_cas_client")
|
||||||
|
def setUp(self, mock_cas_client):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
fake_cas_client = FakeCASClient(cas_login="vieuxcon", entrance_year=2017)
|
||||||
|
mock_cas_client.return_value = fake_cas_client
|
||||||
|
|
||||||
|
self.u_vieuxcon = User.objects.create_user(
|
||||||
|
"vieuxcon", "vieuxcon@ens.fr", "vieuxcon"
|
||||||
|
)
|
||||||
|
self.p_vieuxcon = self.u_vieuxcon.profil
|
||||||
|
self.p_vieuxcon.nom = "Vieux con"
|
||||||
|
self.p_vieuxcon.promotion = "Poufsouffle 2017"
|
||||||
|
self.p_vieuxcon.bio = "Je suis un vieux con encore en scolarité"
|
||||||
|
self.p_vieuxcon.save()
|
||||||
|
|
||||||
|
self.sa_vieuxcon = CASAccount(
|
||||||
|
user=self.u_vieuxcon,
|
||||||
|
cas_login="vieuxcon",
|
||||||
|
entrance_year=2017,
|
||||||
|
)
|
||||||
|
self.sa_vieuxcon.save()
|
||||||
|
|
||||||
|
self.vstage1 = Stage(
|
||||||
|
auteur=self.p_vieuxcon,
|
||||||
|
sujet="Oubliettes",
|
||||||
|
date_debut=date(2018, 5, 10),
|
||||||
|
date_fin=date(2018, 8, 26),
|
||||||
|
type_stage="recherche",
|
||||||
|
niveau_scol="M1",
|
||||||
|
public=False,
|
||||||
|
)
|
||||||
|
self.vstage1.save()
|
||||||
|
self.vstage1.matieres.add(self.matiere2)
|
||||||
|
alieu1 = AvisLieu(stage=self.vstage1, lieu=self.lieu2, chapo="Pas si mal")
|
||||||
|
alieu1.save()
|
||||||
|
|
||||||
|
# Connexion through CAS
|
||||||
|
self.client.login(ticket="dummy")
|
||||||
|
|
||||||
|
"""
|
||||||
|
Vérifie que les seules fiches de stages visibles sont les siennes ou celles
|
||||||
|
publiques
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_stage_visibility_scolarite(self):
|
||||||
|
testurl = reverse("avisstage:stage", kwargs={"pk": self.cstage1.id})
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
self.assertPageNotFound(
|
||||||
|
reverse("avisstage:stage", kwargs={"pk": self.cstage2.id})
|
||||||
|
)
|
||||||
|
|
||||||
|
testurl = reverse("avisstage:stage", kwargs={"pk": self.vstage1.id})
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
"""
|
||||||
|
Vérifie que tous les profils sont visibles
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_profil_visibility_scolarite(self):
|
||||||
|
testurl = reverse(
|
||||||
|
"avisstage:profil", kwargs={"username": self.u_conscrit.username}
|
||||||
|
)
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertContains(r, "Wingardium Leviosa") # Public
|
||||||
|
self.assertNotContains(r, "Avada Kedavra") # Brouillon
|
||||||
|
|
||||||
|
testurl = reverse(
|
||||||
|
"avisstage:profil", kwargs={"username": self.u_archi.username}
|
||||||
|
)
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
testurl = reverse(
|
||||||
|
"avisstage:profil", kwargs={"username": self.u_vieuxcon.username}
|
||||||
|
)
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
"""
|
||||||
|
Vérifie que la recherche et les autres pages sont accessibles
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_pages_visibility_scolarite(self):
|
||||||
|
testurl = reverse("avisstage:recherche")
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
testurl = reverse("avisstage:recherche_resultats")
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertContains(r, "Wingardium Leviosa") # Public
|
||||||
|
self.assertNotContains(r, "Avada Kedavra") # Brouillon
|
||||||
|
|
||||||
|
testurl = (
|
||||||
|
reverse("avisstage:stage_items")
|
||||||
|
+ "?ids="
|
||||||
|
+ ";".join(
|
||||||
|
("%d" % k.id) for k in [self.cstage1, self.cstage2, self.astage1]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertContains(r, "Wingardium Leviosa") # Public
|
||||||
|
self.assertNotContains(r, "Avada Kedavra") # Brouillon
|
||||||
|
|
||||||
|
testurl = reverse("avisstage:feedback")
|
||||||
|
r = self.client.post(
|
||||||
|
testurl, {"objet": "Contact", "message": "Ceci est un texte"}
|
||||||
|
)
|
||||||
|
self.assertRedirects(r, reverse("avisstage:index"))
|
||||||
|
|
||||||
|
testurl = reverse("avisstage:moderation")
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
self.assertRedirects(r, reverse("admin:login") + "?next=" + testurl)
|
||||||
|
|
||||||
|
"""
|
||||||
|
Vérifie que toutes les API sont accessibles et qu'elles ne montrent que les
|
||||||
|
stages publics
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_api_visibility_scolarite(self):
|
||||||
|
testurl = reverse(
|
||||||
|
"avisstage:api_dispatch_list",
|
||||||
|
kwargs={"resource_name": "lieu", "api_name": "v1"},
|
||||||
|
)
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
testurl = reverse(
|
||||||
|
"avisstage:api_dispatch_list",
|
||||||
|
kwargs={"resource_name": "stage", "api_name": "v1"},
|
||||||
|
)
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertContains(r, "Wingardium Leviosa") # Public
|
||||||
|
self.assertNotContains(r, "Avada Kedavra") # Brouillon
|
||||||
|
|
||||||
|
testurl = reverse(
|
||||||
|
"avisstage:api_dispatch_list",
|
||||||
|
kwargs={"resource_name": "profil", "api_name": "v1"},
|
||||||
|
)
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
"""
|
||||||
|
Vérifie que le seul stage modifiable est le sien
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_edit_visibility_scolarite(self):
|
||||||
|
testurl = reverse("avisstage:stage_edit", kwargs={"pk": self.cstage1.id})
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
self.assertEqual(r.status_code, 403)
|
||||||
|
|
||||||
|
testurl = reverse("avisstage:stage_edit", kwargs={"pk": self.astage1.id})
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
self.assertEqual(r.status_code, 403)
|
||||||
|
|
||||||
|
testurl = reverse("avisstage:stage_edit", kwargs={"pk": self.vstage1.id})
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
testurl = reverse("avisstage:stage_publication", kwargs={"pk": self.cstage1.id})
|
||||||
|
r = self.client.post(testurl, {"publier": True})
|
||||||
|
self.assertEqual(r.status_code, 403)
|
||||||
|
|
||||||
|
testurl = reverse("avisstage:stage_publication", kwargs={"pk": self.vstage1.id})
|
||||||
|
r = self.client.post(testurl, {"publier": True})
|
||||||
|
self.assertRedirects(
|
||||||
|
r, reverse("avisstage:stage", kwargs={"pk": self.vstage1.id})
|
||||||
|
)
|
||||||
|
|
||||||
|
testurl = reverse("avisstage:stage_ajout")
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
testurl = reverse("avisstage:profil_edit")
|
||||||
|
r = self.client.get(testurl)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
# TODO : test post()
|
||||||
|
|
|
@ -1,29 +1,69 @@
|
||||||
from django.conf.urls import include, url
|
|
||||||
from . import views, api
|
|
||||||
from tastypie.api import Api
|
from tastypie.api import Api
|
||||||
|
|
||||||
v1_api = Api(api_name='v1')
|
from django.urls import include, path
|
||||||
|
|
||||||
|
from . import api, views, views_search
|
||||||
|
|
||||||
|
v1_api = Api(api_name="v1")
|
||||||
v1_api.register(api.LieuResource())
|
v1_api.register(api.LieuResource())
|
||||||
v1_api.register(api.StageResource())
|
v1_api.register(api.StageResource())
|
||||||
v1_api.register(api.AuteurResource())
|
v1_api.register(api.AuteurResource())
|
||||||
|
|
||||||
|
app_name = "avisstage"
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', views.index, name='index'),
|
path("", views.index, name="index"),
|
||||||
url(r'^perso/$', views.perso, name='perso'),
|
path("perso/", views.perso, name="perso"),
|
||||||
url(r'^faq/$', views.faq, name='faq'),
|
path("faq/", views.faq, name="faq"),
|
||||||
url(r'^stage/nouveau/$', views.manage_stage, name='stage_ajout'),
|
path("stage/nouveau/", views.manage_stage, name="stage_ajout"),
|
||||||
url(r'^stage/(?P<pk>\w+)/$', views.StageView.as_view(), name='stage'),
|
path("stage/<int:pk>/", views.StageView.as_view(), name="stage"),
|
||||||
url(r'^stage/(?P<pk>\w+)/edit/$', views.manage_stage, name='stage_edit'),
|
path("stage/<int:pk>/edit/", views.manage_stage, name="stage_edit"),
|
||||||
url(r'^stage/(?P<pk>\w+)/publication/$', views.publier_stage, name='stage_publication'),
|
path("stage/<int:pk>/publication/", views.publier_stage, name="stage_publication"),
|
||||||
url(r'^stages/majs/$', views.StageListe.as_view(), name='stage_majs'),
|
path("403/archicubes/", views.archicubes_interdits, name="403-archicubes"),
|
||||||
|
path("lieu/save/", views.save_lieu, name="lieu_ajout"),
|
||||||
url(r'^lieu/save/$', views.save_lieu, name='lieu_ajout'),
|
path("profil/show/<str:username>/", views.ProfilView.as_view(), name="profil"),
|
||||||
url(r'^profil/show/(?P<username>\w+)/$', views.ProfilView.as_view(),
|
path("profil/edit/", views.ProfilEdit.as_view(), name="profil_edit"),
|
||||||
name='profil'),
|
path("profil/parametres/", views.MesParametres.as_view(), name="parametres"),
|
||||||
url(r'^profil/edit/$', views.ProfilEdit.as_view(), name='profil_edit'),
|
path(
|
||||||
url(r'^recherche/$', views.recherche, name='recherche'),
|
"profil/emails/<str:email>/aconfirmer/",
|
||||||
url(r'^recherche/resultats/$', views.recherche_resultats, name='recherche_resultats'),
|
views.AdresseAConfirmer.as_view(),
|
||||||
url(r'^feedback/$', views.feedback, name='feedback'),
|
name="emails_aconfirmer",
|
||||||
url(r'^moderation/$', views.statistiques, name='moderation'),
|
),
|
||||||
url(r'^api/', include(v1_api.urls)),
|
path(
|
||||||
|
"profil/emails/<str:email>/supprime/",
|
||||||
|
views.SupprimeAdresse.as_view(),
|
||||||
|
name="emails_supprime",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"profil/emails/<str:email>/reconfirme/",
|
||||||
|
views.ReConfirmeAdresse.as_view(),
|
||||||
|
name="emails_reconfirme",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"profil/emails/<str:email>/principal/",
|
||||||
|
views.RendAdressePrincipale.as_view(),
|
||||||
|
name="emails_principal",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"profil/emails/confirme/<str:key>/",
|
||||||
|
views.ConfirmeAdresse.as_view(),
|
||||||
|
name="emails_confirme",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"profil/mdp/demande/", views.EnvoieLienMotDePasse.as_view(), name="mdp_demande"
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"profil/mdp/<str:uidb64>/<str:token>/",
|
||||||
|
views.DefinirMotDePasse.as_view(),
|
||||||
|
name="mdp_edit",
|
||||||
|
),
|
||||||
|
path("recherche/", views_search.recherche, name="recherche"),
|
||||||
|
path(
|
||||||
|
"recherche/resultats/",
|
||||||
|
views_search.recherche_resultats,
|
||||||
|
name="recherche_resultats",
|
||||||
|
),
|
||||||
|
path("recherche/items/", views_search.stage_items, name="stage_items"),
|
||||||
|
path("feedback/", views.feedback, name="feedback"),
|
||||||
|
path("moderation/", views.statistiques, name="moderation"),
|
||||||
|
path("api/", include(v1_api.urls)),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,4 +1,26 @@
|
||||||
# coding: utf-8
|
from functools import reduce
|
||||||
|
from math import cos, radians, sqrt
|
||||||
|
|
||||||
def choices_length (choices):
|
|
||||||
return reduce (lambda m, choice: max (m, len (choice[0])), choices, 0)
|
def choices_length(choices):
|
||||||
|
return reduce(lambda m, choice: max(m, len(choice[0])), choices, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def en_scolarite(user):
|
||||||
|
return user.profil.en_scolarite
|
||||||
|
|
||||||
|
|
||||||
|
def approximate_distance(a, b):
|
||||||
|
lat_a = radians(a.y)
|
||||||
|
lat_b = radians(b.y)
|
||||||
|
dlon = radians(b.x - a.x)
|
||||||
|
dlon = dlon * cos((lat_a + lat_b) / 2)
|
||||||
|
dlat = lat_a - lat_b
|
||||||
|
distance = 6371000 * sqrt(dlon * dlon + dlat * dlat)
|
||||||
|
return distance
|
||||||
|
|
||||||
|
|
||||||
|
def is_email_ens(mail, none=False):
|
||||||
|
if mail is None:
|
||||||
|
return none
|
||||||
|
return mail.endswith("ens.fr") or mail.endswith("ens.psl.eu")
|
||||||
|
|
|
@ -1,92 +1,139 @@
|
||||||
# coding: utf-8
|
import math
|
||||||
|
import random
|
||||||
|
from collections import Counter, defaultdict
|
||||||
|
|
||||||
from django.shortcuts import render, redirect, get_object_or_404
|
from braces.views import LoginRequiredMixin
|
||||||
|
from simple_email_confirmation.models import EmailAddress
|
||||||
|
|
||||||
from django.views.generic import DetailView, ListView
|
|
||||||
from django.views.generic.edit import UpdateView, CreateView
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.urls import reverse
|
from django.conf import settings
|
||||||
|
from django.contrib import messages
|
||||||
from django.contrib.admin.views.decorators import staff_member_required
|
from django.contrib.admin.views.decorators import staff_member_required
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from braces.views import LoginRequiredMixin
|
from django.contrib.auth.views import PasswordResetConfirmView
|
||||||
from django.http import JsonResponse, HttpResponseForbidden
|
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
from django.db.models import Q, Count
|
from django.db.models import Count, Q
|
||||||
from collections import Counter
|
from django.http import Http404, HttpResponseForbidden, JsonResponse
|
||||||
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
|
from django.urls import reverse, reverse_lazy
|
||||||
|
from django.views.generic import (
|
||||||
|
CreateView,
|
||||||
|
DeleteView,
|
||||||
|
DetailView,
|
||||||
|
FormView,
|
||||||
|
UpdateView,
|
||||||
|
View,
|
||||||
|
)
|
||||||
|
from django.views.generic.detail import SingleObjectMixin
|
||||||
|
|
||||||
from avisstage.models import Normalien, Stage, Lieu, AvisLieu, AvisStage
|
from .forms import (
|
||||||
from avisstage.forms import StageForm, LieuForm, AvisStageForm, AvisLieuForm, FeedbackForm
|
AdresseEmailForm,
|
||||||
from avisstage.views_search import *
|
AvisLieuForm,
|
||||||
|
AvisStageForm,
|
||||||
import random, math
|
FeedbackForm,
|
||||||
|
LieuForm,
|
||||||
|
ReinitMdpForm,
|
||||||
|
StageForm,
|
||||||
|
)
|
||||||
|
from .models import AvisLieu, AvisStage, Lieu, Normalien, Stage
|
||||||
|
from .utils import en_scolarite
|
||||||
|
|
||||||
#
|
#
|
||||||
# LECTURE
|
# LECTURE
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
# Page d'accueil
|
# Page d'accueil
|
||||||
def index(request):
|
def index(request):
|
||||||
num_stages = Stage.objects.filter(public=True).count()
|
num_stages = Stage.objects.filter(public=True).count()
|
||||||
return render(request, 'avisstage/index.html',
|
return render(request, "avisstage/index.html", {"num_stages": num_stages})
|
||||||
{"num_stages": num_stages})
|
|
||||||
|
|
||||||
# Espace personnel
|
# Espace personnel
|
||||||
@login_required
|
@login_required
|
||||||
def perso(request):
|
def perso(request):
|
||||||
return render(request, 'avisstage/perso.html')
|
# HOTFIX (TODO rendre ça plus propre)
|
||||||
|
# Vérifie que le profil existe bien
|
||||||
|
# (suite à un cas où il n'avait pas été initialisé)
|
||||||
|
if not hasattr(request.user, "profil"):
|
||||||
|
profil, created = Normalien.objects.get_or_create(user=request.user)
|
||||||
|
profil.save()
|
||||||
|
|
||||||
|
return render(request, "avisstage/perso.html")
|
||||||
|
|
||||||
|
|
||||||
|
# 403 Archicubes
|
||||||
|
@login_required
|
||||||
|
def archicubes_interdits(request):
|
||||||
|
return render(request, "avisstage/403-archicubes.html")
|
||||||
|
|
||||||
|
|
||||||
# Profil
|
# Profil
|
||||||
#login_required
|
# login_required
|
||||||
class ProfilView(LoginRequiredMixin, DetailView):
|
class ProfilView(LoginRequiredMixin, DetailView):
|
||||||
model = Normalien
|
model = Normalien
|
||||||
template_name = 'avisstage/detail/profil.html'
|
template_name = "avisstage/detail/profil.html"
|
||||||
|
|
||||||
# Récupération du profil
|
# Récupération du profil
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
return Normalien.objects.get(user__username=self.kwargs.get('username'))
|
|
||||||
|
# Restriction d'accès pour les archicubes
|
||||||
|
if (
|
||||||
|
en_scolarite(self.request.user)
|
||||||
|
or self.kwargs.get("username") == self.request.user.username
|
||||||
|
):
|
||||||
|
return get_object_or_404(
|
||||||
|
Normalien, user__username=self.kwargs.get("username")
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise Http404
|
||||||
|
|
||||||
|
|
||||||
# Stage
|
# Stage
|
||||||
#login_required
|
# login_required
|
||||||
class StageView(LoginRequiredMixin, DetailView):
|
class StageView(LoginRequiredMixin, DetailView):
|
||||||
model = Stage
|
model = Stage
|
||||||
template_name = 'avisstage/detail/stage.html'
|
template_name = "avisstage/detail/stage.html"
|
||||||
|
|
||||||
# Restriction aux stages publics ou personnels
|
# Restriction aux stages publics ou personnels
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
filtre = Q(auteur__user_id=self.request.user.id) | Q(public=True)
|
filtre = Q(auteur__user_id=self.request.user.id)
|
||||||
|
|
||||||
|
# Restriction d'accès pour les archicubes
|
||||||
|
if en_scolarite(self.request.user):
|
||||||
|
filtre |= Q(public=True)
|
||||||
|
|
||||||
return Stage.objects.filter(filtre)
|
return Stage.objects.filter(filtre)
|
||||||
|
|
||||||
|
def get_context_data(self, *args, **kwargs):
|
||||||
|
context = super().get_context_data(*args, **kwargs)
|
||||||
|
context["MAPBOX_API_KEY"] = settings.MAPBOX_API_KEY
|
||||||
|
return context
|
||||||
|
|
||||||
# Liste des stages par dernière modification
|
|
||||||
#login_required
|
|
||||||
class StageListe(LoginRequiredMixin, ListView):
|
|
||||||
model = Stage
|
|
||||||
template_name = 'avisstage/recherche/stage.html'
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
return Stage.objects.filter(public=True).order_by('-date_maj')
|
|
||||||
|
|
||||||
# FAQ
|
# FAQ
|
||||||
def faq(request):
|
def faq(request):
|
||||||
return render(request, 'avisstage/faq.html')
|
return render(request, "avisstage/faq.html")
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# EDITION
|
# EDITION
|
||||||
#
|
#
|
||||||
|
|
||||||
# Profil
|
# Profil
|
||||||
#login_required
|
# login_required
|
||||||
class ProfilEdit(LoginRequiredMixin, UpdateView):
|
class ProfilEdit(LoginRequiredMixin, UpdateView):
|
||||||
model = Normalien
|
model = Normalien
|
||||||
fields = ['nom', 'promotion', 'mail', 'contactez_moi', 'bio']
|
fields = ["nom", "promotion", "contactez_moi", "bio"]
|
||||||
template_name = 'avisstage/formulaires/profil.html'
|
template_name = "avisstage/formulaires/profil.html"
|
||||||
|
|
||||||
# Limitation à son propre profil
|
# Limitation à son propre profil
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
return self.request.user.profil
|
return self.request.user.profil
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse('avisstage:perso')
|
return reverse("avisstage:perso")
|
||||||
|
|
||||||
|
|
||||||
# Stage
|
# Stage
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -97,8 +144,9 @@ def manage_stage(request, pk=None):
|
||||||
stage = Stage(auteur=request.user.profil)
|
stage = Stage(auteur=request.user.profil)
|
||||||
avis_stage = AvisStage(stage=stage)
|
avis_stage = AvisStage(stage=stage)
|
||||||
c_del = False
|
c_del = False
|
||||||
last_creation = Stage.objects.filter(auteur=request.user.profil)\
|
last_creation = Stage.objects.filter(auteur=request.user.profil).order_by(
|
||||||
.order_by("-date_creation")[:1]
|
"-date_creation"
|
||||||
|
)[:1]
|
||||||
if len(last_creation) != 0:
|
if len(last_creation) != 0:
|
||||||
last_maj = last_creation[0].date_creation
|
last_maj = last_creation[0].date_creation
|
||||||
else:
|
else:
|
||||||
|
@ -112,44 +160,60 @@ def manage_stage(request, pk=None):
|
||||||
|
|
||||||
# Formset pour les avis des lieux
|
# Formset pour les avis des lieux
|
||||||
AvisLieuFormSet = forms.inlineformset_factory(
|
AvisLieuFormSet = forms.inlineformset_factory(
|
||||||
Stage, AvisLieu, form=AvisLieuForm, can_delete=c_del, extra=0)
|
Stage, AvisLieu, form=AvisLieuForm, can_delete=c_del, extra=0
|
||||||
|
)
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
# Lecture des données
|
# Lecture des données
|
||||||
form = StageForm(request.POST, request=request, instance=stage, prefix="stage")
|
form = StageForm(request.POST, request=request, instance=stage, prefix="stage")
|
||||||
avis_stage_form = AvisStageForm(request.POST,
|
avis_stage_form = AvisStageForm(
|
||||||
instance=avis_stage, prefix="avis")
|
request.POST, instance=avis_stage, prefix="avis"
|
||||||
avis_lieu_formset = AvisLieuFormSet(request.POST, instance=stage,
|
)
|
||||||
prefix="lieux")
|
avis_lieu_formset = AvisLieuFormSet(
|
||||||
|
request.POST, instance=stage, prefix="lieux"
|
||||||
|
)
|
||||||
|
|
||||||
# Validation et enregistrement
|
# Validation et enregistrement
|
||||||
if (form.is_valid() and
|
if (
|
||||||
avis_stage_form.is_valid() and
|
form.is_valid()
|
||||||
avis_lieu_formset.is_valid()):
|
and avis_stage_form.is_valid()
|
||||||
|
and avis_lieu_formset.is_valid()
|
||||||
|
):
|
||||||
stage = form.save()
|
stage = form.save()
|
||||||
avis_stage_form.instance.stage = stage
|
avis_stage_form.instance.stage = stage
|
||||||
avis_stage_form.save()
|
avis_stage_form.save()
|
||||||
avis_lieu_formset.save()
|
avis_lieu_formset.save()
|
||||||
print request.POST
|
# print(request.POST)
|
||||||
if "continuer" in request.POST:
|
if "continuer" in request.POST:
|
||||||
if pk is None:
|
if pk is None:
|
||||||
return redirect(reverse('avisstage:stage_edit',kwargs={'pk':stage.id}))
|
return redirect(
|
||||||
|
reverse("avisstage:stage_edit", kwargs={"pk": stage.id})
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return redirect(reverse('avisstage:stage',
|
return redirect(reverse("avisstage:stage", kwargs={"pk": stage.id}))
|
||||||
kwargs={'pk':stage.id}))
|
|
||||||
else:
|
else:
|
||||||
form = StageForm(instance=stage, prefix="stage")
|
form = StageForm(instance=stage, prefix="stage")
|
||||||
avis_stage_form = AvisStageForm(instance=avis_stage, prefix="avis")
|
avis_stage_form = AvisStageForm(instance=avis_stage, prefix="avis")
|
||||||
avis_lieu_formset = AvisLieuFormSet(instance=stage, prefix="lieux")
|
avis_lieu_formset = AvisLieuFormSet(instance=stage, prefix="lieux")
|
||||||
|
|
||||||
# Affichage du formulaire
|
# Affichage du formulaire
|
||||||
return render(request, "avisstage/formulaires/stage.html",
|
return render(
|
||||||
{'form': form, 'avis_stage_form': avis_stage_form,
|
request,
|
||||||
'avis_lieu_formset': avis_lieu_formset,
|
"avisstage/formulaires/stage.html",
|
||||||
'creation': pk is None, "last_maj": last_maj})
|
{
|
||||||
|
"form": form,
|
||||||
|
"avis_stage_form": avis_stage_form,
|
||||||
|
"avis_lieu_formset": avis_lieu_formset,
|
||||||
|
"creation": pk is None,
|
||||||
|
"last_maj": last_maj,
|
||||||
|
"GOOGLE_API_KEY": settings.GOOGLE_API_KEY,
|
||||||
|
"MAPBOX_API_KEY": settings.MAPBOX_API_KEY,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Ajout d'un lieu de stage
|
# Ajout d'un lieu de stage
|
||||||
#login_required
|
# login_required
|
||||||
|
|
||||||
# Stage
|
# Stage
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -158,9 +222,9 @@ def save_lieu(request):
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
pk = request.POST.get("id", None)
|
pk = request.POST.get("id", None)
|
||||||
print request.POST
|
# print(request.POST)
|
||||||
jitter = False
|
jitter = False
|
||||||
if pk is None or pk == '':
|
if pk is None or pk == "":
|
||||||
lieu = Lieu()
|
lieu = Lieu()
|
||||||
else:
|
else:
|
||||||
# Modification du lieu
|
# Modification du lieu
|
||||||
|
@ -169,7 +233,8 @@ def save_lieu(request):
|
||||||
# On regarde si les stages associés à ce lieu "appartiennent" tous à l'utilisateur
|
# On regarde si les stages associés à ce lieu "appartiennent" tous à l'utilisateur
|
||||||
not_same_user = lieu.stages.exclude(auteur=normalien).count()
|
not_same_user = lieu.stages.exclude(auteur=normalien).count()
|
||||||
|
|
||||||
# Si d'autres personnes ont un stage à cet endroit, on crée un nouveau lieu, un peu à côté
|
# Si d'autres personnes ont un stage à cet endroit,
|
||||||
|
# on crée un nouveau lieu, un peu à côté
|
||||||
if not_same_user > 0:
|
if not_same_user > 0:
|
||||||
lieu = Lieu()
|
lieu = Lieu()
|
||||||
# Servira à bouger un peu le lieu
|
# Servira à bouger un peu le lieu
|
||||||
|
@ -183,51 +248,54 @@ def save_lieu(request):
|
||||||
lieu = form.save(commit=False)
|
lieu = form.save(commit=False)
|
||||||
if jitter:
|
if jitter:
|
||||||
cdx, cdy = lieu.coord.get_coords()
|
cdx, cdy = lieu.coord.get_coords()
|
||||||
ang = random.random() * 6.29;
|
ang = random.random() * 6.29
|
||||||
rad = (random.random() + 0.5) * 3e-4
|
rad = (random.random() + 0.5) * 3e-4
|
||||||
cdx += math.cos(ang) * rad;
|
cdx += math.cos(ang) * rad
|
||||||
cdy += math.sin(ang) * rad;
|
cdy += math.sin(ang) * rad
|
||||||
lieu.coord.set_coords((cdx, cdy))
|
lieu.coord.set_coords((cdx, cdy))
|
||||||
lieu.save()
|
lieu.save()
|
||||||
|
|
||||||
# Élimination des doublons
|
# Élimination des doublons
|
||||||
if pk is None or pk == "":
|
if pk is None or pk == "":
|
||||||
olieux = Lieu.objects.filter(nom=lieu.nom, coord__distance_lte=(lieu.coord, 10))
|
olieux = Lieu.objects.filter(
|
||||||
|
nom=lieu.nom, coord__distance_lte=(lieu.coord, 10)
|
||||||
|
)
|
||||||
for olieu in olieux:
|
for olieu in olieux:
|
||||||
if olieu.type_lieu == lieu.type_lieu and \
|
if (
|
||||||
olieu.ville == lieu.ville and \
|
olieu.type_lieu == lieu.type_lieu
|
||||||
olieu.pays == lieu.pays:
|
and olieu.ville == lieu.ville
|
||||||
|
and olieu.pays == lieu.pays
|
||||||
|
):
|
||||||
return JsonResponse({"success": True, "id": olieu.id})
|
return JsonResponse({"success": True, "id": olieu.id})
|
||||||
|
|
||||||
lieu.save()
|
lieu.save()
|
||||||
return JsonResponse({"success": True, "id": lieu.id})
|
return JsonResponse({"success": True, "id": lieu.id})
|
||||||
else:
|
else:
|
||||||
return JsonResponse({"success": False,
|
return JsonResponse({"success": False, "errors": form.errors})
|
||||||
"errors": form.errors})
|
|
||||||
else:
|
else:
|
||||||
return JsonResponse({"erreur": "Aucune donnée POST"})
|
return JsonResponse({"erreur": "Aucune donnée POST"})
|
||||||
|
|
||||||
|
|
||||||
class LieuAjout(LoginRequiredMixin, CreateView):
|
class LieuAjout(LoginRequiredMixin, CreateView):
|
||||||
model = Lieu
|
model = Lieu
|
||||||
form_class = LieuForm
|
form_class = LieuForm
|
||||||
template_name = 'avisstage/formulaires/lieu.html'
|
template_name = "avisstage/formulaires/lieu.html"
|
||||||
|
|
||||||
# Retourne d'un JSON si requête AJAX
|
# Retourne d'un JSON si requête AJAX
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
if self.request.GET.get("format", "") == "json":
|
if self.request.GET.get("format", "") == "json":
|
||||||
self.object = form.save()
|
self.object = form.save()
|
||||||
return JsonResponse({"success": True,
|
return JsonResponse({"success": True, "id": self.object.id})
|
||||||
"id": self.object.id})
|
|
||||||
else:
|
else:
|
||||||
super(LieuAjout, self).form_valid(form)
|
super(LieuAjout, self).form_valid(form)
|
||||||
|
|
||||||
def form_invalid(self, form):
|
def form_invalid(self, form):
|
||||||
if self.request.GET.get("format", "") == "json":
|
if self.request.GET.get("format", "") == "json":
|
||||||
return JsonResponse({"success": False,
|
return JsonResponse({"success": False, "errors": form.errors})
|
||||||
"errors": form.errors})
|
|
||||||
else:
|
else:
|
||||||
super(LieuAjout, self).form_valid(form)
|
super(LieuAjout, self).form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
# Passage d'un stage en mode public
|
# Passage d'un stage en mode public
|
||||||
@login_required
|
@login_required
|
||||||
def publier_stage(request, pk):
|
def publier_stage(request, pk):
|
||||||
|
@ -249,22 +317,25 @@ def publier_stage(request, pk):
|
||||||
|
|
||||||
return redirect(reverse("avisstage:stage", kwargs={"pk": pk}))
|
return redirect(reverse("avisstage:stage", kwargs={"pk": pk}))
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# FEEDBACK
|
# FEEDBACK
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def feedback(request):
|
def feedback(request):
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
form = FeedbackForm(request.POST)
|
form = FeedbackForm(request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
objet = form.cleaned_data['objet']
|
objet = form.cleaned_data["objet"]
|
||||||
message = form.cleaned_data['message']
|
header = "[From : %s <%s>]\n" % (request.user, request.user.email)
|
||||||
|
message = header + form.cleaned_data["message"]
|
||||||
send_mail(
|
send_mail(
|
||||||
"[experiENS] "+ objet,
|
"[experiENS] " + objet,
|
||||||
message,
|
message,
|
||||||
request.user.username + "@clipper.ens.fr",
|
request.user.email,
|
||||||
['champeno@clipper.ens.fr'],
|
["robin.champenois@ens.fr"],
|
||||||
fail_silently=False,
|
fail_silently=False,
|
||||||
)
|
)
|
||||||
if request.GET.get("format", None) == "json":
|
if request.GET.get("format", None) == "json":
|
||||||
|
@ -272,32 +343,221 @@ def feedback(request):
|
||||||
return redirect(reverse("avisstage:index"))
|
return redirect(reverse("avisstage:index"))
|
||||||
else:
|
else:
|
||||||
if request.GET.get("format", None) == "json":
|
if request.GET.get("format", None) == "json":
|
||||||
return JsonResponse({"success": False,
|
return JsonResponse({"success": False, "errors": form.errors})
|
||||||
"errors": form.errors})
|
|
||||||
else:
|
else:
|
||||||
form = FeedbackForm()
|
form = FeedbackForm()
|
||||||
return render(request, 'avisstage/formulaire/feedback.html', {"form": form})
|
raise Http404()
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# STATISTIQUES
|
# STATISTIQUES
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@staff_member_required
|
@staff_member_required
|
||||||
def statistiques(request):
|
def statistiques(request):
|
||||||
nstages = Stage.objects.count()
|
nstages = Stage.objects.count()
|
||||||
npubstages = Stage.objects.filter(public=True).count()
|
npubstages = Stage.objects.filter(public=True).count()
|
||||||
nbymatiere = Stage.objects.values('matieres__nom').annotate(scount=Count('matieres__nom'))
|
nbymatiere_raw = Stage.objects.values("matieres__nom", "public").annotate(
|
||||||
|
scount=Count("matieres__nom")
|
||||||
|
)
|
||||||
|
nbymatiere = defaultdict(dict)
|
||||||
|
for npm in nbymatiere_raw:
|
||||||
|
nbymatiere[npm["matieres__nom"]][
|
||||||
|
"publics" if npm["public"] else "drafts"
|
||||||
|
] = npm["scount"]
|
||||||
|
for mat, npm in nbymatiere.items():
|
||||||
|
npm["matiere"] = mat
|
||||||
|
nbymatiere = sorted(
|
||||||
|
list(nbymatiere.values()), key=lambda npm: -npm.get("publics", 0)
|
||||||
|
)
|
||||||
|
nbylength = [
|
||||||
|
(
|
||||||
|
"Vide",
|
||||||
|
Stage.objects.filter(len_avis_stage__lt=5).count(),
|
||||||
|
Stage.objects.filter(len_avis_lieux__lt=5).count(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Court",
|
||||||
|
Stage.objects.filter(len_avis_stage__lt=30, len_avis_stage__gt=4).count(),
|
||||||
|
Stage.objects.filter(len_avis_lieux__lt=30, len_avis_lieux__gt=4).count(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Moyen",
|
||||||
|
Stage.objects.filter(len_avis_stage__lt=100, len_avis_stage__gt=29).count(),
|
||||||
|
Stage.objects.filter(len_avis_lieux__lt=100, len_avis_lieux__gt=29).count(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Long",
|
||||||
|
Stage.objects.filter(len_avis_stage__gt=99).count(),
|
||||||
|
Stage.objects.filter(len_avis_lieux__gt=99).count(),
|
||||||
|
),
|
||||||
|
]
|
||||||
nusers = Normalien.objects.count()
|
nusers = Normalien.objects.count()
|
||||||
nauts = Normalien.objects.filter(stages__isnull=False).distinct().count()
|
nauts = Normalien.objects.filter(stages__isnull=False).distinct().count()
|
||||||
nbyaut = Counter(Normalien.objects.filter(stages__isnull=False).annotate(scount=Count('stages')).values_list('scount', flat="True")).items()
|
nbyaut = Counter(
|
||||||
|
Normalien.objects.filter(stages__isnull=False)
|
||||||
|
.annotate(scount=Count("stages"))
|
||||||
|
.values_list("scount", flat="True")
|
||||||
|
).items()
|
||||||
nlieux = Lieu.objects.filter(stages__isnull=False).distinct().count()
|
nlieux = Lieu.objects.filter(stages__isnull=False).distinct().count()
|
||||||
return render(request, 'avisstage/moderation/statistiques.html',
|
return render(
|
||||||
{'num_stages': nstages,
|
request,
|
||||||
'num_stages_pub': npubstages,
|
"avisstage/moderation/statistiques.html",
|
||||||
'num_par_matiere': nbymatiere,
|
{
|
||||||
'num_users': nusers,
|
"num_stages": nstages,
|
||||||
'num_auteurs': nauts,
|
"num_stages_pub": npubstages,
|
||||||
'num_par_auteur': nbyaut,
|
"num_par_matiere": nbymatiere,
|
||||||
'num_lieux_utiles': nlieux})
|
"num_users": nusers,
|
||||||
|
"num_auteurs": nauts,
|
||||||
|
"num_par_auteur": nbyaut,
|
||||||
|
"num_lieux_utiles": nlieux,
|
||||||
|
"num_par_longueur": nbylength,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Compte
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
class MesAdressesMixin(LoginRequiredMixin):
|
||||||
|
slug_url_kwarg = "email"
|
||||||
|
slug_field = "email"
|
||||||
|
confirmed_only = False
|
||||||
|
|
||||||
|
def get_queryset(self, *args, **kwargs):
|
||||||
|
qs = self.request.user.email_address_set.all()
|
||||||
|
if self.confirmed_only:
|
||||||
|
qs = qs.filter(confirmed_at__isnull=False)
|
||||||
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
def _send_confirm_mail(email, request):
|
||||||
|
confirm_url = request.build_absolute_uri(
|
||||||
|
reverse("avisstage:emails_confirme", kwargs={"key": email.key})
|
||||||
|
)
|
||||||
|
send_mail(
|
||||||
|
"[ExperiENS] Confirmez votre adresse a-mail",
|
||||||
|
"""Bonjour,
|
||||||
|
|
||||||
|
Vous venez d'ajouter cette adresse e-mail à votre compte ExperiENS.
|
||||||
|
|
||||||
|
Pour la vérifier, merci de cliquer sur le lien suivant, ou de copier l'adresse dans votre navigateur :
|
||||||
|
|
||||||
|
{confirm_url}
|
||||||
|
|
||||||
|
Cordialement,
|
||||||
|
L'équipe ExperiENS""".format(
|
||||||
|
confirm_url=confirm_url
|
||||||
|
),
|
||||||
|
"experiens-nepasrepondre@eleves.ens.fr",
|
||||||
|
[email.email],
|
||||||
|
fail_silently=False,
|
||||||
|
)
|
||||||
|
return redirect(
|
||||||
|
reverse("avisstage:emails_aconfirmer", kwargs={"email": email.email})
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MesParametres(LoginRequiredMixin, FormView):
|
||||||
|
model = EmailAddress
|
||||||
|
template_name = "avisstage/compte/parametres.html"
|
||||||
|
form_class = AdresseEmailForm
|
||||||
|
|
||||||
|
def get_form_kwargs(self, *args, **kwargs):
|
||||||
|
kwargs = super().get_form_kwargs(*args, **kwargs)
|
||||||
|
kwargs["_user"] = self.request.user
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
new = EmailAddress.objects.create_unconfirmed(
|
||||||
|
form.cleaned_data["email"], self.request.user
|
||||||
|
)
|
||||||
|
return _send_confirm_mail(new, self.request)
|
||||||
|
|
||||||
|
|
||||||
|
class RendAdressePrincipale(MesAdressesMixin, SingleObjectMixin, View):
|
||||||
|
model = EmailAddress
|
||||||
|
confirmed_only = True
|
||||||
|
|
||||||
|
def post(self, *args, **kwargs):
|
||||||
|
if not hasattr(self, "object"):
|
||||||
|
self.object = self.get_object()
|
||||||
|
self.request.user.email = self.object.email
|
||||||
|
self.request.user.save()
|
||||||
|
return redirect(reverse("avisstage:parametres"))
|
||||||
|
|
||||||
|
|
||||||
|
class AdresseAConfirmer(MesAdressesMixin, DetailView):
|
||||||
|
model = EmailAddress
|
||||||
|
template_name = "avisstage/compte/aconfirmer.html"
|
||||||
|
|
||||||
|
|
||||||
|
class ReConfirmeAdresse(MesAdressesMixin, DetailView):
|
||||||
|
model = EmailAddress
|
||||||
|
|
||||||
|
def post(self, *args, **kwargs):
|
||||||
|
email = self.get_object()
|
||||||
|
if email.confirmed_at is None:
|
||||||
|
return _send_confirm_mail(email, self.request)
|
||||||
|
return redirect(reverse("avisstage:parametres"))
|
||||||
|
|
||||||
|
|
||||||
|
class ConfirmeAdresse(LoginRequiredMixin, View):
|
||||||
|
def get(self, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
email = EmailAddress.objects.confirm(
|
||||||
|
self.kwargs["key"], self.request.user, True
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
raise Http404()
|
||||||
|
messages.add_message(
|
||||||
|
self.request,
|
||||||
|
messages.SUCCESS,
|
||||||
|
"L'adresse email {email} a bien été confirmée".format(email=email.email),
|
||||||
|
)
|
||||||
|
return redirect(reverse("avisstage:parametres"))
|
||||||
|
|
||||||
|
|
||||||
|
class SupprimeAdresse(MesAdressesMixin, DeleteView):
|
||||||
|
model = EmailAddress
|
||||||
|
template_name = "avisstage/compte/email_supprime.html"
|
||||||
|
success_url = reverse_lazy("avisstage:parametres")
|
||||||
|
|
||||||
|
def get_queryset(self, *args, **kwargs):
|
||||||
|
qs = super().get_queryset(*args, **kwargs)
|
||||||
|
return qs.exclude(email=self.request.user.email)
|
||||||
|
|
||||||
|
|
||||||
|
class EnvoieLienMotDePasse(LoginRequiredMixin, View):
|
||||||
|
def post(self, *args, **kwargs):
|
||||||
|
form = ReinitMdpForm({"email": self.request.user.email})
|
||||||
|
form.is_valid()
|
||||||
|
form.save(
|
||||||
|
email_template_name="avisstage/mails/reinit_mdp.html",
|
||||||
|
from_email="experiens-nepasrepondre@eleves.ens.fr",
|
||||||
|
subject_template_name="avisstage/mails/reinit_mdp.txt",
|
||||||
|
)
|
||||||
|
messages.add_message(
|
||||||
|
self.request,
|
||||||
|
messages.INFO,
|
||||||
|
(
|
||||||
|
"Un mail a été envoyé à {email}. Merci de vérifier vos indésirables "
|
||||||
|
"si vous ne le recevez pas bientôt"
|
||||||
|
).format(email=self.request.user.email),
|
||||||
|
)
|
||||||
|
return redirect(reverse("avisstage:parametres"))
|
||||||
|
|
||||||
|
|
||||||
|
class DefinirMotDePasse(PasswordResetConfirmView):
|
||||||
|
template_name = "avisstage/compte/edit_mdp.html"
|
||||||
|
success_url = reverse_lazy("avisstage:perso")
|
||||||
|
|
||||||
|
def get_user(self, *args, **kwargs):
|
||||||
|
user = super().get_user(*args, **kwargs)
|
||||||
|
if self.request.user.is_authenticated and user != self.request.user:
|
||||||
|
raise Http404("Ce token n'est pas valide pour votre compte")
|
||||||
|
return user
|
||||||
|
|
|
@ -1,149 +1,305 @@
|
||||||
# coding: utf-8
|
import json
|
||||||
|
import logging
|
||||||
from django.shortcuts import render, redirect, get_object_or_404
|
from datetime import date
|
||||||
from django.contrib.auth.decorators import login_required
|
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.db.models import Q
|
from django.conf import settings
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.core.cache import cache
|
||||||
|
from django.core.paginator import InvalidPage, Paginator
|
||||||
|
from django.db.models import Case, Q, When
|
||||||
|
from django.http import HttpResponseBadRequest, JsonResponse
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
from .documents import StageDocument
|
USE_ELASTICSEARCH = getattr(settings, "USE_ELASTICSEARCH", True)
|
||||||
|
|
||||||
|
if USE_ELASTICSEARCH:
|
||||||
|
from .documents import StageDocument
|
||||||
|
|
||||||
|
from .decorators import en_scolarite_required
|
||||||
from .models import Stage
|
from .models import Stage
|
||||||
from .statics import TYPE_LIEU_OPTIONS, TYPE_STAGE_OPTIONS, NIVEAU_SCOL_OPTIONS
|
from .statics import NIVEAU_SCOL_OPTIONS, TYPE_LIEU_OPTIONS, TYPE_STAGE_OPTIONS
|
||||||
|
|
||||||
|
logger = logging.getLogger("recherche")
|
||||||
|
|
||||||
from datetime import date
|
|
||||||
|
|
||||||
# Recherche
|
# Recherche
|
||||||
class SearchForm(forms.Form):
|
class SearchForm(forms.Form):
|
||||||
generique = forms.CharField(required=False)
|
generique = forms.CharField(required=False)
|
||||||
sujet = forms.CharField(label=u'À propos de', required=False)
|
sujet = forms.CharField(label="À propos de", required=False)
|
||||||
contexte = forms.CharField(label=u'Contexte (lieu, encadrant⋅e⋅s, structure)',
|
contexte = forms.CharField(
|
||||||
required=False)
|
label="Contexte (lieu, encadrant·e·s, structure)", required=False
|
||||||
|
)
|
||||||
|
|
||||||
apres_annee = forms.IntegerField(label=u'Après cette année', required=False)
|
apres_annee = forms.IntegerField(label="Après cette année", required=False)
|
||||||
avant_annee = forms.IntegerField(label=u'Avant cette année', required=False)
|
avant_annee = forms.IntegerField(label="Avant cette année", required=False)
|
||||||
|
|
||||||
type_stage = forms.ChoiceField(label="Type de stage", choices=([('', u'')]
|
type_stage = forms.ChoiceField(
|
||||||
+ list(TYPE_STAGE_OPTIONS)),
|
label="Type de stage",
|
||||||
required=False)
|
choices=([("", "")] + list(TYPE_STAGE_OPTIONS)),
|
||||||
niveau_scol = forms.ChoiceField(label="Année d'étude", choices=([('', u'')]
|
required=False,
|
||||||
+ list(NIVEAU_SCOL_OPTIONS)),
|
)
|
||||||
required=False)
|
niveau_scol = forms.ChoiceField(
|
||||||
|
label="Année d'étude",
|
||||||
|
choices=([("", "")] + list(NIVEAU_SCOL_OPTIONS)),
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
|
||||||
type_lieu = forms.ChoiceField(label=u"Type de lieu d'accueil",
|
type_lieu = forms.ChoiceField(
|
||||||
choices=([('', u'')]
|
label="Type de lieu d'accueil",
|
||||||
+ list(TYPE_LIEU_OPTIONS)),
|
choices=([("", "")] + list(TYPE_LIEU_OPTIONS)),
|
||||||
required=False)
|
required=False,
|
||||||
tri = forms.ChoiceField(label=u'Tri par',
|
)
|
||||||
choices=[('pertinence', u'Pertinence'),
|
tri = forms.ChoiceField(
|
||||||
('-date_maj',u'Dernière mise à jour')],
|
label="Tri par",
|
||||||
required=False, initial='pertinence')
|
choices=[("pertinence", "Pertinence"), ("-date_maj", "Dernière mise à jour")],
|
||||||
|
required=False,
|
||||||
|
initial="pertinence",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def cherche(**kwargs):
|
def cherche(**kwargs):
|
||||||
filtres = Q(public=True)
|
filtres = Q(public=True)
|
||||||
dsl = StageDocument.search()
|
|
||||||
|
|
||||||
use_dsl = False
|
use_dsl = False
|
||||||
|
|
||||||
def field_relevant(field, test_string=True):
|
def field_relevant(field, test_string=True):
|
||||||
return field in kwargs and \
|
return (
|
||||||
kwargs[field] is not None and \
|
field in kwargs
|
||||||
((not test_string) or kwargs[field].strip() != '')
|
and kwargs[field] is not None
|
||||||
|
and ((not test_string) or kwargs[field].strip() != "")
|
||||||
|
)
|
||||||
|
|
||||||
|
if USE_ELASTICSEARCH:
|
||||||
|
dsl = StageDocument.search()
|
||||||
|
|
||||||
#
|
#
|
||||||
# Recherche libre
|
# Recherche libre AVEC ELASTICSEARCH
|
||||||
#
|
#
|
||||||
|
|
||||||
# Champ générique : recherche dans tous les champs
|
# Champ générique : recherche dans tous les champs
|
||||||
if field_relevant("generique"):
|
if field_relevant("generique"):
|
||||||
#print "Filtre generique", kwargs['generique']
|
# print("Filtre generique", kwargs['generique'])
|
||||||
dsl = dsl.query(
|
dsl = dsl.query(
|
||||||
"match",
|
"multi_match",
|
||||||
_all={"query": kwargs["generique"],
|
query=kwargs["generique"],
|
||||||
"fuzziness": "auto"})
|
fuzziness="auto",
|
||||||
|
fields=[
|
||||||
|
"sujet^3",
|
||||||
|
"encadrants",
|
||||||
|
"type_stage",
|
||||||
|
"niveau_scol",
|
||||||
|
"structure",
|
||||||
|
"lieux.*^2",
|
||||||
|
"auteur.nom^2",
|
||||||
|
"thematiques^2",
|
||||||
|
"matieres",
|
||||||
|
],
|
||||||
|
)
|
||||||
use_dsl = True
|
use_dsl = True
|
||||||
|
|
||||||
# Sujet -> Recherche dan les noms de sujets et les thématiques
|
# Sujet -> Recherche dan les noms de sujets et les thématiques
|
||||||
if field_relevant("sujet"):
|
if field_relevant("sujet"):
|
||||||
dsl = dsl.query("multi_match",
|
dsl = dsl.query(
|
||||||
query = kwargs["sujet"],
|
"multi_match",
|
||||||
fields = ['sujet^2', 'thematiques', 'matieres'],
|
query=kwargs["sujet"],
|
||||||
fuzziness = "auto")
|
fields=["sujet^2", "thematiques", "matieres"],
|
||||||
|
fuzziness="auto",
|
||||||
|
)
|
||||||
use_dsl = True
|
use_dsl = True
|
||||||
|
|
||||||
# Contexte -> Encadrants, structure, lieu
|
# Contexte -> Encadrants, structure, lieu
|
||||||
if field_relevant("contexte"):
|
if field_relevant("contexte"):
|
||||||
dsl = dsl.query("multi_match",
|
dsl = dsl.query(
|
||||||
query = kwargs["contexte"],
|
"multi_match",
|
||||||
fields = ['encadrants', 'structure^2',
|
query=kwargs["contexte"],
|
||||||
'lieux.nom', 'lieux.pays', 'lieux.ville'],
|
fields=[
|
||||||
fuzziness = "auto")
|
"encadrants",
|
||||||
|
"structure^2",
|
||||||
|
"lieux.nom",
|
||||||
|
"lieux.pays",
|
||||||
|
"lieux.ville",
|
||||||
|
],
|
||||||
|
fuzziness="auto",
|
||||||
|
)
|
||||||
use_dsl = True
|
use_dsl = True
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Sans ElasticSearch, on active quand même une approximation de
|
||||||
|
# recherche en base de données
|
||||||
|
if field_relevant("generique"):
|
||||||
|
generique = kwargs["generique"]
|
||||||
|
filtres = (
|
||||||
|
Q(sujet__icontains=generique)
|
||||||
|
| Q(thematiques__name__icontains=generique)
|
||||||
|
| Q(matieres__nom__icontains=generique)
|
||||||
|
| Q(lieux__nom__icontains=generique)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Autres champs -> non fonctionnels
|
||||||
|
if field_relevant("sujet") or field_relevant("contexte"):
|
||||||
|
raise NotImplementedError(
|
||||||
|
"ElasticSearch doit être activé pour ce type de recherche"
|
||||||
|
)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Filtres directs db
|
# Filtres directs db
|
||||||
#
|
#
|
||||||
|
|
||||||
# Dates
|
# Dates
|
||||||
if field_relevant('avant_annee', False):
|
if field_relevant("avant_annee", False):
|
||||||
dte = date(kwargs['avant_annee']+1, 1, 1)
|
dte = date(min(2100, kwargs["avant_annee"]) + 1, 1, 1)
|
||||||
filtres &= Q(date_fin__lt=dte)
|
filtres &= Q(date_fin__lt=dte)
|
||||||
|
|
||||||
if field_relevant('apres_annee', False):
|
if field_relevant("apres_annee", False):
|
||||||
dte = date(kwargs['apres_annee'], 1, 1)
|
dte = date(max(2000, kwargs["apres_annee"]), 1, 1)
|
||||||
filtres &= Q(date_debut__gte=dte)
|
filtres &= Q(date_debut__gte=dte)
|
||||||
|
|
||||||
# Type de stage
|
# Type de stage
|
||||||
if field_relevant('type_stage'):
|
if field_relevant("type_stage"):
|
||||||
filtres &= Q(type_stage=kwargs["type_stage"])
|
filtres &= Q(type_stage=kwargs["type_stage"])
|
||||||
|
|
||||||
if field_relevant('niveau_scol'):
|
if field_relevant("niveau_scol"):
|
||||||
filtres &= Q(niveau_scol=kwargs["niveau_scol"])
|
filtres &= Q(niveau_scol=kwargs["niveau_scol"])
|
||||||
|
|
||||||
# Type de lieu
|
# Type de lieu
|
||||||
if field_relevant('type_lieu'):
|
if field_relevant("type_lieu"):
|
||||||
filtres &= Q(lieux__type_lieu=kwargs["type_lieu"])
|
filtres &= Q(lieux__type_lieu=kwargs["type_lieu"])
|
||||||
|
|
||||||
|
# Tri
|
||||||
|
tri = "pertinence"
|
||||||
|
|
||||||
# Application
|
if field_relevant("tri") and kwargs["tri"] in ["-date_maj"]:
|
||||||
if use_dsl:
|
tri = kwargs["tri"]
|
||||||
filtres &= Q(id__in=[s.meta.id for s in dsl.scan()])
|
|
||||||
|
|
||||||
#print filtres
|
|
||||||
resultat = Stage.objects.filter(filtres)
|
|
||||||
tri = 'pertinence'
|
|
||||||
|
|
||||||
if not use_dsl:
|
if not use_dsl:
|
||||||
kwargs['tri'] = '-date_maj'
|
tri = "-date_maj"
|
||||||
|
|
||||||
if field_relevant('tri') and kwargs['tri'] != 'pertinence':
|
# Application
|
||||||
tri = kwargs['tri']
|
resultat = Stage.objects.filter(filtres).distinct()
|
||||||
resultat = resultat.order_by(kwargs['tri'])
|
|
||||||
|
if USE_ELASTICSEARCH and use_dsl:
|
||||||
|
dsl_res = [s.meta.id for s in dsl.scan()]
|
||||||
|
resultat = resultat.filter(id__in=dsl_res)
|
||||||
|
|
||||||
|
if tri == "pertinence":
|
||||||
|
resultat = resultat.order_by(
|
||||||
|
Case(*[When(pk=pk, then=pos) for pos, pk in enumerate(dsl_res)])
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
resultat = resultat.order_by(tri)
|
||||||
|
else:
|
||||||
|
resultat = resultat.order_by(tri)
|
||||||
|
|
||||||
return resultat, tri
|
return resultat, tri
|
||||||
|
|
||||||
@login_required
|
|
||||||
def recherche(request):
|
|
||||||
form = SearchForm()
|
|
||||||
return render(request, 'avisstage/recherche/recherche.html',
|
|
||||||
{"form": form})
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
@en_scolarite_required
|
||||||
|
def recherche(request):
|
||||||
|
form = SearchForm()
|
||||||
|
return render(request, "avisstage/recherche/recherche.html", {"form": form})
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@en_scolarite_required
|
||||||
def recherche_resultats(request):
|
def recherche_resultats(request):
|
||||||
stages = []
|
stages = []
|
||||||
tri = ''
|
tri = ""
|
||||||
vue = 'vue-liste'
|
vue = "vue-liste"
|
||||||
lieux = []
|
lieux = []
|
||||||
|
stageids = []
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
form = SearchForm(request.GET)
|
form = SearchForm(request.GET)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
stages, tri = cherche(**form.cleaned_data)
|
page = request.GET.get("page", 1)
|
||||||
stages = stages.prefetch_related('lieux', 'auteur', 'matieres', 'thematiques')
|
search_args = form.cleaned_data
|
||||||
lieux = [[stageid, lieuid] for (stageid, lieuid) in stages.values_list('id', 'lieux') if lieuid is not None]
|
|
||||||
|
# Gestion du cache
|
||||||
|
lsearch_args = {
|
||||||
|
key: val
|
||||||
|
for key, val in search_args.items()
|
||||||
|
if val != "" and val is not None
|
||||||
|
}
|
||||||
|
cache_key = json.dumps(lsearch_args, sort_keys=True)
|
||||||
|
cached = cache.get(cache_key)
|
||||||
|
if cached is None:
|
||||||
|
# Requête effective
|
||||||
|
stages, tri = cherche(**search_args)
|
||||||
|
stageids = list(stages.values_list("id", flat=True))
|
||||||
|
lieux = [
|
||||||
|
[stageid, lieuid]
|
||||||
|
for (stageid, lieuid) in stages.values_list("id", "lieux")
|
||||||
|
if lieuid is not None
|
||||||
|
]
|
||||||
|
|
||||||
|
# Sauvegarde dans le cache
|
||||||
|
to_cache = {"stages": stageids, "lieux": lieux, "tri": tri}
|
||||||
|
cache.set(cache_key, to_cache, 600)
|
||||||
|
logger.info(cache_key)
|
||||||
|
else:
|
||||||
|
# Lecture du cache
|
||||||
|
stageids = cached["stages"]
|
||||||
|
lieux = cached["lieux"]
|
||||||
|
tri = cached["tri"]
|
||||||
|
logger.info("recherche en cache")
|
||||||
|
|
||||||
|
# Pagination
|
||||||
|
paginator = Paginator(stageids, 25)
|
||||||
|
try:
|
||||||
|
stageids = paginator.page(page)
|
||||||
|
except InvalidPage:
|
||||||
|
stageids = []
|
||||||
|
|
||||||
|
if cached is None:
|
||||||
|
stages = stages[
|
||||||
|
max(0, stageids.start_index() - 1) : stageids.end_index()
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
orderer = Case(
|
||||||
|
*[When(pk=pk, then=pos) for pos, pk in enumerate(stageids)]
|
||||||
|
)
|
||||||
|
stages = Stage.objects.filter(id__in=stageids).order_by(orderer)
|
||||||
|
|
||||||
|
stages = stages.prefetch_related(
|
||||||
|
"lieux", "auteur", "matieres", "thematiques"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
form = SearchForm()
|
form = SearchForm()
|
||||||
if stages:
|
if stages:
|
||||||
vue = 'vue-hybride'
|
vue = "vue-hybride"
|
||||||
return render(request, 'avisstage/recherche/resultats.html',
|
|
||||||
{"form": form, "stages":stages,
|
# Version JSON pour recherche dynamique
|
||||||
"tri": tri, "vue": vue, "lieux": lieux})
|
if request.GET.get("format") == "json":
|
||||||
|
return JsonResponse(
|
||||||
|
{"stages": stages, "page": page, "num_pages": paginator.num_pages}
|
||||||
|
)
|
||||||
|
|
||||||
|
template_name = "avisstage/recherche/resultats.html"
|
||||||
|
if request.GET.get("format") == "raw":
|
||||||
|
template_name = "avisstage/recherche/stage_items.html"
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
template_name,
|
||||||
|
{
|
||||||
|
"form": form,
|
||||||
|
"stages": stages,
|
||||||
|
"paginator": stageids,
|
||||||
|
"tri": tri,
|
||||||
|
"vue": vue,
|
||||||
|
"lieux": lieux,
|
||||||
|
"MAPBOX_API_KEY": settings.MAPBOX_API_KEY,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@en_scolarite_required
|
||||||
|
def stage_items(request):
|
||||||
|
try:
|
||||||
|
stageids = [int(a) for a in request.GET.get("ids", "").split(";")]
|
||||||
|
except ValueError:
|
||||||
|
return HttpResponseBadRequest("Paramètre incorrect")
|
||||||
|
stages = Stage.objects.filter(id__in=stageids).prefetch_related(
|
||||||
|
"lieux", "auteur", "matieres", "thematiques"
|
||||||
|
)
|
||||||
|
return render(request, "avisstage/recherche/stage_items.html", {"stages": stages})
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.core import validators
|
from django.core import validators
|
||||||
|
|
||||||
|
|
||||||
class LatLonWidget(forms.MultiWidget):
|
class LatLonWidget(forms.MultiWidget):
|
||||||
"""
|
"""
|
||||||
A Widget that splits Point input into two latitude/longitude boxes.
|
A Widget that splits Point input into two latitude/longitude boxes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, attrs=None, date_format=None, time_format=None):
|
def __init__(self, attrs=None, date_format=None, time_format=None):
|
||||||
widgets = (forms.HiddenInput(attrs=attrs),
|
widgets = (forms.HiddenInput(attrs=attrs), forms.HiddenInput(attrs=attrs))
|
||||||
forms.HiddenInput(attrs=attrs))
|
|
||||||
super(LatLonWidget, self).__init__(widgets, attrs)
|
super(LatLonWidget, self).__init__(widgets, attrs)
|
||||||
|
|
||||||
def decompress(self, value):
|
def decompress(self, value):
|
||||||
|
@ -23,13 +23,15 @@ class LatLonField(forms.MultiValueField):
|
||||||
srid = 4326
|
srid = 4326
|
||||||
|
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid_latitude' : (u'Entrez une latitude valide.'),
|
"invalid_latitude": ("Entrez une latitude valide."),
|
||||||
'invalid_longitude' : (u'Entrez une longitude valide.'),
|
"invalid_longitude": ("Entrez une longitude valide."),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
fields = (forms.FloatField(min_value=-90, max_value=90),
|
fields = (
|
||||||
forms.FloatField(min_value=-180, max_value=180))
|
forms.FloatField(min_value=-90, max_value=90),
|
||||||
|
forms.FloatField(min_value=-180, max_value=180),
|
||||||
|
)
|
||||||
super(LatLonField, self).__init__(fields, *args, **kwargs)
|
super(LatLonField, self).__init__(fields, *args, **kwargs)
|
||||||
|
|
||||||
def compress(self, data_list):
|
def compress(self, data_list):
|
||||||
|
@ -37,11 +39,11 @@ class LatLonField(forms.MultiValueField):
|
||||||
# Raise a validation error if latitude or longitude is empty
|
# Raise a validation error if latitude or longitude is empty
|
||||||
# (possible if LatLongField has required=False).
|
# (possible if LatLongField has required=False).
|
||||||
if data_list[0] in validators.EMPTY_VALUES:
|
if data_list[0] in validators.EMPTY_VALUES:
|
||||||
raise forms.ValidationError(self.error_messages['invalid_latitude'])
|
raise forms.ValidationError(self.error_messages["invalid_latitude"])
|
||||||
if data_list[1] in validators.EMPTY_VALUES:
|
if data_list[1] in validators.EMPTY_VALUES:
|
||||||
raise forms.ValidationError(self.error_messages['invalid_longitude'])
|
raise forms.ValidationError(self.error_messages["invalid_longitude"])
|
||||||
# SRID=4326;POINT(1.12345789 1.123456789)
|
# SRID=4326;POINT(1.12345789 1.123456789)
|
||||||
srid_str = 'SRID=%d'%self.srid
|
srid_str = "SRID=%d" % self.srid
|
||||||
point_str = 'POINT(%f %f)'%tuple(reversed(data_list))
|
point_str = "POINT(%f %f)" % tuple(reversed(data_list))
|
||||||
return ';'.join([srid_str, point_str])
|
return ";".join([srid_str, point_str])
|
||||||
return None
|
return None
|
||||||
|
|
94
default.nix
Normal file
94
default.nix
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
{
|
||||||
|
sources ? import ./npins,
|
||||||
|
pkgs ? import sources.nixpkgs { },
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
nix-pkgs = import sources.nix-pkgs { inherit pkgs; };
|
||||||
|
|
||||||
|
check = (import sources.git-hooks).run {
|
||||||
|
src = ./.;
|
||||||
|
|
||||||
|
hooks = {
|
||||||
|
# Python hooks
|
||||||
|
black = {
|
||||||
|
enable = true;
|
||||||
|
stages = [ "pre-push" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
isort = {
|
||||||
|
enable = true;
|
||||||
|
stages = [ "pre-push" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
ruff = {
|
||||||
|
enable = true;
|
||||||
|
stages = [ "pre-push" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Misc Hooks
|
||||||
|
commitizen.enable = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
python3 = pkgs.python3.override {
|
||||||
|
packageOverrides = _: _: {
|
||||||
|
inherit (nix-pkgs)
|
||||||
|
authens
|
||||||
|
django-braces
|
||||||
|
django-elasticsearch-dsl
|
||||||
|
django-simple-email-confirmation
|
||||||
|
django-taggit-autosuggest
|
||||||
|
django-tinymce
|
||||||
|
loadcredential
|
||||||
|
spatialite
|
||||||
|
;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
|
||||||
|
{
|
||||||
|
devShell = pkgs.mkShell {
|
||||||
|
name = "annuaire.dev";
|
||||||
|
|
||||||
|
packages = [
|
||||||
|
(python3.withPackages (ps: [
|
||||||
|
ps.authens
|
||||||
|
ps.django
|
||||||
|
ps.django-braces
|
||||||
|
ps.django-elasticsearch-dsl
|
||||||
|
ps.django-simple-email-confirmation
|
||||||
|
ps.django-taggit
|
||||||
|
ps.django-taggit-autosuggest
|
||||||
|
ps.django-tastypie
|
||||||
|
ps.django-tinymce
|
||||||
|
ps.loadcredential
|
||||||
|
|
||||||
|
# Dev packages
|
||||||
|
ps.django-debug-toolbar
|
||||||
|
ps.django-stubs
|
||||||
|
ps.spatialite
|
||||||
|
]))
|
||||||
|
];
|
||||||
|
|
||||||
|
env = {
|
||||||
|
DJANGO_SETTINGS_MODULE = "app.settings";
|
||||||
|
|
||||||
|
CREDENTIALS_DIRECTORY = builtins.toString ./.credentials;
|
||||||
|
|
||||||
|
EXPERIENS_DEBUG = builtins.toJSON true;
|
||||||
|
EXPERIENS_STATIC_ROOT = builtins.toString ./.static;
|
||||||
|
|
||||||
|
EXPERIENS_GDAL_LIBRARY_PATH = "${pkgs.gdal}/lib/libgdal.so";
|
||||||
|
EXPERIENS_GEOS_LIBRARY_PATH = "${pkgs.geos}/lib/libgeos_c.so";
|
||||||
|
};
|
||||||
|
|
||||||
|
shellHook = ''
|
||||||
|
${check.shellHook}
|
||||||
|
|
||||||
|
if [ ! -d .static ]; then
|
||||||
|
mkdir .static
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,5 +0,0 @@
|
||||||
from django_cas_ng.backends import CASBackend
|
|
||||||
|
|
||||||
class ENSCASBackend(CASBackend):
|
|
||||||
def clean_username(self, username):
|
|
||||||
return username.lower().strip()
|
|
|
@ -1,116 +0,0 @@
|
||||||
"""
|
|
||||||
Django settings for experiENS project.
|
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/1.7/topics/settings/
|
|
||||||
|
|
||||||
For the full list of settings and their values, see
|
|
||||||
https://docs.djangoproject.com/en/1.7/ref/settings/
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
|
||||||
import os
|
|
||||||
from django.core.urlresolvers import reverse_lazy
|
|
||||||
|
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
|
||||||
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
|
|
||||||
|
|
||||||
ALLOWED_HOSTS = []
|
|
||||||
|
|
||||||
# Application definition
|
|
||||||
|
|
||||||
INSTALLED_APPS = (
|
|
||||||
'django.contrib.admin',
|
|
||||||
'django.contrib.auth',
|
|
||||||
'django.contrib.contenttypes',
|
|
||||||
'django.contrib.sessions',
|
|
||||||
'django.contrib.messages',
|
|
||||||
'django.contrib.staticfiles',
|
|
||||||
'django.contrib.gis',
|
|
||||||
|
|
||||||
'django_elasticsearch_dsl',
|
|
||||||
|
|
||||||
'tastypie',
|
|
||||||
'django_cas_ng',
|
|
||||||
'braces',
|
|
||||||
'tinymce',
|
|
||||||
'taggit',
|
|
||||||
'taggit_autosuggest',
|
|
||||||
'avisstage'
|
|
||||||
)
|
|
||||||
|
|
||||||
MIDDLEWARE_CLASSES = (
|
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
||||||
'django.middleware.common.CommonMiddleware',
|
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
|
||||||
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
|
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
|
||||||
)
|
|
||||||
|
|
||||||
TEMPLATES = [
|
|
||||||
{
|
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
|
||||||
'DIRS': [
|
|
||||||
# insert your TEMPLATE_DIRS here
|
|
||||||
],
|
|
||||||
'APP_DIRS': True,
|
|
||||||
'OPTIONS': {
|
|
||||||
'context_processors': [
|
|
||||||
'django.contrib.auth.context_processors.auth',
|
|
||||||
'django.template.context_processors.debug',
|
|
||||||
'django.template.context_processors.i18n',
|
|
||||||
'django.template.context_processors.media',
|
|
||||||
'django.template.context_processors.static',
|
|
||||||
'django.template.context_processors.tz',
|
|
||||||
'django.contrib.messages.context_processors.messages',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
ROOT_URLCONF = 'experiENS.urls'
|
|
||||||
|
|
||||||
WSGI_APPLICATION = 'experiENS.wsgi.application'
|
|
||||||
|
|
||||||
|
|
||||||
# Database
|
|
||||||
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
|
|
||||||
|
|
||||||
# Internationalization
|
|
||||||
# https://docs.djangoproject.com/en/1.7/topics/i18n/
|
|
||||||
|
|
||||||
LANGUAGE_CODE = 'fr-fr'
|
|
||||||
|
|
||||||
TIME_ZONE = 'Europe/Paris'
|
|
||||||
|
|
||||||
USE_I18N = True
|
|
||||||
|
|
||||||
USE_L10N = True
|
|
||||||
|
|
||||||
USE_TZ = True
|
|
||||||
|
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
|
||||||
# https://docs.djangoproject.com/en/1.7/howto/static-files/
|
|
||||||
|
|
||||||
STATIC_URL = '/static/'
|
|
||||||
|
|
||||||
AUTHENTICATION_BACKENDS = (
|
|
||||||
'django.contrib.auth.backends.ModelBackend',
|
|
||||||
'experiENS.auth.ENSCASBackend',
|
|
||||||
)
|
|
||||||
|
|
||||||
CAS_SERVER_URL = "https://cas.eleves.ens.fr/" #SPI CAS
|
|
||||||
CAS_VERIFY_URL = "https://cas.eleves.ens.fr/"
|
|
||||||
CAS_IGNORE_REFERER = True
|
|
||||||
CAS_REDIRECT_URL = reverse_lazy('avisstage:perso')
|
|
||||||
CAS_EMAIL_FORMAT = "%s@clipper.ens.fr"
|
|
||||||
CAS_FORCE_CHANGE_USERNAME_CASE = "lower"
|
|
||||||
CAS_VERSION = 'CAS_2_SAML_1_0'
|
|
||||||
|
|
||||||
LOGIN_URL = reverse_lazy('login')
|
|
||||||
LOGOUT_URL = reverse_lazy('logout')
|
|
|
@ -1,36 +0,0 @@
|
||||||
from settings_base import *
|
|
||||||
|
|
||||||
from secrets import SECRET_KEY
|
|
||||||
|
|
||||||
DEBUG = True
|
|
||||||
|
|
||||||
DATABASES = {
|
|
||||||
'default': {
|
|
||||||
'ENGINE': 'django.contrib.gis.db.backends.spatialite',
|
|
||||||
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
INSTALLED_APPS += (
|
|
||||||
'debug_toolbar',
|
|
||||||
)
|
|
||||||
|
|
||||||
MIDDLEWARE_CLASSES = (
|
|
||||||
'debug_toolbar.middleware.DebugToolbarMiddleware',
|
|
||||||
) + MIDDLEWARE_CLASSES
|
|
||||||
|
|
||||||
INTERNAL_IPS = ['127.0.0.1']
|
|
||||||
|
|
||||||
SPATIALITE_LIBRARY_PATH = 'mod_spatialite'
|
|
||||||
|
|
||||||
STATIC_ROOT = "/home/evarin/Bureau/experiENS/static/"
|
|
||||||
|
|
||||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
|
||||||
|
|
||||||
STATIC_URL = "/experiens/static/"
|
|
||||||
|
|
||||||
ELASTICSEARCH_DSL = {
|
|
||||||
'default': {
|
|
||||||
'hosts': 'localhost:9200'
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
from settings_base import *
|
|
||||||
|
|
||||||
from secrets import SECRET_KEY
|
|
||||||
|
|
||||||
import os, sys
|
|
||||||
from django.core.urlresolvers import reverse_lazy
|
|
||||||
|
|
||||||
PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
BASE_DIR = os.path.dirname(PROJECT_DIR)
|
|
||||||
|
|
||||||
DEBUG = False
|
|
||||||
|
|
||||||
ALLOWED_HOSTS = ["www.eleves.ens.fr"]
|
|
||||||
|
|
||||||
ADMINS = (
|
|
||||||
('Robin Champenois', 'champeno@clipper.ens.fr'),
|
|
||||||
)
|
|
||||||
|
|
||||||
ADMIN_LOGINS = [
|
|
||||||
"champeno",
|
|
||||||
]
|
|
||||||
|
|
||||||
SERVER_EMAIL = "experiens@www.eleves.ens.fr"
|
|
||||||
|
|
||||||
ROOT_URL = "/experiens/"
|
|
||||||
|
|
||||||
WSGI_APPLICATION = 'experiENS.wsgi.application'
|
|
||||||
|
|
||||||
DATABASES = {
|
|
||||||
'default': {
|
|
||||||
'ENGINE': 'django.contrib.gis.db.backends.postgis',
|
|
||||||
'NAME': 'experiens',
|
|
||||||
'USER': 'experiens',
|
|
||||||
'PASSWORD': '',
|
|
||||||
'HOST': '',
|
|
||||||
'PORT': '5432',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
STATIC_URL = ROOT_URL + 'static/'
|
|
||||||
MEDIA_URL = ROOT_URL + 'media/'
|
|
||||||
|
|
||||||
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
|
|
||||||
|
|
||||||
EMAIL_HOST = "nef.ens.fr"
|
|
||||||
|
|
||||||
|
|
||||||
ELASTICSEARCH_DSL = {
|
|
||||||
'default': {
|
|
||||||
'hosts': '127.0.0.1:9200'
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
from django.conf import settings
|
|
||||||
from django.conf.urls import include, url
|
|
||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
from django_cas_ng import views as django_cas_views
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
url(r'^', include('avisstage.urls', namespace='avisstage')),
|
|
||||||
|
|
||||||
url(r'^login/$', django_cas_views.login, name = "login"),
|
|
||||||
url(r'^logout/$', django_cas_views.logout, name = "logout"),
|
|
||||||
url(r'^tinymce/', include('tinymce.urls')),
|
|
||||||
url(r'^taggit_autosuggest/', include('taggit_autosuggest.urls')),
|
|
||||||
url(r'^admin/', include(admin.site.urls)),
|
|
||||||
]
|
|
||||||
|
|
||||||
if settings.DEBUG:
|
|
||||||
import debug_toolbar
|
|
||||||
urlpatterns = [
|
|
||||||
url(r'^__debug__/', include(debug_toolbar.urls)),
|
|
||||||
] + urlpatterns
|
|
0
manage.py
Normal file → Executable file
0
manage.py
Normal file → Executable file
80
npins/default.nix
Normal file
80
npins/default.nix
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
# 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`"
|
34
npins/sources.json
Normal file
34
npins/sources.json
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"pins": {
|
||||||
|
"git-hooks": {
|
||||||
|
"type": "Git",
|
||||||
|
"repository": {
|
||||||
|
"type": "GitHub",
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "git-hooks.nix"
|
||||||
|
},
|
||||||
|
"branch": "master",
|
||||||
|
"revision": "3c3e88f0f544d6bb54329832616af7eb971b6be6",
|
||||||
|
"url": "https://github.com/cachix/git-hooks.nix/archive/3c3e88f0f544d6bb54329832616af7eb971b6be6.tar.gz",
|
||||||
|
"hash": "04pwjz423iq2nkazkys905gvsm5j39722ngavrnx42b8msr5k555"
|
||||||
|
},
|
||||||
|
"nix-pkgs": {
|
||||||
|
"type": "Git",
|
||||||
|
"repository": {
|
||||||
|
"type": "Git",
|
||||||
|
"url": "https://git.hubrecht.ovh/hubrecht/nix-pkgs"
|
||||||
|
},
|
||||||
|
"branch": "main",
|
||||||
|
"revision": "024f0d09d4ff1a62e11f5fdd74f2d00d0a77da5c",
|
||||||
|
"url": null,
|
||||||
|
"hash": "0abpyf4pclslg24wmwl3q6y8x5fmhq9winpgkpbb99yw2815j2iz"
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"type": "Channel",
|
||||||
|
"name": "nixpkgs-unstable",
|
||||||
|
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-24.11pre694416.ccc0c2126893/nixexprs.tar.xz",
|
||||||
|
"hash": "0cn1z4wzps8nfqxzr6l5mbn81adcqy2cy2ic70z13fhzicmxfsbx"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"version": 3
|
||||||
|
}
|
|
@ -1,2 +1,3 @@
|
||||||
|
-r requirements.txt
|
||||||
#spatialite-bin
|
#spatialite-bin
|
||||||
django-debug-toolbar
|
django-debug-toolbar
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
psycopg2
|
-r requirements.txt
|
||||||
|
psycopg2-binary==2.7.*
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
django
|
django==2.2.*
|
||||||
django-cas-ng
|
django-taggit==1.3.*
|
||||||
django-taggit
|
django-tinymce==3.2.*
|
||||||
python-ldap
|
django-braces==1.14.*
|
||||||
django-tinymce
|
django-taggit-autosuggest==0.3.*
|
||||||
django-braces
|
pytz==2020.*
|
||||||
django-taggit-autosuggest
|
django-tastypie==0.14.*
|
||||||
pytz
|
lxml==4.6.*
|
||||||
django-tastypie
|
django-elasticsearch-dsl==7.1.*
|
||||||
lxml
|
authens
|
||||||
git+https://github.com/sabricot/django-elasticsearch-dsl
|
django-simple-email-confirmation==0.*
|
||||||
|
|
53
scripts/initalize_allauth.py
Normal file
53
scripts/initalize_allauth.py
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import sys
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
from allauth.account.models import EmailAddress
|
||||||
|
from allauth.socialaccount.models import SocialAccount
|
||||||
|
|
||||||
|
from avisstage.models import Normalien
|
||||||
|
|
||||||
|
accounts = SocialAccount.objects.all().prefetch_related("user")
|
||||||
|
profils = Normalien.objects.all()
|
||||||
|
addresses = EmailAddress.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
addr_dict = defaultdict(set)
|
||||||
|
for addr in addresses:
|
||||||
|
addr_dict[addr.user_id].add(addr.email)
|
||||||
|
|
||||||
|
|
||||||
|
profil_dict = {profil.user_id: profil for profil in profils}
|
||||||
|
|
||||||
|
addr_to_create = []
|
||||||
|
|
||||||
|
for acc in accounts:
|
||||||
|
u = acc.user
|
||||||
|
|
||||||
|
try:
|
||||||
|
profil = profil_dict[u.id]
|
||||||
|
except KeyError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
to_addr = set()
|
||||||
|
if profil.mail:
|
||||||
|
to_addr.add(profil.mail)
|
||||||
|
cp_ml = "%s@clipper.ens.fr" % acc.uid
|
||||||
|
try:
|
||||||
|
cp_ml = acc.extra_data["ldap"]["email"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
to_addr.add(cp_ml)
|
||||||
|
|
||||||
|
addrs = addr_dict[u.id]
|
||||||
|
|
||||||
|
print(u.username, ";".join(list(to_addr)), ";".join(list(addrs)))
|
||||||
|
to_addr -= addrs
|
||||||
|
has_prim = len(addrs) > 0
|
||||||
|
|
||||||
|
for addr in to_addr:
|
||||||
|
ml = EmailAddress(email=addr, user=u, verified=True, primary=has_prim)
|
||||||
|
has_prim = False
|
||||||
|
addr_to_create.append(ml)
|
||||||
|
|
||||||
|
if "--fake" not in sys.argv:
|
||||||
|
EmailAddress.objects.bulk_create(addr_to_create)
|
10
setup.cfg
Normal file
10
setup.cfg
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
[flake8]
|
||||||
|
max-line-length = 99
|
||||||
|
exclude = .git, *.pyc, __pycache__, migrations
|
||||||
|
extend-ignore = E231, E203, E402
|
||||||
|
|
||||||
|
[isort]
|
||||||
|
profile = black
|
||||||
|
known_django = django
|
||||||
|
known_first_party = avisstage
|
||||||
|
sections = FUTURE,STDLIB,THIRDPARTY,DJANGO,FIRSTPARTY,LOCALFOLDER
|
1
shell.nix
Normal file
1
shell.nix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
(import ./. { }).devShell
|
Loading…
Reference in a new issue