Feedback et autres trucs

This commit is contained in:
Evarin 2017-04-20 23:04:07 +02:00
parent 9374a5f158
commit be57b976d2
18 changed files with 243 additions and 55 deletions

View file

@ -1,21 +1,27 @@
# coding: utf-8
from tastypie.resources import ModelResource
from avisstage.models import Lieu
from avisstage.models import Lieu, Stage, Normalien, StageMatiere
from tastypie.authentication import SessionAuthentication
from django.contrib.gis import geos
from tastypie import fields, utils
class LieuResource(ModelResource):
stages = fields.ToManyField("avisstage.api.StageResource", "stages", use_in="detail", full=True)
class Meta:
queryset = Lieu.objects.all()
resource_name = "lieu"
fields = ["nom", "pays", "coord", "type_lieu", "id"]
authentication = SessionAuthentication()
def build_filters(self, filters=None, **kwargs):
if filters is None:
filters = {}
orm_filters = super(LieuResource, self).build_filters()
orm_filters = super(LieuResource, self).build_filters(filters, **kwargs)
if "lng" in filters and "lat" in filters:
lat = float(filters['lat'])
@ -40,5 +46,43 @@ class LieuResource(ModelResource):
bundle.data['distance'] = self.reference_point.distance(bundle.obj.coord)
bundle.data["pays_nom"] = obj.get_pays_display()
bundle.data["type_lieu_nom"] = obj.get_type_lieu_display()
bundle.data["num_stages"] = obj.stages.filter(public=True).count()
return bundle
class StageResource(ModelResource):
class Meta:
queryset = Stage.objects.filter(public=True)
resource_name = "stage"
fields = ["sujet", "date_debut", "date_fin", "matieres", "id"]
authentication = SessionAuthentication()
def build_filters(self, filters=None, **kwargs):
if filters is None:
filters = {}
orm_filters = super(StageResource, self).build_filters(filters, **kwargs)
if "lieux" in filters:
flieux = map(int, filters['lieux'].split(','))
orm_filters['lieux__id__in'] = flieux
return orm_filters
def dehydrate(self, bundle):
bundle = super(StageResource, self).dehydrate(bundle)
obj = bundle.obj
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))
return bundle
class AuteurResource(ModelResource):
stages = fields.ToManyField("avisstage.api.StageResource", "stages", use_in="detail")
class Meta:
queryset = Normalien.objects.all()
resource_name = "profil"
fields = ["id", "nom", "stages"]
authentication = SessionAuthentication()

View file

@ -82,3 +82,7 @@ class LieuForm(forms.ModelForm):
model = Lieu
fields = ['nom', 'type_lieu', 'ville', 'pays', 'coord']
class FeedbackForm(forms.Form):
objet = forms.CharField(label="Objet", required=True)
message = forms.CharField(label="Message", required=True, widget=forms.widgets.Textarea())

View file

@ -5,8 +5,8 @@
$fond: #8fcc33;
$barre: darken($fond, 10%);
$compl: #f99b20;
$jaune: #007afc;
$vert: #09f7b0;
$jaune: #fff60a;
$vert: #1a82dd;//09f7b0;
$rouge: #f70978;
$textfont: 'Dosis', sans-serif;
@ -584,6 +584,9 @@ div.as-results {
.hidden {
display: none;
}
.masked {
visibility: hidden;
}
}
#avis_lieu_vide {
@ -612,3 +615,15 @@ a.lieu-change {
width:100%;
height: 600px;
}
#feedback-button {
position:fixed;
left:0;
top:30%;
color:#fff;
z-index:4;
background: #000;
padding: 14px;
transform: rotateZ(90deg);
transform-origin: bottom left;
}

View file

