From 3523db76c734f8e539814d2d48bd1c69badd3803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89lie=20Bouttier?= Date: Wed, 6 Jul 2016 18:30:15 +0200 Subject: [PATCH] improving reviewers UI --- accounts/mixins.py | 5 ++ components | 2 +- doc/2016-06-15.md | 1 + ponyconf/settings.py | 1 + proposals/forms.py | 4 +- .../migrations/0004_auto_20160706_1446.py | 32 ++++++++ proposals/models.py | 11 ++- .../templates/proposals/topic_detail.html | 59 ++++++++++++++ proposals/templates/proposals/topic_list.html | 13 +-- proposals/urls.py | 5 +- proposals/views.py | 80 ++++++++++++++++--- 11 files changed, 191 insertions(+), 22 deletions(-) create mode 100644 proposals/migrations/0004_auto_20160706_1446.py create mode 100644 proposals/templates/proposals/topic_detail.html diff --git a/accounts/mixins.py b/accounts/mixins.py index 89bf1aa..d1a5689 100644 --- a/accounts/mixins.py +++ b/accounts/mixins.py @@ -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 diff --git a/components b/components index 35805d8..c74943c 160000 --- a/components +++ b/components @@ -1 +1 @@ -Subproject commit 35805d82545cdc2aea0acc6f480f866119a8bb52 +Subproject commit c74943cba752ff6d40a505321ac58bdacbc41857 diff --git a/doc/2016-06-15.md b/doc/2016-06-15.md index 53ac0bf..d7f7779 100644 --- a/doc/2016-06-15.md +++ b/doc/2016-06-15.md @@ -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 diff --git a/ponyconf/settings.py b/ponyconf/settings.py index 4a6e2f0..a380a94 100644 --- a/ponyconf/settings.py +++ b/ponyconf/settings.py @@ -146,6 +146,7 @@ BOWER_COMPONENTS_ROOT = os.path.join(BASE_DIR, 'components') BOWER_INSTALLED_APPS = ( 'bootstrap', 'jquery', + 'jquery-ui', ) LOGIN_REDIRECT_URL = 'home' diff --git a/proposals/forms.py b/proposals/forms.py index d42588f..dfeebf5 100644 --- a/proposals/forms.py +++ b/proposals/forms.py @@ -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']) diff --git a/proposals/migrations/0004_auto_20160706_1446.py b/proposals/migrations/0004_auto_20160706_1446.py new file mode 100644 index 0000000..0ac5c29 --- /dev/null +++ b/proposals/migrations/0004_auto_20160706_1446.py @@ -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, + ), + ] diff --git a/proposals/models.py b/proposals/models.py index 160ef5d..5d38801 100644 --- a/proposals/models.py +++ b/proposals/models.py @@ -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): diff --git a/proposals/templates/proposals/topic_detail.html b/proposals/templates/proposals/topic_detail.html new file mode 100644 index 0000000..54be0ad --- /dev/null +++ b/proposals/templates/proposals/topic_detail.html @@ -0,0 +1,59 @@ +{% extends 'base.html' %} + +{% load staticfiles accounts_tags %} + +{% block topictab %} class="active"{% endblock %} + +{% block css %} +{{ block.super }} + + +{% endblock %} + +{% block content %} + +

{{ topic }}

+ +{% if request|orga %} +
+ {% csrf_token %} +
+
+ +
+ +
+
+
+
+{% endif %} + + + +{% endblock %} + +{% block js_end %} +{{ block.super }} + + + + + + +{% endblock %} diff --git a/proposals/templates/proposals/topic_list.html b/proposals/templates/proposals/topic_list.html index a1494d1..96d1da7 100644 --- a/proposals/templates/proposals/topic_list.html +++ b/proposals/templates/proposals/topic_list.html @@ -11,18 +11,21 @@ -{% if request|staff %} +{% if request|orga %} Add a topic {% endif %} diff --git a/proposals/urls.py b/proposals/urls.py index 7b46030..dedae1e 100644 --- a/proposals/urls.py +++ b/proposals/urls.py @@ -12,7 +12,10 @@ urlpatterns = [ url(r'^talk/by-topic/(?P[-\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[-\w]+)/$', views.TopicUpdate.as_view(), name='edit-topic'), + url(r'^topic/(?P[-\w]+)/$', views.TopicDetail.as_view(), name='show-topic'), + url(r'^topic/(?P[-\w]+)/edit/$', views.TopicUpdate.as_view(), name='edit-topic'), + url(r'^topic/(?P[-\w]+)/add-reviewer/$', views.topic_add_reviewer, name='add-reviewer'), + url(r'^topic/(?P[-\w]+)/remove-reviewer/(?P[\w.@+-]+)/$', views.topic_remove_reviewer, name='remove-reviewer'), url(r'^speakers/$', views.SpeakerList.as_view(), name='list-speakers'), url(r'^speaker/(?P[\w.@+-]+)$', views.user_details, name='show-speaker'), ] diff --git a/proposals/views.py b/proposals/views.py index 171c897..5a2dddb 100644 --- a/proposals/views.py +++ b/proposals/views.py @@ -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'