This commit is contained in:
Guilhem Saurel 2016-07-11 21:33:34 +02:00
parent 8fa2341ff6
commit 878a139075
18 changed files with 71 additions and 91 deletions

View File

@ -1,15 +1,16 @@
from functools import wraps
from django.core.exceptions import PermissionDenied
from accounts.utils import is_orga, is_staff
def orga_required(func):
def orga_required(view_func):
def _is_orga(request, *args, **kwargs):
if is_orga(request, request.user):
return view_func(request, *args, **kwargs)
raise PermissionDenied
return wraps(func)(_is_orga)
return wraps(view_func)(_is_orga)
def staff_required(view_func):

View File

@ -1,26 +1,28 @@
from django import forms
from django.contrib.auth.models import User
from django.forms.models import modelform_factory
from django import forms
from django_select2.forms import Select2Widget, Select2MultipleWidget
from django_select2.forms import Select2Widget
from .models import Participation, Profile
__all__ = ['UserForm', 'ProfileForm', 'ProfileOrgaForm', 'ParticipationOrgaForm']
UserForm = modelform_factory(User, fields=['first_name', 'last_name', 'email', 'username'])
ProfileForm = modelform_factory(Profile, fields=['biography'])
ParticipationForm = modelform_factory(Participation, fields=['transport', 'connector', 'sound', 'videotaped', 'video_licence', 'constraints'],
widgets={'transport': forms.CheckboxSelectMultiple(), 'connector': forms.CheckboxSelectMultiple()})
ParticipationForm = modelform_factory(Participation, fields=['transport', 'connector', 'sound', 'videotaped',
'video_licence', 'constraints'],
widgets={'transport': forms.CheckboxSelectMultiple(),
'connector': forms.CheckboxSelectMultiple()})
ProfileOrgaForm = modelform_factory(Profile, fields=['biography', 'notes'])
ParticipationOrgaForm = modelform_factory(Participation,
fields=['transport', 'connector', 'sound', 'videotaped', 'video_licence', 'constraints', 'orga'],
widgets={'transport': forms.CheckboxSelectMultiple(), 'connector': forms.CheckboxSelectMultiple()})
fields=['transport', 'connector', 'sound', 'videotaped', 'video_licence',
'constraints', 'orga'],
widgets={'transport': forms.CheckboxSelectMultiple(),
'connector': forms.CheckboxSelectMultiple()})
class ParticipationField(forms.ModelChoiceField):
def label_from_instance(self, obj):
@ -29,4 +31,5 @@ class ParticipationField(forms.ModelChoiceField):
class NewParticipationForm(forms.Form):
participant = ParticipationField(User.objects.all(), widget=Select2Widget(), label='Add participant from existing account')
participant = ParticipationField(User.objects.all(), widget=Select2Widget(),
label='Add participant from existing account')

View File

@ -9,8 +9,6 @@ from ponyconf.utils import PonyConfModel, enum_to_choices
from .utils import generate_user_uid
__all__ = ['Profile']
class Profile(PonyConfModel):
@ -46,8 +44,6 @@ class Connector(Option):
class Participation(PonyConfModel):
TRANSPORTS = IntEnum('Transport', 'train plane others')
CONNECTORS = IntEnum('Connector', 'VGA HDMI miniDP')
LICENCES = IntEnum('Video licence', 'CC-Zero CC-BY CC-BY-SA CC-BY-ND CC-BY-NC CC-BY-NC-SA CC-BY-NC-ND')
site = models.ForeignKey(Site, on_delete=models.CASCADE)
@ -78,8 +74,3 @@ class Participation(PonyConfModel):
def is_staff(self):
return self.is_orga() or self.user.topic_set.exists()
@property
def reviewed_topics(self):
from proposals.models import Topic
return Topic.objects.filter(reviewers=self.user).all()

View File

@ -5,7 +5,7 @@ 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, Profile, Transport, Connector
from .models import Connector, Participation, Profile, Transport
def create_default_options(sender, **kwargs):

View File

@ -26,7 +26,7 @@
<td><a href="{{ participation.get_absolute_url }}">{{ participation.user.username }}</a></td>
<td>{{ participation.user.get_full_name }}</td>
<td>{{ participation.is_orga|yesno:"✔,✘" }}</td>
<td>{% for topic in participation.reviewed_topics %}{{ topic.get_link }}{% if not forloop.last %},
<td>{% for topic in participation.user.topic_set.all %}{{ topic.get_link }}{% if not forloop.last %},
{% endif %}{% endfor %}</td>
<td>
<a href="{% url 'conversation' participation.user.username %}" data-toggle="tooltip" data-placement="bottom"

