# 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.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 from django.core.mail import send_mail from django.db.models import Q, Count from collections import Counter, defaultdict 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}) # Espace personnel @login_required def perso(request): return render(request, 'avisstage/perso.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): 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' # Restriction aux stages publics ou personnels def get_queryset(self): filtre = Q(auteur__user_id=self.request.user.id) | Q(public=True) return Stage.objects.filter(filtre) # 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') # # EDITION # # Profil #login_required class ProfilEdit(LoginRequiredMixin, UpdateView): model = Normalien 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') # 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}) # 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'] message = form.cleaned_data['message'] send_mail( "[experiENS] "+ objet, message, request.user.username + "@clipper.ens.fr", ['champeno@clipper.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() 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())] 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, })