removing old conversations app

This commit is contained in:
Élie Bouttier 2017-08-11 23:34:12 +02:00
parent 2452a3497c
commit af143a4fe3
26 changed files with 93 additions and 822 deletions

View File

@ -1 +0,0 @@
default_app_config = 'conversations.apps.ConversationsConfig'

View File

@ -1,7 +0,0 @@
from django.contrib import admin
from .models import ConversationAboutTalk, ConversationWithParticipant, Message
admin.site.register(ConversationWithParticipant)
admin.site.register(ConversationAboutTalk)
admin.site.register(Message)

View File

@ -1,8 +0,0 @@
from django.apps import AppConfig
class ConversationsConfig(AppConfig):
name = 'conversations'
def ready(self):
import conversations.signals # noqa

View File

@ -1,76 +0,0 @@
import re
import chardet
import logging
from functools import reduce
from email import policy
from email.parser import BytesParser
from email.message import EmailMessage
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"])
def email_recv(request):
if not hasattr(settings, 'REPLY_EMAIL') \
or not hasattr(settings, 'REPLY_KEY'):
return HttpResponse(status=501) # Not Implemented
key = request.POST.get('key').strip()
if key != settings.REPLY_KEY:
raise PermissionDenied
if 'email' not in request.FILES:
return HttpResponse(status=400) # Bad Request
msg = request.FILES['email']
msg = BytesParser(policy=policy.default).parsebytes(msg.read())
body = msg.get_body(preferencelist=('plain',))
content = body.get_payload(decode=True)
try:
content = content.decode(body.get_content_charset())
except Exception:
encoding = chardet.detect(content)['encoding']
content = content.decode(encoding)
addr = settings.REPLY_EMAIL
pos = addr.find('@')
name = addr[:pos]
domain = addr[pos+1:]
regexp = '^%s\+(?P<dest>[a-z0-9]{12})(?P<token>[a-z0-9]{60})(?P<key>[a-z0-9]{12})@%s$' % (name, domain)
p = re.compile(regexp)
m = None
addrs = map(lambda x: x.split(',') if x else [], [msg.get('To'), msg.get('Cc')])
addrs = reduce(lambda x, y: x + y, addrs)
for _mto in map(lambda x: x.strip(), addrs):
m = p.match(_mto)
if m:
break
if not m: # no one matches
raise Http404
author = get_object_or_404(User, profile__email_token=m.group('dest'))
message = get_object_or_404(Message, token=m.group('token'))
key = hexdigest_sha256(settings.SECRET_KEY, message.token, author.pk)[0:12]
if key != m.group('key'):
raise PermissionDenied
answer = Message(conversation=message.conversation,
author=author, content=content)
answer.save()
return HttpResponse()

View File

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

View File

@ -1,64 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.3 on 2017-01-13 10:49
from __future__ import unicode_literals
import conversations.utils
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('contenttypes', '0002_remove_content_type_name'),
('accounts', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='ConversationAboutTalk',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
('subscribers', models.ManyToManyField(blank=True, related_name='_conversationabouttalk_subscribers_+', to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='ConversationWithParticipant',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
('participation', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='conversation', to='accounts.Participation')),
('subscribers', models.ManyToManyField(blank=True, related_name='_conversationwithparticipant_subscribers_+', to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Message',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
('object_id', models.PositiveIntegerField()),
('token', models.CharField(default=conversations.utils.generate_message_token, max_length=64, unique=True)),
('content', models.TextField(blank=True)),
('system', models.BooleanField(default=False)),
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
],
options={
'ordering': ['created'],
},
),
]

View File

@ -1,24 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.3 on 2017-01-13 10:49
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('proposals', '0001_initial'),
('conversations', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='conversationabouttalk',
name='talk',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='conversation', to='proposals.Talk'),
),
]

View File