View File

@ -32,10 +32,12 @@ class AccountTests(TestCase):
self.client.login(username='b', password='b')
# He tries with an invalid address
self.client.post(reverse('profile'), {'email': 'bnewdomain.com', 'username': 'z', 'biography': 'tester', 'video_licence': 1})
self.client.post(reverse('profile'), {'email': 'bnewdomain.com', 'username': 'z', 'biography': 'tester',
'video_licence': 1})
self.assertEqual(User.objects.filter(username='z').count(), 0)
self.client.post(reverse('profile'), {'email': 'b@newdomain.com', 'username': 'z', 'biography': 'tester', 'video_licence': 1})
self.client.post(reverse('profile'), {'email': 'b@newdomain.com', 'username': 'z', 'biography': 'tester',
'video_licence': 1})
user = User.objects.get(username='z')
self.assertEqual(user.email, 'b@newdomain.com')

View File

@ -1,20 +1,17 @@
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from django.shortcuts import get_object_or_404, render, redirect
from django.views.generic import ListView
from django.contrib.sites.shortcuts import get_current_site
from django.core.urlresolvers import reverse
from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse
from django.shortcuts import get_object_or_404, redirect, render
from registration.backends.default.views import RegistrationView
from .forms import ParticipationForm, ParticipationOrgaForm, ProfileForm, ProfileOrgaForm, UserForm, NewParticipationForm
from .mixins import StaffRequiredMixin
from .decorators import staff_required
from .models import Participation, Profile
from .forms import (NewParticipationForm, ParticipationForm,
ParticipationOrgaForm, ProfileForm, ProfileOrgaForm, UserForm)
from .models import Participation, Profile, User
from .utils import can_edit_profile, is_orga
from .models import User
RESET_PASSWORD_BUTTON = ('password_reset', 'warning', 'Reset your password')
CHANGE_PASSWORD_BUTTON = ('password_change', 'warning', 'Change password')
@ -30,7 +27,8 @@ def profile(request):
forms = [UserForm(request.POST or None, instance=request.user),
ProfileForm(request.POST or None, instance=request.user.profile),
ParticipationForm(request.POST or None, instance=Participation.objects.get(site=get_current_site(request), user=request.user))]
ParticipationForm(request.POST or None, instance=Participation.objects.get(site=get_current_site(request),
user=request.user))]
if request.method == 'POST':
if all(form.is_valid() for form in forms):
@ -72,10 +70,11 @@ def edit(request, username):
if not can_edit_profile(request, profile):
raise PermissionDenied()
participation_form_class = ParticipationOrgaForm if is_orga(request, request.user) else ParticipationForm
participation_form = 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.objects.get(site=get_current_site(request), user=profile.user))]
participation_form(request.POST or None,
instance=Participation.objects.get(site=get_current_site(request), user=profile.user))]
if request.method == 'POST':
if all(form.is_valid() for form in forms):

View File

@ -2,5 +2,4 @@ from django.forms.models import modelform_factory
from .models import Message
MessageForm = modelform_factory(Message,
fields=['content'])
MessageForm = modelform_factory(Message, fields=['content'])

View File

