Compare commits

..

22 commits

Author SHA1 Message Date
0209ad53ca
feat: Merge settings 2024-10-23 20:05:39 +02:00
4d75efe7c5
fix(manage.py): Make executable 2024-10-23 20:05:39 +02:00
d7b80ea06a
feat: Add nix tooling 2024-10-23 20:05:39 +02:00
1491956e30
chore: Rename experiENS -> app 2024-10-23 20:05:39 +02:00
Robin Champenois
25f965c750 debug search 2021-08-29 14:00:27 +02:00
Robin Champenois
db1d35fbb6 Merge branch 'thubrecht/format' into 'master'
Thubrecht/format

See merge request klub-dev-ens/experiENS!15
2021-07-11 20:52:47 +00:00
02f7b3c8c3 Use search views 2021-07-11 21:20:10 +02:00
d9111cc8cb Import settings in dev mode 2021-07-11 21:18:14 +02:00
732a6a08da More updates 2021-06-29 00:11:18 +02:00
32ba0e6111 flake8 2021-06-28 23:58:02 +02:00
b53170feae On enlève les u devant les strings 2021-06-28 23:57:16 +02:00
5bc518eba6 Isort 2021-06-28 23:57:16 +02:00
7cfc85f1fc Black 2021-06-28 23:57:13 +02:00
Robin Champenois
26ad68ff69 Fix search 2021-06-28 23:29:58 +02:00
Robin Champenois
c40a91fb67 Better search 2021-06-28 23:25:16 +02:00
Robin Champenois
46eacc94da Fix search2? 2021-06-28 23:00:32 +02:00
Robin Champenois
370447d355 Fix search? 2021-06-28 22:23:00 +02:00
Robin Champenois
22b5016687 Remove standard useless token filter 2021-06-28 22:09:08 +02:00
Robin Champenois
3c2f93bccb MàJ ElasticSearch 2021-06-28 21:44:47 +02:00
Robin Champenois
1956f38176 Useless import in prod settings 2021-02-07 19:08:29 +01:00
Robin Champenois
9c1092cf8f Merge branch 'maj2021' into 'master'
Mise à jour 2021

Un certain nombre de changements qu'il était temps d'apporter :
- passage à Django 2.2
- basculement de Allauth à AuthENS (=> gestion des adresses mail avec django-simple-email-confirmation)
- statut "en scolarité/archicube" automatique
- débugs divers (carte des lieux, ...)
- mise à jour des dépendances

See merge request klub-dev-ens/experiENS!13
2021-02-07 18:23:24 +01:00
Robin Champenois
e470a2a268 Mise à jour 2021 2021-02-07 18:23:24 +01:00
33 changed files with 588 additions and 313 deletions

1
.credentials/SECRET_KEY Normal file
View file

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

1
.envrc Normal file
View file

@ -0,0 +1 @@
use nix

4
.gitignore vendored
View file

@ -108,6 +108,6 @@ test.py
.#*
*.sqlite3
.sass-cache
/static/
settings.py
secrets.py
.direnv
.pre-commit-config.yaml

242
app/settings.py Normal file
View file