@ -1,127 +0,0 @@
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 accounts.models import Participation
from ponyconf.utils import PonyConfModel
from proposals.models import Talk
from .utils import generate_message_token, notify_by_email
class Message(PonyConfModel):
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
conversation = GenericForeignKey('content_type', 'object_id')
token = models.CharField(max_length=64, default=generate_message_token, unique=True)
author = models.ForeignKey(User)
content = models.TextField(blank=True)
system = models.BooleanField(default=False)
class Meta:
ordering = ['created']
def __str__(self):
return "Message from %s" % self.author
def get_absolute_url(self):
return self.conversation.get_absolute_url()
class Conversation(PonyConfModel):
subscribers = models.ManyToManyField(User, related_name='+', blank=True)
class Meta:
abstract = True
class ConversationWithParticipant(Conversation):
participation = models.OneToOneField(Participation, related_name='conversation')
messages = GenericRelation(Message)
uri = 'inbox'
template = 'participant_message'
def __str__(self):
return "Conversation with %s" % self.participation.user
def get_absolute_url(self):
return reverse('user-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)
recipients = list(self.subscribers.all())
# Auto-subscribe
if message.author != self.participation.user and message.author not in recipients:
self.subscribers.add(message.author)
data = {
'content': message.content,
'uri': site.domain + reverse('user-conversation', args=[self.participation.user.username]),
}
first = self.messages.first()
if first != message:
ref = first.token
else:
ref = None
notify_by_email('message', data, subject, message.author, recipients, message.token, ref)
if message.author != self.participation.user:
subject = '[%s] Message notification' % site.name
data.update({
'uri': site.domain + reverse('inbox')
})
notify_by_email('message', data, subject, message.author, [self.participation.user], message.token, ref)
class ConversationAboutTalk(Conversation):
talk = models.OneToOneField(Talk, related_name='conversation')
messages = GenericRelation(Message)
uri = 'inbox'
template = 'talk_message'
def __str__(self):
return "Conversation about %s" % self.talk.title
def get_absolute_url(self):
return 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()
if not message.system and message.author not in self.subscribers.all():
self.subscribers.add(message.author)
recipients = self.subscribers.all()
data = {
'uri': site.domain + reverse('show-talk', args=[self.talk.slug]),
}
if first == message:
subject = '[%s] Talk: %s' % (site.name, self.talk.title)
template = 'talk_notification'
ref = None
data.update({
'talk': self.talk,
'proposer': message.author,
'proposer_uri': site.domain + reverse('show-participant', args=[message.author.username])
})
else:
subject = 'Re: [%s] Talk: %s' % (site.name, self.talk.title)
template = 'message'
ref = first.token
data.update({'content': message.content})
notify_by_email(template, data, subject, message.author, recipients, message.token, ref)

View File

@ -1,10 +0,0 @@
#! /bin/bash
# Usage: cat email.txt | post-mail.sh REPLY_KEY@https://example.org/conversations/recv/
# 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.
curl ${@#*\@} -F key=${@%\@*} -F "email=@-;filename=email.txt"

View File

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

View File

@ -1,64 +0,0 @@
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 ponyconf.decorators import disable_for_loaddata
from accounts.models import Participation
from proposals.models import Talk
from proposals.signals import talk_added, talk_edited
from .models import ConversationAboutTalk, ConversationWithParticipant, Message
@receiver(post_save, sender=Participation, dispatch_uid="Create ConversationWithParticipant")
@disable_for_loaddata
def create_conversation_with_participant(sender, instance, created, **kwargs):
if not created:
return
conversation = ConversationWithParticipant(participation=instance)
conversation.save()
@receiver(post_save, sender=Talk, dispatch_uid="Create ConversationAboutTalk")
@disable_for_loaddata
def create_conversation_about_talk(sender, instance, created, **kwargs):
if not created:
return
conversation = ConversationAboutTalk(talk=instance)
conversation.save()
def check_talk(talk):
reviewers = User.objects.filter(Q(topic__talk=talk) | Q(participation__site=talk.site, participation__orga=True))
# Subscribe the reviewers to the conversation about the talk
talk.conversation.subscribers.add(*reviewers)
# Subscribe the reviewers to the conversations with each speaker
for user in talk.speakers.all():
participation, created = Participation.objects.get_or_create(user=user, site=talk.site)
participation.conversation.subscribers.add(*reviewers)
@receiver(talk_added, dispatch_uid="Notify talk added")
def notify_talk_added(sender, instance, author, **kwargs):
check_talk(instance)
message = Message(conversation=instance.conversation, author=author,
content='The talk has been proposed.', system=True)
message.save()
@receiver(talk_edited, dispatch_uid="Notify talk edited")
def notify_talk_edited(sender, instance, author, **kwargs):
check_talk(instance)
message = Message(conversation=instance.conversation, author=author,
content='The talk has been modified.', system=True)
message.save()
@receiver(post_save, sender=Message, dispatch_uid="Notify new message")
@disable_for_loaddata
def notify_new_message(sender, instance, created, **kwargs):
if not created:
# Possibly send a modification notification?
return
instance.conversation.new_message(instance)

View File

@ -1,8 +0,0 @@
<div class="panel panel-{% if message.author == message.conversation.participation.user %}info{% else %}default{% endif %}">
<div class="panel-heading">
{{ message.created }} | <a href="{% url 'show-participant' message.author.username %}">{{ message.author.profile }}</a>
</div>
<div class="panel-body">
{{ message.content|linebreaksbr }}
</div>
</div>

View File

@ -1,15 +0,0 @@
{% load i18n %}
<div class="panel panel-default">
<div class="panel-heading">
{% trans "Send a message" %}
</div>
<div class="panel-body">
<form action="{{ form_url }}" method="post" role="form">
{% csrf_token %}
<div class="form-group">
<textarea class="form-control" name="content" rows="6" required></textarea>
</div>
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-envelope"></span> {% trans "Send" %}</button>
</form>
</div>
</div>

View File

@ -1,17 +0,0 @@
{% extends 'staff.html' %}
{% load i18n %}
{% block correspondentstab %} class="active"{% endblock %}
{% block content %}
<h1>{% blocktrans with correspondent=correspondent.profile %}Conversation with {{ correspondent }}{% endblocktrans %}</h1>
{% for message in message_list %}
{% include 'conversations/_message_detail.html' %}
{% endfor %}
{% include 'conversations/_message_form.html' %}
{% endblock %}

View File

@ -1,36 +0,0 @@
{% extends 'staff.html' %}
{% load bootstrap3 i18n %}
{% block correspondentstab %} class="active"{% endblock %}
{% block content %}
<h1>{% trans "Correspondents" %}</h1>
<p>{% trans "This is the list of participants that you follow." %}</p>
<table class="table table-striped">
<tr>
<th>#</th>
<th>Username</th>
<th>Full name</th>
<th>Administration</th>
</tr>
{% for correspondent in correspondent_list %}
<tr>
<th>{{ forloop.counter }}</th>
<td>{{ correspondent.user.username }}</td>
<td>{{ correspondent.user.get_full_name }}</td>
<td>
<a href="{% url 'user-conversation' correspondent.user.username %}"><span class="glyphicon glyphicon-envelope"></span></a>
{% if request.user in correspondent.conversation.subscribers.all %}
<a href="{% url 'unsubscribe-conversation' correspondent.user.username %}?next={% url 'list-correspondents' %}" data-toggle="tooltip" data-placement="bottom" title="{% trans "Unsubscribe from the conversation" %}"><span class="glyphicon glyphicon-star"></span></a>
{% else %}
<a href="{% url 'subscribe-conversation' correspondent.user.username %}?next={% url 'list-correspondents' %}" data-toggle="tooltip" data-placement="bottom" title="{% trans "Subscribe to the conversation" %}"><span class="glyphicon glyphicon-star-empty"></span></a>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
{% endblock %}

View File

@ -1,8 +0,0 @@
{{ content|linebreaksbr }}
<hr>
{% if answering %}
Reply to this email directly or <a href="https://{{ uri }}">view it online</a>.
{% else %}
<a href="https://{{ uri }}">Reply online</a>.
{% endif %}

View File

@ -1,4 +0,0 @@
{{ content|safe }}
--
Reply {% if answering %}to this email directly or view it {% endif %}online: https://{{ uri }}

View File

@ -1,11 +0,0 @@
Hi!<br />
<br />
A <a href="https://{{ uri }}">new {{ talk.event }}</a> has been proposed by <a href="https://{{ proposer_uri }}">{{ proposer.profile }}</a>!<br />
<br />
Title: {{ talk.title }}<br />
<br />
Description:<br />
<p>{{ talk.description|linebreaksbr }}</p>
{% if answering %}
<hr>
Reply to this email directly to comment this talk.{% endif %}

View File

@ -1,12 +0,0 @@
Hi!
A new talk has been proposed by {{ proposer.profile }}!
See it online: https://{{ uri }}
Title: {{ talk.title }}
Description:
{{ talk.description }}
{% if answering %}
--
Reply to this email directly to comment this talk.{% endif %}

View File

@ -1,18 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block inboxtab %} class="active"{% endblock %}
{% block content %}
<h1>{% trans "Messaging" %}</h1>
<p>{% trans "You can use this page to communicate with the staff." %}</p>
{% for message in message_list %}
{% include 'conversations/_message_detail.html' %}
{% endfor %}
{% include 'conversations/_message_form.html' %}
{% endblock %}

View File

@ -1,96 +0,0 @@
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, override_settings
from django.core import mail
from django.conf import settings
from accounts.models import Participation
from proposals.models import Topic, Talk, Event
from .models import ConversationAboutTalk, ConversationWithParticipant, Message
class ConversationTests(TestCase):
def setUp(self):
a, b, c, d = (User.objects.create_user(guy, email='%s@example.org' % guy, password=guy) for guy in 'abcd')
d.is_superuser = True
d.save()
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)
Message.objects.create(content='aluil', conversation=conversation, author=a)
site = Site.objects.first()
Talk.objects.get_or_create(site=site, proposer=a, title='a talk', description='yay', event=Event.objects.get(site=site, name='other'))
def test_models(self):
talk, participant, message = (model.objects.first() for model in
(ConversationAboutTalk, ConversationWithParticipant, Message))
self.assertEqual(str(talk), 'Conversation about a talk')
self.assertEqual(str(participant), 'Conversation with a')
self.assertEqual(str(message), 'Message from b')
self.assertEqual(message.get_absolute_url(), '/conversations/with/a/')
self.assertEqual(talk.get_absolute_url(), '/talk/details/a-talk')
def test_views(self):
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('list-correspondents')).status_code, 403) # c is not staff
self.assertEqual(self.client.get(reverse('inbox')).status_code, 200)
self.client.post(reverse('inbox'), {'content': 'coucou'})
self.client.login(username='d', password='d')
self.client.post(url, {'content': 'im superuser'})
self.assertEqual(Message.objects.last().content, 'im superuser')
self.client.login(username='d', password='d')
self.assertEqual(self.client.get(reverse('list-correspondents')).status_code, 200)
@override_settings(DEFAULT_FROM_EMAIL='noreply@example.org',
REPLY_EMAIL='reply@example.org',
REPLY_KEY='secret')
class EmailTests(TestCase):
def setUp(self):
for guy in 'abcd':
setattr(self, guy, User.objects.create_user(guy, email='%s@example.org' % guy, password=guy))
a_p = Participation(user=self.a, site=Site.objects.first())
a_p.orga = True
a_p.save()
t = Topic(name='Topic 1', site=Site.objects.first())
t.save()
t.reviewers.add(self.b)
def test_talk_notification(self):
self.client.login(username='c', password='c')
# Check that login create participation
self.assertTrue(Participation.objects.filter(user=self.c, site=Site.objects.first()).exists())
# Propose new talk
topic = Topic.objects.get(name='Topic 1')
response = self.client.post(reverse('add-talk'), {
'title': 'Talk 1',
'description': 'This is the first talk',
'topics': (topic.pk,),
'event': 1,
'speakers': (self.c.pk, self.d.pk),
}, follow=True)
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Talk proposed') # check messages notification
talk = Talk.objects.get(site=Site.objects.first(), title='Talk 1')
conv = ConversationAboutTalk.objects.get(talk=talk)
# Orga and reviewer should have been subscribed to the conversation about the talk
self.assertEqual(set([self.a, self.b]), set(conv.subscribers.all()))
# Both should have received an email notification
self.assertEqual(len(mail.outbox), 2)
for m in mail.outbox:
self.assertEqual(m.from_email, '%s <%s>' % (self.c.profile, settings.DEFAULT_FROM_EMAIL))
self.assertTrue('Talk: %s' % talk.title)
self.assertTrue(len(m.to), 1)
self.assertTrue(m.to[0] in [ self.a.email, self.b.email ])
# Both should have been subscribed to conversations with each speakers
for user in [self.c, self.d]:
# Participation should have been created as the user is a speaker
p = Participation.objects.get(user=user, site=Site.objects.first())
conv = ConversationWithParticipant.objects.get(participation=p)
self.assertEqual(set([self.a, self.b]), set(conv.subscribers.all()))

