conversation app (continuation)
This commit is contained in:
parent
2ceadcd96b
commit
a7d1ccd863
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,3 +2,4 @@
|
||||||
*.pyc
|
*.pyc
|
||||||
*.swp
|
*.swp
|
||||||
*.sqlite3
|
*.sqlite3
|
||||||
|
ponyconf/*_settings.py
|
||||||
|
|
|
@ -5,4 +5,4 @@ class AccountsConfig(AppConfig):
|
||||||
name = 'accounts'
|
name = 'accounts'
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
import accounts.signals # noqa
|
import accounts.signals
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.9.7 on 2016-06-13 18:50
|
# Generated by Django 1.9.7 on 2016-06-14 19:13
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import accounts.models
|
import accounts.utils
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
import django.contrib.sites.managers
|
import django.contrib.sites.managers
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
@ -16,7 +16,6 @@ class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
('sites', '0002_alter_domain_unique'),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -29,8 +28,6 @@ class Migration(migrations.Migration):
|
||||||
('transport', models.IntegerField(blank=True, choices=[(1, 'train'), (2, 'plane')], null=True)),
|
('transport', models.IntegerField(blank=True, choices=[(1, 'train'), (2, 'plane')], null=True)),
|
||||||
('connector', models.IntegerField(blank=True, choices=[(1, 'VGA'), (2, 'HDMI'), (3, 'miniDP')], null=True)),
|
('connector', models.IntegerField(blank=True, choices=[(1, 'VGA'), (2, 'HDMI'), (3, 'miniDP')], null=True)),
|
||||||
('constraints', models.TextField(blank=True)),
|
('constraints', models.TextField(blank=True)),
|
||||||
('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sites.Site')),
|
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
],
|
||||||
managers=[
|
managers=[
|
||||||
('objects', django.db.models.manager.Manager()),
|
('objects', django.db.models.manager.Manager()),
|
||||||
|
@ -42,12 +39,8 @@ class Migration(migrations.Migration):
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('biography', models.TextField(blank=True, verbose_name='Biography')),
|
('biography', models.TextField(blank=True, verbose_name='Biography')),
|
||||||
('email_token', models.CharField(default=accounts.models.generate_user_uid, max_length=12)),
|
('email_token', models.CharField(default=accounts.utils.generate_user_uid, max_length=12)),
|
||||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.AlterUniqueTogether(
|
|
||||||
name='participation',
|
|
||||||
unique_together=set([('site', 'user')]),
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
41
accounts/migrations/0002_auto_20160614_1913.py
Normal file
41
accounts/migrations/0002_auto_20160614_1913.py
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.7 on 2016-06-14 19:13
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
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),
|
||||||
|
('proposals', '0001_initial'),
|
||||||
|
('accounts', '0001_initial'),
|
||||||
|
('sites', '0002_alter_domain_unique'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='participation',
|
||||||
|
name='review_topics',
|
||||||
|
field=models.ManyToManyField(blank=True, to='proposals.Topic'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='participation',
|
||||||
|
name='site',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sites.Site'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='participation',
|
||||||
|
name='user',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='participation',
|
||||||
|
unique_together=set([('site', 'user')]),
|
||||||
|
),
|
||||||
|
]
|
|
@ -5,16 +5,12 @@ from django.contrib.sites.managers import CurrentSiteManager
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.crypto import get_random_string
|
|
||||||
|
|
||||||
__all__ = ['Profile', 'Participation']
|
from .utils import enum_to_choices, generate_user_uid
|
||||||
|
from proposals.models import Topic
|
||||||
|
|
||||||
|
|
||||||
def enum_to_choices(enum):
|
__all__ = ['Profile']
|
||||||
return ((item.value, item.name) for item in list(enum))
|
|
||||||
|
|
||||||
def generate_user_uid():
|
|
||||||
return get_random_string(length=12, allowed_chars='abcdefghijklmnopqrstuvwxyz0123456789')
|
|
||||||
|
|
||||||
|
|
||||||
class Profile(models.Model):
|
class Profile(models.Model):
|
||||||
|
@ -45,6 +41,9 @@ class Participation(models.Model):
|
||||||
connector = models.IntegerField(choices=enum_to_choices(CONNECTORS), blank=True, null=True)
|
connector = models.IntegerField(choices=enum_to_choices(CONNECTORS), blank=True, null=True)
|
||||||
constraints = models.TextField(blank=True)
|
constraints = models.TextField(blank=True)
|
||||||
|
|
||||||
|
# Participe as reviewer for theses topics
|
||||||
|
review_topics = models.ManyToManyField(Topic, blank=True)
|
||||||
|
|
||||||
objects = models.Manager()
|
objects = models.Manager()
|
||||||
on_site = CurrentSiteManager()
|
on_site = CurrentSiteManager()
|
||||||
|
|
||||||
|
|
45
accounts/templates/admin/participants.html
Normal file
45
accounts/templates/admin/participants.html
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
|
||||||
|
{% block admintab %}active{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>Participants</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="table table-striped">
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Username</th>
|
||||||
|
<th>Fullname</th>
|
||||||
|
<th>Administration</th>
|
||||||
|
</tr>
|
||||||
|
{% for participation in participation_list %}
|
||||||
|
<tr>
|
||||||
|
<th>{{ forloop.counter }}</th>
|
||||||
|
<td>{{ participation.user.username }}</td>
|
||||||
|
<td>{{ participation.user.get_full_name }}</td>
|
||||||
|
<td>
|
||||||
|
<a href="{% url 'conversation' participation.user.username %}" data-toggle="tooltip" data-placement="bottom" title="View conversation"><span class="glyphicon glyphicon-envelope"></span></a>
|
||||||
|
{% if request.user in participation.conversation.subscribers.all %}
|
||||||
|
<a href="{% url 'unsubscribe-conversation' participation.user.username %}?next={% url 'participants' %}" data-toggle="tooltip" data-placement="bottom" title="Unsubscribe to conversation"><span class="glyphicon glyphicon-star"></span></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'subscribe-conversation' participation.user.username %}?next={% url 'participants' %}" data-toggle="tooltip" data-placement="bottom" title="Subscribe to conversation"><span class="glyphicon glyphicon-star-empty"></span></a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js_end %}
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(function () {
|
||||||
|
$('[data-toggle="tooltip"]').tooltip()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
{% load bootstrap3 %}
|
{% load bootstrap3 %}
|
||||||
|
|
||||||
|
{% block logintab %} class="active"{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
{% load bootstrap3 %}
|
{% load bootstrap3 %}
|
||||||
|
|
||||||
|
{% block registrationtab %} class="active"{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
|
|
|
@ -2,10 +2,11 @@ from django.conf import settings
|
||||||
from django.conf.urls import include, url
|
from django.conf.urls import include, url
|
||||||
from django.contrib.auth import views as auth_views
|
from django.contrib.auth import views as auth_views
|
||||||
|
|
||||||
from .views import profile
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^profile$', profile, name='profile'),
|
url(r'^profile$', views.profile, name='profile'),
|
||||||
url(r'^logout/$', auth_views.logout, {'next_page': settings.LOGOUT_REDIRECT_URL}, name='logout'),
|
url(r'^logout/$', auth_views.logout, {'next_page': settings.LOGOUT_REDIRECT_URL}, name='logout'),
|
||||||
|
url(r'^admin/participants/$', views.participants, name='participants'),
|
||||||
url(r'', include('django.contrib.auth.urls')),
|
url(r'', include('django.contrib.auth.urls')),
|
||||||
]
|
]
|
||||||
|
|
8
accounts/utils.py
Normal file
8
accounts/utils.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
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')
|
|
@ -1,8 +1,12 @@
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.shortcuts import render
|
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 .forms import ProfileForm, UserForm
|
from .forms import ProfileForm, UserForm
|
||||||
|
from .models import Participation
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -20,3 +24,14 @@ def profile(request):
|
||||||
messages.error(request, 'Please correct those errors.')
|
messages.error(request, 'Please correct those errors.')
|
||||||
|
|
||||||
return render(request, 'accounts/profile.html', {'forms': forms})
|
return render(request, 'accounts/profile.html', {'forms': forms})
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def participants(request):
|
||||||
|
|
||||||
|
if not request.user.is_superuser:
|
||||||
|
raise PermissionDenied()
|
||||||
|
|
||||||
|
participation_list = Participation.on_site.all()
|
||||||
|
|
||||||
|
return render(request, 'admin/participants.html', {'participation_list': participation_list})
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import Conversation, Message
|
from .models import ConversationWithParticipant, ConversationAboutTalk, Message
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Conversation)
|
admin.site.register(ConversationWithParticipant)
|
||||||
|
admin.site.register(ConversationAboutTalk)
|
||||||
admin.site.register(Message)
|
admin.site.register(Message)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.9.7 on 2016-06-13 18:50
|
# Generated by Django 1.9.7 on 2016-06-14 19:13
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import conversations.utils
|
import conversations.utils
|
||||||
|
@ -15,26 +15,42 @@ class Migration(migrations.Migration):
|
||||||
dependencies = [
|
dependencies = [
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
('accounts', '0001_initial'),
|
('accounts', '0001_initial'),
|
||||||
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Conversation',
|
name='ConversationAboutTalk',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('subscribers', models.ManyToManyField(related_name='_conversationabouttalk_subscribers_+', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ConversationWithParticipant',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('participation', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='conversation', to='accounts.Participation')),
|
('participation', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='conversation', to='accounts.Participation')),
|
||||||
('subscribers', models.ManyToManyField(related_name='_conversation_subscribers_+', to=settings.AUTH_USER_MODEL)),
|
('subscribers', models.ManyToManyField(related_name='_conversationwithparticipant_subscribers_+', to=settings.AUTH_USER_MODEL)),
|
||||||
],
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Message',
|
name='Message',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('object_id', models.PositiveIntegerField()),
|
||||||
('token', models.CharField(default=conversations.utils.generate_message_token, max_length=64)),
|
('token', models.CharField(default=conversations.utils.generate_message_token, max_length=64)),
|
||||||
('date', models.DateTimeField(auto_now_add=True)),
|
('date', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('subject', models.CharField(blank=True, max_length=64)),
|
||||||
('content', models.TextField()),
|
('content', models.TextField()),
|
||||||
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
('conversation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='conversations.Conversation')),
|
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'ordering': ['date'],
|
'ordering': ['date'],
|
||||||
|
|
24
conversations/migrations/0002_conversationabouttalk_talk.py
Normal file
24
conversations/migrations/0002_conversationabouttalk_talk.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.7 on 2016-06-14 19:13
|
||||||
|
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'),
|
||||||
|
),
|
||||||
|
]
|
30
conversations/migrations/0003_auto_20160614_2046.py
Normal file
30
conversations/migrations/0003_auto_20160614_2046.py
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.7 on 2016-06-14 20:46
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('conversations', '0002_conversationabouttalk_talk'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='message',
|
||||||
|
name='subject',
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='conversationabouttalk',
|
||||||
|
name='subscribers',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='_conversationabouttalk_subscribers_+', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='conversationwithparticipant',
|
||||||
|
name='subscribers',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='_conversationwithparticipant_subscribers_+', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,22 +1,19 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.auth.models import User
|
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 .utils import generate_message_token, notify_by_email
|
||||||
from accounts.models import Participation
|
from accounts.models import Participation
|
||||||
from .utils import generate_message_token
|
from proposals.models import Talk
|
||||||
|
|
||||||
|
|
||||||
class Conversation(models.Model):
|
|
||||||
|
|
||||||
participation = models.OneToOneField(Participation, related_name='conversation')
|
|
||||||
subscribers = models.ManyToManyField(User, related_name='+')
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "Conversation with %s" % self.participation.user
|
|
||||||
|
|
||||||
|
|
||||||
class Message(models.Model):
|
class Message(models.Model):
|
||||||
|
|
||||||
conversation = models.ForeignKey(Conversation, related_name='messages')
|
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)
|
token = models.CharField(max_length=64, default=generate_message_token)
|
||||||
|
|
||||||
|
@ -29,3 +26,90 @@ class Message(models.Model):
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Message from %s" % self.author
|
return "Message from %s" % self.author
|
||||||
|
|
||||||
|
|
||||||
|
class Conversation(models.Model):
|
||||||
|
|
||||||
|
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 get_site(self):
|
||||||
|
return self.participation.site
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Conversation with %s" % self.participation.user
|
||||||
|
|
||||||
|
def new_message(self, message):
|
||||||
|
site = self.get_site()
|
||||||
|
subject = '[%s] Message notification' % site.name
|
||||||
|
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('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:
|
||||||
|
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 get_site(self):
|
||||||
|
return self.talk.site
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Conversation about %s" % self.talk.title
|
||||||
|
|
||||||
|
def new_message(self, message):
|
||||||
|
site = self.get_site()
|
||||||
|
first = self.messages.first()
|
||||||
|
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-speaker', args=[message.author.username])
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
if message.author not in self.subscribers.all():
|
||||||
|
self.subscribers.add(message.author)
|
||||||
|
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,86 +1,48 @@
|
||||||
from django.db.models.signals import post_save
|
from django.db.models.signals import post_save, m2m_changed
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.template.loader import render_to_string
|
from django.contrib.sites.shortcuts import get_current_site
|
||||||
from django.core import mail
|
from django.contrib.auth.models import User
|
||||||
from django.core.mail import EmailMultiAlternatives
|
|
||||||
|
|
||||||
|
from .models import ConversationWithParticipant, ConversationAboutTalk, Message
|
||||||
from .models import Conversation, Message
|
from .utils import notify_by_email
|
||||||
from .utils import get_reply_addr
|
|
||||||
from proposals.models import Talk, Topic
|
from proposals.models import Talk, Topic
|
||||||
|
from proposals.signals import new_talk
|
||||||
from accounts.models import Participation
|
from accounts.models import Participation
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Participation, dispatch_uid="Create Conversation")
|
@receiver(post_save, sender=Participation, dispatch_uid="Create ConversationWithParticipant")
|
||||||
def create_conversation(sender, instance, created, **kwargs):
|
def create_conversation_with_participant(sender, instance, created, **kwargs):
|
||||||
if not created:
|
if not created:
|
||||||
return
|
return
|
||||||
conversation = Conversation(participation=instance).save()
|
conversation = ConversationWithParticipant(participation=instance)
|
||||||
|
conversation.save()
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_save, sender=Talk, dispatch_uid="Create ConversationAboutTalk")
|
||||||
|
def create_conversation_about_talk(sender, instance, created, **kwargs):
|
||||||
|
if not created:
|
||||||
|
return
|
||||||
|
conversation = ConversationAboutTalk(talk=instance)
|
||||||
|
conversation.save()
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(new_talk, dispatch_uid="Notify new talk")
|
||||||
|
def notify_new_talk(sender, instance, **kwargs):
|
||||||
|
# Subscribe reviewer for these topics to the conversation
|
||||||
|
topics = instance.topics.all()
|
||||||
|
reviewers = User.objects.filter(participation__review_topics=topics).all()
|
||||||
|
instance.conversation.subscribers.add(*reviewers)
|
||||||
|
# Notification of this new talk
|
||||||
|
message = Message(conversation=instance.conversation, author=instance.proposer,
|
||||||
|
content='The talk has been proposed.')
|
||||||
|
message.save()
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Message, dispatch_uid="Notify new message")
|
@receiver(post_save, sender=Message, dispatch_uid="Notify new message")
|
||||||
def notify_new_message(sender, instance, created, **kwargs):
|
def notify_new_message(sender, instance, created, **kwargs):
|
||||||
if not created:
|
if not created:
|
||||||
# We could send a modification notification
|
# Possibly send a modification notification?
|
||||||
return
|
return
|
||||||
message = instance
|
instance.conversation.new_message(instance)
|
||||||
conversation = message.conversation
|
|
||||||
site = conversation.participation.site
|
|
||||||
subject = site.name
|
|
||||||
sender = message.author
|
|
||||||
if sender != conversation.participation.user \
|
|
||||||
and sender not in conversation.subscribers:
|
|
||||||
conversation.subscribers.add(sender)
|
|
||||||
dests = list(conversation.subscribers.all())
|
|
||||||
data = {
|
|
||||||
'content': message.content,
|
|
||||||
'uri': site.domain + reverse('messaging'),
|
|
||||||
}
|
|
||||||
message_id = message.token
|
|
||||||
ref = None
|
|
||||||
if conversation.messages.first().id != message.id:
|
|
||||||
ref = conversation.messages.first().token
|
|
||||||
notify_by_email(data, 'new_message', subject, sender, dests, message_id, ref)
|
|
||||||
|
|
||||||
|
|
||||||
def notify_by_email(data, template, 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/%s.txt' % template, data)
|
|
||||||
html_message = render_to_string('conversations/%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, dests, reply_to, headers in mails:
|
|
||||||
text_message, html_message = message
|
|
||||||
msg = EmailMultiAlternatives(subject, text_message, from_email, dests, 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,18 +1,20 @@
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block messagingtab %} class="active"{% endblock %}
|
{% block admintab %} active{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h1>Messaging</h1>
|
<h1>Messaging</h1>
|
||||||
<p>You can use this page to communicate with the staff.</p>
|
{% block heading %}
|
||||||
|
<a href="{% url 'correspondents' %}" class="btn btn-primary"><span class="glyphicon glyphicon-arrow-left"></span> Go back to correspondents list</a>
|
||||||
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% for message in message_list %}
|
{% for message in message_list %}
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-{% block panelstyleblock %}default{% endblock %}">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
{{ message.date }} | from {{ message.author.profile }}
|
{{ message.date }} | {{ message.author.profile }}
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
{{ message.content }}
|
{{ message.content }}
|
38
conversations/templates/conversations/correspondents.html
Normal file
38
conversations/templates/conversations/correspondents.html
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
|
||||||
|
{% block admintab %} active{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>Correspondents</h1>
|
||||||
|
This is the list of participants that you follow.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="table table-striped">
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Username</th>
|
||||||
|
<th>Fullname</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 '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 'correspondents' %}" data-toggle="tooltip" data-placement="bottom" title="Unsubscribe to conversation"><span class="glyphicon glyphicon-star"></span></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'subscribe-conversation' correspondent.user.username %}?next={% url 'correspondents' %}" data-toggle="tooltip" data-placement="bottom" title="Subscribe to conversation"><span class="glyphicon glyphicon-star-empty"></span></a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -1,4 +1,4 @@
|
||||||
{{ content|safe }}
|
{{ content }}
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
{% if answering %}
|
{% if answering %}
|
|
@ -1,4 +1,4 @@
|
||||||
{{ content|safe }}
|
{{ content }}
|
||||||
|
|
||||||
--
|
--
|
||||||
Reply {% if answering %}to this email directly or view it {% endif %}online: https://{{ uri }}
|
Reply {% if answering %}to this email directly or view it {% endif %}online: https://{{ uri }}
|
|
@ -0,0 +1,11 @@
|
||||||
|
Hi!<br />
|
||||||
|
<br />
|
||||||
|
A <a href="https://{{ uri }}">new talk</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 }}</p>
|
||||||
|
{% if answering %}
|
||||||
|
<hr>
|
||||||
|
Reply to this email directly to comment this talk.{% endif %}
|
|
@ -0,0 +1,12 @@
|
||||||
|
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 %}
|
10
conversations/templates/conversations/inbox.html
Normal file
10
conversations/templates/conversations/inbox.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{% extends 'conversations/conversation.html' %}
|
||||||
|
|
||||||
|
{% block inboxtab %} class="active"{% endblock %}
|
||||||
|
{% block admintab %}{% endblock %}
|
||||||
|
|
||||||
|
{% block heading %}
|
||||||
|
<p>You can use this page to communicate with the staff.</p>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block panelstyleblock %}{% if message.author == message.conversation.participation.user %}info{% else %}success{% endif %}{% endblock %}
|
|
@ -1,13 +0,0 @@
|
||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<div class="h1">{{ conversation }}</div>
|
|
||||||
|
|
||||||
{# for message in conversation.messages %}
|
|
||||||
<p>{{ message }}</p>
|
|
||||||
{% endfor #}
|
|
||||||
|
|
||||||
<!-- TODO -->
|
|
||||||
|
|
||||||
{% endblock %}
|
|
|
@ -5,5 +5,9 @@ from conversations import views, emails
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^recv/$', emails.email_recv),
|
url(r'^recv/$', emails.email_recv),
|
||||||
url(r'^$', views.messaging, name='messaging'),
|
url(r'^inbox/$', views.conversation, name='inbox'),
|
||||||
|
url(r'^$', views.correspondents, name='correspondents'),
|
||||||
|
url(r'^with/(?P<username>[\w.@+-]+)/$', views.conversation, name='conversation'),
|
||||||
|
url(r'^subscribe/(?P<username>[\w.@+-]+)/$', views.subscribe, name='subscribe-conversation'),
|
||||||
|
url(r'^unsubscribe/(?P<username>[\w.@+-]+)/$', views.unsubscribe, name='unsubscribe-conversation'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.crypto import get_random_string
|
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
|
import hashlib
|
||||||
|
|
||||||
|
@ -29,3 +32,44 @@ def get_reply_addr(message_id, dest):
|
||||||
|
|
||||||
def generate_message_token():
|
def generate_message_token():
|
||||||
return get_random_string(length=60, allowed_chars='abcdefghijklmnopqrstuvwxyz0123456789')
|
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)
|
||||||
|
|
|
@ -3,6 +3,9 @@ from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.contrib.sites.shortcuts import get_current_site
|
from django.contrib.sites.shortcuts import get_current_site
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
|
||||||
from accounts.models import Participation
|
from accounts.models import Participation
|
||||||
|
@ -11,9 +14,18 @@ from .forms import MessageForm
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def messaging(request):
|
def conversation(request, username=None):
|
||||||
|
|
||||||
participation = get_object_or_404(Participation, user=request.user, site=get_current_site(request))
|
if username:
|
||||||
|
if not request.user.is_superuser:
|
||||||
|
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
|
conversation = participation.conversation
|
||||||
message_list = conversation.messages.all()
|
message_list = conversation.messages.all()
|
||||||
|
|
||||||
|
@ -23,11 +35,55 @@ def messaging(request):
|
||||||
message = form.save(commit=False)
|
message = form.save(commit=False)
|
||||||
message.conversation = conversation
|
message.conversation = conversation
|
||||||
message.author = request.user
|
message.author = request.user
|
||||||
|
message.subject = "Assistance request from %s" % message.author.profile
|
||||||
message.save()
|
message.save()
|
||||||
messages.success(request, 'Message sent!')
|
messages.success(request, 'Message sent!')
|
||||||
return redirect('messaging')
|
if username:
|
||||||
|
return redirect(reverse('conversation', args=[username]))
|
||||||
|
else:
|
||||||
|
return redirect('inbox')
|
||||||
|
|
||||||
return render(request, 'conversations/messaging.html', {
|
return render(request, template, {
|
||||||
'message_list': message_list,
|
'message_list': message_list,
|
||||||
'form': form,
|
'form': form,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def correspondents(request):
|
||||||
|
|
||||||
|
correspondent_list = Participation.on_site.filter(conversation__subscribers=request.user)
|
||||||
|
|
||||||
|
return render(request, 'conversations/correspondents.html', {
|
||||||
|
'correspondent_list': correspondent_list,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def subscribe(request, username):
|
||||||
|
|
||||||
|
# TODO check admin
|
||||||
|
|
||||||
|
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('conversation', args=[username])
|
||||||
|
|
||||||
|
return redirect(next_url)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def unsubscribe(request, username):
|
||||||
|
|
||||||
|
# TODO check admin
|
||||||
|
|
||||||
|
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('conversation', args=[username])
|
||||||
|
|
||||||
|
return redirect(next_url)
|
||||||
|
|
13
doc/reu-juin
Normal file
13
doc/reu-juin
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
responsable de track
|
||||||
|
|
||||||
|
timeline :
|
||||||
|
19 20 novembre
|
||||||
|
bâtiment A, *B* et C
|
||||||
|
mi octobre : boost logistique
|
||||||
|
mi septembre : convergence programme / sélection d’orateur
|
||||||
|
fin juin / début juillet : appel à participation
|
||||||
|
juin : sponsoring
|
||||||
|
|
||||||
|
attention au chevauchement d’horraire
|
||||||
|
defraiement
|
||||||
|
planing : attention si tous les horateurs arrivent et partent en même temps, afficher quand le mec est dispo
|
|
@ -53,14 +53,23 @@
|
||||||
<ul class="nav navbar-nav navbar-right">
|
<ul class="nav navbar-nav navbar-right">
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
{% if request.user.is_staff %}
|
{% if request.user.is_staff %}
|
||||||
<li><a href="{% url 'admin:index' %}"><span class="glyphicon glyphicon-cog"></span> Administration</a></li>
|
<li class="dropdown{% block admintab %}{% endblock %}">
|
||||||
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><span class="glyphicon glyphicon-cog"></span> Administration <span class="caret"></span></a>
|
||||||
|
<ul class="dropdown-menu" role="menu">
|
||||||
|
<li role="presentation">
|
||||||
|
<a role="menuitem" tabindex="-1" href="{% url 'admin:index' %}"><span class="glyphicon glyphicon-dashboard"></span> Django Admin</a>
|
||||||
|
<a role="menuitem" tabindex="-1" href="{% url 'participants' %}"><span class="glyphicon glyphicon-user"></span> Participants</a>
|
||||||
|
<a role="menuitem" tabindex="-1" href="{% url 'correspondents' %}"><span class="glyphicon glyphicon-envelope"></span> Correspondence</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li{% block messagingtab %}{% endblock %}><a href="{% url 'messaging' %}" data-toggle="tooltip" data-placement="bottom" title="Messaging"><span class="glyphicon glyphicon-envelope"></span> Messaging</a></li>
|
<li{% block inboxtab %}{% endblock %}><a href="{% url 'inbox' %}" data-toggle="tooltip" data-placement="bottom" title="Inbox"><span class="glyphicon glyphicon-envelope"></span> Inbox</a></li>
|
||||||
<li{% block profiletab %}{% endblock %}><a href="{% url 'profile' %}" data-toggle="tooltip" data-placement="bottom" title="Profile"><span class="glyphicon glyphicon-user"></span> {{ request.user.username }}</a></li>
|
<li{% block profiletab %}{% endblock %}><a href="{% url 'profile' %}" data-toggle="tooltip" data-placement="bottom" title="Profile"><span class="glyphicon glyphicon-user"></span> {{ request.user.username }}</a></li>
|
||||||
<li><a href="{% url 'logout' %}" data-toggle="tooltip" data-placement="bottom" title="Logout"><span class="glyphicon glyphicon-log-out"></span></a></li>
|
<li><a href="{% url 'logout' %}" data-toggle="tooltip" data-placement="bottom" title="Logout"><span class="glyphicon glyphicon-log-out"></span></a></li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li><a href="{% url 'login' %}?next={{ request.path }}"><span class="glyphicon glyphicon-log-in"></span> Login</a></li>
|
<li{% block registrationtab %}{% endblock %}><a href="{% url 'registration_register' %}"><span class="glyphicon glyphicon-edit"></span> Register</a></li>
|
||||||
<li><a href="{% url 'registration_register' %}"><span class="glyphicon glyphicon-edit"></span> Register</a></li>
|
<li{% block logintab %}{% endblock %}><a href="{% url 'login' %}?next={{ request.path }}"><span class="glyphicon glyphicon-log-in"></span> Login</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
{% block navbar-right %}{% endblock %}
|
{% block navbar-right %}{% endblock %}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.9.7 on 2016-06-13 18:50
|
# Generated by Django 1.9.7 on 2016-06-14 19:13
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import autoslug.fields
|
import autoslug.fields
|
||||||
|
@ -15,8 +15,8 @@ class Migration(migrations.Migration):
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('sites', '0002_alter_domain_unique'),
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('sites', '0002_alter_domain_unique'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -39,6 +39,7 @@ class Migration(migrations.Migration):
|
||||||
('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='title', unique=True)),
|
('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='title', unique=True)),
|
||||||
('description', models.TextField(blank=True, verbose_name='Description')),
|
('description', models.TextField(blank=True, verbose_name='Description')),
|
||||||
('event', models.IntegerField(choices=[(1, 'conference'), (2, 'workshop'), (3, 'stand'), (4, 'other')], default=1)),
|
('event', models.IntegerField(choices=[(1, 'conference'), (2, 'workshop'), (3, 'stand'), (4, 'other')], default=1)),
|
||||||
|
('proposer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL)),
|
||||||
('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sites.Site')),
|
('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sites.Site')),
|
||||||
('speakers', models.ManyToManyField(through='proposals.Speech', to=settings.AUTH_USER_MODEL)),
|
('speakers', models.ManyToManyField(through='proposals.Speech', to=settings.AUTH_USER_MODEL)),
|
||||||
],
|
],
|
||||||
|
@ -67,6 +68,6 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
migrations.AlterUniqueTogether(
|
migrations.AlterUniqueTogether(
|
||||||
name='speech',
|
name='speech',
|
||||||
unique_together=set([('speaker', 'talk'), ('order', 'talk')]),
|
unique_together=set([('order', 'talk'), ('speaker', 'talk')]),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -8,7 +8,8 @@ from django.db import models
|
||||||
|
|
||||||
from autoslug import AutoSlugField
|
from autoslug import AutoSlugField
|
||||||
|
|
||||||
from accounts.models import enum_to_choices
|
from accounts.utils import enum_to_choices
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['Topic', 'Talk', 'Speech']
|
__all__ = ['Topic', 'Talk', 'Speech']
|
||||||
|
|
||||||
|
@ -31,6 +32,7 @@ class Talk(models.Model):
|
||||||
|
|
||||||
site = models.ForeignKey(Site, on_delete=models.CASCADE)
|
site = models.ForeignKey(Site, on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
proposer = models.ForeignKey(User, related_name='+')
|
||||||
speakers = models.ManyToManyField(User, through='Speech')
|
speakers = models.ManyToManyField(User, through='Speech')
|
||||||
title = models.CharField(max_length=128, verbose_name='Title')
|
title = models.CharField(max_length=128, verbose_name='Title')
|
||||||
slug = AutoSlugField(populate_from='title', unique=True)
|
slug = AutoSlugField(populate_from='title', unique=True)
|
||||||
|
@ -47,6 +49,19 @@ class Talk(models.Model):
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('show-talk', kwargs={'slug': self.slug})
|
return reverse('show-talk', kwargs={'slug': self.slug})
|
||||||
|
|
||||||
|
def is_editable_by(self, user):
|
||||||
|
if user.is_superuser:
|
||||||
|
return True
|
||||||
|
if user == self.proposer:
|
||||||
|
return True
|
||||||
|
if user in talk.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()
|
||||||
|
|
||||||
|
|
||||||
class Speech(models.Model):
|
class Speech(models.Model):
|
||||||
|
|
||||||
|
|
4
proposals/signals.py
Normal file
4
proposals/signals.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
from django.dispatch import Signal
|
||||||
|
|
||||||
|
|
||||||
|
new_talk = Signal(providing_args=["sender", "instance"])
|
|
@ -6,7 +6,9 @@
|
||||||
|
|
||||||
<h1>{{ talk.title }}</h1>
|
<h1>{{ talk.title }}</h1>
|
||||||
|
|
||||||
|
{% if edit_perm %}
|
||||||
<a class="btn btn-primary" href="{% url 'edit-talk' talk.slug %}">edit</a><br />
|
<a class="btn btn-primary" href="{% url 'edit-talk' talk.slug %}">edit</a><br />
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<p>{{ talk.get_event_display }}</p>
|
<p>{{ talk.get_event_display }}</p>
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ from django.views.generic import DetailView, ListView
|
||||||
|
|
||||||
from proposals.forms import TalkForm
|
from proposals.forms import TalkForm
|
||||||
from proposals.models import Speech, Talk, Topic
|
from proposals.models import Speech, Talk, Topic
|
||||||
|
from .signals import new_talk
|
||||||
|
|
||||||
|
|
||||||
def home(request):
|
def home(request):
|
||||||
|
@ -47,8 +48,7 @@ def talk_edit(request, talk=None):
|
||||||
talk = get_object_or_404(Talk, slug=talk)
|
talk = get_object_or_404(Talk, slug=talk)
|
||||||
if talk.site != get_current_site(request):
|
if talk.site != get_current_site(request):
|
||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
if not request.user.is_superuser and request.user not in talk.speakers.all():
|
if not talk.has_perm(request.user):
|
||||||
# FIXME fine permissions
|
|
||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
form = TalkForm(request.POST or None, instance=talk)
|
form = TalkForm(request.POST or None, instance=talk)
|
||||||
if request.method == 'POST' and form.is_valid():
|
if request.method == 'POST' and form.is_valid():
|
||||||
|
@ -56,12 +56,13 @@ def talk_edit(request, talk=None):
|
||||||
talk = form.save()
|
talk = form.save()
|
||||||
messages.success(request, 'Talk modified successfully!')
|
messages.success(request, 'Talk modified successfully!')
|
||||||
else:
|
else:
|
||||||
site = get_current_site(request)
|
|
||||||
talk = form.save(commit=False)
|
talk = form.save(commit=False)
|
||||||
talk.site = site
|
talk.site = get_current_site(request)
|
||||||
|
talk.proposer = request.user
|
||||||
talk.save()
|
talk.save()
|
||||||
form.save_m2m()
|
form.save_m2m()
|
||||||
Speech.objects.create(speaker=request.user, talk=talk)
|
Speech.objects.create(speaker=request.user, talk=talk)
|
||||||
|
new_talk.send(talk.__class__, instance=talk)
|
||||||
messages.success(request, 'Talk proposed successfully!')
|
messages.success(request, 'Talk proposed successfully!')
|
||||||
return redirect(talk.get_absolute_url())
|
return redirect(talk.get_absolute_url())
|
||||||
return render(request, 'proposals/talk_edit.html', {
|
return render(request, 'proposals/talk_edit.html', {
|
||||||
|
@ -71,6 +72,10 @@ def talk_edit(request, talk=None):
|
||||||
|
|
||||||
class TalkDetail(LoginRequiredMixin, DetailView):
|
class TalkDetail(LoginRequiredMixin, DetailView):
|
||||||
queryset = Talk.on_site.all()
|
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
|
||||||
|
|
||||||
|
|
||||||
class TopicList(LoginRequiredMixin, ListView):
|
class TopicList(LoginRequiredMixin, ListView):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user