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
__all__ = ['UserForm', 'ProfileForm', 'ProfileOrgaForm']
__all__ = ['UserForm', 'ProfileForm', 'ProfileOrgaForm', 'ParticipationOrgaForm']
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'])
ParticipationForm = modelform_factory(Participation, fields=['transport', 'connector', 'sound', 'constraints'])
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 .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):

View File

@ -59,10 +59,3 @@ class Participation(PonyConfModel):
def is_staff(self):
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.auth.models import User
from django.contrib.auth.signals import user_logged_in, user_logged_out
from django.contrib.sites.shortcuts import get_current_site
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import Participation
from .models import Participation, Profile
@receiver(user_logged_in)
@ -17,3 +19,10 @@ def on_user_logged_in(sender, request, user, **kwargs):
@receiver(user_logged_out)
def on_user_logged_out(sender, request, **kwargs):
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 accounts.utils import can_edit_profile, is_staff
from accounts.utils import can_edit_profile, is_orga, is_staff
register = template.Library()
@register.filter
def orga(request):
return is_orga(request, request.user)
@register.filter
def staff(request):
return is_staff(request, request.user)

View File

@ -58,5 +58,8 @@ class AccountTests(TestCase):
{'biography': 'foo', 'nootes': 'bar'}).status_code, 200)
self.assertEqual(User.objects.get(username='a').profile.biography, '')
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')

View File

@ -6,6 +6,10 @@ def generate_user_uid():
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):
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 .forms import ParticipationForm, ProfileForm, ProfileOrgaForm, UserForm
from .forms import ParticipationForm, ParticipationOrgaForm, ProfileForm, ProfileOrgaForm, UserForm
from .mixins import StaffRequiredMixin
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')
CHANGE_PASSWORD_BUTTON = ('password_change', 'warning', 'Change password')
@ -49,13 +49,17 @@ def edit(request, username):
if not can_edit_profile(request, profile):
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 form.is_valid():
form.save()
if all(form.is_valid() for form in forms):
for form in forms:
form.save()
messages.success(request, 'Profile updated successfully.')
else:
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
- [ ] premier login -> redirection profile
- [x] premier login -> redirection profile
- [x] temps prévu (20 min / 40 min)
- [x] proposer des talks pour les autres
- [ ] gestion staff
- [x] gestion staff
- [x] autre couleur change password
- [x] 3ième formulaire profile pour les infos de participation
- [x] changer le widget des topics pour des cases à cocher
@ -15,7 +15,7 @@
- [ ] notif modification conf …
- [x] modif de conf : pas de modifs des topics
- [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 : qui est reviewer
- [x] visibilité des talks

View File

@ -1,10 +1,14 @@
from django.forms import CheckboxSelectMultiple
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'],
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>
<ul>
{% for speaker in user_list %}
<li><a href="{% url 'show-speaker' username=speaker.username %}">{{ speaker }}</a></li>
{% for speaker in participation_list %}
<li><a href="{% url 'show-speaker' username=speaker.user.username %}">{{ speaker }}</a></li>
{% empty %}
<li><i>No speakers.</i></li>
{% endfor %}

View File

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

View File

@ -10,8 +10,13 @@
<ul>
{% for topic in topic_list %}
<li>{{ topic.get_link }}{% if request|staff %} ({% for reviewer in topic.reviewers.all %}
{{ reviewer.get_link }}{% if not forloop.last %}, {% endif %}{% endfor %} ){% endif %}</li>
<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 %}
<li><i>No topic.</i></li>
{% endfor %}

View File

@ -11,7 +11,7 @@
<p>{{ profile.biography }}</p>
<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 %}
<h2>Notes</h2>

View File

@ -1,5 +1,4 @@
from django.contrib.auth.models import User
from django.contrib.sites.models import Site
from django.core.urlresolvers import reverse
from django.test import TestCase
@ -11,7 +10,6 @@ from .models import Talk, Topic, Vote
class ProposalsTests(TestCase):
def setUp(self):
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')
c.is_superuser = True
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('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('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.client.login(username='b', password='b')
@ -66,3 +63,10 @@ class ProposalsTests(TestCase):
{'title': 'mega talk', 'description': 'mega', 'event': 1, 'speakers': "2,1"})
self.assertTrue(talk.is_editable_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/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-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/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'^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)
if not participation.orga:
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.db.models import Q
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.utils import is_orga
from .forms import TalkForm
from .forms import TalkForm, TopicForm, TopicOrgaForm
from .models import Talk, Topic, Vote
from .signals import new_talk
from .utils import allowed_talks
@ -25,7 +26,7 @@ def home(request):
@login_required
def talk_list(request):
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)
})
@ -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})
@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
def talk_edit(request, talk=None):
if talk:
@ -86,17 +80,27 @@ class TalkDetail(LoginRequiredMixin, DetailView):
return super().get_context_data(**ctx)
class TopicList(LoginRequiredMixin, ListView):
class TopicList(ListView):
model = Topic
class TopicCreate(StaffRequiredMixin, CreateView):
class TopicMixin(object):
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):
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'
@ -116,6 +120,8 @@ def vote(request, talk, score):
@login_required
def user_details(request, username):
speaker = get_object_or_404(User, username=username)
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),
})