@ -0,0 +1,242 @@
"""
Django settings for the experiENS project
"""
from pathlib import Path
from loadcredential import Credentials
from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _
credentials = Credentials(env_prefix="EXPERIENS_")
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# WARNING: keep the secret key used in production secret!
SECRET_KEY = credentials["SECRET_KEY"]
# WARNING: don't run with debug turned on in production!
DEBUG = credentials.get_json("DEBUG", False)
ALLOWED_HOSTS = credentials.get_json("ALLOWED_HOSTS", [])
ADMINS = credentials.get_json("ADMINS", [])
SITE_ID = 1
###
# ElasticSearch configuration
USE_ELASTICSEARCH = credentials.get_json("USE_ELASTICSEARCH", False)
ELASTICSEARCH_DSL = credentials.get_json(
"ELASTICSEARCH_DSL", {"default": {"hosts": "127.0.0.1:9200"}}
)
###
# Libraries configuration
GDAL_LIBRARY_PATH = credentials.get("GDAL_LIBRARY_PATH")
GEOS_LIBRARY_PATH = credentials.get("GEOS_LIBRARY_PATH")
###
# List the installed applications
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"django.contrib.gis",
"django.contrib.sites",
*(["django_elasticsearch_dsl"] if USE_ELASTICSEARCH else []),
"simple_email_confirmation",
"authens",
"tastypie",
"braces",
"tinymce",
"taggit",
"taggit_autosuggest",
"avisstage",
]
###
# List the installed middlewares
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
###
# The main url configuration
ROOT_URLCONF = "app.urls"
###
# Template configuration:
# - Django Templating Language is used
# - Application directories can be used
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.contrib.auth.context_processors.auth",
"django.template.context_processors.debug",
"django.template.context_processors.i18n",
"django.template.context_processors.media",
"django.template.context_processors.static",
"django.template.context_processors.tz",
"django.template.context_processors.request",
"django.contrib.messages.context_processors.messages",
],
},
},
]
###
# Database configuration
# -> https://docs.djangoproject.com/en/4.2/ref/settings/#databases
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
DATABASES = credentials.get_json(
"DATABASES",
{
"default": {
"ENGINE": "django.contrib.gis.db.backends.spatialite",
"NAME": BASE_DIR / "db.sqlite3",
}
},
)
CACHES = credentials.get_json(
"CACHES",
default={
"default": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
},
},
)
###
# WSGI application configuration
WSGI_APPLICATION = "app.wsgi.application"
###
# Staticfiles configuration
STATIC_ROOT = credentials["STATIC_ROOT"]
STATIC_URL = "/static/"
MEDIA_ROOT = credentials.get("MEDIA_ROOT", BASE_DIR / "media")
MEDIA_URL = "/media/"
###
# Internationalization configuration
# -> https://docs.djangoproject.com/en/4.2/topics/i18n/
LANGUAGE_CODE = "fr-fr"
TIME_ZONE = "Europe/Paris"
USE_I18N = True
USE_L10N = True
USE_TZ = True
LANGUAGES = [
("fr", _("Français")),
]
###
# Authentication configuration
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
"experiENS.auth.ENSCASBackend",
]
CAS_SERVER_URL = "https://cas.eleves.ens.fr/" # SPI CAS
AUTHENS_USE_OLDCAS = False
LOGIN_URL = reverse_lazy("authens:login")
LOGOUT_URL = reverse_lazy("authens:logout")
LOGIN_REDIRECT_URL = reverse_lazy("avisstage:perso")
LOGOUT_REDIRECT_URL = reverse_lazy("avisstage:index")
AUTH_PASSWORD_VALIDATORS = [
{"NAME": f"django.contrib.auth.password_validation.{v}"}
for v in [
"UserAttributeSimilarityValidator",
"MinimumLengthValidator",
"CommonPasswordValidator",
"NumericPasswordValidator",
]
]
###
# Logging configuration
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"handlers": {
"file": {
"level": "INFO",
"class": "logging.FileHandler",
"filename": credentials.get(
"RECHERCHE_LOG_FILE", BASE_DIR / "recherche.log"
),
},
},
"loggers": {
"recherche": {
"handlers": ["file"],
"level": "INFO",
"propagate": True,
},
},
}
###
# LDAP configuration
CLIPPER_LDAP_SERVER = credentials.get("CLIPPER_LDAP_SERVER", "ldaps://localhost:636")
# Development settings
if DEBUG:
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
INSTALLED_APPS += [
"debug_toolbar",
]
MIDDLEWARE = ["debug_toolbar.middleware.DebugToolbarMiddleware", *MIDDLEWARE]

View file

@ -9,8 +9,8 @@ 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()

View file

@ -4,7 +4,7 @@ from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
from avisstage.models import *
from avisstage.models import AvisLieu, AvisStage, Lieu, Normalien, Stage, StageMatiere
class NormalienInline(admin.StackedInline):

View file

@ -1,11 +1,11 @@
from tastypie import fields, utils
from tastypie import fields
from tastypie.authentication import SessionAuthentication
from tastypie.resources import ModelResource
from django.contrib.gis import geos
from django.urls import reverse
from .models import Lieu, Normalien, Stage, StageMatiere
from .models import Lieu, Normalien, Stage
from .utils import approximate_distance