View File

@ -1,13 +0,0 @@
from django.conf.urls import url
from conversations import emails, views
urlpatterns = [
url(r'^recv/$', emails.email_recv), # API
url(r'^inbox/$', views.user_conversation, name='inbox'),
url(r'^$', views.correspondent_list, name='list-correspondents'),
url(r'^with/(?P<username>[\w.@+-]+)/$', views.user_conversation, name='user-conversation'),
url(r'^about/(?P<talk>[\w.@+-]+)/$', views.talk_conversation, name='talk-conversation'),
url(r'^subscribe/(?P<username>[\w.@+-]+)/$', views.subscribe, name='subscribe-conversation'),
url(r'^unsubscribe/(?P<username>[\w.@+-]+)/$', views.unsubscribe, name='unsubscribe-conversation'),
]

View File

@ -1,74 +0,0 @@
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):
r = hashlib.sha256()
for arg in args:
r.update(str(arg).encode('utf-8'))
return r.hexdigest()
def get_reply_addr(message_id, dest):
if not hasattr(settings, 'REPLY_EMAIL'):
return []
addr = settings.REPLY_EMAIL
pos = addr.find('@')
name = addr[:pos]
domain = addr[pos:]
key = hexdigest_sha256(settings.SECRET_KEY, message_id, dest.pk)[0:12]
return ['%s+%s%s%s%s' % (name, dest.profile.email_token, message_id, key, domain)]
def generate_message_token():
return get_random_string(length=60, allowed_chars='abcdefghijklmnopqrstuvwxyz0123456789')
def notify_by_email(template, data, subject, sender, dests, message_id, ref=None):
if hasattr(settings, 'REPLY_EMAIL') and hasattr(settings, 'REPLY_KEY'):
data.update({'answering': True})
text_message = render_to_string('conversations/emails/%s.txt' % template, data)
html_message = render_to_string('conversations/emails/%s.html' % template, data)
from_email = '{name} <{email}>'.format(
name=sender.get_full_name() or sender.username,
email=settings.DEFAULT_FROM_EMAIL)
# Generating headers
headers = {'Message-ID': "<%s.%s>" % (message_id, settings.DEFAULT_FROM_EMAIL)}
if ref:
# This email reference a previous one
headers.update({
'References': '<%s.%s>' % (ref, settings.DEFAULT_FROM_EMAIL),
})
mails = []
for dest in dests:
if not dest.email:
continue
reply_to = get_reply_addr(message_id, dest)
mails += [(subject, (text_message, html_message), from_email, [dest.email], reply_to, headers)]
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.attach_alternative(html_message, 'text/html')
messages += [msg]
with mail.get_connection() as connection:
connection.send_messages(messages)

