forked from AFPy/PonyConf
major overhaul of proposal process
This commit is contained in:
parent
72f254b3d3
commit
1f5377f38d
|
@ -1,9 +1,22 @@
|
|||
from functools import wraps
|
||||
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from functools import wraps
|
||||
|
||||
from cfp.utils import is_staff
|
||||
from cfp.models import Participant
|
||||
|
||||
|
||||
def speaker_required(view_func):
|
||||
def wrapped_view(request, **kwargs):
|
||||
speaker_token = kwargs.pop('speaker_token')
|
||||
# TODO v3: if no speaker token is provided, we should check for a logged user, and if so,
|
||||
# we should check if his/her participating at current conference
|
||||
speaker = get_object_or_404(Participant, site=request.conference.site, token=speaker_token)
|
||||
kwargs['speaker'] = speaker
|
||||
return view_func(request, **kwargs)
|
||||
return wraps(view_func)(wrapped_view)
|
||||
|
||||
|
||||
def staff_required(view_func):
|
||||
|
|
70
cfp/forms.py
70
cfp/forms.py
|
@ -36,6 +36,27 @@ CONFIRMATION_VALUES = [
|
|||
]
|
||||
|
||||
|
||||
class OnSiteNamedModelForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.conference = kwargs.pop('conference')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# we should manually check (site, name) uniqueness as the site is not part of the form
|
||||
def clean_name(self):
|
||||
name = self.cleaned_data['name']
|
||||
if (not self.instance or self.instance.name != name) \
|
||||
and self._meta.model.objects.filter(site=self.conference.site, name=name).exists():
|
||||
raise self.instance.unique_error_message(self._meta.model, ['name'])
|
||||
return name
|
||||
|
||||
def save(self, commit=True):
|
||||
obj = super().save(commit=False)
|
||||
obj.site = self.conference.site
|
||||
if commit:
|
||||
obj.save()
|
||||
return obj
|
||||
|
||||
|
||||
class VolunteerFilterForm(forms.Form):
|
||||
activity = forms.MultipleChoiceField(
|
||||
label=_('Activity'),
|
||||
|
@ -174,15 +195,29 @@ class TalkActionForm(forms.Form):
|
|||
self.fields['room'].choices = [(None, "---------")] + list(rooms.values_list('slug', 'name'))
|
||||
|
||||
|
||||
ParticipantForm = modelform_factory(Participant, fields=('name', 'email', 'biography'))
|
||||
class ParticipantForm(OnSiteNamedModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
social = kwargs.pop('social', True)
|
||||
super().__init__(*args, **kwargs)
|
||||
if not social:
|
||||
for field in ['twitter', 'linkedin', 'github', 'website', 'facebook', 'mastodon']:
|
||||
self.fields.pop(field)
|
||||
|
||||
class Meta:
|
||||
model = Participant
|
||||
fields = ['name', 'email', 'biography', 'twitter', 'linkedin', 'github', 'website', 'facebook', 'mastodon']
|
||||
|
||||
def clean_email(self):
|
||||
email = self.cleaned_data['email']
|
||||
if (not self.instance or self.instance.email != email) \
|
||||
and self._meta.model.objects.filter(site=self.conference.site, email=email).exists():
|
||||
raise self.instance.unique_error_message(self._meta.model, ['email'])
|
||||
return email
|
||||
|
||||
|
||||
class ParticipantStaffForm(ParticipantForm):
|
||||
class Meta(ParticipantForm.Meta):
|
||||
fields = ('name', 'vip', 'email', 'biography')
|
||||
labels = {
|
||||
'name': _('Name'),
|
||||
}
|
||||
fields = ['name', 'vip', 'email', 'phone_number', 'notes'] + ParticipantForm.Meta.fields[3:]
|
||||
|
||||
|
||||
class ParticipantFilterForm(forms.Form):
|
||||
|
@ -220,6 +255,10 @@ class ParticipantFilterForm(forms.Form):
|
|||
self.fields['track'].choices = [('none', _('Not assigned'))] + list(tracks.values_list('slug', 'name'))
|
||||
|
||||
|
||||
class MailForm(forms.Form):
|
||||
email = forms.EmailField(required=True, label=_('Email'))
|
||||
|
||||
|
||||
class UsersWidget(ModelSelect2MultipleWidget):
|
||||
model = User
|
||||
search_fields = [ '%s__icontains' % field for field in UserAdmin.search_fields ]
|
||||
|
@ -273,27 +312,6 @@ class CreateUserForm(forms.ModelForm):
|
|||
return user
|
||||
|
||||
|
||||
class OnSiteNamedModelForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.conference = kwargs.pop('conference')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# we should manually check (site, name) uniqueness as the site is not part of the form
|
||||
def clean_name(self):
|
||||
name = self.cleaned_data['name']
|
||||
if (not self.instance or self.instance.name != name) \
|
||||
and self._meta.model.objects.filter(site=self.conference.site, name=name).exists():
|
||||
raise self.instance.unique_error_message(self._meta.model, ['name'])
|
||||
return name
|
||||
|
||||
def save(self, commit=True):
|
||||
obj = super().save(commit=False)
|
||||
obj.site = self.conference.site
|
||||
if commit:
|
||||
obj.save()
|
||||
return obj
|
||||
|
||||
|
||||
class TrackForm(OnSiteNamedModelForm):
|
||||
class Meta:
|
||||
model = Track
|
||||
|
|
25
cfp/migrations/0018_auto_20171104_1227.py
Normal file
25
cfp/migrations/0018_auto_20171104_1227.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.5 on 2017-11-04 12:27
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cfp', '0017_auto_20171103_1922'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='conference',
|
||||
name='acceptances_disclosure_date',
|
||||
field=models.DateTimeField(blank=True, default=None, null=True, verbose_name='Acceptances disclosure date'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='participant',
|
||||
name='name',
|
||||
field=models.CharField(max_length=128, verbose_name='Name'),
|
||||
),
|
||||
]
|
|
@ -34,6 +34,7 @@ class Conference(models.Model):
|
|||
reply_email = models.CharField(max_length=100, blank=True, verbose_name=_('Reply email'))
|
||||
staff = models.ManyToManyField(User, blank=True, verbose_name=_('Staff members'))
|
||||
secure_domain = models.BooleanField(default=True, verbose_name=_('Secure domain (HTTPS)'))
|
||||
acceptances_disclosure_date = models.DateTimeField(null=True, blank=True, default=None, verbose_name=_('Acceptances disclosure date'))
|
||||
schedule_publishing_date = models.DateTimeField(null=True, blank=True, default=None, verbose_name=_('Schedule publishing date'))
|
||||
schedule_redirection_url = models.URLField(blank=True, default='', verbose_name=_('Schedule redirection URL'),
|
||||
help_text=_('If specified, schedule tab will redirect to this URL.'))
|
||||
|
@ -56,6 +57,11 @@ class Conference(models.Model):
|
|||
.filter(Q(opening_date__isnull=True) | Q(opening_date__lte=now))\
|
||||
.filter(Q(closing_date__isnull=True) | Q(closing_date__gte=now))
|
||||
|
||||
@property
|
||||
def disclosed_acceptances(self):
|
||||
# acceptances are automatically disclosed if the schedule is published
|
||||
return self.acceptances_disclosure_date and self.acceptances_disclosure_date <= timezone.now() or self.schedule_available
|
||||
|
||||
@property
|
||||
def schedule_available(self):
|
||||
return self.schedule_publishing_date and self.schedule_publishing_date <= timezone.now()
|
||||
|
@ -90,7 +96,7 @@ class ParticipantManager(models.Manager):
|
|||
|
||||
class Participant(PonyConfModel):
|
||||
site = models.ForeignKey(Site, on_delete=models.CASCADE)
|
||||
name = models.CharField(max_length=128, verbose_name=_('Your Name'))
|
||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||
email = models.EmailField()
|
||||
biography = models.TextField(verbose_name=_('Biography'))
|
||||
token = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
|
||||
|
@ -110,10 +116,11 @@ class Participant(PonyConfModel):
|
|||
objects = ParticipantManager()
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('participant-details', kwargs={'participant_id': self.token})
|
||||
return reverse('proposal-dashboard', kwargs={'speaker_token': self.token})
|
||||
|
||||
class Meta:
|
||||
# A User can participe only once to a Conference (= Site)
|
||||
unique_together = ('site', 'name')
|
||||
unique_together = ('site', 'email')
|
||||
|
||||
def __str__(self):
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block proposetab %} class="active"{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>
|
||||
{% trans "Participate" %}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<h2>{% trans "Sorry, the Call for Participation is closed!" %}</h2>
|
||||
|
||||
{% endblock %}
|
|
@ -1,27 +0,0 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% load ponyconf_tags i18n %}
|
||||
|
||||
{% block proposetab %} class="active"{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>
|
||||
{% trans "Your proposition have been successfully submitted!" %}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p>{% trans "Thanks for your proposal" %} {{ participant }} !</p>
|
||||
<p>{% trans "You can at anytime:" %}
|
||||
<ul>
|
||||
<li>{% trans "Edit your talk:" %} <a href="{% url 'talk-proposal-edit' talk.token participant.token %}">{% if request.is_secure %}https{% else %}http{% endif %}://{{ conference.site.domain }}{% url 'talk-proposal-edit' talk.token participant.token %}</a></li>
|
||||
<li>{% trans "Add an additionnal speaker:" %} <a href="{% url 'talk-proposal-speaker-add' talk.token %}">{% if request.is_secure %}https{% else %}http{% endif %}://{{ conference.site.domain }}{% url 'talk-proposal-speaker-add' talk.token %}</a></li>
|
||||
<li>{% trans "Edit your profile:" %} <a href="{% url 'talk-proposal-speaker-edit' talk.token participant.token %}">{% if request.is_secure %}https{% else %}http{% endif %}://{{ conference.site.domain }}{% url 'talk-proposal-speaker-edit' talk.token participant.token %}</a></li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>{% trans "An email has been sent to you with those URLs" %}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
77
cfp/templates/cfp/proposal_dashboard.html
Normal file
77
cfp/templates/cfp/proposal_dashboard.html
Normal file
|
@ -0,0 +1,77 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n crispy_forms_tags cfp_tags %}
|
||||
|
||||
{% load ponyconf_tags i18n %}
|
||||
|
||||
{% block proposetab %} class="active"{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>
|
||||
{% blocktrans with name=speaker.name %}Welcome <b>{{ name }}</b>!{% endblocktrans %}
|
||||
<a href="{% url 'proposal-profile-edit' speaker_token=speaker.token %}" class="btn btn-success pull-right">
|
||||
<span class="glyphicon glyphicon-pencil"></span> {% trans "Edit your profile" %}
|
||||
</a>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<h3>{% trans "Your informations" %}</h3>
|
||||
|
||||
<p>
|
||||
<ul>
|
||||
<li><b>{% trans "E-mail:" %}</b> <a href="mailto:{{ speaker.email }}">{{ speaker.email }}</a></li>
|
||||
{% if speaker.twitter %}<li><b>{% trans "Twitter:" %}</b> <a href="{{ speaker.twitter }}">{{ speaker.twitter }}</a></li>{% endif %}
|
||||
{% if speaker.linkedin %}<li><b>{% trans "LinkedIn:" %}</b> <a href="{{ speaker.linkedin }}">{{ speaker.linkedin }}</a></li>{% endif %}
|
||||
{% if speaker.github %}<li><b>{% trans "Github:" %}</b> <a href="{{ speaker.github }}">{{ speaker.github }}</a></li>{% endif %}
|
||||
{% if speaker.website %}<li><b>{% trans "Website:" %}</b> <a href="{{ speaker.website }}">{{ speaker.website }}</a></li>{% endif %}
|
||||
{% if speaker.facebook %}<li><b>{% trans "Facebook:" %}</b> <a href="{{ speaker.facebook }}">{{ speaker.facebook }}</a></li>{% endif %}
|
||||
{% if speaker.mastodon %}<li><b>{% trans "Mastodon:" %}</b> <a href="{{ speaker.mastodon }}">{{ speaker.mastodon }}</a></li>{% endif %}
|
||||
{% if speaker.phone_number %}<li><b>{% trans "Phone number:" %}</b> {{ speaker.phone_number }}</li>{% endif %}
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<h3>{% trans "Biography" %}</h3>
|
||||
|
||||
<p>
|
||||
{% if speaker.biography %}
|
||||
{{ speaker.biography|linebreaksbr }}
|
||||
{% else %}
|
||||
<i>{% trans "No biography." %}</i>
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
<h3>{% trans "Your proposals" %}</h3>
|
||||
|
||||
<p>
|
||||
{% for talk in talks %}
|
||||
{% if forloop.first %}
|
||||
<ul>
|
||||
{% endif %}
|
||||
<li>
|
||||
<a href="{% url 'proposal-talk-details' speaker_token=speaker.token talk_id=talk.pk %}"><b>{{ talk }}</b></a>
|
||||
{% for spkr in talk.speakers|exclude:speaker %}
|
||||
{% if forloop.first %}{% trans "with" %}{% endif %}
|
||||
{{ spkr }}
|
||||
{% if forloop.revcounter == 2 %} {% trans "and" %} {% elif not forloop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
{% if conference.disclosed_acceptances and talk.accepted %}
|
||||
{% if talk.confirmed is None %}
|
||||
<span class="label label-info">{% trans "you must confirm you participation" %}</span>
|
||||
{% elif talk.confirmed %}
|
||||
<span class="label label-success">{% trans "accepted" %}</span>
|
||||
{% else %}
|
||||
<span class="label label-danger">{% trans "cancelled" %}</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</li>
|
||||
{% if forloop.last %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% empty %}
|
||||
<i>{% trans "No proposals." %}</i>
|
||||
{% endfor %}
|
||||
</p>
|
||||
<p>
|
||||
<a href="{% url 'proposal-talk-add' speaker_token=speaker.token %}" class="btn btn-primary">{% trans "New proposal" %}</a>
|
||||
</p>
|
||||
{% endblock %}
|
33
cfp/templates/cfp/proposal_home.html
Normal file
33
cfp/templates/cfp/proposal_home.html
Normal file
|
@ -0,0 +1,33 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% load ponyconf_tags i18n %}
|
||||
|
||||
{% block proposetab %} class="active"{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="page-header">
|
||||
<h1>
|
||||
{% trans "Participate" %}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="col-md-8 col-md-offset-2 alert alert-info">
|
||||
<span class="glyphicon glyphicon-exclamation-sign"></span>
|
||||
{% url 'proposal-mail-token' as mail_token_url %}
|
||||
{% blocktrans %}If you already have submitted a talk and you want to edit it or submit another one, please click <a href="{{ mail_token_url }}">here</a>.{% endblocktrans %}
|
||||
</div>
|
||||
<form method="POST" class="form-horizontal col-md-8 col-md-offset-2">
|
||||
{% csrf_token %}
|
||||
{{ speaker_form|crispy }}
|
||||
{{ talk_form|crispy }}
|
||||
<div class="col-md-12 text-center">
|
||||
<button type="submit" class="btn btn-primary text-center">{% trans "Save" %} <i class="fa fa-check"></i></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
30
cfp/templates/cfp/proposal_mail_token.html
Normal file
30
cfp/templates/cfp/proposal_mail_token.html
Normal file
|
@ -0,0 +1,30 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% load ponyconf_tags i18n %}
|
||||
|
||||
{% block proposetab %} class="active"{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="page-header">
|
||||
<h1>
|
||||
{% trans "Access an existing profile" %}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form method="POST" class="form-horizontal col-md-8 col-md-offset-2">
|
||||
{% csrf_token %}
|
||||
<div class="form-group">
|
||||
{% blocktrans %}To receive a email with a link to access your profile, please enter your email below.{% endblocktrans %}
|
||||
</div>
|
||||
{{ form|crispy }}
|
||||
<div class="col-md-12 text-center">
|
||||
<button type="submit" class="btn btn-primary text-center">{% trans "Save" %} <i class="fa fa-check"></i></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
42
cfp/templates/cfp/proposal_speaker_form.html
Normal file
42
cfp/templates/cfp/proposal_speaker_form.html
Normal file
|
@ -0,0 +1,42 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% load ponyconf_tags i18n %}
|
||||
|
||||
{% block proposetab %} class="active"{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>
|
||||
{% if talk %}
|
||||
{% if co_speaker %}
|
||||
{% trans "Edit a speaker" %}
|
||||
{% else %}
|
||||
{% trans "Add a co-speaker" %}
|
||||
<a href="{% url 'proposal-talk-details' speaker_token=speaker.token talk_id=talk.pk %}" class="btn btn-primary pull-right">
|
||||
<span class="glyphicon glyphicon-chevron-left"></span> {% trans "Go back to the talk" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% trans "Edit your profile" %}
|
||||
{% comment %}
|
||||
<a href="{% url 'proposal-dashboard' speaker_token=speaker.token %}" class="btn btn-primary pull-right">
|
||||
<span class="glyphicon glyphicon-chevron-left"></span> {% trans "My talks" %}
|
||||
</a>
|
||||
{% endcomment %}
|
||||
{% endif %}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form method="POST" class="form-horizontal col-md-8 col-md-offset-2">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
<div class="col-md-12 text-center">
|
||||
<button type="submit" class="btn btn-primary text-center">{% trans "Save" %} <i class="fa fa-check"></i></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
86
cfp/templates/cfp/proposal_talk_details.html
Normal file
86
cfp/templates/cfp/proposal_talk_details.html
Normal file
|
@ -0,0 +1,86 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n crispy_forms_tags %}
|
||||
|
||||
{% load ponyconf_tags i18n %}
|
||||
|
||||
{% block proposetab %} class="active"{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>
|
||||
{{ talk.title }}
|
||||
<div class="pull-right">
|
||||
<a href="{% url 'proposal-dashboard' speaker_token=speaker.token %}" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-chevron-left"></span> {% trans "My profile" %}
|
||||
</a>
|
||||
<a href="{% url 'proposal-talk-add' speaker_token=speaker.token %}" class="btn btn-info">
|
||||
<span class="glyphicon glyphicon-plus"></span> {% trans "New proposal" %}
|
||||
</a>
|
||||
<a href="{% url 'proposal-talk-edit' speaker_token=speaker.token talk_id=talk.pk %}" class="btn btn-success">
|
||||
<span class="glyphicon glyphicon-pencil"></span> {% trans "Edit this proposal" %}
|
||||
</a>
|
||||
</div>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<h3>{% trans "Status" %}</h3>
|
||||
|
||||
{% if not conference.disclosed_acceptances or talk.accepted is None %}
|
||||
<p>{% trans "Reviewing in progress, we will keep you informed by mail." %}</p>
|
||||
{% elif talk.accepted %}
|
||||
<p class="text-success">{% trans "Accepted!" %}</p>
|
||||
{% if talk.confirmed is None %}
|
||||
<p>
|
||||
{% trans "Please confirm your participation:" %}
|
||||
<a href="{% url 'proposal-talk-confirm' speaker_token=speaker.token talk_id=talk.pk %}" class="btn btn-success">{% trans "I will be there!" %}</a>
|
||||
<a href="{% url 'proposal-talk-desist' speaker_token=speaker.token talk_id=talk.pk %}" class="btn btn-danger">{% trans "Sorry, couldn't make it :-(" %}</a>
|
||||
</p>
|
||||
{% elif talk.confirmed %}
|
||||
<p><a href="{% url 'proposal-talk-desist' speaker_token=speaker.token talk_id=talk.pk %}" class="btn btn-danger">
|
||||
{% trans "Sorry, I have to cancel." %}
|
||||
</a></p>
|
||||
{% else %}
|
||||
<p><a href="{% url 'proposal-talk-confirm' speaker_token=speaker.token talk_id=talk.pk %}" class="btn btn-success">
|
||||
{% trans "Good news, I finally could be there!" %}
|
||||
</a></p>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<p>{% trans "Sorry, refused :-(" %}</p>
|
||||
{% endif %}
|
||||
|
||||
<h3>{% trans "Speakers" %}</h3>
|
||||
|
||||
<p>
|
||||
{% for spkr in talk.speakers.all %}
|
||||
{% if forloop.first %}<ul>{% endif %}
|
||||
<li>
|
||||
<a href="{% url 'proposal-speaker-edit' speaker_token=speaker.token talk_id=talk.pk co_speaker_id=spkr.pk %}">{{ spkr }}</a>
|
||||
{% if spkr.pk == speaker.pk %} ({% trans "you!" %}){% endif %}
|
||||
</li>
|
||||
{% if forloop.last %}</ul>{% endif %}
|
||||
{% endfor %}
|
||||
<a href="{% url 'proposal-speaker-add' speaker_token=speaker.token talk_id=talk.pk %}" class="btn btn-sm btn-success">
|
||||
<span class="glyphicon glyphicon-plus"></span> {% trans "Add a co-speaker" %}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<h3>{% trans "Description" %}</h3>
|
||||
|
||||
<p>
|
||||
{% if talk.description %}
|
||||
{{ talk.description|linebreaksbr }}
|
||||
{% else %}
|
||||
<i>{% trans "No description provided." %}</i>
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
<h3>{% trans "Message to organizers" %}</h3>
|
||||
|
||||
<p>
|
||||
{% if talk.notes %}
|
||||
{{ talk.notes|linebreaksbr }}
|
||||
{% else %}
|
||||
<i>{% trans "No description provided." %}</i>
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endblock %}
|
|
@ -8,7 +8,14 @@
|
|||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>
|
||||
{% trans "Participate" %}
|
||||
{% if talk %}
|
||||
{{ talk.title }}
|
||||
{% else %}
|
||||
{% trans "Submit a proposal" %}
|
||||
<a href="{% url 'proposal-dashboard' speaker_token=speaker.token %}" class="btn btn-primary pull-right">
|
||||
<span class="glyphicon glyphicon-chevron-left"></span> {% trans "Go back to proposals list" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
|
@ -16,8 +23,7 @@
|
|||
<div class="col-md-12">
|
||||
<form method="POST" class="form-horizontal col-md-8 col-md-offset-2">
|
||||
{% csrf_token %}
|
||||
{{ participant_form|crispy }}
|
||||
{{ talk_form|crispy }}
|
||||
{{ form|crispy }}
|
||||
<div class="col-md-12 text-center">
|
||||
<button type="submit" class="btn btn-primary text-center">{% trans "Save" %} <i class="fa fa-check"></i></button>
|
||||
</div>
|
|
@ -1,26 +0,0 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% load ponyconf_tags i18n %}
|
||||
|
||||
{% block proposetab %} class="active"{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>
|
||||
{% trans "Participate" %}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form method="POST" class="form-horizontal col-md-8 col-md-offset-2">
|
||||
{% csrf_token %}
|
||||
{{ participant_form|crispy }}
|
||||
<div class="col-md-12 text-center">
|
||||
<button type="submit" class="btn btn-primary text-center">{% trans "Save" %} <i class="fa fa-check"></i></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -17,3 +17,8 @@ def duration_format(value):
|
|||
hours = int(value/60)
|
||||
minutes = value%60
|
||||
return '%d h %02d' % (hours, minutes)
|
||||
|
||||
|
||||
@register.filter
|
||||
def exclude(queryset, excluded):
|
||||
return queryset.exclude(pk=excluded.pk)
|
||||
|
|
19
cfp/urls.py
19
cfp/urls.py
|
@ -4,12 +4,27 @@ from . import views
|
|||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.home, name='home'),
|
||||
url(r'^cfp/$', views.talk_proposal, name='talk-proposal'),
|
||||
# v1.1
|
||||
url(r'^cfp/$', views.proposal_home, name='proposal-home'),
|
||||
url(r'^cfp/token/$', views.proposal_mail_token, name='proposal-mail-token'),
|
||||
url(r'^cfp/(?P<speaker_token>[\w\-]+)/$', views.proposal_dashboard, name='proposal-dashboard'),
|
||||
url(r'^cfp/(?P<speaker_token>[\w\-]+)/profile/$', views.proposal_speaker_edit, name='proposal-profile-edit'),
|
||||
url(r'^cfp/(?P<speaker_token>[\w\-]+)/talk/add/$', views.proposal_talk_edit, name='proposal-talk-add'),
|
||||
url(r'^cfp/(?P<speaker_token>[\w\-]+)/talk/(?P<talk_id>[\w\-]+)/$', views.proposal_talk_details, name='proposal-talk-details'),
|
||||
url(r'^cfp/(?P<speaker_token>[\w\-]+)/talk/(?P<talk_id>[\w\-]+)/edit/$', views.proposal_talk_edit, name='proposal-talk-edit'),
|
||||
url(r'^cfp/(?P<speaker_token>[\w\-]+)/talk/(?P<talk_id>[\w\-]+)/speaker/add/$', views.proposal_speaker_edit, name='proposal-speaker-add'),
|
||||
url(r'^cfp/(?P<speaker_token>[\w\-]+)/talk/(?P<talk_id>[\w\-]+)/confirm/$', views.proposal_talk_acknowledgment, {'confirm': True}, name='proposal-talk-confirm'),
|
||||
url(r'^cfp/(?P<speaker_token>[\w\-]+)/talk/(?P<talk_id>[\w\-]+)/desist/$', views.proposal_talk_acknowledgment, {'confirm': False}, name='proposal-talk-desist'),
|
||||
#url(r'^cfp/(?P<speaker_token>[\w\-]+)/talk/(?P<talk_id>[\w\-]+)/speaker/(?P<co_speaker_id>[\w\-]+)/$', views.proposal_speaker_details, name='proposal-speaker-details'),
|
||||
url(r'^cfp/(?P<speaker_token>[\w\-]+)/talk/(?P<talk_id>[\w\-]+)/speaker/(?P<co_speaker_id>[\w\-]+)/edit/$', views.proposal_speaker_edit, name='proposal-speaker-edit'),
|
||||
url(r'^cfp/(?P<speaker_token>[\w\-]+)/talk/(?P<talk_id>[\w\-]+)/speaker/(?P<co_speaker_id>[\w\-]+)/remove/$', views.proposal_speaker_remove, name='proposal-speaker-remove'),
|
||||
# Backward compatibility
|
||||
url(r'^cfp/(?P<talk_id>[\w\-]+)/speaker/add/$', views.talk_proposal_speaker_edit, name='talk-proposal-speaker-add'),
|
||||
url(r'^cfp/(?P<talk_id>[\w\-]+)/speaker/(?P<participant_id>[\w\-]+)/$', views.talk_proposal_speaker_edit, name='talk-proposal-speaker-edit'),
|
||||
url(r'^cfp/(?P<talk_id>[\w\-]+)/(?P<participant_id>[\w\-]+)/$', views.talk_proposal, name='talk-proposal-edit'),
|
||||
url(r'^cfp/(?P<talk_id>[\w\-]+)/(?P<participant_id>[\w\-]+)/confirm/$', views.talk_acknowledgment, {'confirm': True}, name='talk-confirm'),
|
||||
url(r'^cfp/(?P<talk_id>[\w\-]+)/(?P<participant_id>[\w\-]+)/desist/$', views.talk_acknowledgment, {'confirm': False}, name='talk-desist'),
|
||||
# End backward compatibility
|
||||
url(r'^volunteer/$', views.volunteer_enrole, name='volunteer-enrole'),
|
||||
url(r'^volunteer/(?P<volunteer_id>[\w\-]+)/$', views.volunteer_home, name='volunteer-home'),
|
||||
url(r'^volunteer/(?P<volunteer_id>[\w\-]+)/join/(?P<activity>[\w\-]+)/$', views.volunteer_update_activity, {'join': True}, name='volunteer-join'),
|
||||
|
@ -41,7 +56,7 @@ urlpatterns = [
|
|||
url(r'^staff/schedule/((?P<program_format>[\w]+)/)?$', views.staff_schedule, name='staff-schedule'),
|
||||
url(r'^staff/select2/$', views.Select2View.as_view(), name='django_select2-json'),
|
||||
url(r'^admin/$', views.admin, name='admin'),
|
||||
url(r'^admin/conference/$', views.conference, name='conference'),
|
||||
url(r'^admin/conference/$', views.conference_edit, name='conference'),
|
||||
url(r'^schedule/((?P<program_format>[\w]+)/)?$', views.public_schedule, name='public-schedule'),
|
||||
#url(r'^markdown/$', views.markdown_preview, name='markdown'),
|
||||
]
|
||||
|
|
319
cfp/views.py
319
cfp/views.py
|
@ -20,14 +20,14 @@ from functools import reduce
|
|||
from mailing.models import Message
|
||||
from mailing.forms import MessageForm
|
||||
from .planning import Program
|
||||
from .decorators import staff_required
|
||||
from .decorators import speaker_required, staff_required
|
||||
from .mixins import StaffRequiredMixin, OnSiteMixin, OnSiteFormMixin
|
||||
from .utils import is_staff
|
||||
from .models import Participant, Talk, TalkCategory, Vote, Track, Tag, Room, Volunteer, Activity
|
||||
from .forms import TalkForm, TalkStaffForm, TalkFilterForm, TalkActionForm, \
|
||||
ParticipantForm, ParticipantStaffForm, ParticipantFilterForm, \
|
||||
ConferenceForm, CreateUserForm, TrackForm, RoomForm, \
|
||||
VolunteerForm, VolunteerFilterForm, \
|
||||
VolunteerForm, VolunteerFilterForm, MailForm, \
|
||||
ACCEPTATION_VALUES, CONFIRMATION_VALUES
|
||||
|
||||
|
||||
|
@ -35,7 +35,7 @@ def home(request):
|
|||
if request.conference.home:
|
||||
return render(request, 'cfp/home.html')
|
||||
else:
|
||||
return redirect(reverse('talk-proposal'))
|
||||
return redirect(reverse('proposal-home'))
|
||||
|
||||
|
||||
def volunteer_enrole(request):
|
||||
|
@ -139,43 +139,25 @@ def volunteer_details(request, volunteer_id):
|
|||
})
|
||||
|
||||
|
||||
def talk_proposal(request, talk_id=None, participant_id=None):
|
||||
conference = request.conference
|
||||
site = conference.site
|
||||
def proposal_home(request):
|
||||
if is_staff(request, request.user):
|
||||
categories = TalkCategory.objects.filter(site=site)
|
||||
categories = TalkCategory.objects.filter(site=request.conference.site)
|
||||
else:
|
||||
categories = conference.opened_categories
|
||||
talk = None
|
||||
participant = None
|
||||
|
||||
if talk_id and participant_id:
|
||||
talk = get_object_or_404(Talk, token=talk_id, site=site)
|
||||
participant = get_object_or_404(Participant, token=participant_id, site=site)
|
||||
elif not categories.exists():
|
||||
return render(request, 'cfp/closed.html')
|
||||
|
||||
participant_form = ParticipantForm(request.POST or None, instance=participant)
|
||||
talk_form = TalkForm(request.POST or None, categories=categories, instance=talk)
|
||||
|
||||
if request.method == 'POST' and talk_form.is_valid() and participant_form.is_valid():
|
||||
categories = request.conference.opened_categories
|
||||
speaker_form = ParticipantForm(request.POST or None, conference=request.conference, social=False)
|
||||
talk_form = TalkForm(request.POST or None, categories=categories)
|
||||
if request.method == 'POST' and all(map(lambda f: f.is_valid(), [speaker_form, talk_form])):
|
||||
speaker = speaker_form.save(commit=False)
|
||||
speaker.site = request.conference.site
|
||||
speaker.save()
|
||||
talk = talk_form.save(commit=False)
|
||||
talk.site = site
|
||||
|
||||
participant, created = Participant.objects.get_or_create(email=participant_form.cleaned_data['email'], site=site)
|
||||
participant_form = ParticipantForm(request.POST, instance=participant)
|
||||
participant = participant_form.save()
|
||||
participant.language = request.LANGUAGE_CODE
|
||||
participant.save()
|
||||
|
||||
talk.site = request.conference.site
|
||||
talk.save()
|
||||
talk.speakers.add(participant)
|
||||
|
||||
protocol = 'https' if request.is_secure() else 'http'
|
||||
base_url = protocol+'://'+site.domain
|
||||
url_talk_proposal_edit = base_url + reverse('talk-proposal-edit', args=[talk.token, participant.token])
|
||||
url_talk_proposal_speaker_add = base_url + reverse('talk-proposal-speaker-add', args=[talk.token])
|
||||
url_talk_proposal_speaker_edit = base_url + reverse('talk-proposal-speaker-edit', args=[talk.token, participant.token])
|
||||
talk.speakers.add(speaker)
|
||||
base_url = ('https' if request.is_secure() else 'http') + '://' + request.conference.site.domain
|
||||
url_dashboard = base_url + reverse('proposal-dashboard', kwargs=dict(speaker_token=speaker.token))
|
||||
url_talk_details = base_url + reverse('proposal-talk-details', kwargs=dict(speaker_token=speaker.token, talk_id=talk.pk))
|
||||
url_speaker_add = base_url + reverse('proposal-speaker-add', kwargs=dict(speaker_token=speaker.token, talk_id=talk.pk))
|
||||
body = _("""Hi {},
|
||||
|
||||
Your talk has been submitted for {}.
|
||||
|
@ -185,9 +167,9 @@ Title: {}
|
|||
Description: {}
|
||||
|
||||
You can at anytime:
|
||||
- edit your talk: {}
|
||||
- review and edit your profile: {}
|
||||
- review and edit your talk: {}
|
||||
- add a new co-speaker: {}
|
||||
- edit your profile: {}
|
||||
|
||||
If you have any question, your can answer to this email.
|
||||
|
||||
|
@ -195,89 +177,232 @@ Thanks!
|
|||
|
||||
{}
|
||||
|
||||
""").format(participant.name, conference.name, talk.title, talk.description, url_talk_proposal_edit, url_talk_proposal_speaker_add, url_talk_proposal_speaker_edit, conference.name)
|
||||
|
||||
""").format(
|
||||
speaker.name, request.conference.name,talk.title, talk.description,
|
||||
url_dashboard, url_talk_details, url_speaker_add,
|
||||
request.conference.name,
|
||||
)
|
||||
Message.objects.create(
|
||||
thread=participant.conversation,
|
||||
author=conference,
|
||||
from_email=conference.contact_email,
|
||||
thread=speaker.conversation,
|
||||
author=request.conference,
|
||||
from_email=request.conference.contact_email,
|
||||
content=body,
|
||||
)
|
||||
|
||||
return render(request, 'cfp/complete.html', {'talk': talk, 'participant': participant})
|
||||
|
||||
return render(request, 'cfp/propose.html', {
|
||||
'participant_form': participant_form,
|
||||
'site': site,
|
||||
messages.success(request, _('You proposition have been successfully submitted!'))
|
||||
return redirect(reverse('proposal-talk-details', kwargs=dict(speaker_token=speaker.token, talk_id=talk.pk)))
|
||||
return render(request, 'cfp/proposal_home.html', {
|
||||
'speaker_form': speaker_form,
|
||||
'talk_form': talk_form,
|
||||
})
|
||||
|
||||
|
||||
def talk_proposal_speaker_edit(request, talk_id, participant_id=None):
|
||||
def proposal_mail_token(request):
|
||||
form = MailForm(request.POST or None)
|
||||
if request.method == 'POST' and form.is_valid():
|
||||
try:
|
||||
speaker = Participant.objects.get(site=request.conference.site, email=form.cleaned_data['email'])
|
||||
except Participant.DoesNotExist:
|
||||
messages.error(request, _('Sorry, we do not know this email.'))
|
||||
else:
|
||||
|
||||
talk = get_object_or_404(Talk, token=talk_id, site=request.conference.site)
|
||||
participant = None
|
||||
base_url = ('https' if request.is_secure() else 'http') + '://' + request.conference.site.domain
|
||||
dashboard_url = base_url + reverse('proposal-dashboard', kwargs=dict(speaker_token=speaker.token))
|
||||
body = _("""Hi {},
|
||||
|
||||
if participant_id:
|
||||
participant = get_object_or_404(Participant, token=participant_id, site=request.conference.site)
|
||||
Someone, probably you, ask to access your profile.
|
||||
You can edit your talks or add new ones following this url:
|
||||
|
||||
participant_form = ParticipantForm(request.POST or None, instance=participant)
|
||||
{}
|
||||
|
||||
if request.method == 'POST' and participant_form.is_valid():
|
||||
If you have any question, your can answer to this email.
|
||||
|
||||
participant, created = Participant.objects.get_or_create(email=participant_form.cleaned_data['email'], site=request.conference.site)
|
||||
participant_form = ParticipantForm(request.POST, instance=participant)
|
||||
participant = participant_form.save()
|
||||
participant.save()
|
||||
Sincerely,
|
||||
|
||||
talk.speakers.add(participant)
|
||||
{}
|
||||
|
||||
return render(request,'cfp/complete.html', {'talk': talk, 'participant': participant})
|
||||
|
||||
return render(request, 'cfp/speaker.html', {
|
||||
'participant_form': participant_form,
|
||||
""").format(speaker.name, dashboard_url, request.conference.name)
|
||||
Message.objects.create(
|
||||
thread=speaker.conversation,
|
||||
author=request.conference,
|
||||
from_email=request.conference.contact_email,
|
||||
content=body,
|
||||
)
|
||||
messages.success(request, _('A email have been sent with a link to access to your profil.'))
|
||||
return redirect(reverse('proposal-mail-token'))
|
||||
return render(request, 'cfp/proposal_mail_token.html', {
|
||||
'form': form,
|
||||
})
|
||||
|
||||
|
||||
def talk_acknowledgment(request, talk_id, confirm, participant_id=None):
|
||||
# TODO: handle multiple speakers case
|
||||
talk = get_object_or_404(Talk, token=talk_id, site=request.conference.site)
|
||||
if participant_id:
|
||||
participant = get_object_or_404(Participant, token=participant_id, site=request.conference.site)
|
||||
elif not is_staff(request, request.user):
|
||||
raise PermissionDenied
|
||||
@speaker_required
|
||||
def proposal_dashboard(request, speaker):
|
||||
return render(request, 'cfp/proposal_dashboard.html', {
|
||||
'speaker': speaker,
|
||||
'talks': speaker.talk_set.all(),
|
||||
})
|
||||
|
||||
|
||||
@speaker_required
|
||||
def proposal_talk_details(request, speaker, talk_id):
|
||||
talk = get_object_or_404(Talk, site=request.conference.site, speakers__pk=speaker.pk, pk=talk_id)
|
||||
return render(request, 'cfp/proposal_talk_details.html', {
|
||||
'speaker': speaker,
|
||||
'talk': talk,
|
||||
})
|
||||
|
||||
|
||||
@speaker_required
|
||||
def proposal_talk_edit(request, speaker, talk_id=None):
|
||||
if talk_id:
|
||||
talk = get_object_or_404(Talk, site=request.conference.site, speakers__pk=speaker.pk, pk=talk_id)
|
||||
else:
|
||||
participant = None
|
||||
if not talk.accepted:
|
||||
raise PermissionDenied
|
||||
if talk.confirmed != confirm:
|
||||
talk.confirmed = confirm
|
||||
talk = None
|
||||
if is_staff(request, request.user):
|
||||
categories = TalkCategory.objects.filter(site=request.conference.site)
|
||||
else:
|
||||
categories = request.conference.opened_categories
|
||||
form = TalkForm(request.POST or None, categories=categories, instance=talk)
|
||||
if request.method == 'POST' and form.is_valid():
|
||||
talk = form.save(commit=False)
|
||||
talk.site = request.conference.site
|
||||
talk.save()
|
||||
if confirm:
|
||||
confirmation_message= _('Your participation has been taken into account, thank you!')
|
||||
if participant:
|
||||
thread_note = _('Speaker %(speaker)s confirmed his/her participation.')
|
||||
else:
|
||||
thread_note = _('The talk have been confirmed.')
|
||||
talk.speakers.add(speaker)
|
||||
if talk_id:
|
||||
messages.success(request, _('Changes saved.'))
|
||||
else:
|
||||
confirmation_message = _('We have noted your unavailability.')
|
||||
if participant:
|
||||
thread_note = _('Speaker %(speaker)s CANCELLED his/her participation.')
|
||||
else:
|
||||
thread_note = _('The talk have been cancelled.')
|
||||
if participant_id:
|
||||
thread_note = thread_note % {'speaker': participant}
|
||||
Message.objects.create(thread=talk.conversation, author=participant or request.user, content=thread_note)
|
||||
messages.success(request, confirmation_message)
|
||||
else:
|
||||
# TODO: it could be great to receive the proposition by mail
|
||||
# but this is not crucial as the speaker already have a link in its mailbox
|
||||
messages.success(request, _('You proposition have been successfully submitted!'))
|
||||
return redirect(reverse('proposal-talk-details', kwargs=dict(speaker_token=speaker.token, talk_id=talk.pk)))
|
||||
return render(request, 'cfp/proposal_talk_form.html', {
|
||||
'speaker': speaker,
|
||||
'talk': talk,
|
||||
'form': form,
|
||||
})
|
||||
|
||||
|
||||
@speaker_required
|
||||
def proposal_talk_acknowledgment(request, speaker, talk_id, confirm):
|
||||
# TODO: handle multiple speakers case
|
||||
talk = get_object_or_404(Talk, site=request.conference.site, speakers__pk=speaker.pk, pk=talk_id)
|
||||
if not request.conference.disclosed_acceptances or not talk.accepted:
|
||||
raise PermissionDenied
|
||||
if talk.confirmed == confirm:
|
||||
if confirm:
|
||||
messages.warning(request, _('You already confirmed your participation to this talk.'))
|
||||
else:
|
||||
messages.warning(request, _('You already cancelled your participation to this talk.'))
|
||||
if participant:
|
||||
return redirect(reverse('talk-proposal-edit', kwargs=dict(talk_id=talk_id, participant_id=participant_id)))
|
||||
else:
|
||||
return redirect(reverse('talk-details', kwargs=dict(talk_id=talk_id)))
|
||||
talk.confirmed = confirm
|
||||
talk.save()
|
||||
if confirm:
|
||||
confirmation_message= _('Your participation has been taken into account, thank you!')
|
||||
thread_note = _('Speaker %(speaker)s confirmed his/her participation.' % {'speaker': speaker})
|
||||
else:
|
||||
confirmation_message = _('We have noted your unavailability.')
|
||||
thread_note = _('Speaker %(speaker)s CANCELLED his/her participation.' % {'speaker': speaker})
|
||||
Message.objects.create(thread=talk.conversation, author=speaker, content=thread_note)
|
||||
messages.success(request, confirmation_message)
|
||||
return redirect(reverse('proposal-talk-details', kwargs=dict(speaker_token=speaker.token, talk_id=talk.pk)))
|
||||
|
||||
|
||||
# FIXME his this view really useful?
|
||||
#@speaker_required
|
||||
#def proposal_speaker_details(request, speaker, talk_id, co_speaker_id):
|
||||
# talk = get_object_or_404(Talk, site=request.conference.site, speakers__pk=speaker.pk, pk=talk_id)
|
||||
# co_speaker = get_object_or_404(Participant, site=request.conference.site, talk_set__pk=talk.pk, pk=co_speaker_id)
|
||||
# return render(request, 'cfp/proposal_speaker_details.html', {
|
||||
# 'speaker': speaker,
|
||||
# 'talk': talk,
|
||||
# 'co_speaker': co_speaker,
|
||||
# })
|
||||
|
||||
|
||||
@speaker_required
|
||||
def proposal_speaker_edit(request, speaker, talk_id=None, co_speaker_id=None):
|
||||
if talk_id:
|
||||
talk = get_object_or_404(Talk, site=request.conference.site, speakers__pk=speaker.pk, pk=talk_id)
|
||||
if co_speaker_id:
|
||||
co_speaker = get_object_or_404(Participant, site=request.conference.site, talk__pk=talk.pk, pk=co_speaker_id)
|
||||
else:
|
||||
co_speaker = None
|
||||
else:
|
||||
talk = None
|
||||
co_speaker = None
|
||||
form = ParticipantForm(request.POST or None, conference=request.conference, instance=co_speaker if talk else speaker)
|
||||
if request.method == 'POST' and form.is_valid():
|
||||
# TODO: Allow to add a co-speaker which already exists.
|
||||
# This should be automatically allowed if the speaker already have a talk in common with the co-speaker.
|
||||
# Otherwise, we should send an speaker request to the other user OR allow the other user to join the talk with his token.
|
||||
# This last requirements in planned for v3.
|
||||
edited_speaker = form.save()
|
||||
if talk:
|
||||
talk.speakers.add(edited_speaker)
|
||||
#return redirect(reverse('proposal-speaker-details', kwargs=dict(speaker_token=speaker.token, talk_id=talk.pk)))
|
||||
return redirect(reverse('proposal-talk-details', kwargs=dict(speaker_token=speaker.token, talk_id=talk.pk)))
|
||||
else:
|
||||
return redirect(reverse('proposal-dashboard', kwargs=dict(speaker_token=speaker.token)))
|
||||
return render(request, 'cfp/proposal_speaker_form.html', {
|
||||
'speaker': speaker,
|
||||
'talk': talk,
|
||||
'co_speaker': co_speaker,
|
||||
'form': form,
|
||||
})
|
||||
|
||||
|
||||
@speaker_required
|
||||
def proposal_speaker_remove(request, speaker, talk_id, co_speaker_id):
|
||||
talk = get_object_or_404(Talk, site=request.conference.site, speakers__pk=speaker.pk, pk=talk_id)
|
||||
co_speaker = get_object_or_404(Participant, site=request.conference.site, talk_set__pk=talk.pk, pk=co_speaker_id)
|
||||
return redirect(reverse('proposal-speaker-details', kwargs=dict()))
|
||||
|
||||
|
||||
# BACKWARD COMPATIBILITY
|
||||
def talk_proposal(request, talk_id=None, participant_id=None):
|
||||
if talk_id and participant_id:
|
||||
talk = get_object_or_404(Talk, token=talk_id, site=site)
|
||||
speaker = get_object_or_404(Participant, token=participant_id, site=site)
|
||||
return render(reverse('proposal-talk-edit', kwargs=dict(speaker_token=speaker.token, talk_id=talk.pk)))
|
||||
else:
|
||||
return render(reverse('proposal-home'))
|
||||
|
||||
|
||||
# BACKWARD COMPATIBILITY
|
||||
def talk_proposal_speaker_edit(request, talk_id, participant_id=None):
|
||||
talk = get_object_or_404(Talk, token=talk_id, site=request.conference.site)
|
||||
speaker = talk.speakers.first() # no other choice here…
|
||||
if participant_id:
|
||||
co_speaker = get_object_or_404(Participant, token=participant_id, site=request.conference.site)
|
||||
return redirect(reverse('proposal-speaker-edit', kwargs=dict(speaker_token=speaker.token, talk_id=talk.pk, co_speaker_id=co_speaker.pk)))
|
||||
else:
|
||||
return redirect(reverse('proposal-speaker-add', kwargs=dict(speaker_token=speaker.token, talk_id=talk.pk)))
|
||||
|
||||
|
||||
# TODO: add @staff_required decorator when dropping old links support
|
||||
def talk_acknowledgment(request, talk_id, confirm, participant_id=None):
|
||||
talk = get_object_or_404(Talk, token=talk_id, site=request.conference.site)
|
||||
if participant_id:
|
||||
speaker = get_object_or_404(Participant, token=participant_id, site=request.conference.site)
|
||||
if confirm:
|
||||
return redirect(reverse('proposal-talk-confirm', kwargs=dict(speaker_token=speaker.token, talk_id=talk.pk)))
|
||||
else:
|
||||
return redirect(reverse('proposal-talk-desist', kwargs=dict(speaker_token=speaker.token, talk_id=talk.pk)))
|
||||
elif not is_staff(request, request.user):
|
||||
raise PermissionDenied
|
||||
if not talk.accepted or talk.confirmed == confirm:
|
||||
raise PermissionDenied
|
||||
# TODO: handle multiple speakers case
|
||||
talk.confirmed = confirm
|
||||
talk.save()
|
||||
if confirm:
|
||||
confirmation_message= _('The speaker confirmation have been noted.')
|
||||
thread_note = _('The talk have been confirmed.')
|
||||
else:
|
||||
confirmation_message = _('The speaker unavailability have been noted.')
|
||||
thread_note = _('The talk have been cancelled.')
|
||||
Message.objects.create(thread=talk.conversation, author=request.user, content=thread_note)
|
||||
messages.success(request, confirmation_message)
|
||||
return redirect(reverse('talk-details', kwargs=dict(talk_id=talk_id)))
|
||||
|
||||
|
||||
@staff_required
|
||||
|
@ -529,7 +654,7 @@ class ParticipantUpdate(StaffRequiredMixin, OnSiteMixin, UpdateView):
|
|||
|
||||
|
||||
@staff_required
|
||||
def conference(request):
|
||||
def conference_edit(request):
|
||||
form = ConferenceForm(request.POST or None, instance=request.conference)
|
||||
|
||||
if request.method == 'POST' and form.is_valid():
|
||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -21,7 +21,7 @@
|
|||
<a href="{% url 'home' %}"><span class="glyphicon glyphicon-home"></span> {% trans "Home" %}</a>
|
||||
</li>{% endif %}
|
||||
<li{% block proposetab %}{% endblock %}>
|
||||
<a href="{% url 'talk-proposal' %}"><span class="glyphicon glyphicon-bullhorn"></span> {% trans "Call for participation" %}</a>
|
||||
<a href="{% url 'proposal-home' %}"><span class="glyphicon glyphicon-bullhorn"></span> {% trans "Call for participation" %}</a>
|
||||
</li>
|
||||
{% if conference.schedule_available %}<li{% block publicscheduletab %}{% endblock %}>
|
||||
<a href="{% url 'public-schedule' %}"><span class="glyphicon glyphicon-calendar"></span> {% trans "Schedule" %}</a>
|
||||
|
|
Loading…
Reference in New Issue
Block a user