improving reviewers UI

This commit is contained in:
Élie Bouttier 2016-07-06 18:30:15 +02:00
parent eb0cf5010a
commit 3523db76c7
11 changed files with 191 additions and 22 deletions

View File

@ -11,3 +11,8 @@ class OrgaRequiredMixin(UserPassesTestMixin):
class StaffRequiredMixin(UserPassesTestMixin):
def test_func(self):
return is_staff(self.request, self.request.user)
class SuperuserRequiredMixin(UserPassesTestMixin):
def test_func(self):
return self.request.user.is_superuser

@ -1 +1 @@
Subproject commit 35805d82545cdc2aea0acc6f480f866119a8bb52
Subproject commit c74943cba752ff6d40a505321ac58bdacbc41857

View File

@ -23,3 +23,4 @@
- [x] note sur un speaker
- [ ] ouverture conf
- [ ] mail de bienvenu autre que activation si le mec est inscrit par le staff
- [ ] do not notify speakers of modified talk

View File

@ -146,6 +146,7 @@ BOWER_COMPONENTS_ROOT = os.path.join(BASE_DIR, 'components')
BOWER_INSTALLED_APPS = (
'bootstrap',
'jquery',
'jquery-ui',
)
LOGIN_REDIRECT_URL = 'home'

View File

@ -3,12 +3,10 @@ from django.forms.models import modelform_factory
from proposals.models import Talk, Topic
__all__ = ['TalkForm', 'TopicForm', 'TopicOrgaForm']
__all__ = ['TalkForm', 'TopicForm']
TalkForm = modelform_factory(Talk, fields=['title', 'description', 'topics', 'event', 'speakers'],
widgets={'topics': CheckboxSelectMultiple()})
TopicForm = modelform_factory(Topic, fields=['name'])
TopicOrgaForm = modelform_factory(Topic, fields=['name', 'reviewers'])

View File

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-07-06 14:46
from __future__ import unicode_literals
import django.contrib.sites.managers
from django.db import migrations, models
import django.db.models.deletion
import django.db.models.manager
class Migration(migrations.Migration):
dependencies = [
('sites', '0002_alter_domain_unique'),
('proposals', '0003_talk_accepted'),
]
operations = [
migrations.AlterModelManagers(
name='topic',
managers=[
('objects', django.db.models.manager.Manager()),
('on_site', django.contrib.sites.managers.CurrentSiteManager()),
],
),
migrations.AddField(
model_name='topic',
name='site',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='sites.Site'),
preserve_default=False,
),
]

View File

@ -20,16 +20,25 @@ __all__ = ['Topic', 'Talk']
class Topic(PonyConfModel):
site = models.ForeignKey(Site, on_delete=models.CASCADE)
name = models.CharField(max_length=128, verbose_name='Name', unique=True)
slug = AutoSlugField(populate_from='name', unique=True)
reviewers = models.ManyToManyField(Participation, blank=True)
objects = models.Manager()
on_site = CurrentSiteManager()
@property
def talks(self):
return Talk.objects.filter(topics=self).all()
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('list-talks-by-topic', kwargs={'topic': self.slug})
return reverse('show-topic', kwargs={'slug': self.slug})
class Talk(PonyConfModel):

View File