View File

@ -1,100 +0,0 @@
from django.contrib import messages
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 django.utils.translation import ugettext_lazy as _
from accounts.decorators import staff_required
from accounts.models import Participation
from proposals.models import Talk
from .forms import MessageForm
@login_required
def user_conversation(request, username=None):
if username:
p = Participation.objects.get(user=request.user, site=get_current_site(request))
if not p.is_staff() and not p.is_orga():
raise PermissionDenied()
user = get_object_or_404(User, username=username)
template = 'conversations/conversation.html'
else:
user = request.user
template = 'conversations/inbox.html'
participation = get_object_or_404(Participation, user=user, site=get_current_site(request))
conversation = participation.conversation
message_list = conversation.messages.all()
form = MessageForm(request.POST or None)
if request.method == 'POST' and form.is_valid():
form.instance.conversation = conversation
form.instance.author = request.user
form.save()
messages.success(request, _('Message sent!'))
if username:
return redirect(reverse('user-conversation', args=[username]))
else:
return redirect('inbox')
return render(request, template, {
'correspondent': user,
'message_list': message_list,
'form': form,
})
@login_required
def talk_conversation(request, talk):
talk = get_object_or_404(Talk, slug=talk)
form = MessageForm(request.POST or None)
if request.method == 'POST' and form.is_valid():
form.instance.conversation = talk.conversation
form.instance.author = request.user
form.save()
messages.success(request, 'Message sent!')
return redirect(talk.get_absolute_url())
@staff_required
def correspondent_list(request):
correspondent_list = Participation.objects.filter(site=get_current_site(request),
conversation__subscribers=request.user)
return render(request, 'conversations/correspondent_list.html', {
'correspondent_list': correspondent_list,
})
@staff_required
def subscribe(request, username):
participation = get_object_or_404(Participation, user__username=username, site=get_current_site(request))
participation.conversation.subscribers.add(request.user)
messages.success(request, 'Subscribed.')
next_url = request.GET.get('next') or reverse('user-conversation', args=[username])
return redirect(next_url)
@staff_required
def unsubscribe(request, username):
participation = get_object_or_404(Participation, user__username=username, site=get_current_site(request))
participation.conversation.subscribers.remove(request.user)
messages.success(request, 'Unsubscribed.')
next_url = request.GET.get('next') or reverse('user-conversation', args=[username])
return redirect(next_url)

