removing old conversations app
This commit is contained in:
parent
2452a3497c
commit
af143a4fe3
|
@ -1 +0,0 @@
|
|||
default_app_config = 'conversations.apps.ConversationsConfig'
|
|
@ -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)
|
|
@ -1,8 +0,0 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ConversationsConfig(AppConfig):
|
||||
name = 'conversations'
|
||||
|
||||
def ready(self):
|
||||
import conversations.signals # noqa
|
|
@ -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()
|
|
@ -1,5 +0,0 @@
|
|||
from django.forms.models import modelform_factory
|
||||
|
||||
from .models import Message
|
||||
|
||||
MessageForm = modelform_factory(Message, fields=['content'])
|
|
@ -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'],
|
||||
},
|
||||
),
|
||||
]
|
|
@ -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'),
|
||||
),
|
||||
]
|
|
@ -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)
|
|
@ -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"
|
|
@ -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),
|
||||
}
|
||||
)
|
|
@ -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)
|
|
@ -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>
|
|
@ -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>
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -1,4 +0,0 @@
|
|||
{{ content|safe }}
|
||||
|
||||
--
|
||||
Reply {% if answering %}to this email directly or view it {% endif %}online: https://{{ uri }}
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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()))
|
|
@ -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'),
|
||||
]
|
|
@ -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)
|
|
@ -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)
|
|
@ -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()))
|
Loading…
Reference in New Issue