View file

@ -1,7 +1,7 @@
from django_elasticsearch_dsl import DocType, Index, fields
from elasticsearch_dsl import analyzer, token_filter, tokenizer
from django_elasticsearch_dsl import Document, Index, fields
from elasticsearch_dsl import analyzer, token_filter
from .models import AvisLieu, AvisStage, Stage
from .models import Stage
from .statics import PAYS_OPTIONS
PAYS_DICT = dict(PAYS_OPTIONS)
@ -14,7 +14,6 @@ text_analyzer = analyzer(
tokenizer="standard",
filter=[
"lowercase",
"standard",
"asciifolding",
token_filter("frstop", type="stop", stopwords="_french_"),
token_filter("frsnow", type="snowball", language="French"),
@ -24,23 +23,23 @@ stage.analyzer(text_analyzer)
@stage.doc_type
class StageDocument(DocType):
class StageDocument(Document):
lieux = fields.ObjectField(
properties={
"nom": fields.StringField(),
"ville": fields.StringField(),
"pays": fields.StringField(),
"nom": fields.TextField(),
"ville": fields.TextField(),
"pays": fields.TextField(),
}
)
auteur = fields.ObjectField(
properties={
"nom": fields.StringField(),
"nom": fields.TextField(),
}
)
thematiques = fields.StringField()
matieres = fields.StringField()
thematiques = fields.TextField()
matieres = fields.TextField()
class Meta:
class Django:
model = Stage
fields = [
"sujet",

View file

@ -7,7 +7,7 @@ from django import forms
from django.contrib.auth.forms import PasswordResetForm
from django.utils import timezone
from .models import AvisLieu, AvisStage, Lieu, Normalien, Stage, User
from .models import AvisLieu, AvisStage, Lieu, Stage, User
from .widgets import LatLonField
@ -59,8 +59,10 @@ class StageForm(forms.ModelForm):
"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": "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)",
}
labels = {
"date_debut": "Date de début",
@ -100,11 +102,30 @@ class AvisStageForm(HTMLTrimmerForm):
"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 ?",
"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",
}
@ -123,10 +144,21 @@ class AvisLieuForm(HTMLTrimmerForm):
"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 ?",
"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",
}

View file

@ -1,7 +1,6 @@
from django.core.management.base import BaseCommand, CommandError
from django.db.models import Count
from django.core.management.base import BaseCommand
from avisstage.models import Lieu, Stage
from avisstage.models import Lieu
class Command(BaseCommand):

View file

@ -1,7 +1,7 @@
from django.core.management.base import BaseCommand, CommandError
from django.core.management.base import BaseCommand
from django.db.models import Count
from avisstage.models import Lieu, Stage
from avisstage.models import Stage
class Command(BaseCommand):

View file

@ -1,7 +1,6 @@
from django.core.management.base import BaseCommand, CommandError
from django.db.models import Count
from django.core.management.base import BaseCommand
from avisstage.models import Lieu, Stage
from avisstage.models import Lieu
class Command(BaseCommand):

View file

@ -1,6 +1,6 @@
from datetime import timedelta
from django.core.management.base import BaseCommand, CommandError
from django.core.management.base import BaseCommand
from django.utils import timezone
from avisstage.models import Normalien
@ -13,6 +13,6 @@ class Command(BaseCommand):
return
def handle(self, *args, **options):
old_conn = timezone.now() - timedelta(days=365)
t = timezone.now() - timedelta(days=365)
Normalien.objects.all().update(last_cas_connect=t)
self.stdout.write(self.style.SUCCESS("Terminé"))

View file

@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-06-20 17:45
from __future__ import unicode_literals
import taggit_autosuggest.managers
import tinymce.models

View file

@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-10-02 20:43
from __future__ import unicode_literals
import tinymce.models

View file

@ -1,6 +1,5 @@
from datetime import timedelta
from authens.models import CASAccount
from authens.signals import post_cas_connect
from taggit_autosuggest.managers import TaggableManager
from tinymce.models import HTMLField as RichTextField
@ -9,12 +8,9 @@ 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.forms.widgets import DateInput
from django.template.defaultfilters import slugify
from django.urls import reverse
from django.utils import timezone
from django.utils.functional import cached_property
from django.utils.html import strip_tags
from .statics import (
DEPARTEMENTS_DEFAUT,
@ -26,7 +22,7 @@ from .statics import (
TYPE_STAGE_DICT,
TYPE_STAGE_OPTIONS,
)
from .utils import choices_length, is_email_ens
from .utils import choices_length
def _default_cas_login():
@ -102,6 +98,7 @@ def create_basic_user_profile(sender, instance, created, **kwargs):
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)

View file

@ -1,3 +1,4 @@
# coding: utf-8
import re
from django import template

View file

@ -9,10 +9,11 @@ from django.test import TestCase
from django.urls import reverse
from django.utils import timezone
from .models import AvisLieu, Lieu, Normalien, Stage, StageMatiere, User
from .models import AvisLieu, Lieu, Stage, StageMatiere, User
class ExperiENSTestCase(TestCase):
# Dummy database
def setUp(self):

View file

@ -2,7 +2,7 @@ from tastypie.api import Api
from django.urls import include, path
from . import api, views
from . import api, views, views_search
v1_api = Api(api_name="v1")
v1_api.register(api.LieuResource())
@ -56,9 +56,13 @@ urlpatterns = [
views.DefinirMotDePasse.as_view(),
name="mdp_edit",
),
path("recherche/", views.recherche, name="recherche"),
path("recherche/resultats/", views.recherche_resultats, name="recherche_resultats"),
path("recherche/items/", views.stage_items, name="stage_items"),
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)),

