Compare commits
3 commits
Author | SHA1 | Date | |
---|---|---|---|
|
0065d0cdec | ||
|
78f1013ab1 | ||
|
377cad94ae |
103 changed files with 2012 additions and 5556 deletions
|
@ -1 +0,0 @@
|
|||
insecure-secret-key
|
1
.envrc
1
.envrc
|
@ -1 +0,0 @@
|
|||
use nix
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -108,6 +108,6 @@ test.py
|
|||
.#*
|
||||
*.sqlite3
|
||||
.sass-cache
|
||||
/static/
|
||||
settings.py
|
||||
secrets.py
|
||||
.direnv
|
||||
.pre-commit-config.yaml
|
||||
|
|
24
README.md
24
README.md
|
@ -3,13 +3,11 @@ 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.
|
||||
|
||||
Il est visible sur https://www.eleves.ens.fr/experiens/
|
||||
|
||||
## Développer sur son ordinateur
|
||||
|
||||
Clonez le dépôt. Installez les pré-requis :
|
||||
|
||||
sudo apt-get install libxlst-dev libsals2-dev libxml2-dev libldap2-dev libssl-dev
|
||||
sudo apt-get install libxlst-dev python2.7-dev
|
||||
|
||||
On a besoin de SpatiaLite pour une base de données GIS. Essayez
|
||||
|
||||
|
@ -25,17 +23,15 @@ Ensuite, paramétrez les settings :
|
|||
|
||||
cd experiENS/
|
||||
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
|
||||
cd ../
|
||||
|
||||
Enfin, installez les autres dépendances :
|
||||
|
||||
python3 -m venv venv
|
||||
virtualenv venv
|
||||
. venv/bin/activate
|
||||
pip install --update pip
|
||||
pip install -r requirements-dev.txt
|
||||
pip install -r requirements.txt
|
||||
pip install -r requirements_dev.txt
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
|
||||
|
@ -43,13 +39,9 @@ Vous pouvez alors lancez le serveur de développement
|
|||
|
||||
python manage.py runserver
|
||||
|
||||
C'est bon, vous pouvez développer sur ExpériENS !
|
||||
|
||||
## Configuration de la recherche
|
||||
|
||||
**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.
|
||||
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.
|
||||
|
||||
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -
|
||||
sudo apt-get install apt-transport-https
|
||||
|
@ -60,13 +52,11 @@ Il faut installer elasticsearch 5.*. C'est compliqué. Mais en suivant https://w
|
|||
sudo systemctl enable elasticsearch.service
|
||||
sudo systemctl start elasticsearch.service
|
||||
|
||||
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
|
||||
Et puis, de retour dans le virtualenv python
|
||||
|
||||
python manage.py search_index --rebuild
|
||||
|
||||
Si des erreurs s'affichent, demandez de l'aide sur Merle ou par e-mail.
|
||||
Si des erreurs s'affichent, il y a une cachuète dans le beurre.
|
||||
|
||||
## Changer le CSS
|
||||
|
||||
|
|
6
allauth_archiens/admin.py
Normal file
6
allauth_archiens/admin.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
8
allauth_archiens/apps.py
Normal file
8
allauth_archiens/apps.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AllauthArchiensConfig(AppConfig):
|
||||
name = 'allauth_archiens'
|
0
allauth_archiens/migrations/__init__.py
Normal file
0
allauth_archiens/migrations/__init__.py
Normal file
6
allauth_archiens/models.py
Normal file
6
allauth_archiens/models.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
80
allauth_archiens/provider.py
Normal file
80
allauth_archiens/provider.py
Normal file
|
@ -0,0 +1,80 @@
|
|||
from allauth_ens.providers.clipper.provider import ClipperProvider
|
||||
import ldap
|
||||
|
||||
class LongTermClipperProvider(ClipperProvider):
|
||||
id = 'longterm_clipper'
|
||||
|
||||
def extract_uid(self, data):
|
||||
# Normalize UID
|
||||
uid, _ = data
|
||||
uid = uid.lower().strip()
|
||||
more_data = self.get_ldap_infos(uid)
|
||||
uid = "%s_%s" % (uid, more_data.get("annee", "00"))
|
||||
print(uid)
|
||||
return uid
|
||||
|
||||
def get_ldap_infos(self, clipper):
|
||||
if hasattr(self, "_ldap_infos"):
|
||||
return self._ldap_infos
|
||||
data = {}
|
||||
assert clipper.isalnum()
|
||||
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',
|
||||
ldap.SCOPE_SUBTREE,
|
||||
('(uid=%s)' % (clipper,)),
|
||||
[str("cn"),
|
||||
str("mailRoutingAddress"),
|
||||
str("homeDirectory") ])
|
||||
|
||||
if len(info) > 0:
|
||||
infos = info[0][1]
|
||||
|
||||
# Nom
|
||||
data['nom'] = infos.get('cn', [''])[0].decode("utf-8")
|
||||
|
||||
# Parsing du homeDirectory pour la promotion
|
||||
annee = '00'
|
||||
promotion = 'Inconnue'
|
||||
|
||||
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(), '')
|
||||
promotion = u'%s %s' % (dep, annee)
|
||||
data['annee'] = annee
|
||||
data['promotion'] = promotion
|
||||
|
||||
# Mail
|
||||
pmail = infos.get('mailRoutingAddress', [])
|
||||
data['email'] = '{}@clipper.ens.fr'.format(clipper.strip().lower()) if len(pmail) == 0 else pmail[0]
|
||||
|
||||
|
||||
except ldap.LDAPError:
|
||||
pass
|
||||
|
||||
self._ldap_infos = data
|
||||
return data
|
||||
|
||||
def extract_common_fields(self, data):
|
||||
common = super(ClipperProvider, self).extract_common_fields(data)
|
||||
infos = self.get_ldap_infos(common['username'])
|
||||
common['email'] = infos.get('email',
|
||||
'{}@clipper.ens.fr'.format(common['username'].strip().lower()))
|
||||
common['name'] = infos.get('nom', common['username'])
|
||||
return common
|
||||
|
||||
|
||||
provider_classes = [LongTermClipperProvider]
|
6
allauth_archiens/tests.py
Normal file
6
allauth_archiens/tests.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
6
allauth_archiens/urls.py
Normal file
6
allauth_archiens/urls.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from allauth_cas.urls import default_urlpatterns
|
||||
|
||||
from .provider import LongTermClipperProvider
|
||||
|
||||
urlpatterns = default_urlpatterns(LongTermClipperProvider)
|
15
allauth_archiens/views.py
Normal file
15
allauth_archiens/views.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from allauth_cas import views
|
||||
|
||||
from .provider import LongTermClipperProvider
|
||||
|
||||
|
||||
class LongTermClipperCASAdapter(views.CASAdapter):
|
||||
provider_id = LongTermClipperProvider.id
|
||||
url = 'https://cas.eleves.ens.fr'
|
||||
version = 3
|
||||
|
||||
|
||||
login = views.CASLoginView.adapter_view(LongTermClipperCASAdapter)
|
||||
callback = views.CASCallbackView.adapter_view(LongTermClipperCASAdapter)
|
||||
logout = views.CASLogoutView.adapter_view(LongTermClipperCASAdapter)
|
13
app/auth.py
13
app/auth.py
|
@ -1,13 +0,0 @@
|
|||
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
242
app/settings.py
|
@ -1,242 +0,0 @@
|
|||
"""
|
||||
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
18
app/urls.py
|
@ -1,18 +0,0 @@
|
|||
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
|
|
@ -1,47 +1,34 @@
|
|||
import authens.models as authmod
|
||||
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from avisstage.models import AvisLieu, AvisStage, Lieu, Normalien, Stage, StageMatiere
|
||||
|
||||
from avisstage.models import *
|
||||
|
||||
class NormalienInline(admin.StackedInline):
|
||||
model = Normalien
|
||||
inline_classes = ("collapse open",)
|
||||
|
||||
|
||||
class UserAdmin(UserAdmin):
|
||||
inlines = (NormalienInline, )
|
||||
|
||||
|
||||
class AvisLieuInline(admin.StackedInline):
|
||||
model = AvisLieu
|
||||
inline_classes = ("collapse open",)
|
||||
extra = 0
|
||||
|
||||
|
||||
class AvisStageInline(admin.StackedInline):
|
||||
model = AvisStage
|
||||
inline_classes = ("collapse open",)
|
||||
extra = 0
|
||||
|
||||
|
||||
class StageAdmin(admin.ModelAdmin):
|
||||
inlines = (AvisLieuInline, AvisStageInline)
|
||||
|
||||
|
||||
class StageMatiereAdmin(admin.ModelAdmin):
|
||||
model = StageMatiere
|
||||
prepopulated_fields = {"slug": ("nom",)}
|
||||
|
||||
prepopulated_fields = {"slug": ('nom',)}
|
||||
|
||||
admin.site.unregister(User)
|
||||
admin.site.register(User, UserAdmin)
|
||||
admin.site.register(Lieu)
|
||||
admin.site.register(StageMatiere, StageMatiereAdmin)
|
||||
admin.site.register(Stage, StageAdmin)
|
||||
|
||||
admin.site.register(authmod.CASAccount)
|
||||
admin.site.register(authmod.OldCASAccount)
|
||||
|
|
|
@ -1,25 +1,18 @@
|
|||
from tastypie import fields
|
||||
from tastypie.authentication import SessionAuthentication
|
||||
# coding: utf-8
|
||||
|
||||
from tastypie.resources import ModelResource
|
||||
from tastypie.authentication import SessionAuthentication
|
||||
from tastypie import fields, utils
|
||||
|
||||
from django.contrib.gis import geos
|
||||
from django.urls import reverse
|
||||
|
||||
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
|
||||
|
||||
from .models import Lieu, Stage, Normalien, StageMatiere
|
||||
|
||||
# API principale pour les lieux
|
||||
class LieuResource(ModelResource):
|
||||
# stages = fields.ToManyField("avisstage.api.StageResource",
|
||||
# "stages", use_in="detail", full=True)
|
||||
stages = fields.ToManyField("avisstage.api.StageResource",
|
||||
"stages", use_in="detail", full=True)
|
||||
|
||||
class Meta:
|
||||
queryset = Lieu.objects.all()
|
||||
|
@ -37,15 +30,15 @@ class LieuResource(ModelResource):
|
|||
|
||||
# Trouver les lieux à proximités d'un point donné
|
||||
if "lng" in filters and "lat" in filters:
|
||||
lat = float(filters["lat"])
|
||||
lng = float(filters["lng"])
|
||||
lat = float(filters['lat'])
|
||||
lng = float(filters['lng'])
|
||||
pt = geos.Point((lng,lat), srid=4326)
|
||||
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
|
||||
if "has_stage" in filters:
|
||||
orm_filters["stages__public"] = True
|
||||
orm_filters['stages__public'] = True
|
||||
|
||||
return orm_filters
|
||||
|
||||
|
@ -58,13 +51,12 @@ class LieuResource(ModelResource):
|
|||
bundle = super(LieuResource, self).dehydrate(bundle)
|
||||
|
||||
obj = bundle.obj
|
||||
bundle.data["coord"] = {"lat": float(obj.coord.y), "lng": float(obj.coord.x)}
|
||||
bundle.data['coord'] = {'lat': float(obj.coord.y),
|
||||
'lng': float(obj.coord.x)}
|
||||
|
||||
# Distance au point recherché
|
||||
if "lat" in bundle.request.GET and "lng" in bundle.request.GET:
|
||||
bundle.data["distance"] = approximate_distance(
|
||||
self.reference_point, bundle.obj.coord
|
||||
)
|
||||
# Distance au point recherché (inutile en fait)
|
||||
#if "lat" in bundle.request.GET and "lng" in bundle.request.GET:
|
||||
# bundle.data['distance'] = self.reference_point.distance(bundle.obj.coord)
|
||||
|
||||
# Autres infos utiles
|
||||
bundle.data["pays_nom"] = obj.get_pays_display()
|
||||
|
@ -74,7 +66,6 @@ class LieuResource(ModelResource):
|
|||
|
||||
return bundle
|
||||
|
||||
|
||||
# API sur un stage
|
||||
class StageResource(ModelResource):
|
||||
class Meta:
|
||||
|
@ -83,7 +74,7 @@ class StageResource(ModelResource):
|
|||
fields = ["sujet", "date_debut", "date_fin", "matieres", "id"]
|
||||
|
||||
#login_required
|
||||
authentication = EnScolariteAuthentication()
|
||||
authentication = SessionAuthentication()
|
||||
|
||||
# Filtres personnalisés
|
||||
def build_filters(self, filters=None, **kwargs):
|
||||
|
@ -93,8 +84,8 @@ class StageResource(ModelResource):
|
|||
|
||||
# Récupération des stages à un lieu donné
|
||||
if "lieux" in filters:
|
||||
flieux = map(int, filters["lieux"].split(","))
|
||||
orm_filters["lieux__id__in"] = flieux
|
||||
flieux = map(int, filters['lieux'].split(','))
|
||||
orm_filters['lieux__id__in'] = flieux
|
||||
|
||||
return orm_filters
|
||||
|
||||
|
@ -104,22 +95,18 @@ class StageResource(ModelResource):
|
|||
obj = bundle.obj
|
||||
|
||||
# Affichage des manytomany en condensé
|
||||
bundle.data["auteur"] = obj.auteur.nom
|
||||
bundle.data["thematiques"] = list(
|
||||
obj.thematiques.all().values_list("name", flat=True)
|
||||
)
|
||||
bundle.data["matieres"] = list(obj.matieres.all().values_list("nom", flat=True))
|
||||
bundle.data['auteur'] = obj.auteur.nom
|
||||
bundle.data['thematiques'] = list(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
|
||||
bundle.data["url"] = reverse("avisstage:stage", kwargs={"pk": obj.id})
|
||||
bundle.data['url'] = reverse("avisstage:stage", kwargs={"pk": obj.id});
|
||||
return bundle
|
||||
|
||||
|
||||
# Auteurs des fiches (TODO supprimer ?)
|
||||
class AuteurResource(ModelResource):
|
||||
stages = fields.ToManyField(
|
||||
"avisstage.api.StageResource", "stages", use_in="detail"
|
||||
)
|
||||
stages = fields.ToManyField("avisstage.api.StageResource",
|
||||
"stages", use_in="detail")
|
||||
|
||||
class Meta:
|
||||
queryset = Normalien.objects.all()
|
||||
|
@ -127,4 +114,4 @@ class AuteurResource(ModelResource):
|
|||
fields = ["id", "nom", "stages"]
|
||||
|
||||
#login_required
|
||||
authentication = EnScolariteAuthentication()
|
||||
authentication = SessionAuthentication()
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AvisstageConfig(AppConfig):
|
||||
name = "avisstage"
|
||||
name = 'avisstage'
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
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,69 +1,61 @@
|
|||
from django_elasticsearch_dsl import Document, Index, fields
|
||||
from elasticsearch_dsl import analyzer, token_filter
|
||||
from django_elasticsearch_dsl import DocType, Index, fields
|
||||
from elasticsearch_dsl import analyzer, token_filter, tokenizer
|
||||
|
||||
from .models import Stage
|
||||
from .models import Stage, AvisStage, AvisLieu
|
||||
from .statics import PAYS_OPTIONS
|
||||
|
||||
PAYS_DICT = dict(PAYS_OPTIONS)
|
||||
|
||||
stage = Index("stages")
|
||||
stage.settings(number_of_shards=1, number_of_replicas=0)
|
||||
stage = Index('stages')
|
||||
stage.settings(
|
||||
number_of_shards=1,
|
||||
number_of_replicas=0
|
||||
)
|
||||
|
||||
text_analyzer = analyzer(
|
||||
"default",
|
||||
'default',
|
||||
tokenizer="standard",
|
||||
filter=[
|
||||
"lowercase",
|
||||
"asciifolding",
|
||||
filter=['lowercase', 'standard', 'asciifolding',
|
||||
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.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 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 Django:
|
||||
class Meta:
|
||||
model = Stage
|
||||
fields = [
|
||||
"sujet",
|
||||
"encadrants",
|
||||
"type_stage",
|
||||
"niveau_scol",
|
||||
"structure",
|
||||
"date_debut",
|
||||
"date_fin",
|
||||
'sujet',
|
||||
'encadrants',
|
||||
'type_stage',
|
||||
'niveau_scol',
|
||||
'structure',
|
||||
'date_debut',
|
||||
'date_fin'
|
||||
]
|
||||
|
||||
def prepare_thematiques(self, instance):
|
||||
return ", ".join(
|
||||
instance.thematiques.all().values_list("name", flat=True)
|
||||
).lower()
|
||||
return ", ".join(instance.thematiques.all().values_list("name", flat=True))
|
||||
|
||||
def prepare_matieres(self, instance):
|
||||
return ", ".join(instance.matieres.all().values_list("nom", flat=True)).lower()
|
||||
return ", ".join(instance.matieres.all().values_list("nom", flat=True))
|
||||
|
||||
def prepare_niveau_scol(self, instance):
|
||||
return instance.get_niveau_scol_display().lower()
|
||||
return instance.get_niveau_scol_display()
|
||||
|
||||
def prepare_type_stage(self, instance):
|
||||
return instance.type_stage_fancy.lower()
|
||||
return instance.type_stage_fancy
|
||||
|
||||
def prepare_date_fin(self, instance):
|
||||
return instance.date_fin.year
|
||||
|
@ -71,13 +63,10 @@ class StageDocument(Document):
|
|||
def prepare_date_debut(self, instance):
|
||||
return instance.date_debut.year
|
||||
|
||||
def prepare_sujet(self, instance):
|
||||
return instance.sujet.lower()
|
||||
|
||||
# Hook pour l'affichage des noms de pays
|
||||
def prepare(self, instance):
|
||||
data = super(StageDocument, self).prepare(instance)
|
||||
|
||||
for lieu in data["lieux"]:
|
||||
lieu["pays"] = PAYS_DICT[lieu["pays"]].lower()
|
||||
for lieu in data['lieux']:
|
||||
lieu['pays'] = PAYS_DICT[lieu['pays']]
|
||||
return data
|
||||
|
|
|
@ -1,71 +1,46 @@
|
|||
import re
|
||||
import unicodedata
|
||||
|
||||
from simple_email_confirmation.models import EmailAddress
|
||||
# coding: utf-8
|
||||
|
||||
from django import forms
|
||||
from django.contrib.auth.forms import PasswordResetForm
|
||||
from django.utils import timezone
|
||||
|
||||
from .models import AvisLieu, AvisStage, Lieu, Stage, User
|
||||
from .widgets import LatLonField
|
||||
import re
|
||||
|
||||
from .models import Normalien, Stage, Lieu, AvisLieu, AvisStage
|
||||
from .widgets import LatLonField
|
||||
|
||||
# Sur-classe utile
|
||||
class HTMLTrimmerForm(forms.ModelForm):
|
||||
def clean(self):
|
||||
# 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
|
||||
)
|
||||
trailing_white = re.compile(
|
||||
r"(( \t\n)*<p>( |[ \n\t]|<br[ /]*>)*</p>)+?( \t\n)*$", re.IGNORECASE
|
||||
)
|
||||
leading_white = re.compile(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()
|
||||
|
||||
for (fname, fval) in cleaned_data.items():
|
||||
# Heuristique : les champs commençant par "avis_" sont des champs html
|
||||
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
|
||||
|
||||
|
||||
# Infos sur un stage
|
||||
class StageForm(forms.ModelForm):
|
||||
date_widget = forms.DateInput(
|
||||
attrs={"class": "datepicker", "placeholder": "JJ/MM/AAAA"}
|
||||
)
|
||||
date_debut = forms.DateField(
|
||||
label="Date de début", input_formats=["%d/%m/%Y"], widget=date_widget
|
||||
)
|
||||
date_fin = forms.DateField(
|
||||
label="Date de fin", input_formats=["%d/%m/%Y"], widget=date_widget
|
||||
)
|
||||
date_widget = forms.DateInput(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_fin = forms.DateField(label=u"Date de fin",
|
||||
input_formats=["%d/%m/%Y"], widget=date_widget)
|
||||
|
||||
class Meta:
|
||||
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 = {
|
||||
"thematiques": "Mettez une virgule pour valider votre thématique si la suggestion ne "
|
||||
"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)",
|
||||
"thematiques": u"Mettez une virgule pour valider votre thématique si la suggestion ne correspond pas ou si elle n'existe pas encore",
|
||||
"structure": u"Nom de l'équipe, du laboratoire, de la startup... (si le lieu ne suffit pas)"
|
||||
}
|
||||
labels = {
|
||||
"date_debut": "Date de début",
|
||||
"date_debut": u"Date de début",
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
@ -76,7 +51,7 @@ class StageForm(forms.ModelForm):
|
|||
|
||||
def save(self, commit=True):
|
||||
# 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
|
||||
|
||||
# Date de modification
|
||||
|
@ -87,83 +62,36 @@ class StageForm(forms.ModelForm):
|
|||
stage = super(StageForm, self).save(commit=commit)
|
||||
return stage
|
||||
|
||||
|
||||
# Sous-formulaire des avis sur le stage
|
||||
class AvisStageForm(HTMLTrimmerForm):
|
||||
class Meta:
|
||||
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 = {
|
||||
"chapo": (
|
||||
'"Trop long, pas lu" : une accroche résumant ce que vous avez pensé de ce séjour'
|
||||
),
|
||||
"avis_ambiance": (
|
||||
"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 ?"
|
||||
),
|
||||
"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",
|
||||
"chapo": u"\"Trop long, pas lu\" : une accroche résumant ce que vous avez pensé de ce séjour",
|
||||
"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 ?",
|
||||
"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_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 ?",
|
||||
"les_plus": u"Les principaux points positifs de cette expérience",
|
||||
"les_moins": u"Ce qui aurait pu être mieux",
|
||||
}
|
||||
|
||||
|
||||
class AvisLieuForm(HTMLTrimmerForm):
|
||||
class Meta:
|
||||
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 = {
|
||||
"chapo": (
|
||||
'"Trop long, pas lu" : une accroche résumant ce que vous avez pensé de cet endroit'
|
||||
),
|
||||
"avis_lieustage": (
|
||||
"Qu'avez-vous pensé des lieux où vous travailliez ? Les bâtiments "
|
||||
"étaient-ils modernes ? Était-il agréable d'y travailler ?"
|
||||
),
|
||||
"avis_pratique": (
|
||||
"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",
|
||||
"chapo": u"\"Trop long, pas lu\" : une accroche résumant ce que vous avez pensé de cet endroit",
|
||||
"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 ?",
|
||||
"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 ?",
|
||||
"les_plus": u"Les meilleures raisons de partir à cet endroit",
|
||||
"les_moins": u"Ce qui vous a gêné ou manqué là-bas",
|
||||
}
|
||||
widgets = {
|
||||
"lieu": forms.HiddenInput(attrs={"class":"lieu-hidden"})
|
||||
}
|
||||
widgets = {"lieu": forms.HiddenInput(attrs={"class": "lieu-hidden"})}
|
||||
|
||||
|
||||
# Création d'un nouveau lieu
|
||||
class LieuForm(forms.ModelForm):
|
||||
|
@ -172,59 +100,10 @@ class LieuForm(forms.ModelForm):
|
|||
|
||||
class Meta:
|
||||
model = Lieu
|
||||
fields = ["id", "nom", "type_lieu", "ville", "pays", "coord"]
|
||||
|
||||
fields = ['id', 'nom', 'type_lieu', 'ville', 'pays', 'coord']
|
||||
|
||||
# Widget de feedback
|
||||
class FeedbackForm(forms.Form):
|
||||
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,59 +1,43 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
|
||||
from avisstage.models import Lieu
|
||||
|
||||
#coding: utf-8
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db.models import Count
|
||||
from avisstage.models import Stage, Lieu
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Nettoie les stages à plusieurs lieux identiques"
|
||||
help = 'Nettoie les stages à plusieurs lieux identiques'
|
||||
|
||||
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(
|
||||
"--apply",
|
||||
action="store_true",
|
||||
'--apply',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help="Applies the modifications",
|
||||
help='Applies the modifications',
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
rundb = False
|
||||
if options.get("apply", False):
|
||||
if options.get('apply', False):
|
||||
rundb = True
|
||||
else:
|
||||
print("Les modifications ne seront pas appliquées")
|
||||
print u"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"):
|
||||
lproches = Lieu.objects.filter(
|
||||
id__lt=lieu.id, coord__distance_lte=(lieu.coord, 5)
|
||||
)
|
||||
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))
|
||||
if len(lproches) == 0:
|
||||
continue
|
||||
print(
|
||||
"Doublons possibles pour %s (id=%d, %d avis) :"
|
||||
% (lieu, lieu.id, lieu.avislieu_set.count())
|
||||
)
|
||||
print u"Doublons possibles pour %s (id=%d, %d avis) :" % (lieu, lieu.id, lieu.avislieu_set.count())
|
||||
for plieu in lproches:
|
||||
pprint = " > %s (id=%d, %d avis)" % (
|
||||
plieu,
|
||||
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")))
|
||||
pprint = u" > %s (id=%d, %d avis)" % (plieu, plieu.id, plieu.avislieu_set.count())
|
||||
if plieu.nom == lieu.nom and plieu.ville == lieu.ville and plieu.type_lieu == lieu.type_lieu:
|
||||
print u"%s %s" % (pprint, self.style.SUCCESS(u'-> Suppression'))
|
||||
if rundb:
|
||||
for avis in plieu.avislieu_set.all():
|
||||
avis.lieu = lieu
|
||||
avis.save()
|
||||
plieu.delete()
|
||||
else:
|
||||
print(
|
||||
"%s %s"
|
||||
% (pprint, self.style.WARNING("-> À supprimer manuellement"))
|
||||
)
|
||||
self.stdout.write(self.style.SUCCESS("Nettoyage des lieux effectué"))
|
||||
print u"%s %s" % (pprint, self.style.WARNING(u'-> À supprimer manuellement'))
|
||||
self.stdout.write(self.style.SUCCESS(u'Nettoyage des lieux effectué'))
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
#coding: utf-8
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db.models import Count
|
||||
|
||||
from avisstage.models import Stage
|
||||
|
||||
from avisstage.models import Stage, Lieu
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Nettoie les stages à plusieurs lieux identiques"
|
||||
help = 'Nettoie les stages à plusieurs lieux identiques'
|
||||
|
||||
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(
|
||||
"--apply",
|
||||
action="store_true",
|
||||
'--apply',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help="Applies the modifications",
|
||||
help='Applies the modifications',
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
|
@ -28,16 +27,15 @@ class Command(BaseCommand):
|
|||
return length
|
||||
|
||||
rundb = False
|
||||
if options.get("apply", False):
|
||||
if options.get('apply', False):
|
||||
rundb = True
|
||||
else:
|
||||
print("Les modifications ne seront pas appliquées")
|
||||
print u"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")).filter(
|
||||
c__gte=2, id__gte=min_stage
|
||||
):
|
||||
for stage in Stage.objects.annotate(c=Count("lieux"))\
|
||||
.filter(c__gte=2, id__gte=min_stage):
|
||||
lieuset = {}
|
||||
todel = []
|
||||
problems = []
|
||||
|
@ -54,19 +52,15 @@ class Command(BaseCommand):
|
|||
problems += [(avis, alen), lieuset[aid]]
|
||||
lieuset[aid] = (avis, alen)
|
||||
if len(todel) > 0:
|
||||
print("Doublons détectés dans %s" % (stage,))
|
||||
print u"Doublons détectés dans %s" % (stage,)
|
||||
for avis, alen in todel:
|
||||
print(
|
||||
" > Suppression de l'avis sur %s de %d mots" % (avis.lieu, alen)
|
||||
)
|
||||
print u" > Suppression de l'avis sur %s de %d mots" % \
|
||||
(avis.lieu, alen)
|
||||
if rundb:
|
||||
avis.delete()
|
||||
if len(problems) > 0:
|
||||
self.stdout.write(
|
||||
self.style.WARNING(
|
||||
"Réparation impossible de %s (id=%d)" % (stage, stage.id)
|
||||
)
|
||||
)
|
||||
self.stdout.write(self.style.WARNING(u"Réparation impossible de %s (id=%d)" % (stage, stage.id)))
|
||||
for avis, alen in problems:
|
||||
print(" > Avis sur %s de %d mots" % (avis.lieu, alen))
|
||||
self.stdout.write(self.style.SUCCESS("Nettoyage des stages effectué"))
|
||||
print u" > Avis sur %s de %d mots" % \
|
||||
(avis.lieu, alen)
|
||||
self.stdout.write(self.style.SUCCESS(u'Nettoyage des stages effectué'))
|
||||
|
|
|
@ -1,41 +1,35 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
|
||||
from avisstage.models import Lieu
|
||||
|
||||
#coding: utf-8
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db.models import Count
|
||||
from avisstage.models import Stage, Lieu
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Nettoie les stages à plusieurs lieux identiques"
|
||||
help = 'Nettoie les stages à plusieurs lieux identiques'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
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('del_lieu', type=int, help='Lieu à supprimer')
|
||||
parser.add_argument('repl_lieu', type=int, help='Lieu le remplaçant')
|
||||
parser.add_argument(
|
||||
"--apply",
|
||||
action="store_true",
|
||||
'--apply',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help="Applies the modifications",
|
||||
help='Applies the modifications',
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
rundb = False
|
||||
if options.get("apply", False):
|
||||
if options.get('apply', False):
|
||||
rundb = True
|
||||
else:
|
||||
print("Les modifications ne seront pas appliquées")
|
||||
print u"Les modifications ne seront pas appliquées"
|
||||
|
||||
plieu = Lieu.objects.get(id=options["del_lieu"])
|
||||
lieu = Lieu.objects.get(id=options["repl_lieu"])
|
||||
print(
|
||||
"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())
|
||||
)
|
||||
plieu = Lieu.objects.get(id=options['del_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 u"Remplacement par %s (id=%d, %d avis)" % (lieu, lieu.id, lieu.avislieu_set.count())
|
||||
if rundb:
|
||||
for avis in plieu.avislieu_set.all():
|
||||
avis.lieu = lieu
|
||||
avis.save()
|
||||
plieu.delete()
|
||||
self.stdout.write(self.style.SUCCESS("Terminé"))
|
||||
self.stdout.write(self.style.SUCCESS(u'Terminé'))
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
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,37 +2,30 @@
|
|||
# Generated by Django 1.11.2 on 2017-10-02 20:43
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import tinymce.models
|
||||
|
||||
from django.db import migrations, models
|
||||
import tinymce.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("avisstage", "0001_initial"),
|
||||
('avisstage', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="avisstage",
|
||||
name="avis_prestage",
|
||||
field=tinymce.models.HTMLField(
|
||||
blank=True, default="", verbose_name="Avant le stage"
|
||||
),
|
||||
model_name='avisstage',
|
||||
name='avis_prestage',
|
||||
field=tinymce.models.HTMLField(blank=True, default='', verbose_name='Avant le stage'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="stage",
|
||||
name="len_avis_lieux",
|
||||
field=models.IntegerField(
|
||||
default=0, verbose_name="Longueur des avis de lieu"
|
||||
),
|
||||
model_name='stage',
|
||||
name='len_avis_lieux',
|
||||
field=models.IntegerField(default=0, verbose_name='Longueur des avis de lieu'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="stage",
|
||||
name="len_avis_stage",
|
||||
field=models.IntegerField(
|
||||
default=0, verbose_name="Longueur des avis de stage"
|
||||
),
|
||||
model_name='stage',
|
||||
name='len_avis_stage',
|
||||
field=models.IntegerField(default=0, verbose_name='Longueur des avis de stage'),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,357 +0,0 @@
|
|||
# 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",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,107 +0,0 @@
|
|||
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"))
|
|
@ -1,18 +0,0 @@
|
|||
# Generated by Django 2.2.17 on 2021-01-17 20:46
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("avisstage", "0004_allauth_to_authens"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="normalien",
|
||||
name="en_scolarite",
|
||||
field=models.BooleanField(blank=True, default=False),
|
||||
),
|
||||
]
|
|
@ -1,37 +0,0 @@
|
|||
# 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,154 +1,126 @@
|
|||
from datetime import timedelta
|
||||
# coding: utf-8
|
||||
|
||||
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 tinymce.models import HTMLField as RichTextField
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
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
|
||||
from .statics import DEPARTEMENTS_DEFAUT, PAYS_OPTIONS, TYPE_LIEU_OPTIONS, TYPE_STAGE_OPTIONS, TYPE_LIEU_DICT, TYPE_STAGE_DICT, NIVEAU_SCOL_OPTIONS, NIVEAU_SCOL_DICT
|
||||
|
||||
|
||||
def _default_cas_login():
|
||||
return (timezone.now() - timedelta(days=365)).date()
|
||||
|
||||
import ldap
|
||||
|
||||
#
|
||||
# Profil Normalien (extension du modèle User)
|
||||
#
|
||||
|
||||
|
||||
class Normalien(models.Model):
|
||||
user = models.OneToOneField(
|
||||
User, related_name="profil", on_delete=models.SET_NULL, null=True
|
||||
)
|
||||
user = models.OneToOneField(User, related_name="profil")
|
||||
|
||||
# Infos spécifiques
|
||||
nom = models.CharField("Nom complet", max_length=255, blank=True)
|
||||
promotion = models.CharField("Promotion", max_length=40, blank=True)
|
||||
contactez_moi = models.BooleanField(
|
||||
"Inviter les visiteurs à me contacter",
|
||||
default=True,
|
||||
help_text="Affiche votre adresse e-mail principale sur votre profil public",
|
||||
)
|
||||
bio = models.TextField("À propos de moi", blank=True, default="")
|
||||
last_cas_login = models.DateField(default=_default_cas_login)
|
||||
nom = models.CharField(u"Nom complet", max_length=255, blank=True)
|
||||
promotion = models.CharField(u"Promotion", max_length=40, blank=True)
|
||||
mail = models.EmailField(u"Adresse e-mail permanente",
|
||||
max_length=200, blank=True)
|
||||
contactez_moi = models.BooleanField(u"Inviter les visiteurs à me contacter",
|
||||
default=True)
|
||||
bio = models.TextField(u"À propos de moi", blank=True, default="");
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Profil élève"
|
||||
verbose_name_plural = "Profils élèves"
|
||||
verbose_name = u"Profil élève"
|
||||
verbose_name_plural = u"Profils élèves"
|
||||
|
||||
def __str__(self):
|
||||
return "%s (%s)" % (self.nom, self.user.username)
|
||||
def __unicode__(self):
|
||||
return u"%s (%s)" % (self.nom, self.user.username)
|
||||
|
||||
# Liste des stages publiés
|
||||
def stages_publics(self):
|
||||
return self.stages.filter(public=True).order_by("-date_debut")
|
||||
return self.stages.filter(public=True).order_by('-date_debut')
|
||||
|
||||
def has_nonENS_email(self):
|
||||
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):
|
||||
# Hook à la création d'un nouvel utilisateur : récupération de ses infos par LDAP
|
||||
def create_user_profile(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
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)
|
||||
|
||||
if not created and profil.promotion != "":
|
||||
return
|
||||
info = l.search_s('dc=spi,dc=ens,dc=fr',
|
||||
ldap.SCOPE_SUBTREE,
|
||||
('(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()
|
||||
|
||||
|
||||
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)
|
||||
except ldap.LDAPError:
|
||||
pass
|
||||
post_save.connect(create_user_profile, sender=User)
|
||||
|
||||
#
|
||||
# Lieu de stage
|
||||
#
|
||||
|
||||
|
||||
class Lieu(models.Model):
|
||||
# Général
|
||||
nom = models.CharField("Nom de l'institution d'accueil", max_length=250)
|
||||
type_lieu = models.CharField(
|
||||
"Type de structure d'accueil",
|
||||
nom = models.CharField(u"Nom de l'institution d'accueil",
|
||||
max_length=250)
|
||||
type_lieu = models.CharField(u"Type de structure d'accueil",
|
||||
default="universite",
|
||||
choices=TYPE_LIEU_OPTIONS,
|
||||
max_length=choices_length(TYPE_LIEU_OPTIONS),
|
||||
)
|
||||
max_length=choices_length(TYPE_LIEU_OPTIONS))
|
||||
|
||||
# Infos géographiques
|
||||
ville = models.CharField("Ville", max_length=200)
|
||||
pays = models.CharField(
|
||||
"Pays", choices=PAYS_OPTIONS, max_length=choices_length(PAYS_OPTIONS)
|
||||
)
|
||||
ville = models.CharField(u"Ville",
|
||||
max_length=200)
|
||||
pays = models.CharField(u"Pays",
|
||||
choices=PAYS_OPTIONS,
|
||||
max_length=choices_length(PAYS_OPTIONS))
|
||||
|
||||
# Coordonnées
|
||||
# objects = geomodels.GeoManager() # Requis par GeoDjango
|
||||
coord = geomodels.PointField("Coordonnées", geography=True, srid=4326)
|
||||
objects = geomodels.GeoManager() # Requis par GeoDjango
|
||||
coord = geomodels.PointField(u"Coordonnées",
|
||||
geography=True,
|
||||
srid = 4326)
|
||||
|
||||
# Type du lieu en plus joli
|
||||
@property
|
||||
|
@ -159,83 +131,70 @@ class Lieu(models.Model):
|
|||
def type_lieu_fem(self):
|
||||
return TYPE_LIEU_DICT.get(self.type_lieu, ("lieu", False))[1]
|
||||
|
||||
def __str__(self):
|
||||
return "%s (%s)" % (self.nom, self.ville)
|
||||
def __unicode__(self):
|
||||
return u"%s (%s)" % (self.nom, self.ville)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Lieu"
|
||||
verbose_name_plural = "Lieux"
|
||||
|
||||
|
||||
#
|
||||
# Matières des stages
|
||||
#
|
||||
|
||||
|
||||
class StageMatiere(models.Model):
|
||||
nom = models.CharField("Nom", max_length=30)
|
||||
nom = models.CharField(u"Nom", max_length=30)
|
||||
slug = models.SlugField()
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Matière des stages"
|
||||
verbose_name_plural = "Matières des stages"
|
||||
|
||||
def __str__(self):
|
||||
def __unicode__(self):
|
||||
return self.nom
|
||||
|
||||
|
||||
#
|
||||
# Un stage
|
||||
#
|
||||
|
||||
|
||||
class Stage(models.Model):
|
||||
# Misc
|
||||
auteur = models.ForeignKey(
|
||||
Normalien, related_name="stages", on_delete=models.SET_NULL, null=True
|
||||
)
|
||||
public = models.BooleanField("Visible publiquement", default=False)
|
||||
date_creation = models.DateTimeField("Créé le", default=timezone.now)
|
||||
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)
|
||||
auteur = models.ForeignKey(Normalien, related_name="stages")
|
||||
public = models.BooleanField(u"Visible publiquement", default=False)
|
||||
date_creation = models.DateTimeField(u"Créé le", default=timezone.now)
|
||||
date_maj = models.DateTimeField(u"Mis à jour le", default=timezone.now)
|
||||
len_avis_stage = models.IntegerField(u"Longueur des avis de stage", default=0)
|
||||
len_avis_lieux = models.IntegerField(u"Longueur des avis de lieu", default=0)
|
||||
|
||||
# Caractéristiques du stage
|
||||
sujet = models.CharField("Sujet", max_length=500)
|
||||
sujet = models.CharField(u"Sujet", max_length=500)
|
||||
|
||||
date_debut = models.DateField("Date de début", null=True)
|
||||
date_fin = models.DateField("Date de fin", null=True)
|
||||
date_debut = models.DateField(u"Date de début", null=True)
|
||||
date_fin = models.DateField(u"Date de fin", null=True)
|
||||
|
||||
type_stage = models.CharField(
|
||||
"Type",
|
||||
type_stage = models.CharField(u"Type",
|
||||
default="stage",
|
||||
choices=TYPE_STAGE_OPTIONS,
|
||||
max_length=choices_length(TYPE_STAGE_OPTIONS),
|
||||
)
|
||||
niveau_scol = models.CharField(
|
||||
"Année de scolarité",
|
||||
max_length=choices_length(TYPE_STAGE_OPTIONS))
|
||||
niveau_scol = models.CharField(u"Année de scolarité",
|
||||
default="",
|
||||
choices=NIVEAU_SCOL_OPTIONS,
|
||||
max_length=choices_length(NIVEAU_SCOL_OPTIONS),
|
||||
blank=True,
|
||||
)
|
||||
blank=True)
|
||||
|
||||
thematiques = TaggableManager("Thématiques", blank=True)
|
||||
matieres = models.ManyToManyField(
|
||||
StageMatiere, verbose_name="Matière(s)", related_name="stages"
|
||||
)
|
||||
encadrants = models.CharField("Encadrant⋅e⋅s", max_length=500, blank=True)
|
||||
structure = models.CharField("Structure d'accueil", max_length=500, blank=True)
|
||||
thematiques = TaggableManager(u"Thématiques", blank=True)
|
||||
matieres = models.ManyToManyField(StageMatiere, verbose_name=u"Matière(s)", related_name="stages")
|
||||
encadrants = models.CharField(u"Encadrant⋅e⋅s", max_length=500, blank=True)
|
||||
structure = models.CharField(u"Structure d'accueil", max_length=500, blank=True)
|
||||
|
||||
# Avis
|
||||
lieux = models.ManyToManyField(
|
||||
Lieu, related_name="stages", through="AvisLieu", blank=True
|
||||
)
|
||||
lieux = models.ManyToManyField(Lieu, related_name="stages",
|
||||
through="AvisLieu", blank=True)
|
||||
|
||||
# Affichage des avis ordonnés
|
||||
@property
|
||||
def avis_lieux(self):
|
||||
return self.avislieu_set.order_by("order")
|
||||
return self.avislieu_set.order_by('order')
|
||||
|
||||
# Shortcut pour affichage rapide
|
||||
@property
|
||||
|
@ -249,7 +208,6 @@ class Stage(models.Model):
|
|||
@property
|
||||
def type_stage_fancy(self):
|
||||
return TYPE_STAGE_DICT.get(self.type_stage, ("stage", False))[0]
|
||||
|
||||
@property
|
||||
def type_stage_fem(self):
|
||||
return TYPE_STAGE_DICT.get(self.type_stage, ("stage", False))[1]
|
||||
|
@ -259,16 +217,12 @@ class Stage(models.Model):
|
|||
def niveau_scol_fancy(self):
|
||||
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):
|
||||
return reverse("avisstage:stage", self)
|
||||
return reverse('avisstage:stage', self)
|
||||
|
||||
def __str__(self):
|
||||
return "%s (par %s)" % (self.sujet, self.auteur.user.username)
|
||||
def __unicode__(self):
|
||||
return u"%s (par %s)" % (self.sujet, self.auteur.user.username)
|
||||
|
||||
def update_stats(self, save=True):
|
||||
def get_len(obj):
|
||||
|
@ -293,67 +247,57 @@ class Stage(models.Model):
|
|||
class Meta:
|
||||
verbose_name = "Stage"
|
||||
|
||||
|
||||
#
|
||||
# Les avis
|
||||
#
|
||||
|
||||
|
||||
class AvisStage(models.Model):
|
||||
stage = models.OneToOneField(
|
||||
Stage, related_name="avis_stage", on_delete=models.CASCADE
|
||||
)
|
||||
stage = models.OneToOneField(Stage, related_name="avis_stage")
|
||||
|
||||
chapo = models.TextField("En quelques mots", blank=True)
|
||||
avis_ambiance = RichTextField("L'ambiance de travail", blank=True)
|
||||
avis_sujet = RichTextField("La mission", blank=True)
|
||||
avis_admin = RichTextField("Formalités et administration", blank=True)
|
||||
avis_prestage = RichTextField("Avant le stage", blank=True, default="")
|
||||
chapo = models.TextField(u"En quelques mots", blank=True)
|
||||
avis_ambiance = RichTextField(u"L'ambiance de travail", blank=True)
|
||||
avis_sujet = RichTextField(u"La mission", blank=True)
|
||||
avis_admin = RichTextField(u"Formalités et administration", blank=True)
|
||||
avis_prestage = RichTextField(u"Avant le stage", blank=True, default="")
|
||||
|
||||
les_plus = models.TextField("Les plus de cette expérience", blank=True)
|
||||
les_moins = models.TextField("Les moins de cette expérience", blank=True)
|
||||
les_plus = models.TextField(u"Les plus de cette expérience", blank=True)
|
||||
les_moins = models.TextField(u"Les moins de cette expérience", blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return "Avis sur {%s} par %s" % (
|
||||
self.stage.sujet,
|
||||
self.stage.auteur.user.username,
|
||||
)
|
||||
def __unicode__(self):
|
||||
return u"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)
|
||||
@property
|
||||
def avis_all(self):
|
||||
fields = ["avis_sujet", "avis_ambiance", "avis_admin", "avis_prestage"]
|
||||
return [
|
||||
(AvisStage._meta.get_field(field).verbose_name, getattr(self, field, ""))
|
||||
for field in fields
|
||||
]
|
||||
fields = ['avis_sujet', 'avis_ambiance', 'avis_admin', 'avis_prestage']
|
||||
return [(AvisStage._meta.get_field(field).verbose_name,
|
||||
getattr(self, field, '')) for field in fields]
|
||||
|
||||
|
||||
class AvisLieu(models.Model):
|
||||
stage = models.ForeignKey(Stage, on_delete=models.CASCADE)
|
||||
lieu = models.ForeignKey(Lieu, on_delete=models.CASCADE)
|
||||
stage = models.ForeignKey(Stage)
|
||||
lieu = models.ForeignKey(Lieu)
|
||||
order = models.IntegerField("Ordre", default=0)
|
||||
|
||||
chapo = models.TextField("En quelques mots", blank=True)
|
||||
avis_lieustage = RichTextField("Les lieux de travail", blank=True)
|
||||
avis_pratique = RichTextField("S'installer - conseils pratiques", blank=True)
|
||||
avis_tourisme = RichTextField("Dans les parages", blank=True)
|
||||
chapo = models.TextField(u"En quelques mots", blank=True)
|
||||
avis_lieustage = RichTextField(u"Les lieux de travail", blank=True)
|
||||
avis_pratique = RichTextField(u"S'installer - conseils pratiques",
|
||||
blank=True)
|
||||
avis_tourisme = RichTextField(u"Dans les parages", blank=True)
|
||||
|
||||
les_plus = models.TextField("Les plus du lieu", blank=True)
|
||||
les_moins = models.TextField("Les moins du lieu", blank=True)
|
||||
les_plus = models.TextField(u"Les plus du lieu", blank=True)
|
||||
les_moins = models.TextField(u"Les moins du lieu", blank=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Avis sur un lieu de stage"
|
||||
verbose_name_plural = "Avis sur un lieu de stage"
|
||||
|
||||
def __str__(self):
|
||||
return "Avis sur {%s} par %s" % (self.lieu.nom, self.stage.auteur.user_id)
|
||||
def __unicode__(self):
|
||||
return u"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)
|
||||
@property
|
||||
def avis_all(self):
|
||||
fields = ["avis_lieustage", "avis_pratique", "avis_tourisme"]
|
||||
return [
|
||||
(AvisLieu._meta.get_field(field).verbose_name, getattr(self, field, ""))
|
||||
for field in fields
|
||||
]
|
||||
fields = ['avis_lieustage', 'avis_pratique', 'avis_tourisme']
|
||||
return [(AvisLieu._meta.get_field(field).verbose_name,
|
||||
getattr(self, field, '')) for field in fields]
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
@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+ */
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
@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,8 +1,4 @@
|
|||
@import "_miniheader.scss";
|
||||
|
||||
body.recherche {
|
||||
|
||||
section.content {
|
||||
section.content.recherche {
|
||||
form.recherche {
|
||||
.generale {
|
||||
display: inline-block;
|
||||
|
@ -93,7 +89,6 @@ body.recherche {
|
|||
font-size: 0.9em;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
&.vue-hybride #voir_hybride,
|
||||
&.vue-carte #voir_carte,
|
||||
|
@ -129,14 +124,14 @@ body.recherche {
|
|||
left: 60px;
|
||||
}
|
||||
|
||||
&.vue-hybride section.content,
|
||||
&.vue-carte section.content {
|
||||
&.vue-hybride, &.vue-carte {
|
||||
width: 100%;
|
||||
min-width: unset;
|
||||
max-width: unset;
|
||||
min-height: unset;
|
||||
max-height: unset;
|
||||
height: 100vh;
|
||||
height: 90vh;
|
||||
height: calc(100vh - 30px);
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
|
@ -150,8 +145,7 @@ body.recherche {
|
|||
}
|
||||
|
||||
&.vue-liste .recherche-carte,
|
||||
&.vue-carte .recherche-liste,
|
||||
&.vue-carte header {
|
||||
&.vue-carte .recherche-liste {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
@ -162,23 +156,13 @@ body.recherche {
|
|||
}
|
||||
|
||||
&.vue-hybride {
|
||||
|
||||
@include miniHeader;
|
||||
|
||||
header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
z-index: 15;
|
||||
}
|
||||
section.content {
|
||||
display: flex;
|
||||
|
||||
.recherche-liste {
|
||||
padding-top: 60px;
|
||||
width: 500px;
|
||||
width: 100%;
|
||||
min-width: 400px;
|
||||
max-width: 500px;
|
||||
flex: 1;
|
||||
|
||||
.dates {
|
||||
display:none;
|
||||
|
@ -200,7 +184,6 @@ body.recherche {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#carte {
|
||||
width:100%;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
@import "_miniheader.scss";
|
||||
|
||||
@media screen and (max-width: 850px) {
|
||||
header {
|
||||
font-size: 0.9em;
|
||||
|
@ -20,8 +18,67 @@
|
|||
}
|
||||
|
||||
@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;
|
||||
|
||||
@include miniHeader;
|
||||
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;
|
||||
}
|
||||
|
||||
#feedback-button {
|
||||
transform: unset;
|
||||
|
@ -177,14 +234,14 @@
|
|||
display: block;
|
||||
text-align: left;
|
||||
font-size: 0.95em;
|
||||
color: darken($compl, 20%);
|
||||
color: $compl * 0.8;
|
||||
margin-top: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.help_text {
|
||||
text-align: right;
|
||||
color: darken($fond, 60%);
|
||||
color: $fond * 0.4;
|
||||
}
|
||||
|
||||
.input {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
@import "compass/reset";
|
||||
@import "_definitions.scss";
|
||||
@import "_fonts.scss";
|
||||
// @import url('https://fonts.googleapis.com/css?family=Dosis:300,500,700|Alegreya:700|Lato:300,300i,700');
|
||||
@import url('https://fonts.googleapis.com/css?family=Dosis:300,500,700|Alegreya:700|Lato:300,300i,700');
|
||||
|
||||
// Général
|
||||
|
||||
|
@ -46,7 +45,7 @@ em, i {
|
|||
|
||||
a {
|
||||
font-weight: bold;
|
||||
color: darken($compl, 10%);
|
||||
color: $compl * 0.9;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
@ -107,7 +106,7 @@ header {
|
|||
color: lighten($fond, 40%);
|
||||
|
||||
&:hover {
|
||||
background: darken($barre, 40%);
|
||||
background: $barre * 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -162,25 +161,13 @@ 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
|
||||
|
||||
.condensed-stages {
|
||||
li {
|
||||
display: table;
|
||||
width: 100%;
|
||||
//border: 1px solid $fond * 1.3;
|
||||
background: #fff;
|
||||
margin: 12px;
|
||||
|
||||
|
@ -269,8 +256,6 @@ p.warning {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a.hoverlink {
|
||||
position: absolute;
|
||||
|
@ -281,6 +266,8 @@ a.hoverlink {
|
|||
height: 100%;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ul.infos {
|
||||
margin: 0 -3px;
|
||||
|
@ -362,57 +349,6 @@ 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
|
||||
|
@ -482,7 +418,7 @@ input[type="submit"], .btn {
|
|||
font: $textfontsize $textfont;
|
||||
background-color: $fond;
|
||||
color: #fff;
|
||||
border: 1px solid darken($fond, 30%);
|
||||
border: 1px solid $fond * 0.7;
|
||||
border-radius: 5px;
|
||||
padding: 8px 12px;
|
||||
display: inline-block;
|
||||
|
@ -566,20 +502,15 @@ form {
|
|||
|
||||
// taggit autosuggest
|
||||
|
||||
ul.as-selections,
|
||||
.selectize-control.multi {
|
||||
ul.as-selections {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
li {
|
||||
display:inline-block;
|
||||
}
|
||||
.selectize-input, .selectize-dropdown {
|
||||
font-size: 100%;
|
||||
line-height: 1.1;
|
||||
}
|
||||
.as-selection-item,
|
||||
.selectize-input > div {
|
||||
|
||||
.as-selection-item {
|
||||
padding: 0 5px;
|
||||
background: $compl;
|
||||
color: #fff;
|
||||
|
@ -611,8 +542,6 @@ ul.as-selections,
|
|||
|
||||
div.as-results {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
|
||||
ul {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
|
@ -703,16 +632,8 @@ div.as-results {
|
|||
// Widget choix et ajout de lieux
|
||||
|
||||
#lieu_widget {
|
||||
.window-content {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.lieu-ui {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-width: 150px;
|
||||
flex: 2;
|
||||
|
||||
.map {
|
||||
height: 400px;
|
||||
width: 100%;
|
||||
|
@ -725,67 +646,10 @@ div.as-results {
|
|||
}
|
||||
}
|
||||
|
||||
.lieu-choixmodif, .lieu-options {
|
||||
.lieu-choixmodif {
|
||||
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 {
|
||||
.lieu-choixmodif {
|
||||
display: unset;
|
||||
|
@ -887,13 +751,6 @@ a.lieu-change {
|
|||
padding: 15px;
|
||||
text-align: center;
|
||||
margin: 15px auto;
|
||||
|
||||
.archicubes {
|
||||
border-top: 2px solid $vert;
|
||||
margin-top: 5px;
|
||||
padding-top: 5px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
|
||||
article.promo {
|
||||
|
@ -1000,27 +857,6 @@ 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
|
||||
|
|
File diff suppressed because it is too large
Load diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,4 +1,4 @@
|
|||
function initEditStage(STATIC_URL, API_URL, MAPBOX_API_KEY) {
|
||||
function initEditStage(STATIC_URL, API_URL) {
|
||||
var has_changes = true;
|
||||
var stage_object_id = $("#stage_object_id").val();
|
||||
$(window).on('beforeunload',
|
||||
|
@ -39,7 +39,7 @@ function initEditStage(STATIC_URL, API_URL, MAPBOX_API_KEY) {
|
|||
var slts = $("select[multiple]").selectize();
|
||||
|
||||
// CHOIX DU LIEU
|
||||
var lieu_select = new SelectLieuWidget(STATIC_URL, API_URL, MAPBOX_API_KEY,
|
||||
var lieu_select = new SelectLieuWidget(STATIC_URL, API_URL,
|
||||
$("#lieu_widget"), lieuChoisi);
|
||||
var avis_lieu_template = $("#avis_lieu_vide").remove().html();
|
||||
var lieux_liste = $("#lieux-selector");
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
function InterfaceRecherche(STATIC_ROOT, API_LIEU, MAPBOX_API_KEY, ITEMS_URL, lieux) {
|
||||
function InterfaceRecherche(STATIC_ROOT, API_LIEU, lieux) {
|
||||
var interface_mode, main_container;
|
||||
var lieux_map = {}, lieux_list = [], stages_map = {}, lieux_db = {};
|
||||
var stages_db = {};
|
||||
var details_liste_data;
|
||||
var stages_data = {};
|
||||
var marqueurs = L.markerClusterGroup();
|
||||
var marqueurs_db = {};
|
||||
var changevue;
|
||||
|
@ -14,10 +13,8 @@ function InterfaceRecherche(STATIC_ROOT, API_LIEU, MAPBOX_API_KEY, ITEMS_URL, li
|
|||
// TODO se souvenir des préférences d'affichage
|
||||
|
||||
function initInterface() {
|
||||
main_container = $("body");
|
||||
if (sessionStorage && sessionStorage.interface_mode) {
|
||||
interface_mode = sessionStorage.interface_mode;
|
||||
} else if (main_container.hasClass("vue-liste")) {
|
||||
main_container = $(".content.recherche");
|
||||
if (main_container.hasClass("vue-liste")) {
|
||||
interface_mode = "liste";
|
||||
} else if (main_container.hasClass("vue-carte")) {
|
||||
interface_mode = "carte";
|
||||
|
@ -46,17 +43,6 @@ function InterfaceRecherche(STATIC_ROOT, API_LIEU, MAPBOX_API_KEY, ITEMS_URL, li
|
|||
});
|
||||
|
||||
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
|
||||
|
@ -67,11 +53,8 @@ function InterfaceRecherche(STATIC_ROOT, API_LIEU, MAPBOX_API_KEY, ITEMS_URL, li
|
|||
|
||||
function changeInterface(mode) {
|
||||
interface_mode = mode;
|
||||
main_container.removeClass("vue-carte vue-hybride vue-liste")
|
||||
$(".content.recherche").removeClass("vue-carte vue-hybride vue-liste")
|
||||
.addClass("vue-"+mode);
|
||||
if (sessionStorage) {
|
||||
sessionStorage.interface_mode = mode;
|
||||
}
|
||||
if (mode=="hybride" || mode=="carte") {
|
||||
initCarte();
|
||||
map.invalidateSize();
|
||||
|
@ -83,12 +66,7 @@ function InterfaceRecherche(STATIC_ROOT, API_LIEU, MAPBOX_API_KEY, ITEMS_URL, li
|
|||
function initCarte() {
|
||||
if (map !== undefined) return;
|
||||
map = L.map("carte").panTo([30, 15]).setZoom(1);
|
||||
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
|
||||
});
|
||||
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>'});
|
||||
map.addLayer(layer);
|
||||
|
||||
$.getJSON(API_LIEU + "set/"+lieux_list.join(';')+"/?format=json", onLoadLieux);
|
||||
|
@ -106,7 +84,7 @@ function InterfaceRecherche(STATIC_ROOT, API_LIEU, MAPBOX_API_KEY, ITEMS_URL, li
|
|||
var greenIcon = makeIcon('red');
|
||||
var blueIcon = makeIcon('blue', 1.2);
|
||||
|
||||
// Chargement des infos
|
||||
// Chargeùent des infos
|
||||
function onLoadLieux(data){
|
||||
console.log(data);
|
||||
var lieux = data.objects;
|
||||
|
@ -155,7 +133,7 @@ function InterfaceRecherche(STATIC_ROOT, API_LIEU, MAPBOX_API_KEY, ITEMS_URL, li
|
|||
var html = $("<div>").html(marqueur._popup_header);
|
||||
var stageliste = $("<ul>");
|
||||
$.each(lieux_map[data.id], function(i, item) {
|
||||
var stage_el = stages_db[item];
|
||||
var stage_el = $("#resultat-stage-"+item);
|
||||
var url = stage_el.find('a.stage-sujet').attr('href');
|
||||
var sujet = stage_el.find('a.stage-sujet').text();
|
||||
var auteur = stage_el.find('.auteur').text();
|
||||
|
@ -168,42 +146,19 @@ function InterfaceRecherche(STATIC_ROOT, API_LIEU, MAPBOX_API_KEY, ITEMS_URL, li
|
|||
marqueur.setPopupContent(html[0]);
|
||||
}
|
||||
|
||||
// Affichage de la liste hybride au survol + chargement asynchrone
|
||||
|
||||
function showDetailsListeListener (evt) {
|
||||
showDetailsListe(this._lieu_data);
|
||||
}
|
||||
|
||||
function showDetailsListe (data, is_callback) {
|
||||
function showDetailsListe (data) {
|
||||
main_container.addClass("vue-details");
|
||||
var to_load = [];
|
||||
var liste_el = $("#resultats-details");
|
||||
$.each(liste_el.children(), function(i, item){$(item).remove();});
|
||||
$.each(lieux_map[data.id], function(i, item) {
|
||||
var stage_el = stages_db[item];
|
||||
if (stage_el === undefined) {
|
||||
to_load.push(item);
|
||||
return;
|
||||
}
|
||||
var stage_el = $("#resultat-stage-"+item);
|
||||
var new_el = $("<li>", {class:"stage"}).html(stage_el.html());
|
||||
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 () {
|
||||
|
@ -212,7 +167,6 @@ function InterfaceRecherche(STATIC_ROOT, API_LIEU, MAPBOX_API_KEY, ITEMS_URL, li
|
|||
}
|
||||
|
||||
function hideDetailsListeListener () {
|
||||
details_liste_data = undefined;
|
||||
if (details_lock === false)
|
||||
main_container.removeClass("vue-details");
|
||||
else
|
||||
|
@ -241,33 +195,6 @@ function InterfaceRecherche(STATIC_ROOT, API_LIEU, MAPBOX_API_KEY, ITEMS_URL, li
|
|||
$("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__
|
||||
|
||||
initInterface();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
function SelectLieuWidget(STATIC_ROOT, API_LIEU, MAPBOX_API_KEY, target, callback) {
|
||||
function SelectLieuWidget(STATIC_ROOT, API_LIEU, target, callback) {
|
||||
|
||||
//
|
||||
// INITIALISATION
|
||||
|
@ -15,10 +15,6 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, MAPBOX_API_KEY, target, callbac
|
|||
var message_el = $el.find(".lieu-message");
|
||||
var closer = $el.find(".window-closer");
|
||||
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();
|
||||
|
||||
// Variables globales
|
||||
|
@ -63,12 +59,7 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, MAPBOX_API_KEY, target, callbac
|
|||
|
||||
// Affiche la carte
|
||||
map = L.map(map_el[0]).setView([48.8422411,2.3430553], 15);
|
||||
var layer = L.tileLayer('https://api.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
|
||||
});
|
||||
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>'});
|
||||
map.addLayer(layer);
|
||||
map.addLayer(marqueurs);
|
||||
|
||||
|
@ -158,10 +149,8 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, MAPBOX_API_KEY, target, callbac
|
|||
|
||||
lieux_db.suggestion = data;
|
||||
|
||||
panMapTo(data.coord);
|
||||
map.panTo(data.coord);
|
||||
lieuSurCarte(data, redIcon);
|
||||
newlieubtn_el.prop("_lieustage_data", data)
|
||||
.on("click", choixLieuStage);
|
||||
|
||||
// Affichage des suggestions
|
||||
askForSuggestions(place.geometry.location);
|
||||
|
@ -177,52 +166,17 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, MAPBOX_API_KEY, target, callbac
|
|||
|
||||
// Callback suggestions
|
||||
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");
|
||||
suggestions_el.html("");
|
||||
lieuglobal_el.addClass("with-options");
|
||||
map.invalidateSize();
|
||||
data.objects.sort(function(a,b){ return a.distance-b.distance; });
|
||||
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");
|
||||
|
||||
function showdistance (dist) {
|
||||
if(dist<1000) return Math.round(dist) + "m";
|
||||
else return (Math.round(dist/100)/10) + "km";
|
||||
}
|
||||
// Affichage sur la carte
|
||||
$.each(data.objects, function(i, item) {
|
||||
var plieu = lieux_db[item.id];
|
||||
if(plieu !== undefined) {
|
||||
plieu.distance = item.distance;
|
||||
if(plieu !== undefined)
|
||||
item = plieu;
|
||||
} else
|
||||
else
|
||||
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);
|
||||
});
|
||||
|
||||
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
|
||||
|
@ -247,14 +201,10 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, MAPBOX_API_KEY, target, callbac
|
|||
data.marqueur = marqueur;
|
||||
|
||||
// Création de la description
|
||||
var desc = $("<div>").append($("<h4>").text(data.nom))
|
||||
.append($("<p>")
|
||||
.text(data.ville+", "+data.pays_nom));
|
||||
var desc = $("<div>").append($("<h3>").text(data.nom))
|
||||
.append($("<p>").text(data.ville+", "+data.pays_nom));
|
||||
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);"})
|
||||
.prop("_lieustage_data", data)
|
||||
|
@ -279,7 +229,7 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, MAPBOX_API_KEY, target, callbac
|
|||
function resetOrigLieu() {
|
||||
var data = this._lieustage_data;
|
||||
data.marqueur.setLatLng(data.orig_coord);
|
||||
panMapTo(data.orig_coord);
|
||||
map.panTo(data.orig_coord);
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -298,15 +248,12 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, MAPBOX_API_KEY, target, callbac
|
|||
if (modification == true) {
|
||||
setUIMode("ajout");
|
||||
form_el.find("#id_id").val(choix.id);
|
||||
form_el.find(".form-title").text("Modifier un lieu");
|
||||
form_el.find("h3").text("Modifier un lieu");
|
||||
} else {
|
||||
setUIMode("edit");
|
||||
form_el.find("#id_id").val('');
|
||||
form_el.find(".form-title").text("Créer un lieu");
|
||||
form_el.find("h3").text("Créer un lieu");
|
||||
}
|
||||
|
||||
map.invalidateSize();
|
||||
panMapTo(choix.coord);
|
||||
}
|
||||
|
||||
// Envoi du formulaire
|
||||
|
@ -359,7 +306,7 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, MAPBOX_API_KEY, target, callbac
|
|||
var lieu = lieux_db[modiflieu_id];
|
||||
|
||||
lieu.fromSuggestion = true;
|
||||
panMapTo(lieu.coord);
|
||||
map.panTo(lieu.coord);
|
||||
lieuSurCarte(lieu, greenIcon);
|
||||
|
||||
showForm(lieu, true);
|
||||
|
@ -371,13 +318,9 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, MAPBOX_API_KEY, target, callbac
|
|||
var lieu = lieux_db[modiflieu_id];
|
||||
|
||||
lieu.fromSuggestion = false;
|
||||
|
||||
map.invalidateSize();
|
||||
panMapTo(lieu.coord);
|
||||
map.panTo(lieu.coord);
|
||||
lieuSurCarte(lieu, greenIcon);
|
||||
newlieubtn_el.prop("_lieustage_data", lieu)
|
||||
.on("click", choixLieuStage);
|
||||
lieux_db.suggestion = lieu;
|
||||
|
||||
askForSuggestions(lieu.coord);
|
||||
}
|
||||
|
||||
|
@ -389,7 +332,6 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, MAPBOX_API_KEY, target, callbac
|
|||
initUI();
|
||||
|
||||
input.val('');
|
||||
lieuglobal_el.removeClass("with-options");
|
||||
|
||||
// Nettoyage
|
||||
hideMessage();
|
||||
|
@ -402,14 +344,14 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, MAPBOX_API_KEY, target, callbac
|
|||
if (lieu_id === undefined) {
|
||||
// Choix d'un nouveau lieu : pas grand-chose à faire
|
||||
ui_mode_creation = true;
|
||||
$el.find(".window-title").text("Ajouter un lieu");
|
||||
$el.find("h3").text("Ajouter un lieu");
|
||||
map_el.addClass("masked");
|
||||
} else {
|
||||
// Lieu déjà existant
|
||||
lieu_id = lieu_id * 1;
|
||||
ui_mode_creation = false;
|
||||
modiflieu_id = lieu_id;
|
||||
$el.find(".window-title").text("Modifier le lieu");
|
||||
$el.find("h3").text("Modifier le lieu");
|
||||
|
||||
// Chargement des infos
|
||||
if(lieux_db[lieu_id] === undefined) {
|
||||
|
@ -422,10 +364,6 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, MAPBOX_API_KEY, target, callbac
|
|||
}
|
||||
}
|
||||
|
||||
function panMapTo(coord) {
|
||||
map.panTo(coord);
|
||||
}
|
||||
|
||||
// Fermeture du widget
|
||||
function closeWidget () {
|
||||
$el.removeClass("visible");
|
||||
|
@ -435,7 +373,7 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, MAPBOX_API_KEY, target, callbac
|
|||
// Le lieu est choisi, on appelle le callback
|
||||
function choixLieuStage() {
|
||||
var choix = this._lieustage_data;
|
||||
if(!choix.fromSuggestion && this !== newlieubtn_el[0])
|
||||
if(!choix.fromSuggestion)
|
||||
callback(choix);
|
||||
else
|
||||
showForm(choix);
|
||||
|
|
|
@ -1,337 +1,330 @@
|
|||
# coding: utf-8
|
||||
|
||||
DEPARTEMENTS_DEFAUT = (
|
||||
("phy", "Physique"),
|
||||
("maths", "Maths"),
|
||||
("bio", "Biologie"),
|
||||
("chimie", "Chimie"),
|
||||
("geol", "Géosciences"),
|
||||
("dec", "DEC"),
|
||||
("info", "Informatique"),
|
||||
("litt", "Littéraire"),
|
||||
("guests", "Pensionnaires étrangers"),
|
||||
("pei", "PEI"),
|
||||
('phy', u'Physique'),
|
||||
('maths', u'Maths'),
|
||||
('bio', u'Biologie'),
|
||||
('chimie', u'Chimie'),
|
||||
('geol', u'Géosciences'),
|
||||
('dec', u'DEC'),
|
||||
('info', u'Informatique'),
|
||||
('litt', u'Littéraire'),
|
||||
('guests', u'Pensionnaires étrangers'),
|
||||
('pei', u'PEI'),
|
||||
)
|
||||
|
||||
TYPE_STAGE_OPTIONS = (
|
||||
(
|
||||
"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"),
|
||||
(u'Recherche :', (
|
||||
('recherche', u"Stage académique"),
|
||||
('recherche_autre', u"Stage non-académique"),
|
||||
('sejour_dri', u"Séjour de recherche DRI"),
|
||||
)),
|
||||
(u'Stage sans visée de recherche :', (
|
||||
('pro', u"Stage en entreprise"),
|
||||
('admin', u"Stage en admin./ONG/orga. internationale"),
|
||||
)),
|
||||
(u'Enseignement :', (
|
||||
('lectorat', u"Lectorat DRI"),
|
||||
('autre_teach', u"Autre expérience d'enseignement"),
|
||||
)),
|
||||
('autre', u"Autre"),
|
||||
)
|
||||
|
||||
# Dictionnaire des type de stage (et de leur genre, True=féminin)
|
||||
TYPE_STAGE_DICT = {
|
||||
"recherche": ("stage de recherche académique", False),
|
||||
"recherche_autre": ("stage de recherche non-académique", False),
|
||||
"sejour_dri": ("séjour de recherche DRI", False),
|
||||
"pro": ("stage en entreprise sans visée de recherche", False),
|
||||
"admin": ("stage en administration, ONG ou organisation internationale", False),
|
||||
"lectorat": ("lectorat DRI", False),
|
||||
"autre_teach": ("expérience de recherche", True),
|
||||
"autre": ("expérience", True),
|
||||
'recherche': (u"stage de recherche académique", False),
|
||||
'recherche_autre': (u"stage de recherche non-académique", False),
|
||||
'sejour_dri': (u"séjour de recherche DRI", False),
|
||||
'pro': (u"stage en entreprise sans visée de recherche", False),
|
||||
'admin': (u"stage en administration, ONG ou organisation internationale", False),
|
||||
'lectorat': (u"lectorat DRI", False),
|
||||
'autre_teach': (u"expérience de recherche", True),
|
||||
'autre': (u"expérience", True),
|
||||
}
|
||||
|
||||
TYPE_LIEU_OPTIONS = (
|
||||
("universite", "Université"),
|
||||
("entreprise", "Entreprise"),
|
||||
("centrerecherche", "Centre de recherche"),
|
||||
("administration", "Administration"),
|
||||
("autre", "Autre"),
|
||||
('universite', u"Université"),
|
||||
('entreprise', u"Entreprise"),
|
||||
('centrerecherche', u"Centre de recherche"),
|
||||
('administration', u"Administration"),
|
||||
('autre', u"Autre"),
|
||||
)
|
||||
|
||||
# Place du stage dans le cursus
|
||||
|
||||
NIVEAU_SCOL_OPTIONS = (
|
||||
("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"),
|
||||
('L3', u"Licence 3"),
|
||||
('M1', u"Master 1"),
|
||||
('M2', u"Master 2"),
|
||||
('DOC', u"Pré-doctorat"),
|
||||
('CST', u"Césure"),
|
||||
('BLA', u"Année blanche"),
|
||||
('VAC', u"Vacances scolaires"),
|
||||
('MIT', u"Mi-temps en parallèle des études"),
|
||||
('', u"Autre"),
|
||||
)
|
||||
|
||||
NIVEAU_SCOL_DICT = {
|
||||
"L3": "pendant sa troisième année de Licence",
|
||||
"M1": "pendant sa première année de Master",
|
||||
"M2": "pendant sa deuxième année de Master",
|
||||
"DOC": "pendant son année de pré-doctorat",
|
||||
"CST": "pendant une année de césure",
|
||||
"BLA": "pendant une année blanche",
|
||||
"VAC": "pendant des vacances scolaires",
|
||||
"MIT": "à mi-temps en parallèle des études",
|
||||
"L3": u"pendant sa troisième année de Licence",
|
||||
"M1": u"pendant sa première année de Master",
|
||||
"M2": u"pendant sa deuxième année de Master",
|
||||
"DOC": u"pendant son année de pré-doctorat",
|
||||
"CST": u"pendant une année de césure",
|
||||
"BLA": u"pendant une année blanche",
|
||||
"VAC": u"pendant des vacances scolaires",
|
||||
"MIT": u"à mi-temps en parallèle des études",
|
||||
}
|
||||
|
||||
|
||||
# Dictionnaire des noms de lieux (et de leur genre, True=féminin)
|
||||
TYPE_LIEU_DICT = {
|
||||
"universite": ("université", True),
|
||||
"entreprise": ("entreprise", True),
|
||||
"centrerecherche": ("centre de recherche", False),
|
||||
"administration": ("administration", True),
|
||||
"autre": ("lieu", False),
|
||||
'universite': (u"université", True),
|
||||
'entreprise': (u"entreprise", True),
|
||||
'centrerecherche': (u"centre de recherche", False),
|
||||
'administration': (u"administration", True),
|
||||
'autre': (u"lieu", False),
|
||||
}
|
||||
|
||||
PAYS_OPTIONS = (
|
||||
("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"),
|
||||
("AF", u"Afghanistan"),
|
||||
("AL", u"Albanie"),
|
||||
("AQ", u"Antarctique"),
|
||||
("DZ", u"Algérie"),
|
||||
("AS", u"Samoa Américaines"),
|
||||
("AD", u"Andorre"),
|
||||
("AO", u"Angola"),
|
||||
("AG", u"Antigua-et-Barbuda"),
|
||||
("AZ", u"Azerbaïdjan"),
|
||||
("AR", u"Argentine"),
|
||||
("AU", u"Australie"),
|
||||
("AT", u"Autriche"),
|
||||
("BS", u"Bahamas"),
|
||||
("BH", u"Bahreïn"),
|
||||
("BD", u"Bangladesh"),
|
||||
("AM", u"Arménie"),
|
||||
("BB", u"Barbade"),
|
||||
("BE", u"Belgique"),
|
||||
("BM", u"Bermudes"),
|
||||
("BT", u"Bhoutan"),
|
||||
("BO", u"Bolivie"),
|
||||
("BA", u"Bosnie-Herzégovine"),
|
||||
("BW", u"Botswana"),
|
||||
("BV", u"Île Bouvet"),
|
||||
("BR", u"Brésil"),
|
||||
("BZ", u"Belize"),
|
||||
("IO", u"Territoire Britannique de l'Océan Indien"),
|
||||
("SB", u"Îles Salomon"),
|
||||
("VG", u"Îles Vierges Britanniques"),
|
||||
("BN", u"Brunéi Darussalam"),
|
||||
("BG", u"Bulgarie"),
|
||||
("MM", u"Myanmar"),
|
||||
("BI", u"Burundi"),
|
||||
("BY", u"Bélarus"),
|
||||
("KH", u"Cambodge"),
|
||||
("CM", u"Cameroun"),
|
||||
("CA", u"Canada"),
|
||||
("CV", u"Cap-vert"),
|
||||
("KY", u"Îles Caïmanes"),
|
||||
("CF", u"République Centrafricaine"),
|
||||
("LK", u"Sri Lanka"),
|
||||
("TD", u"Tchad"),
|
||||
("CL", u"Chili"),
|
||||
("CN", u"Chine"),
|
||||
("TW", u"Taïwan"),
|
||||
("CX", u"Île Christmas"),
|
||||
("CC", u"Îles Cocos (Keeling)"),
|
||||
("CO", u"Colombie"),
|
||||
("KM", u"Comores"),
|
||||
("YT", u"Mayotte"),
|
||||
("CG", u"République du Congo"),
|
||||
("CD", u"République Démocratique du Congo"),
|
||||
("CK", u"Îles Cook"),
|
||||
("CR", u"Costa Rica"),
|
||||
("HR", u"Croatie"),
|
||||
("CU", u"Cuba"),
|
||||
("CY", u"Chypre"),
|
||||
("CZ", u"République Tchèque"),
|
||||
("BJ", u"Bénin"),
|
||||
("DK", u"Danemark"),
|
||||
("DM", u"Dominique"),
|
||||
("DO", u"République Dominicaine"),
|
||||
("EC", u"Équateur"),
|
||||
("SV", u"El Salvador"),
|
||||
("GQ", u"Guinée Équatoriale"),
|
||||
("ET", u"Éthiopie"),
|
||||
("ER", u"Érythrée"),
|
||||
("EE", u"Estonie"),
|
||||
("FO", u"Îles Féroé"),
|
||||
("FK", u"Îles (malvinas) Falkland"),
|
||||
("GS", u"Géorgie du Sud et les Îles Sandwich du Sud"),
|
||||
("FJ", u"Fidji"),
|
||||
("FI", u"Finlande"),
|
||||
("AX", u"Îles Åland"),
|
||||
("FR", u"France"),
|
||||
("GF", u"Guyane Française"),
|
||||
("PF", u"Polynésie Française"),
|
||||
("TF", u"Terres Australes Françaises"),
|
||||
("DJ", u"Djibouti"),
|
||||
("GA", u"Gabon"),
|
||||
("GE", u"Géorgie"),
|
||||
("GM", u"Gambie"),
|
||||
("PS", u"Territoire Palestinien Occupé"),
|
||||
("DE", u"Allemagne"),
|
||||
("GH", u"Ghana"),
|
||||
("GI", u"Gibraltar"),
|
||||
("KI", u"Kiribati"),
|
||||
("GR", u"Grèce"),
|
||||
("GL", u"Groenland"),
|
||||
("GD", u"Grenade"),
|
||||
("GP", u"Guadeloupe"),
|
||||
("GU", u"Guam"),
|
||||
("GT", u"Guatemala"),
|
||||
("GN", u"Guinée"),
|
||||
("GY", u"Guyana"),
|
||||
("HT", u"Haïti"),
|
||||
("HM", u"Îles Heard et Mcdonald"),
|
||||
("VA", u"Saint-Siège (état de la Cité du Vatican)"),
|
||||
("HN", u"Honduras"),
|
||||
("HK", u"Hong-Kong"),
|
||||
("HU", u"Hongrie"),
|
||||
("IS", u"Islande"),
|
||||
("IN", u"Inde"),
|
||||
("ID", u"Indonésie"),
|
||||
("IR", u"République Islamique d'Iran"),
|
||||
("IQ", u"Iraq"),
|
||||
("IE", u"Irlande"),
|
||||
("IL", u"Israël"),
|
||||
("IT", u"Italie"),
|
||||
("CI", u"Côte d'Ivoire"),
|
||||
("JM", u"Jamaïque"),
|
||||
("JP", u"Japon"),
|
||||
("KZ", u"Kazakhstan"),
|
||||
("JO", u"Jordanie"),
|
||||
("KE", u"Kenya"),
|
||||
("KP", u"République Populaire Démocratique de Corée"),
|
||||
("KR", u"République de Corée"),
|
||||
("KW", u"Koweït"),
|
||||
("KG", u"Kirghizistan"),
|
||||
("LA", u"République Démocratique Populaire Lao"),
|
||||
("LB", u"Liban"),
|
||||
("LS", u"Lesotho"),
|
||||
("LV", u"Lettonie"),
|
||||
("LR", u"Libéria"),
|
||||
("LY", u"Jamahiriya Arabe Libyenne"),
|
||||
("LI", u"Liechtenstein"),
|
||||
("LT", u"Lituanie"),
|
||||
("LU", u"Luxembourg"),
|
||||
("MO", u"Macao"),
|
||||
("MG", u"Madagascar"),
|
||||
("MW", u"Malawi"),
|
||||
("MY", u"Malaisie"),
|
||||
("MV", u"Maldives"),
|
||||
("ML", u"Mali"),
|
||||
("MT", u"Malte"),
|
||||
("MQ", u"Martinique"),
|
||||
("MR", u"Mauritanie"),
|
||||
("MU", u"Maurice"),
|
||||
("MX", u"Mexique"),
|
||||
("MC", u"Monaco"),
|
||||
("MN", u"Mongolie"),
|
||||
("MD", u"République de Moldova"),
|
||||
("MS", u"Montserrat"),
|
||||
("MA", u"Maroc"),
|
||||
("MZ", u"Mozambique"),
|
||||
("OM", u"Oman"),
|
||||
("NA", u"Namibie"),
|
||||
("NR", u"Nauru"),
|
||||
("NP", u"Népal"),
|
||||
("NL", u"Pays-Bas"),
|
||||
("AN", u"Antilles Néerlandaises"),
|
||||
("AW", u"Aruba"),
|
||||
("NC", u"Nouvelle-Calédonie"),
|
||||
("VU", u"Vanuatu"),
|
||||
("NZ", u"Nouvelle-Zélande"),
|
||||
("NI", u"Nicaragua"),
|
||||
("NE", u"Niger"),
|
||||
("NG", u"Nigéria"),
|
||||
("NU", u"Niué"),
|
||||
("NF", u"Île Norfolk"),
|
||||
("NO", u"Norvège"),
|
||||
("MP", u"Îles Mariannes du Nord"),
|
||||
("UM", u"Îles Mineures Éloignées des États-Unis"),
|
||||
("FM", u"États Fédérés de Micronésie"),
|
||||
("MH", u"Îles Marshall"),
|
||||
("PW", u"Palaos"),
|
||||
("PK", u"Pakistan"),
|
||||
("PA", u"Panama"),
|
||||
("PG", u"Papouasie-Nouvelle-Guinée"),
|
||||
("PY", u"Paraguay"),
|
||||
("PE", u"Pérou"),
|
||||
("PH", u"Philippines"),
|
||||
("PN", u"Pitcairn"),
|
||||
("PL", u"Pologne"),
|
||||
("PT", u"Portugal"),
|
||||
("GW", u"Guinée-Bissau"),
|
||||
("TL", u"Timor-Leste"),
|
||||
("PR", u"Porto Rico"),
|
||||
("QA", u"Qatar"),
|
||||
("RE", u"Réunion"),
|
||||
("RO", u"Roumanie"),
|
||||
("RU", u"Fédération de Russie"),
|
||||
("RW", u"Rwanda"),
|
||||
("SH", u"Sainte-Hélène"),
|
||||
("KN", u"Saint-Kitts-et-Nevis"),
|
||||
("AI", u"Anguilla"),
|
||||
("LC", u"Sainte-Lucie"),
|
||||
("PM", u"Saint-Pierre-et-Miquelon"),
|
||||
("VC", u"Saint-Vincent-et-les Grenadines"),
|
||||
("SM", u"Saint-Marin"),
|
||||
("ST", u"Sao Tomé-et-Principe"),
|
||||
("SA", u"Arabie Saoudite"),
|
||||
("SN", u"Sénégal"),
|
||||
("SC", u"Seychelles"),
|
||||
("SL", u"Sierra Leone"),
|
||||
("SG", u"Singapour"),
|
||||
("SK", u"Slovaquie"),
|
||||
("VN", u"Viet Nam"),
|
||||
("SI", u"Slovénie"),
|
||||
("SO", u"Somalie"),
|
||||
("ZA", u"Afrique du Sud"),
|
||||
("ZW", u"Zimbabwe"),
|
||||
("ES", u"Espagne"),
|
||||
("EH", u"Sahara Occidental"),
|
||||
("SD", u"Soudan"),
|
||||
("SR", u"Suriname"),
|
||||
("SJ", u"Svalbard etÎle Jan Mayen"),
|
||||
("SZ", u"Swaziland"),
|
||||
("SE", u"Suède"),
|
||||
("CH", u"Suisse"),
|
||||
("SY", u"République Arabe Syrienne"),
|
||||
("TJ", u"Tadjikistan"),
|
||||
("TH", u"Thaïlande"),
|
||||
("TG", u"Togo"),
|
||||
("TK", u"Tokelau"),
|
||||
("TO", u"Tonga"),
|
||||
("TT", u"Trinité-et-Tobago"),
|
||||
("AE", u"Émirats Arabes Unis"),
|
||||
("TN", u"Tunisie"),
|
||||
("TR", u"Turquie"),
|
||||
("TM", u"Turkménistan"),
|
||||
("TC", u"Îles Turks et Caïques"),
|
||||
("TV", u"Tuvalu"),
|
||||
("UG", u"Ouganda"),
|
||||
("UA", u"Ukraine"),
|
||||
("MK", u"L'ex-République Yougoslave de Macédoine"),
|
||||
("EG", u"Égypte"),
|
||||
("GB", u"Royaume-Uni"),
|
||||
("IM", u"Île de Man"),
|
||||
("TZ", u"République-Unie de Tanzanie"),
|
||||
("US", u"États-Unis"),
|
||||
("VI", u"Îles Vierges des États-Unis"),
|
||||
("BF", u"Burkina Faso"),
|
||||
("UY", u"Uruguay"),
|
||||
("UZ", u"Ouzbékistan"),
|
||||
("VE", u"Venezuela"),
|
||||
("WF", u"Wallis et Futuna"),
|
||||
("WS", u"Samoa"),
|
||||
("YE", u"Yémen"),
|
||||
("CS", u"Serbie-et-Monténégro"),
|
||||
("ZM", u"Zambie"),
|
||||
)
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
{% 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 %}
|
|
@ -1,14 +0,0 @@
|
|||
{% 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,6 +5,8 @@
|
|||
<meta charset="utf-8" />
|
||||
<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;">
|
||||
<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-ui.min.js" %}"></script>
|
||||
{% if request.user.is_authenticated %}
|
||||
|
@ -14,12 +16,9 @@
|
|||
{% endif %}
|
||||
|
||||
{% 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>
|
||||
|
||||
<body class="{% block bodyclass %}{% endblock %}">
|
||||
<body>
|
||||
<header>
|
||||
<h1><a href="{% url 'avisstage:index' %}">ExperiENS{# <span class='beta'>beta</span>#}</a></h1>
|
||||
|
||||
|
@ -28,18 +27,16 @@
|
|||
<ul id="menu">
|
||||
{% if user.is_authenticated %}
|
||||
<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>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<li><a href="{% url 'avisstage:faq' %}">FAQ</a></li>
|
||||
{% if user.is_staff %}
|
||||
<li><a href="{% url 'avisstage:moderation' %}">Modo</a></li>
|
||||
{% endif %}
|
||||
{% if user.is_authenticated %}
|
||||
<li><a href="{% url "authens:logout" %}"><span class="username">{{ user.username }}</span><br/> Déconnexion</a></li>
|
||||
<li><a href="{% url 'logout' %}"><span class="username">{{ user.username }}</span><br/> Déconnexion</a></li>
|
||||
{% else %}
|
||||
<li><a href="{% url "authens:login" %}">Connexion</a></li>
|
||||
<li><a href="{% url 'login' %}">Connexion</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
{% 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 %}
|
|
@ -1,29 +0,0 @@
|
|||
{% 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 %}
|
|
@ -1,18 +0,0 @@
|
|||
{% 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 %}
|
|
@ -1,76 +0,0 @@
|
|||
{% extends "avisstage/base.html" %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block title %}Mes paramètres - ExperiENS{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Mes paramètres</h1>
|
||||
|
||||
<article>
|
||||
<h2>Adresses e-mail</h2>
|
||||
<ul class="mes-emails">
|
||||
{% for email in request.user.email_address_set.all %}
|
||||
<li>
|
||||
<span class="adresse">{{ email.email }}
|
||||
<span class="confirmee">{{ email.confirmed_at|yesno:"✓,✗"|safe }}</span></span>
|
||||
{% if email.confirmed_at %}
|
||||
<span class="principale">
|
||||
{% if email.email == user.email %}
|
||||
Principale
|
||||
{% else %}
|
||||
<form action="{% url "avisstage:emails_principal" email.email %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<input type="submit" value="Rendre principale">
|
||||
</form>
|
||||
{% endif %}
|
||||
</span>
|
||||
|
||||
{% else %}
|
||||
<span class="confirmer">
|
||||
<form action="{% url "avisstage:emails_reconfirme" email.email %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<input type="submit" value="Renvoyer le lien de confirmation">
|
||||
</form>
|
||||
</span>
|
||||
{% endif %}
|
||||
<span class="supprimer">
|
||||
{% if not email.email == user.email %}
|
||||
<a href="{% url "avisstage:emails_supprime" email.email %}">Supprimer</a>
|
||||
{% endif %}
|
||||
</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
<li>
|
||||
<form action="" method="POST">
|
||||
{% csrf_token %}
|
||||
{{ form.non_field_errors }}
|
||||
{% for field in form %}
|
||||
{{ field.errors }}
|
||||
<div class="field">
|
||||
<div class="input">
|
||||
{{ field }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<input type="submit" value="Ajouter l'adresse">
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<h2>Mot de passe</h2>
|
||||
<section class="profil">
|
||||
{% if request.user.password and request.user.has_usable_password %}
|
||||
<p>Un mot de passe interne est déjà défini pour ce compte.</p>
|
||||
{% else %}
|
||||
<p>Aucun mot de passe n'est défini pour ce compte. Créez-en un pour pouvoir vous connecter après la fin de votre scolarité à l'ENS.</p>
|
||||
{% endif %}
|
||||
<form action="{% url "avisstage:mdp_demande" %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<input type="submit" value="Définir un nouveau mot de passe" />
|
||||
</form>
|
||||
<p>En cliquant sur ce bouton, un lien unique vous sera envoyé à votre adresse e-mail principale ({{ request.user.email }}) qui vous donnera accès au formulaire d'édition du mot de passe.</p>
|
||||
</section>
|
||||
</article>
|
||||
{% endblock %}
|
|
@ -5,10 +5,10 @@
|
|||
<script type="text/javascript" src="{% static "js/render.js" %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}Profil de {{ object.nom_complet }} - ExperiENS{% endblock %}
|
||||
{% block title %}Profil de {{ object.nom }} - ExperiENS{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Profil de {{ object.nom_complet }}
|
||||
<h1>Profil de {{ object.nom }}
|
||||
{% if object.user == user %}
|
||||
<a href="{% url "avisstage:profil_edit" %}" class="btn edit-btn">Modifier mes infos</a>
|
||||
{% endif %}
|
||||
|
@ -18,7 +18,7 @@
|
|||
<p class="promo">Promotion : <b>{{ object.promotion }}</b></p>
|
||||
<p class="contact">
|
||||
{% if object.contactez_moi %}
|
||||
Contact : <a href="mailto:{{ object.preferred_email }}">{{ object.preferred_email }}</a>
|
||||
Contact : <a href="mailto:{{ object.mail }}">{{ object.mail }}</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -6,9 +6,6 @@
|
|||
{% block extra_head %}
|
||||
<script src="{% static 'js/toc.min.js' %}" type="text/javascript"></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" %}" />
|
||||
<script type="text/javascript" src="{% static "js/tile.stamen.js" %}"></script>
|
||||
<script type="text/javascript">
|
||||
|
@ -16,12 +13,7 @@
|
|||
var STATIC_ROOT = "{{ STATIC_URL|escapejs }}";
|
||||
function initStageMap(lieux) {
|
||||
var map = L.map("stage-map");
|
||||
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 }}"
|
||||
});
|
||||
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>'});
|
||||
map.addLayer(layer);
|
||||
|
||||
function makeIcon(couleur){
|
||||
|
@ -59,25 +51,22 @@
|
|||
<p>Cette page n'est qu'un brouillon, vous seul pouvez le voir. <input type="submit" value="Publier ce stage" name="publier" /></p>
|
||||
{% endif %}
|
||||
</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>
|
||||
{% endif %}
|
||||
<article class="stage">
|
||||
<section class="misc">
|
||||
<div class="misc-content {% if object.all_lieux %}withmap{% endif %}">
|
||||
<div class="misc-content {% if object.lieux.all %}withmap{% endif %}">
|
||||
<div class="desc">
|
||||
<div class="misc-hdr">
|
||||
<h1>{{ object.sujet }}</h1>
|
||||
<p class="dates"><span class="year">{{ object.date_debut|date:"Y" }}</span><span class="debut">{{ object.date_debut|date:"d/m" }}</span><span class="fin">{{ object.date_fin|date:"d/m" }}</span></p>
|
||||
</div>
|
||||
<p><a href="{% url "avisstage:profil" object.auteur.user.username %}">{{ object.auteur.nom_complet }}</a>
|
||||
<p><a href="{% url "avisstage:profil" object.auteur.user.username %}">{{ object.auteur.nom }}</a>
|
||||
a fait {{ object.type_stage_fem|yesno:"cette,ce" }} <b>{{ object.type_stage_fancy }}</b>
|
||||
{% if object.niveau_scol %}{{ object.niveau_scol_fancy }},{% endif %}
|
||||
{% if object.structure %}au sein de {{ object.structure }}{% endif %}{% if object.encadrants %}, supervisé par {{ object.encadrants }}{% endif %}.</p>
|
||||
{% if object.all_lieux %}<p>Cela s'est passé à :
|
||||
{% for lieu in object.all_lieux %}{{ lieu.nom }} ({{ lieu.ville }}){% if not forloop.last %}, {% endif %}{% endfor %}.</p>
|
||||
{% if object.lieux.all %}<p>Cela s'est passé à :
|
||||
{% for lieu in object.lieux.all %}{{ lieu.nom }} ({{ lieu.ville }}){% if not forloop.last %}, {% endif %}{% endfor %}.</p>
|
||||
{% endif %}
|
||||
|
||||
<ul class="infos">
|
||||
|
@ -90,12 +79,12 @@
|
|||
</ul>
|
||||
</div>
|
||||
|
||||
{% if object.all_lieux %}
|
||||
{% if object.lieux.all %}
|
||||
<div class="map">
|
||||
<div id="stage-map"></div>
|
||||
<script type="text/javascript">
|
||||
var lieux = [
|
||||
{% for lieu in object.all_lieux %}
|
||||
{% for lieu in object.lieux.all %}
|
||||
{
|
||||
coord: {lat: "{{ lieu.coord.y|escapejs }}", lon: "{{ lieu.coord.x|escapejs }}" },
|
||||
popup: "<h3>{{ lieu.nom|escapejs }}</h3>" +
|
||||
|
|
|
@ -74,8 +74,7 @@
|
|||
|
||||
<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>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.
|
||||
<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>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
|
@ -89,7 +88,7 @@
|
|||
<p>Faites-en part en cliquant sur le bouton feedback, on est preneur !</p>
|
||||
|
||||
<h3>Qui est derrière ?</h3>
|
||||
<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>
|
||||
<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>
|
||||
|
||||
</article>
|
||||
</section>
|
||||
|
|
|
@ -18,13 +18,6 @@
|
|||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<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" />
|
||||
<input type="submit" />
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
{% block title %}{% if creation %}Nouvelle expérience{% else %}Modification d'une expérience{% endif %}{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
<script type="text/javascript" src="//maps.googleapis.com/maps/api/js?libraries=places&key={{ GOOGLE_API_KEY }}"></script>
|
||||
<script type="text/javascript" src="//maps.googleapis.com/maps/api/js?libraries=places&key=AIzaSyDd4innPShfHcW8KDJB833vZHZSsqt-ACw"></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.markercluster.js" %}"></script>
|
||||
|
@ -16,19 +16,14 @@
|
|||
<link rel="stylesheet" type="text/css" href="{% static "css/MarkerCluster.Default.css" %}" />
|
||||
<script type="text/javascript" src="{% static "js/selectize.min.js" %}"></script>
|
||||
<link rel="stylesheet" type="text/css" href="{% static "css/selectize.css" %}" />
|
||||
<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" src="{% static "js/editstage.js" %}"></script>
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
initEditStage(
|
||||
"{{ STATIC_URL|escapejs }}",
|
||||
"{% url 'avisstage:api_dispatch_list' resource_name="lieu" api_name="v1" %}",
|
||||
"{{ MAPBOX_API_KEY|escapejs }}");
|
||||
"{% url 'avisstage:api_dispatch_list' resource_name="lieu" api_name="v1" %}");
|
||||
});
|
||||
var django = {};
|
||||
django.jQuery = $;
|
||||
</script>
|
||||
<script type="text/javascript" src="{% static "jquery-autosuggest/js/jquery.autoSuggest.minified.js" %}"> </script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
|
|
@ -11,9 +11,8 @@
|
|||
|
||||
{% if not user.is_authenticated %}
|
||||
<div class="entrer">
|
||||
<p><a href="{% url "authens:login.cas" %}" class="btn">Connexion</a></p>
|
||||
<p class="helptext">Connexion via le serveur central d'authentification ENS <br />(identifiants clipper)</p>
|
||||
<p class="archicubes"><a href="{% url "authens:login.pwd" %}">Accès archicubes</a> <br /><i>Pour continuer à tenir à jour ses fiches, sans voir celles des autres</i></p>
|
||||
<p><a href="{% url 'login' %}" class="btn">Connexion</a></p>
|
||||
<p class="helptext">Connexion via le serveur central d'authentification ENS (identifiants clipper)</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
@ -24,13 +23,7 @@
|
|||
</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>
|
||||
{% 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 %}
|
||||
{% if user.is_authenticated %}<p><a href="{% url 'avisstage:recherche' %}" class="btn">Rechercher des stages</a></p>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="explications">
|
||||
|
@ -43,8 +36,8 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
Un projet du <a href="https://www.eleves.ens.fr/home/klub-dev/">Klub Dev ENS</a>
|
||||
<div class="betacadre">
|
||||
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".
|
||||
</div>
|
||||
</article>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
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 +0,0 @@
|
|||
[ExperiENS] Définition du mot de passe
|
|
@ -11,22 +11,10 @@
|
|||
<article>
|
||||
<h2>Stages</h2>
|
||||
<p>{{ num_stages }} stages créés, {{ num_stages_pub }} stages publiés</p>
|
||||
<table class="stats">
|
||||
<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>
|
||||
<p>{% for npm in num_par_matiere %}
|
||||
{{ npm.scount }} en {{ npm.matieres__nom }},
|
||||
{% endfor %}
|
||||
</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>
|
||||
</p>
|
||||
<h2>Utilisateurs</h2>
|
||||
<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 %}
|
||||
|
|
|
@ -4,60 +4,7 @@
|
|||
{% block title %}Espace personnel - ExperiENS{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<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>
|
||||
<h1>Bonjour {{ user.profil.nom }} !</h1>
|
||||
|
||||
<article>
|
||||
<h2>Mes stages</h2>
|
||||
|
@ -76,4 +23,25 @@
|
|||
</li>
|
||||
</ul>
|
||||
</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 %}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<p class="generale">
|
||||
<span>
|
||||
{{ form.generique }}
|
||||
<input type="submit" action="submit" value="Chercher" class="submitSearch" />
|
||||
<input type="submit" action="submit" value="Chercher un stage"/>
|
||||
</span>
|
||||
<a class="toggle_avancee" href="#" onclick="$('#recherche_avancee').toggleClass('expanded'); return false;">Recherche avancée</a>
|
||||
</p>
|
||||
|
@ -20,7 +20,7 @@
|
|||
{% endif %}
|
||||
{% endfor %}
|
||||
<li class="btnsubmit">
|
||||
<input type="submit" action="submit" value="Chercher" class="submitSearch"/>
|
||||
<input type="submit" action="submit" value="Chercher un stage"/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
{% block title %}Chercher un stage - ExperiENS{% endblock %}
|
||||
|
||||
{% block bodyclass %}recherche{% endblock %}
|
||||
{% block extra_content_class %}recherche{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Chercher un stage</h1>
|
||||
|
|
|
@ -6,17 +6,14 @@
|
|||
{% block extra_head %}
|
||||
<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/recherche.js' %}?v2"></script>
|
||||
<script type="text/javascript" src="{% static 'js/recherche.js' %}"></script>
|
||||
<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.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>
|
||||
{% endblock %}
|
||||
|
||||
{% block bodyclass %}recherche {{ vue }}{% endblock %}
|
||||
{% block extra_content_class %}recherche {{ vue }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="recherche-liste" id="recherche-liste">
|
||||
|
@ -33,11 +30,42 @@
|
|||
<li><a href="javascript:void(0);" id="voir_carte">Carte</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<p class="numresults">{{ paginator.paginator.count }} expérience{{ paginator.paginator.count|pluralize }} trouvée{{ paginator.paginator.count|pluralize }}</p>
|
||||
<p class="numresults">{{ stages|length }} expérience{{ stages|length|pluralize }} trouvée{{ stages|length|pluralize }}</p>
|
||||
{% endif %}
|
||||
|
||||
<ul class="stage-liste" id="resultats">
|
||||
{% include "avisstage/recherche/stage_items.html" %}
|
||||
{% 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 %}
|
||||
</ul>
|
||||
|
||||
</article>
|
||||
|
@ -54,17 +82,12 @@
|
|||
<div id="carte"></div>
|
||||
<div id="vue-options2" class="vue-options">
|
||||
<ul>
|
||||
<li><a href="javascript:void(0);" id="voir_hybride">Afficher la liste et les menus</a></li>
|
||||
<li><a href="javascript:void(0);" id="voir_hybride">Afficher la liste</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
var lieux = [{{ lieux|join:',' }}];
|
||||
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);
|
||||
var interfaceRecherche = new InterfaceRecherche("{{ STATIC_URL|escapejs }}", "{% url 'avisstage:api_dispatch_list' resource_name="lieu" api_name="v1" %}", lieux);
|
||||
</script>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
{% 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,27 +4,16 @@
|
|||
<div class="window-bg"></div>
|
||||
<div class="window-content">
|
||||
<a class="window-closer" href="javascript:void(0);"></a>
|
||||
<h2 class="window-title">Choisir un lieu</h2>
|
||||
<h2>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>
|
||||
|
||||
<div class="message"></div>
|
||||
|
||||
{# UI avec carte et autocomplete #}
|
||||
<div class="lieu-global">
|
||||
<div class="lieu-ui">
|
||||
</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 #}
|
||||
<div class="lieu-choixmodif">
|
||||
<h4>Que voulez-vous faire pour le lieu : <span class="lieu-choixrappel"></span> ?</h4>
|
||||
|
@ -37,7 +26,7 @@
|
|||
{# Formulaire de création/modification #}
|
||||
<div class="lieu-form">{% load staticfiles %}
|
||||
<form action="{% url 'avisstage:lieu_ajout' %}" method="post" id="lieu_ajout">
|
||||
<h2 class="form-title">Ajouter un lieu</h2>
|
||||
<h2>Ajouter un lieu</h2>
|
||||
<p class="help_text">Vous pouvez déplacer le curseur pour indiquer précisément la bonne position</p>
|
||||
{% csrf_token %}
|
||||
{% for field in form.hidden_fields %}
|
||||
|
|
|
@ -1,33 +1,30 @@
|
|||
# coding: utf-8
|
||||
import re
|
||||
|
||||
from django import template
|
||||
|
||||
from avisstage.forms import FeedbackForm, LieuForm
|
||||
from avisstage.forms import LieuForm, FeedbackForm
|
||||
import re
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.inclusion_tag("avisstage/templatetags/widget_lieu.html")
|
||||
@register.inclusion_tag('avisstage/templatetags/widget_lieu.html')
|
||||
def lieu_widget():
|
||||
form = LieuForm()
|
||||
return {"form": form}
|
||||
|
||||
|
||||
@register.inclusion_tag("avisstage/templatetags/widget_feedback.html")
|
||||
@register.inclusion_tag('avisstage/templatetags/widget_feedback.html')
|
||||
def feedback_widget():
|
||||
form = FeedbackForm()
|
||||
return {"form": form}
|
||||
|
||||
|
||||
@register.filter
|
||||
def typonazisme(value):
|
||||
value = re.sub(r"(\w)\s*([?!:])", "\\1 \\2", value)
|
||||
value = re.sub(r"(\w)\s*([,.])", "\\1\\2", value)
|
||||
value = re.sub(r"([?!:,.])(\w)", "\\1 \\2", value)
|
||||
#print value
|
||||
#return value
|
||||
value = re.sub(r'(\w)\s*([?!:])', u'\\1 \\2', value)
|
||||
value = re.sub(r'(\w)\s*([,.])', u'\\1\\2', value)
|
||||
value = re.sub(r'([?!:,.])(\w)', u'\\1 \\2', value)
|
||||
return value
|
||||
|
||||
|
||||
@register.filter
|
||||
def avis_len(value):
|
||||
if value < 5:
|
||||
|
@ -36,10 +33,3 @@ def avis_len(value):
|
|||
return "court"
|
||||
else:
|
||||
return "long"
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def url_replace(request, field, value):
|
||||
dict_ = request.GET.copy()
|
||||
dict_[field] = value
|
||||
return dict_.urlencode()
|
||||
|
|
|
@ -1,565 +1,3 @@
|
|||
from datetime import date, timedelta
|
||||
from unittest import mock
|
||||
|
||||
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()
|
||||
# Create your tests here.
|
||||
|
|
|
@ -1,69 +1,29 @@
|
|||
from django.conf.urls import include, url
|
||||
from . import views, api
|
||||
from tastypie.api import Api
|
||||
|
||||
from django.urls import include, path
|
||||
|
||||
from . import api, views, views_search
|
||||
|
||||
v1_api = Api(api_name="v1")
|
||||
v1_api = Api(api_name='v1')
|
||||
v1_api.register(api.LieuResource())
|
||||
v1_api.register(api.StageResource())
|
||||
v1_api.register(api.AuteurResource())
|
||||
|
||||
app_name = "avisstage"
|
||||
urlpatterns = [
|
||||
path("", views.index, name="index"),
|
||||
path("perso/", views.perso, name="perso"),
|
||||
path("faq/", views.faq, name="faq"),
|
||||
path("stage/nouveau/", views.manage_stage, name="stage_ajout"),
|
||||
path("stage/<int:pk>/", views.StageView.as_view(), name="stage"),
|
||||
path("stage/<int:pk>/edit/", views.manage_stage, name="stage_edit"),
|
||||
path("stage/<int:pk>/publication/", views.publier_stage, name="stage_publication"),
|
||||
path("403/archicubes/", views.archicubes_interdits, name="403-archicubes"),
|
||||
path("lieu/save/", views.save_lieu, name="lieu_ajout"),
|
||||
path("profil/show/<str:username>/", views.ProfilView.as_view(), name="profil"),
|
||||
path("profil/edit/", views.ProfilEdit.as_view(), name="profil_edit"),
|
||||
path("profil/parametres/", views.MesParametres.as_view(), name="parametres"),
|
||||
path(
|
||||
"profil/emails/<str:email>/aconfirmer/",
|
||||
views.AdresseAConfirmer.as_view(),
|
||||
name="emails_aconfirmer",
|
||||
),
|
||||
path(
|
||||
"profil/emails/<str:email>/supprime/",
|
||||
views.SupprimeAdresse.as_view(),
|
||||
name="emails_supprime",
|
||||
),
|
||||
path(
|
||||
"profil/emails/<str:email>/reconfirme/",
|
||||
views.ReConfirmeAdresse.as_view(),
|
||||
name="emails_reconfirme",
|
||||
),
|
||||
path(
|
||||
"profil/emails/<str:email>/principal/",
|
||||
views.RendAdressePrincipale.as_view(),
|
||||
name="emails_principal",
|
||||
),
|
||||
path(
|
||||
"profil/emails/confirme/<str:key>/",
|
||||
views.ConfirmeAdresse.as_view(),
|
||||
name="emails_confirme",
|
||||
),
|
||||
path(
|
||||
"profil/mdp/demande/", views.EnvoieLienMotDePasse.as_view(), name="mdp_demande"
|
||||
),
|
||||
path(
|
||||
"profil/mdp/<str:uidb64>/<str:token>/",
|
||||
views.DefinirMotDePasse.as_view(),
|
||||
name="mdp_edit",
|
||||
),
|
||||
path("recherche/", views_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)),
|
||||
url(r'^$', views.index, name='index'),
|
||||
url(r'^compte/profil/$', views.perso, name='perso'),
|
||||
url(r'^faq/$', views.faq, name='faq'),
|
||||
url(r'^stage/nouveau/$', views.manage_stage, name='stage_ajout'),
|
||||
url(r'^stage/(?P<pk>\w+)/$', views.StageView.as_view(), name='stage'),
|
||||
url(r'^stage/(?P<pk>\w+)/edit/$', views.manage_stage, name='stage_edit'),
|
||||
url(r'^stage/(?P<pk>\w+)/publication/$', views.publier_stage, name='stage_publication'),
|
||||
url(r'^stages/majs/$', views.StageListe.as_view(), name='stage_majs'),
|
||||
|
||||
url(r'^lieu/save/$', views.save_lieu, name='lieu_ajout'),
|
||||
url(r'^profil/show/(?P<username>\w+)/$', views.ProfilView.as_view(),
|
||||
name='profil'),
|
||||
url(r'^profil/edit/$', views.ProfilEdit.as_view(), name='profil_edit'),
|
||||
url(r'^recherche/$', views.recherche, name='recherche'),
|
||||
url(r'^recherche/resultats/$', views.recherche_resultats, name='recherche_resultats'),
|
||||
url(r'^feedback/$', views.feedback, name='feedback'),
|
||||
url(r'^moderation/$', views.statistiques, name='moderation'),
|
||||
url(r'^api/', include(v1_api.urls)),
|
||||
]
|
||||
|
|
|
@ -1,26 +1,4 @@
|
|||
from functools import reduce
|
||||
from math import cos, radians, sqrt
|
||||
|
||||
# coding: utf-8
|
||||
|
||||
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,120 +1,74 @@
|
|||
import math
|
||||
import random
|
||||
from collections import Counter, defaultdict
|
||||
# coding: utf-8
|
||||
|
||||
from braces.views import LoginRequiredMixin
|
||||
from simple_email_confirmation.models import EmailAddress
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
|
||||
from django.views.generic import DetailView, ListView
|
||||
from django.views.generic.edit import UpdateView, CreateView
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.urls import reverse
|
||||
from django.contrib.admin.views.decorators import staff_member_required
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.views import PasswordResetConfirmView
|
||||
from braces.views import LoginRequiredMixin
|
||||
from django.http import JsonResponse, HttpResponseForbidden
|
||||
from django.core.mail import send_mail
|
||||
from django.db.models import Count, Q
|
||||
from django.http import Http404, HttpResponseForbidden, JsonResponse
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.views.generic import (
|
||||
CreateView,
|
||||
DeleteView,
|
||||
DetailView,
|
||||
FormView,
|
||||
UpdateView,
|
||||
View,
|
||||
)
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
from django.db.models import Q, Count
|
||||
from collections import Counter
|
||||
|
||||
from .forms import (
|
||||
AdresseEmailForm,
|
||||
AvisLieuForm,
|
||||
AvisStageForm,
|
||||
FeedbackForm,
|
||||
LieuForm,
|
||||
ReinitMdpForm,
|
||||
StageForm,
|
||||
)
|
||||
from .models import AvisLieu, AvisStage, Lieu, Normalien, Stage
|
||||
from .utils import en_scolarite
|
||||
from avisstage.models import Normalien, Stage, Lieu, AvisLieu, AvisStage
|
||||
from avisstage.forms import StageForm, LieuForm, AvisStageForm, AvisLieuForm, FeedbackForm
|
||||
from avisstage.views_search import *
|
||||
|
||||
import random, math
|
||||
|
||||
#
|
||||
# LECTURE
|
||||
#
|
||||
|
||||
|
||||
# Page d'accueil
|
||||
def index(request):
|
||||
num_stages = Stage.objects.filter(public=True).count()
|
||||
return render(request, "avisstage/index.html", {"num_stages": num_stages})
|
||||
|
||||
return render(request, 'avisstage/index.html',
|
||||
{"num_stages": num_stages})
|
||||
|
||||
# Espace personnel
|
||||
@login_required
|
||||
def perso(request):
|
||||
# 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")
|
||||
|
||||
return render(request, 'avisstage/perso.html')
|
||||
|
||||
# Profil
|
||||
#login_required
|
||||
class ProfilView(LoginRequiredMixin, DetailView):
|
||||
model = Normalien
|
||||
template_name = "avisstage/detail/profil.html"
|
||||
template_name = 'avisstage/detail/profil.html'
|
||||
|
||||
# Récupération du profil
|
||||
def get_object(self):
|
||||
|
||||
# Restriction d'accès pour les archicubes
|
||||
if (
|
||||
en_scolarite(self.request.user)
|
||||
or self.kwargs.get("username") == self.request.user.username
|
||||
):
|
||||
return get_object_or_404(
|
||||
Normalien, user__username=self.kwargs.get("username")
|
||||
)
|
||||
else:
|
||||
raise Http404
|
||||
|
||||
return Normalien.objects.get(user__username=self.kwargs.get('username'))
|
||||
|
||||
# Stage
|
||||
#login_required
|
||||
class StageView(LoginRequiredMixin, DetailView):
|
||||
model = Stage
|
||||
template_name = "avisstage/detail/stage.html"
|
||||
template_name = 'avisstage/detail/stage.html'
|
||||
|
||||
# Restriction aux stages publics ou personnels
|
||||
def get_queryset(self):
|
||||
filtre = Q(auteur__user_id=self.request.user.id)
|
||||
|
||||
# Restriction d'accès pour les archicubes
|
||||
if en_scolarite(self.request.user):
|
||||
filtre |= Q(public=True)
|
||||
|
||||
filtre = Q(auteur__user_id=self.request.user.id) | Q(public=True)
|
||||
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
|
||||
def faq(request):
|
||||
return render(request, "avisstage/faq.html")
|
||||
|
||||
return render(request, 'avisstage/faq.html')
|
||||
|
||||
#
|
||||
# EDITION
|
||||
|
@ -124,16 +78,15 @@ def faq(request):
|
|||
#login_required
|
||||
class ProfilEdit(LoginRequiredMixin, UpdateView):
|
||||
model = Normalien
|
||||
fields = ["nom", "promotion", "contactez_moi", "bio"]
|
||||
template_name = "avisstage/formulaires/profil.html"
|
||||
fields = ['nom', 'promotion', 'mail', 'contactez_moi', 'bio']
|
||||
template_name = 'avisstage/formulaires/profil.html'
|
||||
|
||||
# Limitation à son propre profil
|
||||
def get_object(self):
|
||||
return self.request.user.profil
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse("avisstage:perso")
|
||||
|
||||
return reverse('avisstage:perso')
|
||||
|
||||
# Stage
|
||||
@login_required
|
||||
|
@ -144,9 +97,8 @@ def manage_stage(request, pk=None):
|
|||
stage = Stage(auteur=request.user.profil)
|
||||
avis_stage = AvisStage(stage=stage)
|
||||
c_del = False
|
||||
last_creation = Stage.objects.filter(auteur=request.user.profil).order_by(
|
||||
"-date_creation"
|
||||
)[:1]
|
||||
last_creation = Stage.objects.filter(auteur=request.user.profil)\
|
||||
.order_by("-date_creation")[:1]
|
||||
if len(last_creation) != 0:
|
||||
last_maj = last_creation[0].date_creation
|
||||
else:
|
||||
|
@ -160,57 +112,41 @@ def manage_stage(request, pk=None):
|
|||
|
||||
# Formset pour les avis des lieux
|
||||
AvisLieuFormSet = forms.inlineformset_factory(
|
||||
Stage, AvisLieu, form=AvisLieuForm, can_delete=c_del, extra=0
|
||||
)
|
||||
Stage, AvisLieu, form=AvisLieuForm, can_delete=c_del, extra=0)
|
||||
|
||||
if request.method == "POST":
|
||||
# Lecture des données
|
||||
form = StageForm(request.POST, request=request, instance=stage, prefix="stage")
|
||||
avis_stage_form = AvisStageForm(
|
||||
request.POST, instance=avis_stage, prefix="avis"
|
||||
)
|
||||
avis_lieu_formset = AvisLieuFormSet(
|
||||
request.POST, instance=stage, prefix="lieux"
|
||||
)
|
||||
avis_stage_form = AvisStageForm(request.POST,
|
||||
instance=avis_stage, prefix="avis")
|
||||
avis_lieu_formset = AvisLieuFormSet(request.POST, instance=stage,
|
||||
prefix="lieux")
|
||||
|
||||
# Validation et enregistrement
|
||||
if (
|
||||
form.is_valid()
|
||||
and avis_stage_form.is_valid()
|
||||
and avis_lieu_formset.is_valid()
|
||||
):
|
||||
if (form.is_valid() and
|
||||
avis_stage_form.is_valid() and
|
||||
avis_lieu_formset.is_valid()):
|
||||
stage = form.save()
|
||||
avis_stage_form.instance.stage = stage
|
||||
avis_stage_form.save()
|
||||
avis_lieu_formset.save()
|
||||
# print(request.POST)
|
||||
print request.POST
|
||||
if "continuer" in request.POST:
|
||||
if pk is None:
|
||||
return redirect(
|
||||
reverse("avisstage:stage_edit", kwargs={"pk": stage.id})
|
||||
)
|
||||
return redirect(reverse('avisstage:stage_edit',kwargs={'pk':stage.id}))
|
||||
else:
|
||||
return redirect(reverse("avisstage:stage", kwargs={"pk": stage.id}))
|
||||
return redirect(reverse('avisstage:stage',
|
||||
kwargs={'pk':stage.id}))
|
||||
else:
|
||||
form = StageForm(instance=stage, prefix="stage")
|
||||
avis_stage_form = AvisStageForm(instance=avis_stage, prefix="avis")
|
||||
avis_lieu_formset = AvisLieuFormSet(instance=stage, prefix="lieux")
|
||||
|
||||
# Affichage du formulaire
|
||||
return render(
|
||||
request,
|
||||
"avisstage/formulaires/stage.html",
|
||||
{
|
||||
"form": form,
|
||||
"avis_stage_form": avis_stage_form,
|
||||
"avis_lieu_formset": avis_lieu_formset,
|
||||
"creation": pk is None,
|
||||
"last_maj": last_maj,
|
||||
"GOOGLE_API_KEY": settings.GOOGLE_API_KEY,
|
||||
"MAPBOX_API_KEY": settings.MAPBOX_API_KEY,
|
||||
},
|
||||
)
|
||||
|
||||
return render(request, "avisstage/formulaires/stage.html",
|
||||
{'form': form, 'avis_stage_form': avis_stage_form,
|
||||
'avis_lieu_formset': avis_lieu_formset,
|
||||
'creation': pk is None, "last_maj": last_maj})
|
||||
|
||||
# Ajout d'un lieu de stage
|
||||
#login_required
|
||||
|
@ -222,9 +158,9 @@ def save_lieu(request):
|
|||
|
||||
if request.method == "POST":
|
||||
pk = request.POST.get("id", None)
|
||||
# print(request.POST)
|
||||
print request.POST
|
||||
jitter = False
|
||||
if pk is None or pk == "":
|
||||
if pk is None or pk == '':
|
||||
lieu = Lieu()
|
||||
else:
|
||||
# Modification du lieu
|
||||
|
@ -233,8 +169,7 @@ def save_lieu(request):
|
|||
# On regarde si les stages associés à ce lieu "appartiennent" tous à l'utilisateur
|
||||
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:
|
||||
lieu = Lieu()
|
||||
# Servira à bouger un peu le lieu
|
||||
|
@ -248,54 +183,51 @@ def save_lieu(request):
|
|||
lieu = form.save(commit=False)
|
||||
if jitter:
|
||||
cdx, cdy = lieu.coord.get_coords()
|
||||
ang = random.random() * 6.29
|
||||
ang = random.random() * 6.29;
|
||||
rad = (random.random() + 0.5) * 3e-4
|
||||
cdx += math.cos(ang) * rad
|
||||
cdy += math.sin(ang) * rad
|
||||
cdx += math.cos(ang) * rad;
|
||||
cdy += math.sin(ang) * rad;
|
||||
lieu.coord.set_coords((cdx, cdy))
|
||||
lieu.save()
|
||||
|
||||
# Élimination des doublons
|
||||
if pk is None or pk == "":
|
||||
olieux = Lieu.objects.filter(
|
||||
nom=lieu.nom, coord__distance_lte=(lieu.coord, 10)
|
||||
)
|
||||
olieux = Lieu.objects.filter(nom=lieu.nom, coord__distance_lte=(lieu.coord, 10))
|
||||
for olieu in olieux:
|
||||
if (
|
||||
olieu.type_lieu == lieu.type_lieu
|
||||
and olieu.ville == lieu.ville
|
||||
and olieu.pays == lieu.pays
|
||||
):
|
||||
if olieu.type_lieu == lieu.type_lieu and \
|
||||
olieu.ville == lieu.ville and \
|
||||
olieu.pays == lieu.pays:
|
||||
return JsonResponse({"success": True, "id": olieu.id})
|
||||
|
||||
lieu.save()
|
||||
return JsonResponse({"success": True, "id": lieu.id})
|
||||
else:
|
||||
return JsonResponse({"success": False, "errors": form.errors})
|
||||
return JsonResponse({"success": False,
|
||||
"errors": form.errors})
|
||||
else:
|
||||
return JsonResponse({"erreur": "Aucune donnée POST"})
|
||||
|
||||
|
||||
class LieuAjout(LoginRequiredMixin, CreateView):
|
||||
model = Lieu
|
||||
form_class = LieuForm
|
||||
template_name = "avisstage/formulaires/lieu.html"
|
||||
template_name = 'avisstage/formulaires/lieu.html'
|
||||
|
||||
# Retourne d'un JSON si requête AJAX
|
||||
def form_valid(self, form):
|
||||
if self.request.GET.get("format", "") == "json":
|
||||
self.object = form.save()
|
||||
return JsonResponse({"success": True, "id": self.object.id})
|
||||
return JsonResponse({"success": True,
|
||||
"id": self.object.id})
|
||||
else:
|
||||
super(LieuAjout, self).form_valid(form)
|
||||
|
||||
def form_invalid(self, form):
|
||||
if self.request.GET.get("format", "") == "json":
|
||||
return JsonResponse({"success": False, "errors": form.errors})
|
||||
return JsonResponse({"success": False,
|
||||
"errors": form.errors})
|
||||
else:
|
||||
super(LieuAjout, self).form_valid(form)
|
||||
|
||||
|
||||
# Passage d'un stage en mode public
|
||||
@login_required
|
||||
def publier_stage(request, pk):
|
||||
|
@ -317,25 +249,22 @@ def publier_stage(request, pk):
|
|||
|
||||
return redirect(reverse("avisstage:stage", kwargs={"pk": pk}))
|
||||
|
||||
|
||||
#
|
||||
# FEEDBACK
|
||||
#
|
||||
|
||||
|
||||
@login_required
|
||||
def feedback(request):
|
||||
if request.method == "POST":
|
||||
form = FeedbackForm(request.POST)
|
||||
if form.is_valid():
|
||||
objet = form.cleaned_data["objet"]
|
||||
header = "[From : %s <%s>]\n" % (request.user, request.user.email)
|
||||
message = header + form.cleaned_data["message"]
|
||||
objet = form.cleaned_data['objet']
|
||||
message = form.cleaned_data['message']
|
||||
send_mail(
|
||||
"[experiENS] "+ objet,
|
||||
message,
|
||||
request.user.email,
|
||||
["robin.champenois@ens.fr"],
|
||||
request.user.username + "@clipper.ens.fr",
|
||||
['champeno@clipper.ens.fr'],
|
||||
fail_silently=False,
|
||||
)
|
||||
if request.GET.get("format", None) == "json":
|
||||
|
@ -343,221 +272,32 @@ def feedback(request):
|
|||
return redirect(reverse("avisstage:index"))
|
||||
else:
|
||||
if request.GET.get("format", None) == "json":
|
||||
return JsonResponse({"success": False, "errors": form.errors})
|
||||
return JsonResponse({"success": False,
|
||||
"errors": form.errors})
|
||||
else:
|
||||
form = FeedbackForm()
|
||||
raise Http404()
|
||||
return render(request, 'avisstage/formulaire/feedback.html', {"form": form})
|
||||
|
||||
|
||||
#
|
||||
# STATISTIQUES
|
||||
#
|
||||
|
||||
|
||||
@login_required
|
||||
@staff_member_required
|
||||
def statistiques(request):
|
||||
nstages = Stage.objects.count()
|
||||
npubstages = Stage.objects.filter(public=True).count()
|
||||
nbymatiere_raw = Stage.objects.values("matieres__nom", "public").annotate(
|
||||
scount=Count("matieres__nom")
|
||||
)
|
||||
nbymatiere = 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(),
|
||||
),
|
||||
]
|
||||
nbymatiere = Stage.objects.values('matieres__nom').annotate(scount=Count('matieres__nom'))
|
||||
nusers = Normalien.objects.count()
|
||||
nauts = Normalien.objects.filter(stages__isnull=False).distinct().count()
|
||||
nbyaut = Counter(
|
||||
Normalien.objects.filter(stages__isnull=False)
|
||||
.annotate(scount=Count("stages"))
|
||||
.values_list("scount", flat="True")
|
||||
).items()
|
||||
nbyaut = Counter(Normalien.objects.filter(stages__isnull=False).annotate(scount=Count('stages')).values_list('scount', flat="True")).items()
|
||||
nlieux = Lieu.objects.filter(stages__isnull=False).distinct().count()
|
||||
return render(
|
||||
request,
|
||||
"avisstage/moderation/statistiques.html",
|
||||
{
|
||||
"num_stages": nstages,
|
||||
"num_stages_pub": npubstages,
|
||||
"num_par_matiere": nbymatiere,
|
||||
"num_users": nusers,
|
||||
"num_auteurs": nauts,
|
||||
"num_par_auteur": nbyaut,
|
||||
"num_lieux_utiles": nlieux,
|
||||
"num_par_longueur": nbylength,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# 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
|
||||
return render(request, 'avisstage/moderation/statistiques.html',
|
||||
{'num_stages': nstages,
|
||||
'num_stages_pub': npubstages,
|
||||
'num_par_matiere': nbymatiere,
|
||||
'num_users': nusers,
|
||||
'num_auteurs': nauts,
|
||||
'num_par_auteur': nbyaut,
|
||||
'num_lieux_utiles': nlieux})
|
||||
|
|
|
@ -1,305 +1,149 @@
|
|||
import json
|
||||
import logging
|
||||
from datetime import date
|
||||
# coding: utf-8
|
||||
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
||||
from django import forms
|
||||
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 django.db.models import Q
|
||||
|
||||
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 .statics import NIVEAU_SCOL_OPTIONS, TYPE_LIEU_OPTIONS, TYPE_STAGE_OPTIONS
|
||||
|
||||
logger = logging.getLogger("recherche")
|
||||
from .statics import TYPE_LIEU_OPTIONS, TYPE_STAGE_OPTIONS, NIVEAU_SCOL_OPTIONS
|
||||
|
||||
from datetime import date
|
||||
|
||||
# Recherche
|
||||
class SearchForm(forms.Form):
|
||||
generique = forms.CharField(required=False)
|
||||
sujet = forms.CharField(label="À propos de", required=False)
|
||||
contexte = forms.CharField(
|
||||
label="Contexte (lieu, encadrant·e·s, structure)", required=False
|
||||
)
|
||||
sujet = forms.CharField(label=u'À propos de', required=False)
|
||||
contexte = forms.CharField(label=u'Contexte (lieu, encadrant⋅e⋅s, structure)',
|
||||
required=False)
|
||||
|
||||
apres_annee = forms.IntegerField(label="Après cette année", required=False)
|
||||
avant_annee = forms.IntegerField(label="Avant cette année", required=False)
|
||||
apres_annee = forms.IntegerField(label=u'Après cette année', required=False)
|
||||
avant_annee = forms.IntegerField(label=u'Avant cette année', required=False)
|
||||
|
||||
type_stage = forms.ChoiceField(
|
||||
label="Type de stage",
|
||||
choices=([("", "")] + list(TYPE_STAGE_OPTIONS)),
|
||||
required=False,
|
||||
)
|
||||
niveau_scol = forms.ChoiceField(
|
||||
label="Année d'étude",
|
||||
choices=([("", "")] + list(NIVEAU_SCOL_OPTIONS)),
|
||||
required=False,
|
||||
)
|
||||
type_stage = forms.ChoiceField(label="Type de stage", choices=([('', u'')]
|
||||
+ list(TYPE_STAGE_OPTIONS)),
|
||||
required=False)
|
||||
niveau_scol = forms.ChoiceField(label="Année d'étude", choices=([('', u'')]
|
||||
+ list(NIVEAU_SCOL_OPTIONS)),
|
||||
required=False)
|
||||
|
||||
type_lieu = forms.ChoiceField(
|
||||
label="Type de lieu d'accueil",
|
||||
choices=([("", "")] + list(TYPE_LIEU_OPTIONS)),
|
||||
required=False,
|
||||
)
|
||||
tri = forms.ChoiceField(
|
||||
label="Tri par",
|
||||
choices=[("pertinence", "Pertinence"), ("-date_maj", "Dernière mise à jour")],
|
||||
required=False,
|
||||
initial="pertinence",
|
||||
)
|
||||
type_lieu = forms.ChoiceField(label=u"Type de lieu d'accueil",
|
||||
choices=([('', u'')]
|
||||
+ list(TYPE_LIEU_OPTIONS)),
|
||||
required=False)
|
||||
tri = forms.ChoiceField(label=u'Tri par',
|
||||
choices=[('pertinence', u'Pertinence'),
|
||||
('-date_maj',u'Dernière mise à jour')],
|
||||
required=False, initial='pertinence')
|
||||
|
||||
|
||||
def cherche(**kwargs):
|
||||
filtres = Q(public=True)
|
||||
dsl = StageDocument.search()
|
||||
|
||||
use_dsl = False
|
||||
|
||||
def field_relevant(field, test_string=True):
|
||||
return (
|
||||
field in kwargs
|
||||
and kwargs[field] is not None
|
||||
and ((not test_string) or kwargs[field].strip() != "")
|
||||
)
|
||||
|
||||
if USE_ELASTICSEARCH:
|
||||
dsl = StageDocument.search()
|
||||
return field in kwargs and \
|
||||
kwargs[field] is not None and \
|
||||
((not test_string) or kwargs[field].strip() != '')
|
||||
|
||||
#
|
||||
# Recherche libre AVEC ELASTICSEARCH
|
||||
# Recherche libre
|
||||
#
|
||||
|
||||
# Champ générique : recherche dans tous les champs
|
||||
if field_relevant("generique"):
|
||||
# print("Filtre generique", kwargs['generique'])
|
||||
#print "Filtre generique", kwargs['generique']
|
||||
dsl = dsl.query(
|
||||
"multi_match",
|
||||
query=kwargs["generique"],
|
||||
fuzziness="auto",
|
||||
fields=[
|
||||
"sujet^3",
|
||||
"encadrants",
|
||||
"type_stage",
|
||||
"niveau_scol",
|
||||
"structure",
|
||||
"lieux.*^2",
|
||||
"auteur.nom^2",
|
||||
"thematiques^2",
|
||||
"matieres",
|
||||
],
|
||||
)
|
||||
"match",
|
||||
_all={"query": kwargs["generique"],
|
||||
"fuzziness": "auto"})
|
||||
use_dsl = True
|
||||
|
||||
# Sujet -> Recherche dan les noms de sujets et les thématiques
|
||||
if field_relevant("sujet"):
|
||||
dsl = dsl.query(
|
||||
"multi_match",
|
||||
dsl = dsl.query("multi_match",
|
||||
query = kwargs["sujet"],
|
||||
fields=["sujet^2", "thematiques", "matieres"],
|
||||
fuzziness="auto",
|
||||
)
|
||||
fields = ['sujet^2', 'thematiques', 'matieres'],
|
||||
fuzziness = "auto")
|
||||
use_dsl = True
|
||||
|
||||
# Contexte -> Encadrants, structure, lieu
|
||||
if field_relevant("contexte"):
|
||||
dsl = dsl.query(
|
||||
"multi_match",
|
||||
dsl = dsl.query("multi_match",
|
||||
query = kwargs["contexte"],
|
||||
fields=[
|
||||
"encadrants",
|
||||
"structure^2",
|
||||
"lieux.nom",
|
||||
"lieux.pays",
|
||||
"lieux.ville",
|
||||
],
|
||||
fuzziness="auto",
|
||||
)
|
||||
fields = ['encadrants', 'structure^2',
|
||||
'lieux.nom', 'lieux.pays', 'lieux.ville'],
|
||||
fuzziness = "auto")
|
||||
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
|
||||
#
|
||||
|
||||
# Dates
|
||||
if field_relevant("avant_annee", False):
|
||||
dte = date(min(2100, kwargs["avant_annee"]) + 1, 1, 1)
|
||||
if field_relevant('avant_annee', False):
|
||||
dte = date(kwargs['avant_annee']+1, 1, 1)
|
||||
filtres &= Q(date_fin__lt=dte)
|
||||
|
||||
if field_relevant("apres_annee", False):
|
||||
dte = date(max(2000, kwargs["apres_annee"]), 1, 1)
|
||||
if field_relevant('apres_annee', False):
|
||||
dte = date(kwargs['apres_annee'], 1, 1)
|
||||
filtres &= Q(date_debut__gte=dte)
|
||||
|
||||
# Type de stage
|
||||
if field_relevant("type_stage"):
|
||||
if field_relevant('type_stage'):
|
||||
filtres &= Q(type_stage=kwargs["type_stage"])
|
||||
|
||||
if field_relevant("niveau_scol"):
|
||||
if field_relevant('niveau_scol'):
|
||||
filtres &= Q(niveau_scol=kwargs["niveau_scol"])
|
||||
|
||||
# Type de lieu
|
||||
if field_relevant("type_lieu"):
|
||||
if field_relevant('type_lieu'):
|
||||
filtres &= Q(lieux__type_lieu=kwargs["type_lieu"])
|
||||
|
||||
# Tri
|
||||
tri = "pertinence"
|
||||
|
||||
if field_relevant("tri") and kwargs["tri"] in ["-date_maj"]:
|
||||
tri = kwargs["tri"]
|
||||
|
||||
if not use_dsl:
|
||||
tri = "-date_maj"
|
||||
|
||||
# Application
|
||||
resultat = Stage.objects.filter(filtres).distinct()
|
||||
if use_dsl:
|
||||
filtres &= Q(id__in=[s.meta.id for s in dsl.scan()])
|
||||
|
||||
if USE_ELASTICSEARCH and use_dsl:
|
||||
dsl_res = [s.meta.id for s in dsl.scan()]
|
||||
resultat = resultat.filter(id__in=dsl_res)
|
||||
#print filtres
|
||||
resultat = Stage.objects.filter(filtres)
|
||||
tri = 'pertinence'
|
||||
|
||||
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)
|
||||
if not use_dsl:
|
||||
kwargs['tri'] = '-date_maj'
|
||||
|
||||
if field_relevant('tri') and kwargs['tri'] != 'pertinence':
|
||||
tri = kwargs['tri']
|
||||
resultat = resultat.order_by(kwargs['tri'])
|
||||
|
||||
return resultat, tri
|
||||
|
||||
|
||||
@login_required
|
||||
@en_scolarite_required
|
||||
def recherche(request):
|
||||
form = SearchForm()
|
||||
return render(request, "avisstage/recherche/recherche.html", {"form": form})
|
||||
|
||||
return render(request, 'avisstage/recherche/recherche.html',
|
||||
{"form": form})
|
||||
|
||||
@login_required
|
||||
@en_scolarite_required
|
||||
def recherche_resultats(request):
|
||||
stages = []
|
||||
tri = ""
|
||||
vue = "vue-liste"
|
||||
tri = ''
|
||||
vue = 'vue-liste'
|
||||
lieux = []
|
||||
stageids = []
|
||||
if request.method == "GET":
|
||||
form = SearchForm(request.GET)
|
||||
if form.is_valid():
|
||||
page = request.GET.get("page", 1)
|
||||
search_args = form.cleaned_data
|
||||
|
||||
# Gestion du cache
|
||||
lsearch_args = {
|
||||
key: val
|
||||
for key, val in search_args.items()
|
||||
if val != "" and val is not None
|
||||
}
|
||||
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"
|
||||
)
|
||||
stages, tri = cherche(**form.cleaned_data)
|
||||
stages = stages.prefetch_related('lieux', 'auteur', 'matieres', 'thematiques')
|
||||
lieux = [[stageid, lieuid] for (stageid, lieuid) in stages.values_list('id', 'lieux') if lieuid is not None]
|
||||
else:
|
||||
form = SearchForm()
|
||||
if stages:
|
||||
vue = "vue-hybride"
|
||||
|
||||
# Version JSON pour recherche dynamique
|
||||
if request.GET.get("format") == "json":
|
||||
return JsonResponse(
|
||||
{"stages": stages, "page": page, "num_pages": paginator.num_pages}
|
||||
)
|
||||
|
||||
template_name = "avisstage/recherche/resultats.html"
|
||||
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})
|
||||
vue = 'vue-hybride'
|
||||
return render(request, 'avisstage/recherche/resultats.html',
|
||||
{"form": form, "stages":stages,
|
||||
"tri": tri, "vue": vue, "lieux": lieux})
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
from django import forms
|
||||
from django.core import validators
|
||||
|
||||
|
||||
class LatLonWidget(forms.MultiWidget):
|
||||
"""
|
||||
A Widget that splits Point input into two latitude/longitude boxes.
|
||||
"""
|
||||
|
||||
def __init__(self, attrs=None, date_format=None, time_format=None):
|
||||
widgets = (forms.HiddenInput(attrs=attrs), forms.HiddenInput(attrs=attrs))
|
||||
widgets = (forms.HiddenInput(attrs=attrs),
|
||||
forms.HiddenInput(attrs=attrs))
|
||||
super(LatLonWidget, self).__init__(widgets, attrs)
|
||||
|
||||
def decompress(self, value):
|
||||
|
@ -23,15 +23,13 @@ class LatLonField(forms.MultiValueField):
|
|||
srid = 4326
|
||||
|
||||
default_error_messages = {
|
||||
"invalid_latitude": ("Entrez une latitude valide."),
|
||||
"invalid_longitude": ("Entrez une longitude valide."),
|
||||
'invalid_latitude' : (u'Entrez une latitude valide.'),
|
||||
'invalid_longitude' : (u'Entrez une longitude valide.'),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
fields = (
|
||||
forms.FloatField(min_value=-90, max_value=90),
|
||||
forms.FloatField(min_value=-180, max_value=180),
|
||||
)
|
||||
fields = (forms.FloatField(min_value=-90, max_value=90),
|
||||
forms.FloatField(min_value=-180, max_value=180))
|
||||
super(LatLonField, self).__init__(fields, *args, **kwargs)
|
||||
|
||||
def compress(self, data_list):
|
||||
|
@ -39,11 +37,11 @@ class LatLonField(forms.MultiValueField):
|
|||
# Raise a validation error if latitude or longitude is empty
|
||||
# (possible if LatLongField has required=False).
|
||||
if data_list[0] in validators.EMPTY_VALUES:
|
||||
raise forms.ValidationError(self.error_messages["invalid_latitude"])
|
||||
raise forms.ValidationError(self.error_messages['invalid_latitude'])
|
||||
if data_list[1] in validators.EMPTY_VALUES:
|
||||
raise forms.ValidationError(self.error_messages["invalid_longitude"])
|
||||
raise forms.ValidationError(self.error_messages['invalid_longitude'])
|
||||
# SRID=4326;POINT(1.12345789 1.123456789)
|
||||
srid_str = "SRID=%d" % self.srid
|
||||
point_str = "POINT(%f %f)" % tuple(reversed(data_list))
|
||||
return ";".join([srid_str, point_str])
|
||||
srid_str = 'SRID=%d'%self.srid
|
||||
point_str = 'POINT(%f %f)'%tuple(reversed(data_list))
|
||||
return ';'.join([srid_str, point_str])
|
||||
return None
|
||||
|
|
94
default.nix
94
default.nix
|
@ -1,94 +0,0 @@
|
|||
{
|
||||
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
|
||||
'';
|
||||
};
|
||||
}
|
0
experiENS/__init__.py
Normal file
0
experiENS/__init__.py
Normal file
5
experiENS/auth.py
Normal file
5
experiENS/auth.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from django_cas_ng.backends import CASBackend
|
||||
|
||||
class ENSCASBackend(CASBackend):
|
||||
def clean_username(self, username):
|
||||
return username.lower().strip()
|
147
experiENS/settings_base.py
Normal file
147
experiENS/settings_base.py
Normal file
|
@ -0,0 +1,147 @@
|
|||
#coding: utf-8
|
||||
"""
|
||||
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
|
||||
from django.contrib import messages
|
||||
|
||||
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.contrib.sites',
|
||||
|
||||
'django_elasticsearch_dsl',
|
||||
|
||||
'widget_tweaks',
|
||||
'allauth_ens',
|
||||
'allauth',
|
||||
'allauth.account',
|
||||
'allauth.socialaccount',
|
||||
'allauth_cas',
|
||||
# 'allauth_ens.providers.clipper',
|
||||
'allauth_archiens',
|
||||
|
||||
'tastypie',
|
||||
'braces',
|
||||
'tinymce',
|
||||
'taggit',
|
||||
'taggit_autosuggest',
|
||||
'avisstage'
|
||||
)
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
)
|
||||
|
||||
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',
|
||||
'django.template.context_processors.request',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
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',
|
||||
'allauth.account.auth_backends.AuthenticationBackend',
|
||||
# 'experiENS.auth.ENSCASBackend',
|
||||
)
|
||||
|
||||
SITE_ID = 1
|
||||
|
||||
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')
|
||||
LOGIN_REDIRECT_URL = reverse_lazy('avisstage:perso')
|
||||
ACCOUNT_HOME_URL = reverse_lazy('avisstage:perso')
|
||||
ACCOUNT_DETAILS_URL = reverse_lazy('avisstage:profil_edit')
|
||||
|
||||
SOCIALACCOUNT_PROVIDERS = {
|
||||
# …
|
||||
|
||||
'clipper': {
|
||||
|
||||
# These settings control whether a message containing a link to
|
||||
# disconnect from the CAS server is added when users log out.
|
||||
'MESSAGE_SUGGEST_LOGOUT_ON_LOGOUT': True,
|
||||
'MESSAGE_SUGGEST_LOGOUT_ON_LOGOUT_LEVEL': messages.INFO,
|
||||
|
||||
},
|
||||
}
|
36
experiENS/settings_dev.py
Normal file
36
experiENS/settings_dev.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
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'
|
||||
},
|
||||
}
|
52
experiENS/settings_prod.py
Normal file
52
experiENS/settings_prod.py
Normal file
|
@ -0,0 +1,52 @@
|
|||
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'
|
||||
},
|
||||
}
|
25
experiENS/urls.py
Normal file
25
experiENS/urls.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
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
|
||||
from allauth_ens.views import capture_login, capture_logout
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^', include('avisstage.urls', namespace='avisstage')),
|
||||
url(r'^compte/', include('allauth.urls')),
|
||||
|
||||
url(r'^login/$', capture_login, name="login"),
|
||||
url(r'^logout/$', capture_logout, name="logout"),
|
||||
# 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
|
|
@ -8,9 +8,7 @@ https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
|
|||
"""
|
||||
|
||||
import os
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "experiENS.settings")
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings")
|
||||
|
||||
application = get_wsgi_application()
|
0
manage.py
Executable file → Normal file
0
manage.py
Executable file → Normal file
|
@ -1,80 +0,0 @@
|
|||
# Generated by npins. Do not modify; will be overwritten regularly
|
||||
let
|
||||
data = builtins.fromJSON (builtins.readFile ./sources.json);
|
||||
version = data.version;
|
||||
|
||||
mkSource =
|
||||
spec:
|
||||
assert spec ? type;
|
||||
let
|
||||
path =
|
||||
if spec.type == "Git" then
|
||||
mkGitSource spec
|
||||
else if spec.type == "GitRelease" then
|
||||
mkGitSource spec
|
||||
else if spec.type == "PyPi" then
|
||||
mkPyPiSource spec
|
||||
else if spec.type == "Channel" then
|
||||
mkChannelSource spec
|
||||
else
|
||||
builtins.throw "Unknown source type ${spec.type}";
|
||||
in
|
||||
spec // { outPath = path; };
|
||||
|
||||
mkGitSource =
|
||||
{
|
||||
repository,
|
||||
revision,
|
||||
url ? null,
|
||||
hash,
|
||||
branch ? null,
|
||||
...
|
||||
}:
|
||||
assert repository ? type;
|
||||
# At the moment, either it is a plain git repository (which has an url), or it is a GitHub/GitLab repository
|
||||
# In the latter case, there we will always be an url to the tarball
|
||||
if url != null then
|
||||
(builtins.fetchTarball {
|
||||
inherit url;
|
||||
sha256 = hash; # FIXME: check nix version & use SRI hashes
|
||||
})
|
||||
else
|
||||
assert repository.type == "Git";
|
||||
let
|
||||
urlToName =
|
||||
url: rev:
|
||||
let
|
||||
matched = builtins.match "^.*/([^/]*)(\\.git)?$" repository.url;
|
||||
|
||||
short = builtins.substring 0 7 rev;
|
||||
|
||||
appendShort = if (builtins.match "[a-f0-9]*" rev) != null then "-${short}" else "";
|
||||
in
|
||||
"${if matched == null then "source" else builtins.head matched}${appendShort}";
|
||||
name = urlToName repository.url revision;
|
||||
in
|
||||
builtins.fetchGit {
|
||||
url = repository.url;
|
||||
rev = revision;
|
||||
inherit name;
|
||||
# hash = hash;
|
||||
};
|
||||
|
||||
mkPyPiSource =
|
||||
{ url, hash, ... }:
|
||||
builtins.fetchurl {
|
||||
inherit url;
|
||||
sha256 = hash;
|
||||
};
|
||||
|
||||
mkChannelSource =
|
||||
{ url, hash, ... }:
|
||||
builtins.fetchTarball {
|
||||
inherit url;
|
||||
sha256 = hash;
|
||||
};
|
||||
in
|
||||
if version == 3 then
|
||||
builtins.mapAttrs (_: mkSource) data.pins
|
||||
else
|
||||
throw "Unsupported format version ${toString version} in sources.json. Try running `npins upgrade`"
|
|
@ -1,34 +0,0 @@
|
|||
{
|
||||
"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,3 +1,2 @@
|
|||
-r requirements.txt
|
||||
#spatialite-bin
|
||||
django-debug-toolbar
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
-r requirements.txt
|
||||
psycopg2-binary==2.7.*
|
||||
psycopg2
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
django==2.2.*
|
||||
django-taggit==1.3.*
|
||||
django-tinymce==3.2.*
|
||||
django-braces==1.14.*
|
||||
django-taggit-autosuggest==0.3.*
|
||||
pytz==2020.*
|
||||
django-tastypie==0.14.*
|
||||
lxml==4.6.*
|
||||
django-elasticsearch-dsl==7.1.*
|
||||
authens
|
||||
django-simple-email-confirmation==0.*
|
||||
django==1.11.*
|
||||
django-cas-ng
|
||||
django-taggit
|
||||
python-ldap
|
||||
django-tinymce
|
||||
django-braces
|
||||
django-taggit-autosuggest
|
||||
pytz
|
||||
django-tastypie
|
||||
lxml
|
||||
git+https://github.com/sabricot/django-elasticsearch-dsl#egg=django_elasticsearch_dsl
|
||||
django-allauth-ens
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue