Various Stuffs

- unified orga forms
- unified staff / orga | util / mixin / filter
- moved a signal into its signals.py
- profile edition now edits User / Profile / Participation
- topic can be edited
- orga can set topics reviewers
- removed view list-talks-by-speaker (his talks appears on his page)
- everybody can see topics (template show more stuff the more permissions you have)
This commit is contained in:
Guilhem Saurel 2016-07-05 01:16:33 +02:00
parent ed12a0a214
commit d9b3846cf1
18 changed files with 97 additions and 52 deletions

View File

@ -3,13 +3,16 @@ from django.forms.models import modelform_factory
from .models import Participation, Profile from .models import Participation, Profile
__all__ = ['UserForm', 'ProfileForm', 'ProfileOrgaForm'] __all__ = ['UserForm', 'ProfileForm', 'ProfileOrgaForm', 'ParticipationOrgaForm']
UserForm = modelform_factory(User, fields=['first_name', 'last_name', 'email', 'username']) UserForm = modelform_factory(User, fields=['first_name', 'last_name', 'email', 'username'])
ParticipationForm = modelform_factory(Participation, fields=['transport', 'connector', 'sound', 'constraints'])
ProfileForm = modelform_factory(Profile, fields=['biography']) ProfileForm = modelform_factory(Profile, fields=['biography'])
ParticipationForm = modelform_factory(Participation, fields=['transport', 'connector', 'sound', 'constraints'])
ProfileOrgaForm = modelform_factory(Profile, fields=['biography', 'notes']) ProfileOrgaForm = modelform_factory(Profile, fields=['biography', 'notes'])
ParticipationOrgaForm = modelform_factory(Participation,
fields=['transport', 'connector', 'sound', 'constraints', 'orga'])

View File

@ -1,6 +1,11 @@
from django.contrib.auth.mixins import UserPassesTestMixin from django.contrib.auth.mixins import UserPassesTestMixin
from .utils import is_staff from .utils import is_orga, is_staff
class OrgaRequiredMixin(UserPassesTestMixin):
def test_func(self):
return is_orga(self.request, self.request.user)
class StaffRequiredMixin(UserPassesTestMixin): class StaffRequiredMixin(UserPassesTestMixin):

View File

@ -59,10 +59,3 @@ class Participation(PonyConfModel):
def is_staff(self): def is_staff(self):
return self.user.is_superuser or self.orga or self.topic_set.exists() return self.user.is_superuser or self.orga or self.topic_set.exists()
def create_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
models.signals.post_save.connect(create_profile, sender=User, weak=False, dispatch_uid='create_profile')

View File

@ -1,9 +1,11 @@
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.models import User
from django.contrib.auth.signals import user_logged_in, user_logged_out from django.contrib.auth.signals import user_logged_in, user_logged_out
from django.contrib.sites.shortcuts import get_current_site from django.contrib.sites.shortcuts import get_current_site
from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
from .models import Participation from .models import Participation, Profile
@receiver(user_logged_in) @receiver(user_logged_in)
@ -17,3 +19,10 @@ def on_user_logged_in(sender, request, user, **kwargs):
@receiver(user_logged_out) @receiver(user_logged_out)
def on_user_logged_out(sender, request, **kwargs): def on_user_logged_out(sender, request, **kwargs):
messages.success(request, 'Goodbye!', fail_silently=True) # FIXME messages.success(request, 'Goodbye!', fail_silently=True) # FIXME
def create_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
post_save.connect(create_profile, sender=User, weak=False, dispatch_uid='create_profile')

View File

@ -1,10 +1,15 @@
from django import template from django import template
from accounts.utils import can_edit_profile, is_staff from accounts.utils import can_edit_profile, is_orga, is_staff
register = template.Library() register = template.Library()
@register.filter
def orga(request):
return is_orga(request, request.user)
@register.filter @register.filter
def staff(request): def staff(request):
return is_staff(request, request.user) return is_staff(request, request.user)

View File