93
mailing/tests.py Normal file
View File

@ -0,0 +1,93 @@
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, override_settings
from django.core import mail
from django.conf import settings
from .models import Message, MessageThread, MessageCorrespondent
#class MailingTests(TestCase):
# def setUp(self):
# a, b, c, d = (User.objects.create_user(guy, email='%s@example.org' % guy, password=guy) for guy in 'abcd')
# d.is_superuser = True
# d.save()
# 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)
# Message.objects.create(content='aluil', conversation=conversation, author=a)
# site = Site.objects.first()
# Talk.objects.get_or_create(site=site, proposer=a, title='a talk', description='yay', event=Event.objects.get(site=site, name='other'))
#
# def test_models(self):
# talk, participant, message = (model.objects.first() for model in
# (ConversationAboutTalk, ConversationWithParticipant, Message))
# self.assertEqual(str(talk), 'Conversation about a talk')
# self.assertEqual(str(participant), 'Conversation with a')
# self.assertEqual(str(message), 'Message from b')
# self.assertEqual(message.get_absolute_url(), '/conversations/with/a/')
# self.assertEqual(talk.get_absolute_url(), '/talk/details/a-talk')
#
# def test_views(self):
# 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('list-correspondents')).status_code, 403) # c is not staff
# self.assertEqual(self.client.get(reverse('inbox')).status_code, 200)
# self.client.post(reverse('inbox'), {'content': 'coucou'})
# self.client.login(username='d', password='d')
# self.client.post(url, {'content': 'im superuser'})
# self.assertEqual(Message.objects.last().content, 'im superuser')
# self.client.login(username='d', password='d')
# self.assertEqual(self.client.get(reverse('list-correspondents')).status_code, 200)
#
#
#@override_settings(DEFAULT_FROM_EMAIL='noreply@example.org',
# REPLY_EMAIL='reply@example.org',
# REPLY_KEY='secret')
#class EmailTests(TestCase):
# def setUp(self):
# for guy in 'abcd':
# setattr(self, guy, User.objects.create_user(guy, email='%s@example.org' % guy, password=guy))
# a_p = Participation(user=self.a, site=Site.objects.first())
# a_p.orga = True
# a_p.save()
# t = Topic(name='Topic 1', site=Site.objects.first())
# t.save()
# t.reviewers.add(self.b)
#
#
# def test_talk_notification(self):
# self.client.login(username='c', password='c')
# # Check that login create participation
# self.assertTrue(Participation.objects.filter(user=self.c, site=Site.objects.first()).exists())
# # Propose new talk
# topic = Topic.objects.get(name='Topic 1')
# response = self.client.post(reverse('add-talk'), {
# 'title': 'Talk 1',
# 'description': 'This is the first talk',
# 'topics': (topic.pk,),
# 'event': 1,
# 'speakers': (self.c.pk, self.d.pk),
# }, follow=True)
# self.assertEqual(response.status_code, 200)
# self.assertContains(response, 'Talk proposed') # check messages notification
# talk = Talk.objects.get(site=Site.objects.first(), title='Talk 1')
# conv = ConversationAboutTalk.objects.get(talk=talk)
# # Orga and reviewer should have been subscribed to the conversation about the talk
# self.assertEqual(set([self.a, self.b]), set(conv.subscribers.all()))
# # Both should have received an email notification
# self.assertEqual(len(mail.outbox), 2)
# for m in mail.outbox:
# self.assertEqual(m.from_email, '%s <%s>' % (self.c.profile, settings.DEFAULT_FROM_EMAIL))
# self.assertTrue('Talk: %s' % talk.title)
# self.assertTrue(len(m.to), 1)
# self.assertTrue(m.to[0] in [ self.a.email, self.b.email ])
# # Both should have been subscribed to conversations with each speakers
# for user in [self.c, self.d]:
# # Participation should have been created as the user is a speaker
# p = Participation.objects.get(user=user, site=Site.objects.first())
# conv = ConversationWithParticipant.objects.get(participation=p)
# self.assertEqual(set([self.a, self.b]), set(conv.subscribers.all()))