diff --git a/accounts/admin.py b/accounts/admin.py index 5a90ca6..250b009 100644 --- a/accounts/admin.py +++ b/accounts/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin -from accounts.models import Profile, Participation +from accounts.models import Participation, Profile admin.site.register(Profile) # FIXME extend user admin admin.site.register(Participation) diff --git a/accounts/apps.py b/accounts/apps.py index ea7a982..f4628d2 100644 --- a/accounts/apps.py +++ b/accounts/apps.py @@ -5,4 +5,4 @@ class AccountsConfig(AppConfig): name = 'accounts' def ready(self): - import accounts.signals + import accounts.signals # noqa diff --git a/accounts/migrations/0004_remove_participation_review_topics.py b/accounts/migrations/0004_remove_participation_review_topics.py new file mode 100644 index 0000000..a729052 --- /dev/null +++ b/accounts/migrations/0004_remove_participation_review_topics.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-06-19 20:26 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0003_auto_20160615_2031'), + ] + + operations = [ + migrations.RemoveField( + model_name='participation', + name='review_topics', + ), + ] diff --git a/accounts/models.py b/accounts/models.py index 8218988..6cbf174 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -6,9 +6,9 @@ from django.contrib.sites.models import Site from django.core.urlresolvers import reverse from django.db import models -from .utils import enum_to_choices, generate_user_uid -from proposals.models import Topic +from ponyconf.utils import enum_to_choices +from .utils import generate_user_uid __all__ = ['Profile'] @@ -41,9 +41,6 @@ class Participation(models.Model): connector = models.IntegerField(choices=enum_to_choices(CONNECTORS), blank=True, null=True) constraints = models.TextField(blank=True) - # Participe as reviewer for theses topics - review_topics = models.ManyToManyField(Topic, blank=True) - objects = models.Manager() on_site = CurrentSiteManager() diff --git a/accounts/signals.py b/accounts/signals.py index 34bc1e8..653aa91 100644 --- a/accounts/signals.py +++ b/accounts/signals.py @@ -1,14 +1,14 @@ -from django.contrib.auth.signals import user_logged_in, user_logged_out -from django.dispatch import receiver from django.contrib import messages +from django.contrib.auth.signals import user_logged_in, user_logged_out from django.contrib.sites.shortcuts import get_current_site +from django.dispatch import receiver from .models import Participation @receiver(user_logged_in) -def on_user_logged_in(sender, request, **kwargs): - Participation.on_site.get_or_create(user=request.user, site=get_current_site(request)) +def on_user_logged_in(sender, request, user, **kwargs): + Participation.on_site.get_or_create(user=user, site=get_current_site(request)) messages.success(request, 'Welcome!', fail_silently=True) # FIXME diff --git a/accounts/templates/admin/participant.html b/accounts/templates/admin/participant.html new file mode 100644 index 0000000..aa97fca --- /dev/null +++ b/accounts/templates/admin/participant.html @@ -0,0 +1,20 @@ +{% extends 'base.html' %} + +{% load bootstrap3 %} + +{% block admintab %}active{% endblock %} + +{% block content %} + + + +
+
User
{{ participation.user.profil }}
+
Arrival
{{ participation.arrival }}
+
+ + + +{% endblock %} diff --git a/accounts/tests.py b/accounts/tests.py index 7e63be5..ede365f 100644 --- a/accounts/tests.py +++ b/accounts/tests.py @@ -3,7 +3,9 @@ from django.contrib.sites.models import Site from django.core.urlresolvers import reverse from django.test import TestCase -from .models import Profile, Participation +from ponyconf.utils import full_link + +from .models import Participation, Profile ROOT_URL = 'accounts' @@ -19,7 +21,7 @@ class AccountTests(TestCase): self.client.login(username='b', password='b') for model in [Profile, Participation]: item = model.objects.first() - self.assertEqual(self.client.get(item.get_absolute_url()).status_code, 200) + self.assertEqual(self.client.get(full_link(item)).status_code, 200) self.assertTrue(str(item)) def test_views(self): @@ -40,3 +42,12 @@ class AccountTests(TestCase): self.assertEqual(user.email, 'b@newdomain.com') self.assertEqual(user.profile.biography, 'tester') self.client.logout() + + def test_participant_views(self): + self.assertEqual(self.client.get(reverse('participants')).status_code, 302) + self.client.login(username='b', password='b') + self.assertEqual(self.client.get(reverse('participants')).status_code, 403) + b = User.objects.get(username='b') + b.is_superuser = True + b.save() + self.assertEqual(self.client.get(reverse('participants')).status_code, 200) diff --git a/accounts/urls.py b/accounts/urls.py index 189fb36..10d0622 100644 --- a/accounts/urls.py +++ b/accounts/urls.py @@ -8,5 +8,6 @@ urlpatterns = [ url(r'^profile$', views.profile, name='profile'), url(r'^logout/$', auth_views.logout, {'next_page': settings.LOGOUT_REDIRECT_URL}, name='logout'), url(r'^admin/participants/$', views.participants, name='participants'), + url(r'^admin/participant/(?P[\w.@+-]+)$', views.participant, name='show-participation'), url(r'', include('django.contrib.auth.urls')), ] diff --git a/accounts/utils.py b/accounts/utils.py index 166ecba..f78f926 100644 --- a/accounts/utils.py +++ b/accounts/utils.py @@ -1,8 +1,5 @@ from django.utils.crypto import get_random_string -def enum_to_choices(enum): - return ((item.value, item.name) for item in list(enum)) - def generate_user_uid(): return get_random_string(length=12, allowed_chars='abcdefghijklmnopqrstuvwxyz0123456789') diff --git a/accounts/views.py b/accounts/views.py index 136c42d..0cd9bfe 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -1,9 +1,7 @@ from django.contrib import messages from django.contrib.auth.decorators import login_required -from django.shortcuts import render from django.core.exceptions import PermissionDenied -from django.contrib.sites.shortcuts import get_current_site -from django.contrib.auth.models import User +from django.shortcuts import get_object_or_404, render from .forms import ProfileForm, UserForm from .models import Participation @@ -35,3 +33,8 @@ def participants(request): participation_list = Participation.on_site.all() return render(request, 'admin/participants.html', {'participation_list': participation_list}) + + +def participant(request, username): + return render(request, 'admin/participant.html', + {'participant': get_object_or_404(Participation, user__username=username)}) diff --git a/conversations/admin.py b/conversations/admin.py index 4565006..431bad1 100644 --- a/conversations/admin.py +++ b/conversations/admin.py @@ -1,7 +1,6 @@ from django.contrib import admin -from .models import ConversationWithParticipant, ConversationAboutTalk, Message - +from .models import ConversationAboutTalk, ConversationWithParticipant, Message admin.site.register(ConversationWithParticipant) admin.site.register(ConversationAboutTalk) diff --git a/conversations/apps.py b/conversations/apps.py index 73892cf..7fc0e2b 100644 --- a/conversations/apps.py +++ b/conversations/apps.py @@ -5,4 +5,4 @@ class ConversationsConfig(AppConfig): name = 'conversations' def ready(self): - import conversations.signals + import conversations.signals # noqa diff --git a/conversations/emails.py b/conversations/emails.py index e76052c..c7f2ff0 100644 --- a/conversations/emails.py +++ b/conversations/emails.py @@ -1,20 +1,18 @@ -from django.shortcuts import get_object_or_404 -from django.core.exceptions import PermissionDenied -from django.views.decorators.http import require_http_methods -from django.conf import settings -from django.http import HttpResponse -from django.views.decorators.csrf import csrf_exempt -from django.http import Http404 -from django.contrib.auth.models import User -from django.core.mail import mail_admins - -from .utils import hexdigest_sha256 -from .models import Message - import email import re from sys import version_info as python_version +from django.conf import settings +from django.contrib.auth.models import User +from django.core.exceptions import PermissionDenied +from django.http import Http404, HttpResponse +from django.shortcuts import get_object_or_404 +from django.views.decorators.csrf import csrf_exempt +from django.views.decorators.http import require_http_methods + +from .models import Message +from .utils import hexdigest_sha256 + @csrf_exempt @require_http_methods(["POST"]) @@ -22,14 +20,14 @@ def email_recv(request): if not hasattr(settings, 'REPLY_EMAIL') \ or not hasattr(settings, 'REPLY_KEY'): - return HttpResponse(status=501) # Not Implemented + return HttpResponse(status=501) # Not Implemented key = request.POST.get('key') if key != settings.REPLY_KEY: raise PermissionDenied if 'email' not in request.FILES: - raise HttpResponse(status=400) # Bad Request + raise HttpResponse(status=400) # Bad Request msg = request.FILES['email'] if python_version < (3,): @@ -37,10 +35,6 @@ def email_recv(request): else: msg = email.message_from_bytes(msg.read()) - mfrom = msg.get('From') - mto = msg.get('To') - subject = msg.get('Subject') - if msg.is_multipart(): msgs = msg.get_payload() for m in msgs: @@ -63,11 +57,11 @@ def email_recv(request): regexp = '^%s\+(?P[a-z0-9]{12})(?P[a-z0-9]{60})(?P[a-z0-9]{12})@%s$' % (name, domain) p = re.compile(regexp) m = None - for _mto in map(lambda x: x.strip(), mto.split(',')): + for _mto in map(lambda x: x.strip(), msg.get('To').split(',')): m = p.match(_mto) if m: break - if not m: # no one matches + if not m: # no one matches raise Http404 author = get_object_or_404(User, profile__email_token=m.group('dest')) diff --git a/conversations/forms.py b/conversations/forms.py index 92c6b8e..d263b9f 100644 --- a/conversations/forms.py +++ b/conversations/forms.py @@ -2,9 +2,5 @@ from django.forms.models import modelform_factory from .models import Message - MessageForm = modelform_factory(Message, - fields=['content']) - - - + fields=['content']) diff --git a/conversations/models.py b/conversations/models.py index a9e7568..1fe2efa 100644 --- a/conversations/models.py +++ b/conversations/models.py @@ -1,13 +1,14 @@ -from django.db import models from django.contrib.auth.models import User from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.contenttypes.models import ContentType from django.core.urlresolvers import reverse +from django.db import models -from .utils import generate_message_token, notify_by_email from accounts.models import Participation from proposals.models import Talk +from .utils import generate_message_token, notify_by_email + class Message(models.Model): @@ -27,6 +28,9 @@ class Message(models.Model): def __str__(self): return "Message from %s" % self.author + def get_absolute_url(self): + return self.conversation.get_absolute_url() + class Conversation(models.Model): @@ -36,7 +40,7 @@ class Conversation(models.Model): abstract = True -class ConversationWithParticipant(Conversation): +class ConversationWithParticipant(Conversation): participation = models.OneToOneField(Participation, related_name='conversation') messages = GenericRelation(Message) @@ -44,12 +48,15 @@ class ConversationWithParticipant(Conversation): uri = 'inbox' template = 'participant_message' - def get_site(self): - return self.participation.site - def __str__(self): return "Conversation with %s" % self.participation.user + def get_absolute_url(self): + return reverse('conversation', kwargs={'username': self.participation.user.username}) + + def get_site(self): + return self.participation.site + def new_message(self, message): site = self.get_site() subject = '[%s] Conversation with %s' % (site.name, self.participation.user.profile) @@ -84,12 +91,15 @@ class ConversationAboutTalk(Conversation): uri = 'inbox' template = 'talk_message' - def get_site(self): - return self.talk.site - def __str__(self): return "Conversation about %s" % self.talk.title + def get_absolute_url(self): + self.talk.get_absolute_url() + + def get_site(self): + return self.talk.site + def new_message(self, message): site = self.get_site() first = self.messages.first() diff --git a/conversations/signals.py b/conversations/signals.py index 561b29f..2103a07 100644 --- a/conversations/signals.py +++ b/conversations/signals.py @@ -1,15 +1,12 @@ -from django.db.models.signals import post_save, m2m_changed -from django.dispatch import receiver -from django.conf import settings -from django.core.urlresolvers import reverse -from django.contrib.sites.shortcuts import get_current_site from django.contrib.auth.models import User +from django.db.models.signals import post_save +from django.dispatch import receiver -from .models import ConversationWithParticipant, ConversationAboutTalk, Message -from .utils import notify_by_email -from proposals.models import Talk, Topic -from proposals.signals import new_talk from accounts.models import Participation +from proposals.models import Talk +from proposals.signals import new_talk + +from .models import ConversationAboutTalk, ConversationWithParticipant, Message @receiver(post_save, sender=Participation, dispatch_uid="Create ConversationWithParticipant") @@ -31,15 +28,14 @@ def create_conversation_about_talk(sender, instance, created, **kwargs): @receiver(new_talk, dispatch_uid="Notify new talk") def notify_new_talk(sender, instance, **kwargs): # Subscribe reviewer for these topics to conversations - topics = instance.topics.all() - reviewers = User.objects.filter(participation__review_topics=topics).all() + reviewers = User.objects.filter(participation__topic__talk=instance) instance.conversation.subscribers.add(*reviewers) for user in instance.speakers.all(): participation = Participation.on_site.get(user=user) participation.conversation.subscribers.add(*reviewers) # Notification of this new talk message = Message(conversation=instance.conversation, author=instance.proposer, - content='The talk has been proposed.') + content='The talk has been proposed.') message.save() diff --git a/conversations/tests.py b/conversations/tests.py index e860168..8a87855 100644 --- a/conversations/tests.py +++ b/conversations/tests.py @@ -1,27 +1,29 @@ 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 accounts.models import Participation -from .models import Conversation, Message -from .utils import get_reply_addr +from .models import ConversationWithParticipant, Message class ConversationTests(TestCase): def setUp(self): - a, b = (User.objects.create_user(guy, email='%s@example.org' % guy, password=guy) for guy in 'ab') - participation = Participation.objects.create(user=a, site=Site.objects.first()) - conversation = Conversation.objects.create(speaker=speaker) - Message.objects.create(token='pipo', conversation=conversation, author=a, content='allo') + a, b, c = (User.objects.create_user(guy, email='%s@example.org' % guy, password=guy) for guy in 'abc') + pa, _ = Participation.objects.get_or_create(user=a, site=Site.objects.first()) + conversation, _ = ConversationWithParticipant.objects.get_or_create(participation=pa) + Message.objects.create(content='allo', conversation=conversation, author=b) def test_models(self): - self.assertEqual(str(Conversation.objects.first()), 'Conversation with a') - self.assertEqual(str(Message.objects.first()), 'Message from a') + self.assertEqual(str(ConversationWithParticipant.objects.first()), 'Conversation with a') + self.assertEqual(str(Message.objects.first()), 'Message from b') def test_views(self): - self.assertEqual(self.client.get(Conversation.objects.first().get_absolute_url()).status_code, 200) - - def test_utils(self): - ret = ['pipo+ 11183704aabfddb3d694ff4f24c0daadfa2d8d2193336e345f92a6fd3ffb6a19e7@example.org'] - self.assertEqual(get_reply_addr(1, User.objects.first()), ret) + url = ConversationWithParticipant.objects.first().get_absolute_url() + self.assertEqual(self.client.get(url).status_code, 302) + self.client.login(username='c', password='c') + self.assertEqual(self.client.get(url).status_code, 403) + self.assertEqual(self.client.get(reverse('correspondents')).status_code, 200) + self.assertEqual(self.client.get(reverse('inbox')).status_code, 200) + self.client.post(reverse('inbox'), {'content': 'coucou'}) diff --git a/conversations/urls.py b/conversations/urls.py index f9cd5e9..67b148b 100644 --- a/conversations/urls.py +++ b/conversations/urls.py @@ -1,7 +1,6 @@ from django.conf.urls import url -from conversations import views, emails - +from conversations import emails, views urlpatterns = [ url(r'^recv/$', emails.email_recv), diff --git a/conversations/utils.py b/conversations/utils.py index 23d1b33..1a48cda 100644 --- a/conversations/utils.py +++ b/conversations/utils.py @@ -1,11 +1,11 @@ -from django.conf import settings -from django.utils.crypto import get_random_string -from django.template.loader import render_to_string -from django.core.mail import EmailMultiAlternatives -from django.core import mail - import hashlib +from django.conf import settings +from django.core import mail +from django.core.mail import EmailMultiAlternatives +from django.template.loader import render_to_string +from django.utils.crypto import get_random_string + def hexdigest_sha256(*args): @@ -47,9 +47,7 @@ def notify_by_email(template, data, subject, sender, dests, message_id, ref=None email=settings.DEFAULT_FROM_EMAIL) # Generating headers - headers = { - 'Message-ID': "<%s.%s>" % (message_id, settings.DEFAULT_FROM_EMAIL), - } + headers = {'Message-ID': "<%s.%s>" % (message_id, settings.DEFAULT_FROM_EMAIL)} if ref: # This email reference a previous one headers.update({ @@ -68,7 +66,8 @@ def notify_by_email(template, data, subject, sender, dests, message_id, ref=None messages = [] for subject, message, from_email, dest_emails, reply_to, headers in mails: text_message, html_message = message - msg = EmailMultiAlternatives(subject, text_message, from_email, dest_emails, reply_to=reply_to, headers=headers) + msg = EmailMultiAlternatives(subject, text_message, from_email, dest_emails, reply_to=reply_to, + headers=headers) msg.attach_alternative(html_message, 'text/html') messages += [msg] with mail.get_connection() as connection: diff --git a/conversations/views.py b/conversations/views.py index dcc5d1d..5df8c50 100644 --- a/conversations/views.py +++ b/conversations/views.py @@ -1,15 +1,13 @@ -from django.shortcuts import render -from django.shortcuts import get_object_or_404, redirect, render -from django.contrib.sites.shortcuts import get_current_site -from django.contrib.auth.decorators import login_required from django.contrib import messages -from django.core.exceptions import PermissionDenied +from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User +from django.contrib.sites.shortcuts import get_current_site +from django.core.exceptions import PermissionDenied from django.core.urlresolvers import reverse - +from django.shortcuts import get_object_or_404, redirect, render from accounts.models import Participation -from .models import Message + from .forms import MessageForm @@ -64,8 +62,7 @@ def subscribe(request, username): # TODO check admin - participation = get_object_or_404(Participation, user__username=username, - site=get_current_site(request)) + participation = get_object_or_404(Participation, user__username=username, site=get_current_site(request)) participation.conversation.subscribers.add(request.user) messages.success(request, 'Subscribed.') @@ -79,8 +76,7 @@ def unsubscribe(request, username): # TODO check admin - participation = get_object_or_404(Participation, user__username=username, - site=get_current_site(request)) + participation = get_object_or_404(Participation, user__username=username, site=get_current_site(request)) participation.conversation.subscribers.remove(request.user) messages.success(request, 'Unsubscribed.') diff --git a/ponyconf/settings.py b/ponyconf/settings.py index 63845e2..7b1551d 100644 --- a/ponyconf/settings.py +++ b/ponyconf/settings.py @@ -179,4 +179,4 @@ BOOTSTRAP3 = { AUTHENTICATION_BACKENDS = ['yeouia.backends.YummyEmailOrUsernameInsensitiveAuth'] LOGOUT_REDIRECT_URL = 'home' -ACCOUNT_ACTIVATION_DAYS = 7 # django-registration +ACCOUNT_ACTIVATION_DAYS = 7 # django-registration diff --git a/ponyconf/utils.py b/ponyconf/utils.py new file mode 100644 index 0000000..dd7244b --- /dev/null +++ b/ponyconf/utils.py @@ -0,0 +1,10 @@ +from django.contrib.sites.shortcuts import get_current_site + + +def enum_to_choices(enum): + return ((item.value, item.name) for item in list(enum)) + + +def full_link(obj, request=None): + protocol = 'https' if request is None or request.is_secure() else 'http' + return '%s://%s%s' % (protocol, get_current_site(request), obj.get_absolute_url()) diff --git a/proposals/migrations/0002_topic_reviewers.py b/proposals/migrations/0002_topic_reviewers.py new file mode 100644 index 0000000..be206db --- /dev/null +++ b/proposals/migrations/0002_topic_reviewers.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-06-19 20:26 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0004_remove_participation_review_topics'), + ('proposals', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='topic', + name='reviewers', + field=models.ManyToManyField(blank=True, to='accounts.Participation'), + ), + ] diff --git a/proposals/models.py b/proposals/models.py index 7abec75..7b27345 100644 --- a/proposals/models.py +++ b/proposals/models.py @@ -8,8 +8,8 @@ from django.db import models from autoslug import AutoSlugField -from accounts.utils import enum_to_choices - +from accounts.models import Participation +from ponyconf.utils import enum_to_choices __all__ = ['Topic', 'Talk', 'Speech'] @@ -19,6 +19,8 @@ class Topic(models.Model): name = models.CharField(max_length=128, verbose_name='Name', unique=True) slug = AutoSlugField(populate_from='name', unique=True) + reviewers = models.ManyToManyField(Participation, blank=True) + def __str__(self): return self.name @@ -54,13 +56,13 @@ class Talk(models.Model): return True if user == self.proposer: return True - if user in talk.speakers.all(): + if user in self.speakers.all(): return True try: participation = Participation.on_site.get(user=user) except Participation.DoesNotExists: return False - return self.topics.filter(pk=participation.review_topics.pk).exists() + return self.topics.filter(reviewers=participation).exists() class Speech(models.Model): diff --git a/proposals/signals.py b/proposals/signals.py index 3713338..ddf438a 100644 --- a/proposals/signals.py +++ b/proposals/signals.py @@ -1,4 +1,3 @@ from django.dispatch import Signal - new_talk = Signal(providing_args=["sender", "instance"]) diff --git a/proposals/tests.py b/proposals/tests.py index e0b442b..1cefa5b 100644 --- a/proposals/tests.py +++ b/proposals/tests.py @@ -1,20 +1,25 @@ 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 .models import Talk, Topic, Speech +from accounts.models import Participation + +from .models import Speech, Talk, Topic class ProposalsTests(TestCase): def setUp(self): - for guy in 'ab': - User.objects.create_user(guy, email='%s@example.org' % guy, password=guy) - Topic.objects.create(name='pipo') + 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() def test_everything(self): # talk-edit self.client.login(username='a', password='a') - self.client.post(reverse('add-talk'), {'title': 'super talk', 'description': 'super', 'event': 1}) + self.client.post(reverse('add-talk'), {'title': 'super talk', 'description': 'super', 'event': 1, 'topics': 1}) self.assertEqual(str(Talk.on_site.first()), 'super talk') self.client.post(reverse('edit-talk', kwargs={'talk': 'super-talk'}), {'title': 'mega talk', 'description': 'mega', 'event': 1}) @@ -41,3 +46,9 @@ class ProposalsTests(TestCase): self.assertEqual(self.client.get(item.get_absolute_url()).status_code, 200) self.assertTrue(str(item)) self.assertEqual(Speech.objects.first().username(), 'a') + + # Talkis_editable_by + a, b, c = User.objects.all() + self.assertTrue(talk.is_editable_by(c)) + Speech.objects.create(talk=talk, speaker=b, order=2) + self.assertTrue(talk.is_editable_by(b)) diff --git a/proposals/views.py b/proposals/views.py index 69f5745..c262a48 100644 --- a/proposals/views.py +++ b/proposals/views.py @@ -9,6 +9,7 @@ from django.views.generic import DetailView, ListView from proposals.forms import TalkForm from proposals.models import Speech, Talk, Topic + from .signals import new_talk @@ -48,7 +49,7 @@ def talk_edit(request, talk=None): talk = get_object_or_404(Talk, slug=talk) if talk.site != get_current_site(request): raise PermissionDenied() - if not talk.has_perm(request.user): + if not talk.is_editable_by(request.user): raise PermissionDenied() form = TalkForm(request.POST or None, instance=talk) if request.method == 'POST' and form.is_valid(): @@ -72,10 +73,9 @@ def talk_edit(request, talk=None): class TalkDetail(LoginRequiredMixin, DetailView): queryset = Talk.on_site.all() + def get_context_data(self, **kwargs): - context = super(TalkDetail, self).get_context_data(**kwargs) - context['edit_perm'] = self.object.is_editable_by(self.request.user) - return context + return super().get_context_data(edit_perm=self.object.is_editable_by(self.request.user), **kwargs) class TopicList(LoginRequiredMixin, ListView):