@ -58,5 +58,8 @@ class AccountTests(TestCase):
{'biography': 'foo', 'nootes': 'bar'}).status_code, 200) {'biography': 'foo', 'nootes': 'bar'}).status_code, 200)
self.assertEqual(User.objects.get(username='a').profile.biography, '') self.assertEqual(User.objects.get(username='a').profile.biography, '')
self.assertEqual(self.client.post(reverse('edit-participant', kwargs={'username': 'a'}), self.assertEqual(self.client.post(reverse('edit-participant', kwargs={'username': 'a'}),
{'biography': 'foo', 'notes': 'bar'}).status_code, 200) {'biography': 'foo', 'notes': 'bar', 'first_name': 'Jules', 'username': 'a',
'last_name': 'César', 'email': 'a@example.org', 'transport': 1,
'connector': 1, 'constraints': 'nope', 'orga': 0,
}).status_code, 200)
self.assertEqual(User.objects.get(username='a').profile.biography, 'foo') self.assertEqual(User.objects.get(username='a').profile.biography, 'foo')

View File

@ -6,6 +6,10 @@ def generate_user_uid():
return get_random_string(length=12, allowed_chars='abcdefghijklmnopqrstuvwxyz0123456789') return get_random_string(length=12, allowed_chars='abcdefghijklmnopqrstuvwxyz0123456789')
def is_orga(request, user):
return user.is_authenticated() and user.participation_set.get(site=get_current_site(request)).orga
def is_staff(request, user): def is_staff(request, user):
return user.is_authenticated() and user.participation_set.get(site=get_current_site(request)).is_staff() return user.is_authenticated() and user.participation_set.get(site=get_current_site(request)).is_staff()

View File

@ -6,10 +6,10 @@ from django.views.generic import ListView
from registration.backends.default.views import RegistrationView from registration.backends.default.views import RegistrationView
from .forms import ParticipationForm, ProfileForm, ProfileOrgaForm, UserForm from .forms import ParticipationForm, ParticipationOrgaForm, ProfileForm, ProfileOrgaForm, UserForm
from .mixins import StaffRequiredMixin from .mixins import StaffRequiredMixin
from .models import Participation, Profile from .models import Participation, Profile
from .utils import can_edit_profile from .utils import can_edit_profile, is_orga
RESET_PASSWORD_BUTTON = ('password_reset', 'warning', 'Reset your password') RESET_PASSWORD_BUTTON = ('password_reset', 'warning', 'Reset your password')
CHANGE_PASSWORD_BUTTON = ('password_change', 'warning', 'Change password') CHANGE_PASSWORD_BUTTON = ('password_change', 'warning', 'Change password')
@ -49,13 +49,17 @@ def edit(request, username):
if not can_edit_profile(request, profile): if not can_edit_profile(request, profile):
raise PermissionDenied() raise PermissionDenied()
form = ProfileOrgaForm(request.POST or None, instance=profile) participation_form_class = ParticipationOrgaForm if is_orga(request, request.user) else ParticipationForm
forms = [UserForm(request.POST or None, instance=profile.user),
ProfileOrgaForm(request.POST or None, instance=profile),
participation_form_class(request.POST or None, instance=Participation.on_site.get(user=profile.user))]
if request.method == 'POST': if request.method == 'POST':
if form.is_valid(): if all(form.is_valid() for form in forms):
form.save() for form in forms:
form.save()
messages.success(request, 'Profile updated successfully.') messages.success(request, 'Profile updated successfully.')
else: else:
messages.error(request, 'Please correct those errors.') messages.error(request, 'Please correct those errors.')
return render(request, 'accounts/edit_profile.html', {'form': form, 'profile': profile}) return render(request, 'accounts/edit_profile.html', {'forms': forms, 'profile': profile})

View File

