experiENS/avisstage/views.py

567 lines
17 KiB
Python
Raw Normal View History

# coding: utf-8
2017-04-20 01:53:29 +02:00
from django.shortcuts import render, redirect, get_object_or_404
2017-04-04 00:31:50 +02:00
from django.views.generic import (
2021-02-07 00:41:22 +01:00
DetailView,
ListView,
UpdateView,
CreateView,
TemplateView,
DeleteView,
FormView,
View,
)
from django.views.generic.detail import SingleObjectMixin
2017-04-07 03:01:27 +02:00
from django import forms
from django.urls import reverse, reverse_lazy
2019-02-25 17:17:58 +01:00
from django.conf import settings
2017-06-27 23:14:24 +02:00
from django.contrib.admin.views.decorators import staff_member_required
2017-04-07 03:01:27 +02:00
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.contrib import messages
2017-04-07 03:01:27 +02:00
from braces.views import LoginRequiredMixin
2018-12-28 23:46:24 +01:00
from django.http import JsonResponse, HttpResponseForbidden, Http404
2017-04-20 23:04:07 +02:00
from django.core.mail import send_mail
2017-06-27 23:14:24 +02:00
from django.db.models import Q, Count
2018-09-09 00:17:11 +02:00
from collections import Counter, defaultdict
from simple_email_confirmation.models import EmailAddress
2017-04-07 03:01:27 +02:00
2018-12-28 23:46:24 +01:00
from .models import Normalien, Stage, Lieu, AvisLieu, AvisStage
from .forms import (
2021-02-07 00:41:22 +01:00
StageForm,
LieuForm,
AvisStageForm,
AvisLieuForm,
FeedbackForm,
AdresseEmailForm,
ReinitMdpForm,
)
2018-12-28 23:46:24 +01:00
from .utils import en_scolarite
from .views_search import *
2017-04-07 03:01:27 +02:00
import random, math
2017-05-02 23:23:26 +02:00
#
# LECTURE
#
2017-04-07 03:01:27 +02:00
# Page d'accueil
2017-04-05 00:23:35 +02:00
def index(request):
num_stages = Stage.objects.filter(public=True).count()
2021-02-07 00:41:22 +01:00
return render(request, "avisstage/index.html", {"num_stages": num_stages})
2017-04-05 00:23:35 +02:00
2017-04-07 03:01:27 +02:00
# Espace personnel
@login_required
2017-04-05 00:23:35 +02:00
def perso(request):
2019-08-07 18:14:33 +02:00
# 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()
2021-02-07 00:41:22 +01:00
return render(request, "avisstage/perso.html")
2017-04-05 00:23:35 +02:00
2018-12-28 23:46:24 +01:00
# 403 Archicubes
@login_required
def archicubes_interdits(request):
2021-02-07 00:41:22 +01:00
return render(request, "avisstage/403-archicubes.html")
2018-12-28 23:46:24 +01:00
2017-04-07 03:01:27 +02:00
# Profil
2021-02-07 00:41:22 +01:00
# login_required
2017-05-16 22:56:19 +02:00
class ProfilView(LoginRequiredMixin, DetailView):
2017-05-02 23:23:26 +02:00
model = Normalien
2021-02-07 00:41:22 +01:00
template_name = "avisstage/detail/profil.html"
2017-05-02 23:23:26 +02:00
2017-05-16 22:56:19 +02:00
# Récupération du profil
2017-05-02 23:23:26 +02:00
def get_object(self):
2021-02-07 00:41:22 +01:00
2018-12-28 23:46:24 +01:00
# Restriction d'accès pour les archicubes
2021-02-07 00:41:22 +01:00
if (
en_scolarite(self.request.user)
or self.kwargs.get("username") == self.request.user.username
):
return get_object_or_404(
2021-02-07 00:41:22 +01:00
Normalien, user__username=self.kwargs.get("username")
)
2018-12-28 23:46:24 +01:00
else:
raise Http404
2017-05-02 23:23:26 +02:00
2021-02-07 00:41:22 +01:00
2017-05-02 23:23:26 +02:00
# Stage
2021-02-07 00:41:22 +01:00
# login_required
2017-05-16 22:56:19 +02:00
class StageView(LoginRequiredMixin, DetailView):
2017-05-02 23:23:26 +02:00
model = Stage
2021-02-07 00:41:22 +01:00
template_name = "avisstage/detail/stage.html"
2017-05-02 23:23:26 +02:00
# Restriction aux stages publics ou personnels
def get_queryset(self):
2018-12-28 23:46:24 +01:00
filtre = Q(auteur__user_id=self.request.user.id)
2021-02-07 00:41:22 +01:00
2018-12-28 23:46:24 +01:00
# Restriction d'accès pour les archicubes
if en_scolarite(self.request.user):
filtre |= Q(public=True)
2017-05-02 23:23:26 +02:00
2018-12-28 23:46:24 +01:00
return Stage.objects.filter(filtre)
2017-06-26 22:40:35 +02:00
2019-02-25 17:17:58 +01:00
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
2021-02-07 00:41:22 +01:00
context["MAPBOX_API_KEY"] = settings.MAPBOX_API_KEY
2019-02-25 17:17:58 +01:00
return context
2017-05-02 23:23:26 +02:00
2021-02-07 00:41:22 +01:00
2017-05-12 23:29:29 +02:00
# FAQ
def faq(request):
2021-02-07 00:41:22 +01:00
return render(request, "avisstage/faq.html")
2017-05-12 23:29:29 +02:00
2017-05-02 23:23:26 +02:00
#
# EDITION
#
# Profil
2021-02-07 00:41:22 +01:00
# login_required
2017-05-16 22:56:19 +02:00
class ProfilEdit(LoginRequiredMixin, UpdateView):
2017-04-07 03:01:27 +02:00
model = Normalien
2021-02-07 00:41:22 +01:00
fields = ["nom", "promotion", "contactez_moi", "bio"]
template_name = "avisstage/formulaires/profil.html"
2017-04-07 03:01:27 +02:00
2017-05-02 23:23:26 +02:00
# Limitation à son propre profil
2017-04-07 03:01:27 +02:00
def get_object(self):
return self.request.user.profil
2021-02-07 00:41:22 +01:00
2017-04-07 03:01:27 +02:00
def get_success_url(self):
2021-02-07 00:41:22 +01:00
return reverse("avisstage:perso")
2017-04-07 03:01:27 +02:00
2017-05-02 23:23:26 +02:00
# Stage
2017-04-13 22:50:00 +02:00
@login_required
2017-04-20 01:53:29 +02:00
def manage_stage(request, pk=None):
2017-05-02 23:23:26 +02:00
# Objet de base
2018-03-21 13:14:14 +01:00
last_maj = None
2017-04-20 01:53:29 +02:00
if pk is None:
2017-04-13 22:50:00 +02:00
stage = Stage(auteur=request.user.profil)
avis_stage = AvisStage(stage=stage)
c_del = False
2021-02-07 00:41:22 +01:00
last_creation = Stage.objects.filter(auteur=request.user.profil).order_by(
"-date_creation"
)[:1]
2018-03-21 13:14:14 +01:00
if len(last_creation) != 0:
last_maj = last_creation[0].date_creation
2017-04-13 22:50:00 +02:00
else:
2017-05-16 23:53:30 +02:00
try:
stage = Stage.objects.filter(auteur=request.user.profil).get(pk=pk)
except Stage.DoesNotExist:
return HttpResponseForbidden()
2018-03-21 13:14:14 +01:00
last_maj = stage.date_maj
2017-04-13 22:50:00 +02:00
avis_stage, _ = AvisStage.objects.get_or_create(stage=stage)
c_del = True
2017-05-02 23:23:26 +02:00
# Formset pour les avis des lieux
2017-04-13 22:50:00 +02:00
AvisLieuFormSet = forms.inlineformset_factory(
2021-02-07 00:41:22 +01:00
Stage, AvisLieu, form=AvisLieuForm, can_delete=c_del, extra=0
)
2017-04-13 22:50:00 +02:00
if request.method == "POST":
2017-05-02 23:23:26 +02:00
# Lecture des données
2017-04-13 22:50:00 +02:00
form = StageForm(request.POST, request=request, instance=stage, prefix="stage")
2021-02-07 00:41:22 +01:00
avis_stage_form = AvisStageForm(
request.POST, instance=avis_stage, prefix="avis"
)
avis_lieu_formset = AvisLieuFormSet(
request.POST, instance=stage, prefix="lieux"
)
2017-05-02 23:23:26 +02:00
# Validation et enregistrement
2021-02-07 00:41:22 +01:00
if (
form.is_valid()
and avis_stage_form.is_valid()
and avis_lieu_formset.is_valid()
):
2017-04-13 22:50:00 +02:00
stage = form.save()
avis_stage_form.instance.stage = stage
avis_stage_form.save()
avis_lieu_formset.save()
2021-02-07 00:41:22 +01:00
# print(request.POST)
2017-05-16 23:53:30 +02:00
if "continuer" in request.POST:
if pk is None:
2021-02-07 00:41:22 +01:00
return redirect(
reverse("avisstage:stage_edit", kwargs={"pk": stage.id})
)
2017-05-16 23:53:30 +02:00
else:
2021-02-07 00:41:22 +01:00
return redirect(reverse("avisstage:stage", kwargs={"pk": stage.id}))
2017-04-13 22:50:00 +02:00
else:
form = StageForm(instance=stage, prefix="stage")
avis_stage_form = AvisStageForm(instance=avis_stage, prefix="avis")
avis_lieu_formset = AvisLieuFormSet(instance=stage, prefix="lieux")
2017-05-02 23:23:26 +02:00
# Affichage du formulaire
2021-02-07 00:41:22 +01:00
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,
},
)
2017-06-16 00:17:25 +02:00
2017-05-02 23:23:26 +02:00
# Ajout d'un lieu de stage
2021-02-07 00:41:22 +01:00
# login_required
# Stage
@login_required
def save_lieu(request):
normalien = request.user.profil
2021-02-07 00:41:22 +01:00
if request.method == "POST":
pk = request.POST.get("id", None)
2021-02-07 00:41:22 +01:00
# print(request.POST)
jitter = False
2021-02-07 00:41:22 +01:00
if pk is None or pk == "":
lieu = Lieu()
else:
# Modification du lieu
lieu = get_object_or_404(Lieu, pk=pk)
# 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é
if not_same_user > 0:
lieu = Lieu()
# Servira à bouger un peu le lieu
jitter = True
2021-02-07 00:41:22 +01:00
# Lecture des données
form = LieuForm(request.POST, instance=lieu)
# Validation et enregistrement
if form.is_valid():
lieu = form.save(commit=False)
if jitter:
cdx, cdy = lieu.coord.get_coords()
2021-02-07 00:41:22 +01:00
ang = random.random() * 6.29
rad = (random.random() + 0.5) * 3e-4
2021-02-07 00:41:22 +01:00
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 == "":
2021-02-07 00:41:22 +01:00
olieux = Lieu.objects.filter(
nom=lieu.nom, coord__distance_lte=(lieu.coord, 10)
)
for olieu in olieux:
2021-02-07 00:41:22 +01:00
if (
olieu.type_lieu == lieu.type_lieu
and olieu.ville == lieu.ville
and olieu.pays == lieu.pays
):
return JsonResponse({"success": True, "id": olieu.id})
2021-02-07 00:41:22 +01:00
lieu.save()
return JsonResponse({"success": True, "id": lieu.id})
else:
2021-02-07 00:41:22 +01:00
return JsonResponse({"success": False, "errors": form.errors})
else:
return JsonResponse({"erreur": "Aucune donnée POST"})
2021-02-07 00:41:22 +01:00
2017-05-16 22:56:19 +02:00
class LieuAjout(LoginRequiredMixin, CreateView):
model = Lieu
form_class = LieuForm
2021-02-07 00:41:22 +01:00
template_name = "avisstage/formulaires/lieu.html"
2017-05-02 23:23:26 +02:00
# Retourne d'un JSON si requête AJAX
def form_valid(self, form):
if self.request.GET.get("format", "") == "json":
self.object = form.save()
2021-02-07 00:41:22 +01:00
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":
2021-02-07 00:41:22 +01:00
return JsonResponse({"success": False, "errors": form.errors})
else:
super(LieuAjout, self).form_valid(form)
2017-04-20 01:53:29 +02:00
2021-02-07 00:41:22 +01:00
2017-05-02 23:23:26 +02:00
# Passage d'un stage en mode public
2017-04-20 01:53:29 +02:00
@login_required
def publier_stage(request, pk):
if request.method != "POST":
return HttpResponseForbidden()
stage = get_object_or_404(Stage, pk=pk)
2017-05-02 23:23:26 +02:00
# Stage non possédé par l'utilisateur
2017-04-20 01:53:29 +02:00
if stage.auteur != request.user.profil:
return HttpResponseForbidden()
2017-05-02 23:23:26 +02:00
# Mise à jour du statut
2017-04-20 01:53:29 +02:00
if "publier" in request.POST:
stage.public = True
else:
stage.public = False
2017-05-02 23:23:26 +02:00
2017-04-20 01:53:29 +02:00
stage.save()
2021-02-07 00:41:22 +01:00
2017-04-20 01:53:29 +02:00
return redirect(reverse("avisstage:stage", kwargs={"pk": pk}))
2017-04-20 23:04:07 +02:00
2021-02-07 00:41:22 +01:00
2017-05-02 23:23:26 +02:00
#
2017-04-20 23:04:07 +02:00
# FEEDBACK
2017-05-02 23:23:26 +02:00
#
2021-02-07 00:41:22 +01:00
2017-04-20 23:04:07 +02:00
@login_required
2017-04-05 00:23:35 +02:00
def feedback(request):
2017-04-20 23:04:07 +02:00
if request.method == "POST":
form = FeedbackForm(request.POST)
if form.is_valid():
2021-02-07 00:41:22 +01:00
objet = form.cleaned_data["objet"]
header = "[From : %s <%s>]\n" % (request.user, request.user.email)
message = header + form.cleaned_data["message"]
2017-04-20 23:04:07 +02:00
send_mail(
2021-02-07 00:41:22 +01:00
"[experiENS] " + objet,
2017-04-20 23:04:07 +02:00
message,
2019-07-26 17:00:38 +02:00
request.user.email,
2021-02-07 00:41:22 +01:00
["robin.champenois@ens.fr"],
2017-04-20 23:04:07 +02:00
fail_silently=False,
)
if request.GET.get("format", None) == "json":
return JsonResponse({"success": True})
return redirect(reverse("avisstage:index"))
else:
if request.GET.get("format", None) == "json":
2021-02-07 00:41:22 +01:00
return JsonResponse({"success": False, "errors": form.errors})
2017-04-20 23:04:07 +02:00
else:
form = FeedbackForm()
2018-12-29 23:23:36 +01:00
raise Http404()
2017-06-27 23:14:24 +02:00
#
# STATISTIQUES
#
2021-02-07 00:41:22 +01:00
2017-06-27 23:14:24 +02:00
@login_required
@staff_member_required
def statistiques(request):
nstages = Stage.objects.count()
npubstages = Stage.objects.filter(public=True).count()
2021-02-07 00:41:22 +01:00
nbymatiere_raw = Stage.objects.values("matieres__nom", "public").annotate(
scount=Count("matieres__nom")
)
2018-09-09 00:17:11 +02:00
nbymatiere = defaultdict(dict)
for npm in nbymatiere_raw:
2021-02-07 00:41:22 +01:00
nbymatiere[npm["matieres__nom"]][
"publics" if npm["public"] else "drafts"
] = npm["scount"]
2018-09-09 00:17:11 +02:00
for mat, npm in nbymatiere.items():
npm["matiere"] = mat
2021-02-07 00:41:22 +01:00
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(),
),
]
2017-06-27 23:14:24 +02:00
nusers = Normalien.objects.count()
nauts = Normalien.objects.filter(stages__isnull=False).distinct().count()
2021-02-07 00:41:22 +01:00
nbyaut = Counter(
Normalien.objects.filter(stages__isnull=False)
.annotate(scount=Count("stages"))
.values_list("scount", flat="True")
).items()
2017-06-27 23:14:24 +02:00
nlieux = Lieu.objects.filter(stages__isnull=False).distinct().count()
2021-02-07 00:41:22 +01:00
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
#
2021-02-07 00:41:22 +01:00
class MesAdressesMixin(LoginRequiredMixin):
slug_url_kwarg = "email"
slug_field = "email"
confirmed_only = False
2021-02-07 00:41:22 +01:00
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
2021-02-07 00:41:22 +01:00
def _send_confirm_mail(email, request):
confirm_url = request.build_absolute_uri(
2021-02-07 00:41:22 +01:00
reverse("avisstage:emails_confirme", kwargs={"key": email.key})
)
send_mail(
"[ExperiENS] Confirmez votre adresse a-mail",
2021-02-07 00:41:22 +01:00
"""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,
2021-02-07 00:41:22 +01:00
L'équipe ExperiENS""".format(
confirm_url=confirm_url
),
"experiens-nepasrepondre@eleves.ens.fr",
[email.email],
fail_silently=False,
)
2021-02-07 00:41:22 +01:00
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
2021-02-07 00:41:22 +01:00
def form_valid(self, form):
new = EmailAddress.objects.create_unconfirmed(
2021-02-07 00:41:22 +01:00
form.cleaned_data["email"], self.request.user
)
return _send_confirm_mail(new, self.request)
2021-02-07 00:41:22 +01:00
class RendAdressePrincipale(MesAdressesMixin, SingleObjectMixin, View):
model = EmailAddress
confirmed_only = True
2021-02-07 00:41:22 +01:00
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"))
2021-02-07 00:41:22 +01:00
class AdresseAConfirmer(MesAdressesMixin, DetailView):
model = EmailAddress
template_name = "avisstage/compte/aconfirmer.html"
2021-02-07 00:41:22 +01:00
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"))
2021-02-07 00:41:22 +01:00
class ConfirmeAdresse(LoginRequiredMixin, View):
def get(self, *args, **kwargs):
try:
2021-02-07 00:41:22 +01:00
email = EmailAddress.objects.confirm(
self.kwargs["key"], self.request.user, True
)
except Exception as e:
raise Http404()
messages.add_message(
2021-02-07 00:41:22 +01:00
self.request,
messages.SUCCESS,
"L'adresse email {email} a bien été confirmée".format(email=email.email),
)
return redirect(reverse("avisstage:parametres"))
2021-02-07 00:41:22 +01:00
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)
2021-02-07 00:41:22 +01:00
class EnvoieLienMotDePasse(LoginRequiredMixin, View):
def post(self, *args, **kwargs):
form = ReinitMdpForm({"email": self.request.user.email})
form.is_valid()
form.save(
2021-02-07 00:41:22 +01:00
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(
2021-02-07 00:41:22 +01:00
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"))
2021-02-07 00:41:22 +01:00
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