View file

@ -10,7 +10,6 @@ from django.conf import settings
from django.contrib import messages
from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.auth.decorators import login_required
from django.contrib.auth.tokens import default_token_generator
from django.contrib.auth.views import PasswordResetConfirmView
from django.core.mail import send_mail
from django.db.models import Count, Q
@ -22,8 +21,6 @@ from django.views.generic import (
DeleteView,
DetailView,
FormView,
ListView,
TemplateView,
UpdateView,
View,
)
@ -40,12 +37,12 @@ from .forms import (
)
from .models import AvisLieu, AvisStage, Lieu, Normalien, Stage
from .utils import en_scolarite
from .views_search import *
#
# LECTURE
#
# Page d'accueil
def index(request):
num_stages = Stage.objects.filter(public=True).count()
@ -236,7 +233,8 @@ 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
@ -514,7 +512,7 @@ class ConfirmeAdresse(LoginRequiredMixin, View):
email = EmailAddress.objects.confirm(
self.kwargs["key"], self.request.user, True
)
except Exception as e:
except Exception:
raise Http404()
messages.add_message(
self.request,
@ -546,9 +544,10 @@ class EnvoieLienMotDePasse(LoginRequiredMixin, View):
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
),
(
"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"))

View file

@ -6,10 +6,10 @@ 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 Paginator
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 get_object_or_404, redirect, render
from django.shortcuts import render
USE_ELASTICSEARCH = getattr(settings, "USE_ELASTICSEARCH", True)
@ -22,12 +22,13 @@ from .statics import NIVEAU_SCOL_OPTIONS, TYPE_LIEU_OPTIONS, TYPE_STAGE_OPTIONS
logger = logging.getLogger("recherche")
# 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
label="Contexte (lieu, encadrant·e·s, structure)", required=False
)
apres_annee = forms.IntegerField(label="Après cette année", required=False)
@ -79,7 +80,20 @@ def cherche(**kwargs):
if field_relevant("generique"):
# print("Filtre generique", kwargs['generique'])
dsl = dsl.query(
"match", _all={"query": kwargs["generique"], "fuzziness": "auto"}
"multi_match",
query=kwargs["generique"],
fuzziness="auto",
fields=[
"sujet^3",
"encadrants",
"type_stage",
"niveau_scol",
"structure",
"lieux.*^2",
"auteur.nom^2",
"thematiques^2",
"matieres",
],
)
use_dsl = True
@ -133,11 +147,11 @@ def cherche(**kwargs):
# Dates
if field_relevant("avant_annee", False):
dte = date(kwargs["avant_annee"] + 1, 1, 1)
dte = date(min(2100, kwargs["avant_annee"]) + 1, 1, 1)
filtres &= Q(date_fin__lt=dte)
if field_relevant("apres_annee", False):
dte = date(kwargs["apres_annee"], 1, 1)
dte = date(max(2000, kwargs["apres_annee"]), 1, 1)
filtres &= Q(date_debut__gte=dte)
# Type de stage
@ -151,19 +165,29 @@ def cherche(**kwargs):
if field_relevant("type_lieu"):
filtres &= Q(lieux__type_lieu=kwargs["type_lieu"])
# Application
if USE_ELASTICSEARCH and use_dsl:
filtres &= Q(id__in=[s.meta.id for s in dsl.scan()])
# print(filtres)
resultat = Stage.objects.filter(filtres)
# Tri
tri = "pertinence"
if not use_dsl:
kwargs["tri"] = "-date_maj"
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_ELASTICSEARCH and use_dsl:
dsl_res = [s.meta.id for s in dsl.scan()]
resultat = resultat.filter(id__in=dsl_res)
if tri == "pertinence":
resultat = resultat.order_by(
Case(*[When(pk=pk, then=pos) for pos, pk in enumerate(dsl_res)])
)
else:
resultat = resultat.order_by(tri)
else:
resultat = resultat.order_by(tri)
return resultat, tri