@ -1,8 +1,8 @@
- [ ] virer lusername - [ ] virer lusername
- [ ] premier login -> redirection profile - [x] premier login -> redirection profile
- [x] temps prévu (20 min / 40 min) - [x] temps prévu (20 min / 40 min)
- [x] proposer des talks pour les autres - [x] proposer des talks pour les autres
- [ ] gestion staff - [x] gestion staff
- [x] autre couleur change password - [x] autre couleur change password
- [x] 3ième formulaire profile pour les infos de participation - [x] 3ième formulaire profile pour les infos de participation
- [x] changer le widget des topics pour des cases à cocher - [x] changer le widget des topics pour des cases à cocher
@ -15,7 +15,7 @@
- [ ] notif modification conf … - [ ] notif modification conf …
- [x] modif de conf : pas de modifs des topics - [x] modif de conf : pas de modifs des topics
- [x] rajouter les created sur un peu tous les modèles - [x] rajouter les created sur un peu tous les modèles
- [ ] UI reviewers (pour le staff pour nommer des reviewers) - [x] UI reviewers (pour le staff pour nommer des reviewers)
- [x] topic : permettre au staff den rajouter - [x] topic : permettre au staff den rajouter
- [x] topic : qui est reviewer - [x] topic : qui est reviewer
- [x] visibilité des talks - [x] visibilité des talks

View File

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

View File

@ -7,8 +7,8 @@
<h1>Speakers:</h1> <h1>Speakers:</h1>
<ul> <ul>
{% for speaker in user_list %} {% for speaker in participation_list %}
<li><a href="{% url 'show-speaker' username=speaker.username %}">{{ speaker }}</a></li> <li><a href="{% url 'show-speaker' username=speaker.user.username %}">{{ speaker }}</a></li>
{% empty %} {% empty %}
<li><i>No speakers.</i></li> <li><i>No speakers.</i></li>
{% endfor %} {% endfor %}

View File

@ -6,7 +6,7 @@
{% block content %} {% block content %}
<h1>Propose a Topic</h1> <h1>Topic</h1>
{% include "_form.html" %} {% include "_form.html" %}

View File

@ -10,8 +10,13 @@
<ul> <ul>
{% for topic in topic_list %} {% for topic in topic_list %}
<li>{{ topic.get_link }}{% if request|staff %} ({% for reviewer in topic.reviewers.all %} <li>
{{ reviewer.get_link }}{% if not forloop.last %}, {% endif %}{% endfor %} ){% endif %}</li> {{ topic.get_link }}
{% if request|staff %} ({% for reviewer in topic.reviewers.all %} {{ reviewer.get_link }}
{% if not forloop.last %}, {% endif %}{% endfor %} )
{% if request|orga %} - <a href="{% url 'edit-topic' topic.slug %}">edit</a>{% endif %}
{% endif %}
</li>
{% empty %} {% empty %}
<li><i>No topic.</i></li> <li><i>No topic.</i></li>
{% endfor %} {% endfor %}

View File

@ -11,7 +11,7 @@
<p>{{ profile.biography }}</p> <p>{{ profile.biography }}</p>
<h2>Talks</h2> <h2>Talks</h2>
<a href="{% url 'list-talks-by-speaker' profile.user.username %}">See the list of talks with this speaker</a> {% include "proposals/_talk_list.html" %}
{% if request|edit_profile:profile %} {% if request|edit_profile:profile %}
<h2>Notes</h2> <h2>Notes</h2>

View File