@ -4,7 +4,7 @@
# Get the value of REPLY_KEY from the django setting.
# Postfix users can set up an alias file with this content:
# reply: "|/path/to/post-mail.sh mykey@https://example.org/conversations/recv/
# don't forget to run postalias and to add the alias file to main.cf under alias_map.
# reply: "|/path/to/post-mail.sh mykey@https://example.org/conversations/recv/
# don't forget to run postalias and to add the alias file to main.cf under alias_map.
curl ${@#*\@} -F key=${@%\@*} -F "email=@-"

View File

@ -1,8 +1,8 @@
#!/usr/bin/env python
import sys
import requests
import requests
if len(sys.argv) != 2:
print("Usage: %s KEY@URL" % sys.argv[0])
@ -11,7 +11,7 @@ if len(sys.argv) != 2:
key, url = sys.argv[1].split('@')
email = sys.stdin.buffer.raw.read()
sys.stdout.buffer.write(email) # DO NOT REMOVE
sys.stdout.buffer.write(email) # DO NOT REMOVE
requests.post(
url,

View File

@ -1,11 +1,11 @@
from django.contrib.auth.models import User
from django.db.models import Q
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.db.models import Q
from accounts.models import Participation
from proposals.models import Talk
from proposals.signals import *
from proposals.signals import talk_added, talk_edited
from .models import ConversationAboutTalk, ConversationWithParticipant, Message

View File

@ -6,8 +6,8 @@ from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse
from django.shortcuts import get_object_or_404, redirect, render
from accounts.decorators import staff_required
from accounts.models import Participation
from accounts.decorators import orga_required, staff_required
from proposals.models import Talk
from .forms import MessageForm
@ -66,7 +66,8 @@ def talk_conversation(request, talk):
@login_required
def correspondents(request):
correspondent_list = Participation.objects.filter(site=get_current_site(request), conversation__subscribers=request.user)
correspondent_list = Participation.objects.filter(site=get_current_site(request),
conversation__subscribers=request.user)
return render(request, 'conversations/correspondents.html', {
'correspondent_list': correspondent_list,

View File

@ -5,9 +5,6 @@ from django_select2.forms import Select2TagWidget
from proposals.models import Talk, Topic
__all__ = ['TalkForm', 'TopicCreateForm', 'TopicUpdateForm']
TalkForm = modelform_factory(Talk, fields=['title', 'description', 'topics', 'event', 'speakers'],
widgets={'topics': CheckboxSelectMultiple(), 'speakers': Select2TagWidget()})

View File

@ -13,8 +13,6 @@ from ponyconf.utils import PonyConfModel, enum_to_choices
from .utils import query_sum
__all__ = ['Topic', 'Talk']
class Topic(PonyConfModel):
@ -72,7 +70,7 @@ class Talk(PonyConfModel):
class Vote(PonyConfModel):
talk = models.ForeignKey(Talk)
user = models.ForeignKey(Participation)
user = models.ForeignKey(User)
vote = models.IntegerField(validators=[MinValueValidator(-2), MaxValueValidator(2)], default=0)
class Meta:

View File

@ -1,13 +1,9 @@
from django.dispatch import Signal, receiver
from django.db.models.signals import m2m_changed
from django.dispatch import Signal, receiver
from .models import Talk, Topic
from accounts.models import Participation
__all__ = [ 'talk_added', 'talk_edited' ]
from .models import Talk, Topic
talk_added = Signal(providing_args=["sender", "instance", "author"])
talk_edited = Signal(providing_args=["sender", "instance", "author"])

View File

@ -1,7 +1,7 @@
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
from django.contrib.sites.models import Site
from accounts.models import Participation
@ -56,17 +56,17 @@ class ProposalsTests(TestCase):
# Talk.is_{editable,moderable}_by
a, b, c = User.objects.all()
self.assertTrue(talk.is_editable_by(c)) # c is superuser
self.assertTrue(talk.is_moderable_by(c)) # c is superuser
self.assertFalse(talk.is_editable_by(b)) # b is not speaker
self.assertFalse(talk.is_moderable_by(b)) # b is not orga
self.assertTrue(talk.is_editable_by(c)) # c is superuser
self.assertTrue(talk.is_moderable_by(c)) # c is superuser
self.assertFalse(talk.is_editable_by(b)) # b is not speaker
self.assertFalse(talk.is_moderable_by(b)) # b is not orga
self.client.login(username='a', password='a')
self.client.post(reverse('edit-talk', kwargs={'talk': 'super-talk'}),
{'title': 'mega talk', 'description': 'mega', 'event': 1, 'speakers': (a.pk, b.pk)})
talk = Talk.objects.get(slug='super-talk')
self.assertTrue(b in talk.speakers.all())
self.assertTrue(talk.is_editable_by(b)) # b is speaker now
self.assertFalse(talk.is_moderable_by(b)) # b is not orga
self.assertTrue(talk.is_editable_by(b)) # b is speaker now
self.assertFalse(talk.is_moderable_by(b)) # b is not orga
def test_topic_edition_permissions(self):
# Only orga and superuser can edit topics
@ -75,6 +75,6 @@ class ProposalsTests(TestCase):
self.assertEqual(self.client.get(reverse('edit-topic', kwargs={'slug': 'topipo'})).status_code, 302)
Participation.objects.filter(user__username='b').update(orga=True)
self.assertEqual(self.client.get(reverse('edit-topic', kwargs={'slug': 'topipo'})).status_code, 200)
self.client.login(username='c', password='c') # superuser
self.client.login(username='c', password='c') # superuser
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

@ -1,6 +1,6 @@
from django.contrib.sites.shortcuts import get_current_site
from django.db.models import Q, Sum
from django.db.models.functions import Coalesce
from django.contrib.sites.shortcuts import get_current_site
from accounts.models import Participation
@ -10,7 +10,6 @@ def query_sum(queryset, field):
def allowed_talks(talks, request):
participation = Participation.objects.get(site=get_current_site(request), user=request.user)
if not participation.is_orga():
talks = talks.filter(Q(topics__reviewers=participation.user) | Q(speakers=request.user) | Q(proposer=request.user))
if not Participation.objects.get(site=get_current_site(request), user=request.user).is_orga():
talks = talks.filter(Q(topics__reviewers=request.user) | Q(speakers=request.user) | Q(proposer=request.user))
return talks.distinct()

View File

@ -8,17 +8,13 @@ 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, SuperuserRequiredMixin
from accounts.models import Participation
from accounts.utils import is_orga
from accounts.mixins import OrgaRequiredMixin, StaffRequiredMixin
from .forms import TalkForm, TopicCreateForm, TopicUpdateForm
from .models import Talk, Topic, Vote
from .signals import talk_added, talk_edited
from .utils import allowed_talks
from .signals import *
def home(request):
@ -27,9 +23,10 @@ def home(request):
@login_required
def talk_list(request):
talks = Talk.objects.filter(site=get_current_site(request))
return render(request, 'proposals/talks.html', {
'my_talks': Talk.objects.filter(site=get_current_site(request)).filter(Q(speakers=request.user) | Q(proposer=request.user)).distinct(),
'other_talks': allowed_talks(Talk.objects.filter(site=get_current_site(request)).exclude(speakers=request.user, proposer=request.user), request)
'my_talks': talks.filter(Q(speakers=request.user) | Q(proposer=request.user)).distinct(),
'other_talks': allowed_talks(talks.exclude(Q(speakers=request.user) | Q(proposer=request.user)), request)
})
@ -74,19 +71,16 @@ class TalkDetail(LoginRequiredMixin, DetailView):
return Talk.objects.filter(site=get_current_site(self.request)).all()
def get_context_data(self, **ctx):
user = self.request.user
if self.object.is_moderable_by(user):
vote = Vote.objects.filter(talk=self.object, user=Participation.objects.get(site=get_current_site(self.request), user=user)).first()
if self.object.is_moderable_by(self.request.user):
vote = Vote.objects.filter(talk=self.object, user=self.request.user).first()
ctx.update(edit_perm=True, moderate_perm=True, vote=vote,
form_url=reverse('talk-conversation', kwargs={'talk': self.object.slug}))
else:
ctx['edit_perm'] = self.object.is_editable_by(user)
ctx['edit_perm'] = self.object.is_editable_by(self.request.user)
return super().get_context_data(**ctx)
class TopicMixin(object):
model = Topic
def get_queryset(self):
return Topic.objects.filter(site=get_current_site(self.request)).all()
@ -96,10 +90,12 @@ class TopicList(LoginRequiredMixin, TopicMixin, ListView):
class TopicCreate(OrgaRequiredMixin, TopicMixin, CreateView):
model = Topic
form_class = TopicCreateForm
def form_valid(self, form):
form.instance.site = get_current_site(self.request)
return super(TopicCreate, self).form_valid(form)
return super().form_valid(form)
class TopicUpdate(OrgaRequiredMixin, TopicMixin, UpdateView):
@ -111,18 +107,16 @@ class SpeakerList(StaffRequiredMixin, ListView):
template_name = 'proposals/speaker_list.html'
def get_queryset(self):
current_site = get_current_site(self.request)
return User.objects.filter(talk__in=Talk.objects.filter(site=current_site)).all().distinct()
site = get_current_site(self.request)
return User.objects.filter(talk__in=Talk.objects.filter(site=site)).all().distinct()
@login_required
def vote(request, talk, score):
current_site = get_current_site(request)
talk = get_object_or_404(Talk, site=current_site, slug=talk)
user = Participation.objects.get(site=current_site, user=request.user)
talk = get_object_or_404(Talk, site=get_current_site(request), slug=talk)
if not talk.is_moderable_by(request.user):
raise PermissionDenied()
vote, created = Vote.objects.get_or_create(talk=talk, user=user)
vote, created = Vote.objects.get_or_create(talk=talk, user=request.user)
vote.vote = int(score)
vote.save()
messages.success(request, "Vote successfully %s" % ('created' if created else 'updated'))