94
default.nix Normal file
View file

@ -0,0 +1,94 @@
{
sources ? import ./npins,
pkgs ? import sources.nixpkgs { },
}:
let
nix-pkgs = import sources.nix-pkgs { inherit pkgs; };
check = (import sources.git-hooks).run {
src = ./.;
hooks = {
# Python hooks
black = {
enable = true;
stages = [ "pre-push" ];
};
isort = {
enable = true;
stages = [ "pre-push" ];
};
ruff = {
enable = true;
stages = [ "pre-push" ];
};
# Misc Hooks
commitizen.enable = true;
};
};
python3 = pkgs.python3.override {
packageOverrides = _: _: {
inherit (nix-pkgs)
authens
django-braces
django-elasticsearch-dsl
django-simple-email-confirmation
django-taggit-autosuggest
django-tinymce
loadcredential
spatialite
;
};
};
in
{
devShell = pkgs.mkShell {
name = "annuaire.dev";
packages = [
(python3.withPackages (ps: [
ps.authens
ps.django
ps.django-braces
ps.django-elasticsearch-dsl
ps.django-simple-email-confirmation
ps.django-taggit
ps.django-taggit-autosuggest
ps.django-tastypie
ps.django-tinymce
ps.loadcredential
# Dev packages
ps.django-debug-toolbar
ps.django-stubs
ps.spatialite
]))
];
env = {
DJANGO_SETTINGS_MODULE = "app.settings";
CREDENTIALS_DIRECTORY = builtins.toString ./.credentials;
EXPERIENS_DEBUG = builtins.toJSON true;
EXPERIENS_STATIC_ROOT = builtins.toString ./.static;
EXPERIENS_GDAL_LIBRARY_PATH = "${pkgs.gdal}/lib/libgdal.so";
EXPERIENS_GEOS_LIBRARY_PATH = "${pkgs.geos}/lib/libgeos_c.so";
};
shellHook = ''
${check.shellHook}
if [ ! -d .static ]; then
mkdir .static
fi
'';
};
}

View file