@ -1,5 +1,4 @@
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.sites.models import Site
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.test import TestCase from django.test import TestCase
@ -11,7 +10,6 @@ from .models import Talk, Topic, Vote
class ProposalsTests(TestCase): class ProposalsTests(TestCase):
def setUp(self): def setUp(self):
a, b, c = (User.objects.create_user(guy, email='%s@example.org' % guy, password=guy) for guy in 'abc') a, b, c = (User.objects.create_user(guy, email='%s@example.org' % guy, password=guy) for guy in 'abc')
Participation.objects.create(user=a, site=Site.objects.first())
Topic.objects.create(name='topipo') Topic.objects.create(name='topipo')
c.is_superuser = True c.is_superuser = True
c.save() c.save()
@ -35,7 +33,6 @@ class ProposalsTests(TestCase):
self.assertEqual(self.client.get(reverse(view)).status_code, 302 if view == 'list-speakers' else 200) self.assertEqual(self.client.get(reverse(view)).status_code, 302 if view == 'list-speakers' else 200)
self.assertEqual(self.client.get(reverse('edit-talk', kwargs={'talk': talk.slug})).status_code, 200) self.assertEqual(self.client.get(reverse('edit-talk', kwargs={'talk': talk.slug})).status_code, 200)
self.assertEqual(self.client.get(reverse('show-talk', kwargs={'slug': talk.slug})).status_code, 200) self.assertEqual(self.client.get(reverse('show-talk', kwargs={'slug': talk.slug})).status_code, 200)
self.assertEqual(self.client.get(reverse('list-talks-by-speaker', kwargs={'speaker': 'a'})).status_code, 200)
self.assertEqual(self.client.get(reverse('show-speaker', kwargs={'username': 'a'})).status_code, 200) self.assertEqual(self.client.get(reverse('show-speaker', kwargs={'username': 'a'})).status_code, 200)
self.client.login(username='b', password='b') self.client.login(username='b', password='b')
@ -66,3 +63,10 @@ class ProposalsTests(TestCase):
{'title': 'mega talk', 'description': 'mega', 'event': 1, 'speakers': "2,1"}) {'title': 'mega talk', 'description': 'mega', 'event': 1, 'speakers': "2,1"})
self.assertTrue(talk.is_editable_by(b)) self.assertTrue(talk.is_editable_by(b))
self.assertFalse(talk.is_moderable_by(b)) self.assertFalse(talk.is_moderable_by(b))
# Only orga can edit topics
self.client.login(username='c', password='c')
self.assertEqual(self.client.get(reverse('edit-topic', kwargs={'slug': 'topipo'})).status_code, 302)
Participation.on_site.filter(user=c).update(orga=True)
self.assertEqual(self.client.get(reverse('edit-topic', kwargs={'slug': 'topipo'})).status_code, 200)
self.assertEqual(self.client.get(reverse('list-topics')).status_code, 200)

View File

@ -10,9 +10,9 @@ urlpatterns = [
url(r'^talk/vote/(?P<talk>[-\w]+)/(?P<score>[-0-2]+)$', views.vote, name='vote'), url(r'^talk/vote/(?P<talk>[-\w]+)/(?P<score>[-0-2]+)$', views.vote, name='vote'),
url(r'^talk/details/(?P<slug>[-\w]+)$', views.TalkDetail.as_view(), name='show-talk'), url(r'^talk/details/(?P<slug>[-\w]+)$', views.TalkDetail.as_view(), name='show-talk'),
url(r'^talk/by-topic/(?P<topic>[-\w]+)$', views.talk_list_by_topic, name='list-talks-by-topic'), url(r'^talk/by-topic/(?P<topic>[-\w]+)$', views.talk_list_by_topic, name='list-talks-by-topic'),
url(r'^talk/by-speaker/(?P<speaker>[\w.@+-]+)$', views.talk_list_by_speaker, name='list-talks-by-speaker'),
url(r'^topic/$', views.TopicList.as_view(), name='list-topics'), url(r'^topic/$', views.TopicList.as_view(), name='list-topics'),
url(r'^topic/add/$', views.TopicCreate.as_view(), name='add-topic'), 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'^speakers/$', views.SpeakerList.as_view(), name='list-speakers'), url(r'^speakers/$', views.SpeakerList.as_view(), name='list-speakers'),
url(r'^speaker/(?P<username>[\w.@+-]+)$', views.user_details, name='show-speaker'), url(r'^speaker/(?P<username>[\w.@+-]+)$', views.user_details, name='show-speaker'),
] ]

View File