@ -217,7 +217,7 @@ header h1 {
/* line 159, ../../sass/screen.scss */
.stagelist li.stage-publie:before {
content: "Publié";
background: #3af9c0;
background: #419be9;
}
/* line 163, ../../sass/screen.scss */
.stagelist li.stage-ajout:before {
@ -231,7 +231,7 @@ article.stage {
}
/* line 175, ../../sass/screen.scss */
article.stage h2 {
background: #6ea3db;
background: #ddda78;
color: #fff;
padding: 10px 20px;
margin: -20px;
@ -353,13 +353,13 @@ article.stage section .plusmoins > div > div p {
}
/* line 289, ../../sass/screen.scss */
article.stage section .plusmoins .plus > div {
background: #07df9f;
background: #1775c6;
}
/* line 292, ../../sass/screen.scss */
article.stage section .plusmoins .plus:before {
content: "Les +";
vertical-align: bottom;
color: #06c78d;
color: #1567af;
}
/* line 299, ../../sass/screen.scss */
article.stage section .plusmoins .moins > div {
@ -381,8 +381,8 @@ article.stage section .plusmoins .moins:before {
}
/* line 318, ../../sass/screen.scss */
.edit-box.public {
background: #cffdef;
border: 1px solid #06ad7b;
background: #cae3f9;
border: 1px solid #125b9b;
}
/* line 323, ../../sass/screen.scss */
.edit-box.prive {
@ -657,13 +657,17 @@ div.as-results ul li.as-message {
.lieu-ui .hidden {
display: none;
}
/* line 587, ../../sass/screen.scss */
.lieu-ui .masked {
visibility: hidden;
}
/* line 589, ../../sass/screen.scss */
/* line 592, ../../sass/screen.scss */
#avis_lieu_vide {
display: none;
}
/* line 593, ../../sass/screen.scss */
/* line 596, ../../sass/screen.scss */
a.lieu-change {
color: #fff;
background: #f99b20;
@ -676,14 +680,27 @@ a.lieu-change {
border-radius: 5px;
margin-right: 7px;
}
/* line 605, ../../sass/screen.scss */
/* line 608, ../../sass/screen.scss */
a.lieu-change.ajout:before {
content: "+";
margin-right: 5px;
}
/* line 611, ../../sass/screen.scss */
/* line 614, ../../sass/screen.scss */
#stages-map {
width: 100%;
height: 600px;
}
/* line 619, ../../sass/screen.scss */
#feedback-button {
position: fixed;
left: 0;
top: 30%;
color: #fff;
z-index: 4;
background: #000;
padding: 14px;
transform: rotateZ(90deg);
transform-origin: bottom left;
}

View file

@ -1,5 +1,5 @@
function SelectLieuWidget(STATIC_ROOT, API_LIEU, target, callback) {
var map, input, autocomplete;
var map, input, autocomplete, map_el;
var $el = $(target);
var ui_el = $el.find('.lieu-ui');
var form_el = $el.find('form');
@ -18,7 +18,7 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, target, callback) {
closer.on("click", closeWidget).attr("href", "javascript:void(0);");
$el.find(".window-bg").on("click", closeWidget);
var map_el = $("<div>", {class: "map"});
map_el = $("<div>", {class: "map"});
input = $("<input>",
{type:"text",
placeholder:"Chercher un établissement..."});
@ -37,12 +37,21 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, target, callback) {
autocomplete.addListener('place_changed', handlePlaceSearch);
}
function showMessage(message) {
content_el.find(".message").text(message);
}
function hideMessage() {
showMessage("");
}
function setLieuOrigine (lieu) {
map.panTo(lieu.coord);
lieuSurCarte(lieu, greenIcon);
}
function handleLieuOrigine(data) {
hideMessage();
lieux_db[data.id] = data;
setLieuOrigine(data);
askForSuggestions(data.coord);
@ -52,16 +61,19 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, target, callback) {
$el.addClass("visible").removeClass("ajout");
if(!ui_ready)
initUI();
hideMessage();
ui_el.removeClass("hidden");
form_el.detach();
if (lieu_id === undefined) {
$el.find("h3").text("Ajouter un lieu");
map_el.addClass("masked");
} else {
lieu_id = lieu_id * 1;
$el.find("h3").text("Modifier le lieu");
if(lieux_db[lieu_id] === undefined) {
$.getJSON(API_LIEU + lieu_id + "/?format=json",
handleLieuOrigine);
showMessage("Chargement...");
ui_el.addClass("hidden");
} else {
handleLieuOrigine(lieux_db[lieu_id]);
@ -150,6 +162,7 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, target, callback) {
}
function lieuSurCarte(data, icone) {
map_el.removeClass("masked");
// data : des données sur un lieu, sérialisé comme par tastypie
if(data.marqueur !== undefined) {
if(map.hasLayer(data.marqueur))
@ -219,15 +232,16 @@ function SelectLieuWidget(STATIC_ROOT, API_LIEU, target, callback) {
form_el.serialize(),
onLieuCreated);
form_el.detach();
content_el.append($("<p>").text("Envoi en cours..."));
showMessage("Envoi en cours...");
return false;
}
function onLieuCreated(data) {
console.log(data);
if(data.success = false)
if(data.success = false) {
content_el.find(".message").text("Erreur : "+data.errors);
content_el.append(form_el);
else {
} else {
lieux_db.suggestion.id = data.id;
callback(lieux_db.suggestion);
}

View file

@ -1,4 +1,4 @@
{% load staticfiles %}
{% load staticfiles avisstage_tags %}
<!doctype html>
<html>
<head>
@ -24,14 +24,14 @@
<nav>
<ul id="menu">
<li><a href="{% url 'avisstage:index' %}">Accueil</a></li>
{% if user.username %}
{% if user.is_authenticated %}
<li><a href="{% url 'avisstage:perso' %}">Mes stages</a></li>
<li><a href="{% url 'avisstage:recherche' %}">Recherche</a></li>
{% endif %}
{% if user.is_staff %}
<li><a href="{% url 'admin:index' %}">Administration</a></li>
{% endif %}
{% if user.username %}
{% if user.is_authenticated %}
<li><a href="{% url 'logout' %}"><span class="username">{{ user.username }}</span><br/> Déconnexion</a></li>
{% else %}
<li><a href="{% url 'login' %}">Connexion</a></li>
@ -40,27 +40,9 @@
</nav>
</header>
{# BLOCK FEEDBACK #}
{% if request.user.is_authenticated %}
<div id="feedback_win" class="win_bg">
<div class="win_centrer">
<div class="win_content">
<h2>Envoyer un avis sur le site<a class="win_close" href="javascript:showFeedback(false)">X</a></h2>
<form method="POST" action="{% url 'avisstage:feedback' %}?next={{ request.path|urlencode }}">
{% csrf_token %}
<p>Connecté en tant que {{ user.profil }}</p>
<p><label for="id_feedback-message">Commentaire :</label><textarea name="feedback-message" id="id_feedback-message"></textarea></p>
<input type="submit" />
</form>
</div>
</div>
</div>
<a id="feedback_btn" href="javascript:showFeedback(true)">
Feedback
</a>
{% if user.is_authenticated %}
{% feedback_widget %}
{% endif %}
{# ENDBLOCK FEEDBACK #}
<section class="content">
{% if messages %}

View file

@ -1,6 +1,8 @@
{% extends "avisstage/base.html" %}
{% load staticfiles %}
{% block title %}Profil de {{ object.nom }} - ExperiENS{% endblock %}
{% block content %}
<h1>Profil de {{ object.nom }}</h1>
{% if object.user == user %}

View file

@ -1,6 +1,8 @@
{% extends "avisstage/base.html" %}
{% load staticfiles %}
{% block title %}{{ object.sujet }} - ExperiENS{% endblock %}
{% block extra_head %}
<script src="{% static 'js/toc.min.js' %}" type="text/javascript"></script>
<script type="text/javascript" src="{% static "js/leaflet.js" %}"></script>

View file

@ -12,8 +12,11 @@
<script type="text/javascript" src="{% static "js/select_lieu.js" %}"></script>
<script type="text/javascript">
$(function() {
// DATE FIELDS
$(".datepicker").datepicker({ dateFormat: 'dd/mm/yy' });
// Process rich text fields
// RICH TEXT FIELDS
var txtr = $("textarea.tinymce");
$.each(txtr, function(i, item) {
var newitem = $("<div>", {"class":"tinymce"}).html(item.value)
@ -30,7 +33,7 @@
language: "fr_FR",
});
// Process select multiple fields
// SELECT MULTIPLE FIELDS
var slts = $("select[multiple]");
var NULL_VAL = " ";
$.each(slts, function(i, item) {
@ -65,7 +68,7 @@
$item.remove();
});
// Widget du choix du lieu
// CHOIX DU LIEU
var lieu_select = new SelectLieuWidget(
"{{ STATIC_URL|escapejs }}",
"{% url 'avisstage:api_dispatch_list' resource_name="lieu" api_name="v1" %}",
@ -110,7 +113,7 @@
cnt.val(i_form+1);
$("#avis_lieu_container").append(dest_form);
} else {
// Changer le lieu
// Changer un lieu existant
dest_form = $("#avis-lieux-"+lieu_focus);
dest_btn = lieux_liste.find("#change-lieux-"+lieu_focus);
}
@ -121,9 +124,9 @@
.val(lieu.id);
lieu_select.closeWidget();
}
// TODO gérer le cas de l'actualisation du formulaire où le lieu affiché n'est plus le vrai lieu
// À l'envoi du formulaire
// CLEANUP ON SENDING
$("#stageform").submit(function() {
$.each(txtr, function(i, item) {
item.value = tinyMCE.get(item.fakeinput.attr("id")).getContent();

View file

@ -1,6 +1,8 @@
{% extends "avisstage/base.html" %}
{% load staticfiles %}
{% block title %}ExperiENS - Partagez vos expériences de stage !{% endblock %}
{% block content %}
<div class="homeh1">
<h1>ExperiENS <span class="beta">beta</span></h1>

View file

@ -1,6 +1,8 @@
{% extends "avisstage/base.html" %}
{% load staticfiles %}
{% block title %}Espace personnel - ExperiENS{% endblock %}
{% block content %}
<h1>Mon espace personnel</h1>
<p><a href="{% url "avisstage:profil" user %}">{{ user.profil.nom }}</a> <a href="{% url "avisstage:profil_edit" %}">Modifier mes infos</a></p>

View file

@ -1,6 +1,9 @@
{% extends "avisstage/base.html" %}
{% load staticfiles %}
{% block title %}Chercher un stage - ExperiENS{% endblock %}
{% block extra_head %}
<script type="text/javascript" src="{% static "js/leaflet.js" %}"></script>
<script type="text/javascript" src="{% static "js/leaflet.markercluster.js" %}"></script>
@ -34,7 +37,9 @@
var lieux = data.objects;
$.each(lieux, function(i, item) {
var marqueur = L.marker(item.coord, {icon: greenIcon});
marqueur.bindPopup("<h3>"+item.nom+"</h3>");
var txt = item.num_stages > 1 ? item.num_stages+" stages ici": "1 stage ici";
marqueur.bindPopup("<h3>"+item.nom+"</h3>"+
"<p>"+txt+"</p>");
marqueurs.addLayer(marqueur);
});
map.addLayer(marqueurs);

View file

@ -0,0 +1,60 @@
{% load staticfiles %}
<div id="feedback_widget" class="window">
<script type="text/javascript">
$(function(){
function showFeedback() {
$("#feedback_widget").addClass("visible");
}
function hideFeedback() {
$("#feedback_widget").removeClass("visible");
}
function onFeedbackSent(data) {
if(data.success)
widget.find(".message").text("Message envoyé");
else {
widget.find(".message").text("Erreur : "+data);
}
}
$("#feedback-button").on("click", showFeedback);
var widget = $("#feedback_widget");
widget.find(".window-closer").on("click", hideFeedback);
widget.find(".window-bg").on("click", hideFeedback);
var form = widget.find("form");
form.on("submit", function(){
$.post(form.attr("action")+"?format=json", form.serialize(), onFeedbackSent);
form.detach();
widget.find(".message").text("Envoi en cours");
return false;
});
});
</script>
<div class="window-bg"></div>
<div class="window-content">
<a class="window-closer" href="javascript:void(0);"></a>
<h2>Donnez votre avis !</h2>
<div class="feedback-form">
<div class="message"></div>
<form action="{% url 'avisstage:feedback' %}" method="post" id="feedback">
{% csrf_token %}
{% for field in form.hidden_fields %}
{{ field }}
{% endfor %}
{% for field in form.visible_fields %}
{{ 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" />
</form>
</div>
</div>
</div>
<a id="feedback-button" href="javascript:void(0);">Feedback</a>

View file

@ -5,6 +5,7 @@
<div class="window-content">
<a class="window-closer"></a>
<h2>Choisir un lieu</h2>
<div class="message"></div>
<div class="lieu-ui">
</div>
<div class="lieu-form">{% load staticfiles %}

View file

@ -1,6 +1,6 @@
from django import template
from avisstage.forms import LieuForm
from avisstage.forms import LieuForm, FeedbackForm
register = template.Library()
@ -8,3 +8,8 @@ register = template.Library()
def lieu_widget():
form = LieuForm()
return {"form": form}
@register.inclusion_tag('avisstage/templatetags/widget_feedback.html')
def feedback_widget():
form = FeedbackForm()
return {"form": form}

View file

@ -4,6 +4,8 @@ from tastypie.api import Api
v1_api = Api(api_name='v1')
v1_api.register(api.LieuResource())
v1_api.register(api.StageResource())
v1_api.register(api.AuteurResource())
urlpatterns = [
url(r'^$', views.index, name='index'),

View file

@ -9,9 +9,10 @@ from django.urls import reverse
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 avisstage.models import Normalien, Stage, Lieu, AvisLieu, AvisStage
from avisstage.forms import StageForm, LieuForm, AvisStageForm, AvisLieuForm
from avisstage.forms import StageForm, LieuForm, AvisStageForm, AvisLieuForm, FeedbackForm
# Page d'accueil
def index(request):
@ -124,8 +125,33 @@ def publier_stage(request, pk):
stage.save()
return redirect(reverse("avisstage:stage", kwargs={"pk": pk}))
@login_required
def recherche(request):
return render(request, 'avisstage/recherche.html')
# FEEDBACK
@login_required
def feedback(request):
return render(request, 'avisstage/feedback.html')
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})

View file

@ -22,3 +22,5 @@ MIDDLEWARE_CLASSES += (
SPATIALITE_LIBRARY_PATH = 'mod_spatialite'
STATIC_ROOT = "/home/evarin/Bureau/experiENS/static/"
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'