@ -1,143 +0,0 @@
"""
Django settings for experiENS project.
For more information on this file, see
https://docs.djangoproject.com/en/1.7/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.7/ref/settings/
"""
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
from django.urls import reverse_lazy
from .secrets import GOOGLE_API_KEY, MAPBOX_API_KEY, SECRET_KEY
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",
#'allauth', # Uncomment that part when you
#'allauth.account', # apply migration
#'allauth.socialaccount', # Allauth -> AuthENS
"simple_email_confirmation",
"authens",
"tastypie",
"braces",
"tinymce",
"taggit",
"taggit_autosuggest",
"avisstage",
]
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",
)
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.template.context_processors.request",
"django.contrib.messages.context_processors.messages",
],
},
},
]
ROOT_URLCONF = "experiENS.urls"
WSGI_APPLICATION = "experiENS.wsgi.application"
# Database
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
# Internationalization
# https://docs.djangoproject.com/en/1.7/topics/i18n/
LANGUAGE_CODE = "fr"
TIME_ZONE = "Europe/Paris"
USE_I18N = True
USE_L10N = True
USE_TZ = True
SITE_ID = 1
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.7/howto/static-files/
STATIC_URL = "/static/"
AUTHENTICATION_BACKENDS = (
"django.contrib.auth.backends.ModelBackend",
"experiENS.auth.ENSCASBackend",
)
CAS_SERVER_URL = "https://cas.eleves.ens.fr/" # SPI CAS
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")
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"handlers": {
"file": {
"level": "INFO",
"class": "logging.FileHandler",
"filename": os.path.join(BASE_DIR, "recherche.log"),
},
},
"loggers": {
"recherche": {
"handlers": ["file"],
"level": "INFO",
"propagate": True,
},
},
}

View file

@ -1,42 +0,0 @@
from .settings_base import *
DEBUG = True
DATABASES = {
"default": {
"ENGINE": "django.contrib.gis.db.backends.spatialite",
"NAME": os.path.join(BASE_DIR, "db.sqlite3"),
}
}
USE_DEBUG_TOOLBAR = False
if USE_DEBUG_TOOLBAR:
INSTALLED_APPS += [
"debug_toolbar",
]
MIDDLEWARE = ("debug_toolbar.middleware.DebugToolbarMiddleware",) + MIDDLEWARE
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"},
}
CLIPPER_LDAP_SERVER = "ldaps://localhost:636"
# Changer à True pour développer avec ES
USE_ELASTICSEARCH = False
if not USE_ELASTICSEARCH:
INSTALLED_APPS.remove("django_elasticsearch_dsl")

View file

@ -1,52 +0,0 @@
import os
import sys
from django.core.urlresolvers import reverse_lazy
from .settings_base import *
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"},
}
CLIPPER_LDAP_SERVER = "ldaps://ldap.spi.ens.fr:636"
DEFAULT_FROM_EMAIL = "experiens-no-reply@www.eleves.ens.fr"

0
manage.py Normal file → Executable file
View file

80
npins/default.nix Normal file
View file

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

34
npins/sources.json Normal file
View file

@ -0,0 +1,34 @@
{
"pins": {
"git-hooks": {
"type": "Git",
"repository": {
"type": "GitHub",
"owner": "cachix",
"repo": "git-hooks.nix"
},
"branch": "master",
"revision": "3c3e88f0f544d6bb54329832616af7eb971b6be6",
"url": "https://github.com/cachix/git-hooks.nix/archive/3c3e88f0f544d6bb54329832616af7eb971b6be6.tar.gz",
"hash": "04pwjz423iq2nkazkys905gvsm5j39722ngavrnx42b8msr5k555"
},
"nix-pkgs": {
"type": "Git",
"repository": {
"type": "Git",
"url": "https://git.hubrecht.ovh/hubrecht/nix-pkgs"
},
"branch": "main",
"revision": "024f0d09d4ff1a62e11f5fdd74f2d00d0a77da5c",
"url": null,
"hash": "0abpyf4pclslg24wmwl3q6y8x5fmhq9winpgkpbb99yw2815j2iz"
},
"nixpkgs": {
"type": "Channel",
"name": "nixpkgs-unstable",
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-24.11pre694416.ccc0c2126893/nixexprs.tar.xz",
"hash": "0cn1z4wzps8nfqxzr6l5mbn81adcqy2cy2ic70z13fhzicmxfsbx"
}
},
"version": 3
}

View file

@ -1,7 +1,7 @@
[flake8]
max-line-length = 99
exclude = .git, *.pyc, __pycache__, migrations
extend-ignore = E231, E203
extend-ignore = E231, E203, E402
[isort]
profile = black

1
shell.nix Normal file
View file

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