@ -12,4 +12,4 @@ def allowed_talks(talks, request):
participation = Participation.on_site.get(user=request.user) participation = Participation.on_site.get(user=request.user)
if not participation.orga: if not participation.orga:
talks = talks.filter(Q(topics__reviewers=participation) | Q(speakers=request.user) | Q(proposer=request.user)) talks = talks.filter(Q(topics__reviewers=participation) | Q(speakers=request.user) | Q(proposer=request.user))
return talks return talks.distinct()

View File

@ -7,12 +7,13 @@ from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db.models import Q from django.db.models import Q
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import get_object_or_404, redirect, render
from django.views.generic import CreateView, DetailView, ListView from django.views.generic import CreateView, DetailView, ListView, UpdateView
from accounts.mixins import StaffRequiredMixin from accounts.mixins import OrgaRequiredMixin, StaffRequiredMixin
from accounts.models import Participation from accounts.models import Participation
from accounts.utils import is_orga
from .forms import TalkForm from .forms import TalkForm, TopicForm, TopicOrgaForm
from .models import Talk, Topic, Vote from .models import Talk, Topic, Vote
from .signals import new_talk from .signals import new_talk
from .utils import allowed_talks from .utils import allowed_talks
@ -25,7 +26,7 @@ def home(request):
@login_required @login_required
def talk_list(request): def talk_list(request):
return render(request, 'proposals/talks.html', { return render(request, 'proposals/talks.html', {
'my_talks': Talk.on_site.filter(Q(speakers=request.user) | Q(proposer=request.user)), 'my_talks': Talk.on_site.filter(Q(speakers=request.user) | Q(proposer=request.user)).distinct(),
'other_talks': allowed_talks(Talk.on_site.exclude(speakers=request.user, proposer=request.user), request) 'other_talks': allowed_talks(Talk.on_site.exclude(speakers=request.user, proposer=request.user), request)
}) })
@ -37,13 +38,6 @@ def talk_list_by_topic(request, topic):
return render(request, 'proposals/talk_list.html', {'title': 'Talks related to %s:' % topic, 'talk_list': talks}) return render(request, 'proposals/talk_list.html', {'title': 'Talks related to %s:' % topic, 'talk_list': talks})
@login_required
def talk_list_by_speaker(request, speaker):
speaker = get_object_or_404(User, username=speaker)
talks = allowed_talks(Talk.on_site.filter(speakers=speaker), request)
return render(request, 'proposals/talk_list.html', {'title': 'Talks with %s:' % speaker, 'talk_list': talks})
@login_required @login_required
def talk_edit(request, talk=None): def talk_edit(request, talk=None):
if talk: if talk:
@ -86,17 +80,27 @@ class TalkDetail(LoginRequiredMixin, DetailView):
return super().get_context_data(**ctx) return super().get_context_data(**ctx)
class TopicList(LoginRequiredMixin, ListView): class TopicList(ListView):
model = Topic model = Topic
class TopicCreate(StaffRequiredMixin, CreateView): class TopicMixin(object):
model = Topic model = Topic
fields = ['name']
def get_form_class(self):
return TopicOrgaForm if is_orga(self.request, self.request.user) else TopicForm
class TopicCreate(StaffRequiredMixin, TopicMixin, CreateView):
pass
class TopicUpdate(OrgaRequiredMixin, TopicMixin, UpdateView):
pass
class SpeakerList(StaffRequiredMixin, ListView): class SpeakerList(StaffRequiredMixin, ListView):
queryset = User.objects.filter(talk__in=Talk.on_site.all()).distinct() queryset = Participation.on_site.filter(user__talk__in=Talk.on_site.all()).distinct()
template_name = 'proposals/speaker_list.html' template_name = 'proposals/speaker_list.html'
@ -116,6 +120,8 @@ def vote(request, talk, score):
@login_required @login_required
def user_details(request, username): def user_details(request, username):
speaker = get_object_or_404(User, username=username)
return render(request, 'proposals/user_details.html', { return render(request, 'proposals/user_details.html', {
'profile': get_object_or_404(User, username=username).profile, 'profile': speaker.profile,
'talk_list': allowed_talks(Talk.on_site.filter(speakers=speaker), request),
}) })