@ -0,0 +1,59 @@
{% extends 'base.html' %}
{% load staticfiles accounts_tags %}
{% block topictab %} class="active"{% endblock %}
{% block css %}
{{ block.super }}
<link rel="stylesheet" href="{% static 'jquery-ui/themes/base/jquery-ui.min.css' %}">
<link rel="stylesheet" href="{% static 'jquery-ui/themes/base/autocomplete.css' %}">
{% endblock %}
{% block content %}
<h1>{{ topic }}</h1>
{% if request|orga %}
<form class="form-inline" method="post" action="{% url 'add-reviewer' topic.slug %}" role="form" id="add-reviewer-form">
{% csrf_token %}
<div class="form-group">
<div class="input-group ui-widget">
<input type="text" class="form-control" name="user" placeholder="add reviewer" value="">
<div class="input-group-addon">
<a href="javascript:void(0);" onclick="$('#add-reviewer-form').submit();"><span class="glyphicon glyphicon-plus"></span></a>
</div>
</div>
</div>
</form>
{% endif %}
<ul>
{% for reviewer in topic.reviewers.all %}
<li>
{{ reviewer }}
{% if request|orga %} - <a href="{% url 'remove-reviewer' topic.slug reviewer.user.username %}">remove</a>{% endif %}
</li>
{% empty %}
<li><i>No reviewers.</i></li>
{% endfor %}
</ul>
{% endblock %}
{% block js_end %}
{{ block.super }}
<script src="{% static 'jquery-ui/ui/minified/core.min.js' %}"></script>
<script src="{% static 'jquery-ui/ui/minified/widget.min.js' %}"></script>
<script src="{% static 'jquery-ui/ui/minified/position.min.js' %}"></script>
<script src="{% static 'jquery-ui/ui/minified/menu.min.js' %}"></script>
<script src="{% static 'jquery-ui/ui/minified/autocomplete.min.js' %}"></script>
<script type="text/javascript">
$('input[name="user"]').autocomplete({
source: "{% url 'add-reviewer' topic.slug %}",
onSelect: function (data) {
$('input[name="user"]').val(data.data);
}
});
</script>
{% endblock %}

View File

@ -11,18 +11,21 @@
<ul>
{% for topic in topic_list %}
<li>
{{ topic.get_link }}
{{ topic }}:
{% if request|staff %}
{% if topic.reviewers.exists %} ({% for reviewer in topic.reviewers.all %} {{ reviewer.get_link }} {% if not forloop.last %}, {% endif %}{% endfor %}) {% endif %}
{% if request|orga %} - <a href="{% url 'edit-topic' topic.slug %}">edit</a>{% endif %}
<a href="{{ topic.get_absolute_url }}">{{ topic.reviewers.count }} reviewer{{ topic.reviewers.count|pluralize }}</a>
and
{% comment %}{% if topic.reviewers.exists %} ({% for reviewer in topic.reviewers.all %} {{ reviewer.get_link }} {% if not forloop.last %}, {% endif %}{% endfor %}) {% endif %}{% endcomment %}
{% endif %}
<a href="{% url 'list-talks-by-topic' topic.slug %}">{{ topic.talks.count }} talk{{ topic.talks.count|pluralize }}</a>.
{% if request.user.is_superuser %} - <a href="{% url 'edit-topic' topic.slug %}">edit</a>{% endif %}
</li>
{% empty %}
<li><i>No topic.</i></li>
<li><i>No topics.</i></li>
{% endfor %}
</ul>
{% if request|staff %}
{% if request|orga %}
<a href="{% url 'add-topic' %}" class="btn btn-success">Add a topic</a>
{% endif %}

View File

@ -12,7 +12,10 @@ urlpatterns = [
url(r'^talk/by-topic/(?P<topic>[-\w]+)$', views.talk_list_by_topic, name='list-talks-by-topic'),
url(r'^topic/$', views.TopicList.as_view(), name='list-topics'),
url(r'^topic/add/$', views.TopicCreate.as_view(), name='add-topic'),
url(r'^topic/edit/(?P<slug>[-\w]+)/$', views.TopicUpdate.as_view(), name='edit-topic'),
url(r'^topic/(?P<slug>[-\w]+)/$', views.TopicDetail.as_view(), name='show-topic'),
url(r'^topic/(?P<slug>[-\w]+)/edit/$', views.TopicUpdate.as_view(), name='edit-topic'),
url(r'^topic/(?P<slug>[-\w]+)/add-reviewer/$', views.topic_add_reviewer, name='add-reviewer'),
url(r'^topic/(?P<slug>[-\w]+)/remove-reviewer/(?P<username>[\w.@+-]+)/$', views.topic_remove_reviewer, name='remove-reviewer'),
url(r'^speakers/$', views.SpeakerList.as_view(), name='list-speakers'),
url(r'^speaker/(?P<username>[\w.@+-]+)$', views.user_details, name='show-speaker'),
]

