# coding: utf-8 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.urls import reverse from django.conf import settings from django.contrib.admin.views.decorators import staff_member_required from django.contrib.auth.decorators import login_required from braces.views import LoginRequiredMixin from django.http import JsonResponse, HttpResponseForbidden, Http404 from django.core.mail import send_mail from django.db.models import Q, Count from collections import Counter, defaultdict from .models import Normalien, Stage, Lieu, AvisLieu, AvisStage from .forms import StageForm, LieuForm, AvisStageForm, AvisLieuForm, FeedbackForm from .utils import en_scolarite from .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}) # 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') # Profil #login_required class ProfilView(LoginRequiredMixin, DetailView): model = Normalien 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 # Stage #login_required class StageView(LoginRequiredMixin, DetailView): model = Stage 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) 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 # FAQ def faq(request): return render(request, 'avisstage/faq.html') # # EDITION # # Profil #login_required class ProfilEdit(LoginRequiredMixin, UpdateView): model = Normalien fields = ['nom', 'promotion', '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') # Stage @login_required def manage_stage(request, pk=None): # Objet de base last_maj = None if pk is 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] if len(last_creation) != 0: last_maj = last_creation[0].date_creation else: try: stage = Stage.objects.filter(auteur=request.user.profil).get(pk=pk) except Stage.DoesNotExist: return HttpResponseForbidden() last_maj = stage.date_maj avis_stage, _ = AvisStage.objects.get_or_create(stage=stage) c_del = True # Formset pour les avis des lieux AvisLieuFormSet = forms.inlineformset_factory( 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") # Validation et enregistrement 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) if "continuer" in request.POST: if pk is None: return redirect(reverse('avisstage:stage_edit',kwargs={'pk':stage.id})) else: 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}) # Ajout d'un lieu de stage #login_required # Stage @login_required def save_lieu(request): normalien = request.user.profil if request.method == "POST": pk = request.POST.get("id", None) #print(request.POST) jitter = False 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 # 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() ang = random.random() * 6.29; rad = (random.random() + 0.5) * 3e-4 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)) for olieu in olieux: 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}) else: return JsonResponse({"erreur": "Aucune donnée POST"}) class LieuAjout(LoginRequiredMixin, CreateView): model = Lieu form_class = LieuForm 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}) 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}) else: super(LieuAjout, self).form_valid(form) # Passage d'un stage en mode public @login_required def publier_stage(request, pk): if request.method != "POST": return HttpResponseForbidden() stage = get_object_or_404(Stage, pk=pk) # Stage non possédé par l'utilisateur if stage.auteur != request.user.profil: return HttpResponseForbidden() # Mise à jour du statut if "publier" in request.POST: stage.public = True else: stage.public = False stage.save() 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'] send_mail( "[experiENS] "+ objet, message, request.user.email, ['robin.champenois@ens.fr'], 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": return JsonResponse({"success": False, "errors": form.errors}) else: form = FeedbackForm() raise Http404() # # 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())] 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() 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, })