View File

@ -8,12 +8,14 @@ from django.core.urlresolvers import reverse
from django.db.models import Q
from django.shortcuts import get_object_or_404, redirect, render
from django.views.generic import CreateView, DetailView, ListView, UpdateView
from django.http import JsonResponse
from django.core.exceptions import ObjectDoesNotExist
from accounts.mixins import OrgaRequiredMixin, StaffRequiredMixin
from accounts.mixins import OrgaRequiredMixin, StaffRequiredMixin, SuperuserRequiredMixin
from accounts.models import Participation
from accounts.utils import is_orga
from .forms import TalkForm, TopicForm, TopicOrgaForm
from .forms import TalkForm, TopicForm
from .models import Talk, Topic, Vote
from .utils import allowed_talks
from .signals import *
@ -81,25 +83,81 @@ class TalkDetail(LoginRequiredMixin, DetailView):
return super().get_context_data(**ctx)
class TopicList(LoginRequiredMixin, ListView):
model = Topic
class TopicMixin(object):
model = Topic
def get_form_class(self):
return TopicOrgaForm if is_orga(self.request, self.request.user) else TopicForm
queryset = Topic.on_site.all()
form_class = TopicForm
class TopicCreate(StaffRequiredMixin, TopicMixin, CreateView):
class TopicList(LoginRequiredMixin, TopicMixin, ListView):
pass
class TopicUpdate(OrgaRequiredMixin, TopicMixin, UpdateView):
class TopicCreate(OrgaRequiredMixin, TopicMixin, CreateView):
def form_valid(self, form):
form.instance.site = get_current_site(self.request)
return super(TopicCreate, self).form_valid(form)
class TopicUpdate(SuperuserRequiredMixin, TopicMixin, UpdateView):
pass
class TopicDetail(StaffRequiredMixin, TopicMixin, DetailView):
pass
@login_required
def topic_add_reviewer(request, slug):
if not Participation.objects.get(user=request.user).is_orga():
raise PermissionDenied()
topic = get_object_or_404(Topic, slug=slug)
if request.method == 'POST':
user = request.POST.get('user')
try:
user = User.objects.get(username=user)
except ObjectDoesNotExist:
messages.error(request, 'User not found.')
else:
participation, created = Participation.on_site.get_or_create(user=user, site=get_current_site(request))
if participation in topic.reviewers.all():
messages.info(request, 'User is already a reviewer of this topic.')
else:
topic.reviewers.add(participation)
topic.save()
messages.success(request, 'User add to reviewer of this topic successfully.')
return redirect(topic.get_absolute_url())
else:
term = request.GET.get('term')
if not term:
raise Http404()
query = Q(username__icontains=term) \
| Q(first_name__icontains=term) \
| Q(last_name__icontains=term)
users = User.objects \
.exclude(id__in=topic.reviewers.values('user__id')) \
.filter(query)[:10]
response = []
for user in users:
response += [{
'label': str(user.profile),
'value': user.username,
}]
return JsonResponse(response, safe=False)
@login_required
def topic_remove_reviewer(request, slug, username):
if not Participation.objects.get(user=request.user).is_orga():
raise PermissionDenied()
topic = get_object_or_404(Topic, slug=slug)
participation = get_object_or_404(Participation, user__username=username)
topic.reviewers.remove(participation)
return redirect(topic.get_absolute_url())
class SpeakerList(StaffRequiredMixin, ListView):
queryset = Participation.on_site.filter(user__talk__in=Talk.on_site.all()).distinct()
template_name = 'proposals/speaker_list.html'