WIP: complete the public CFP form. More testing is needed

We will probabily recreate migrations of the cfp application from
scratch before deploying in production
This commit is contained in:
Lionel Porcheron 2017-06-06 01:12:47 +02:00 committed by Élie Bouttier
parent f5dcf1a2d6
commit 29ea8403e7
12 changed files with 311 additions and 59 deletions

View File

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-06-05 20:23
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cfp', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='participant',
name='name',
field=models.CharField(blank=True, max_length=128),
),
migrations.AlterField(
model_name='talk',
name='category',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cfp.TalkCategory', verbose_name='Intervention kind'),
),
migrations.AlterField(
model_name='talk',
name='title',
field=models.CharField(max_length=128, verbose_name='Title'),
),
]

View File

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-06-05 20:35
from __future__ import unicode_literals
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
('cfp', '0002_auto_20170605_2023'),
]
operations = [
migrations.AddField(
model_name='participant',
name='token',
field=models.UUIDField(default=uuid.uuid4, editable=False),
),
migrations.AlterField(
model_name='talk',
name='description',
field=models.TextField(blank=True, verbose_name='Description of your talk'),
),
migrations.AlterField(
model_name='talk',
name='notes',
field=models.TextField(blank=True, help_text='If you have any constraint or if you have anything that may help you to select your talk, like a video or slides of your talk, please write it down here', verbose_name='Message to organizers'),
),
migrations.AlterField(
model_name='talk',
name='title',
field=models.CharField(max_length=128, verbose_name='Talk Title'),
),
]

View File

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-06-05 20:43
from __future__ import unicode_literals
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
('cfp', '0003_auto_20170605_2035'),
]
operations = [
migrations.AddField(
model_name='talk',
name='token',
field=models.UUIDField(default=uuid.uuid4, editable=False),
),
]

View File

@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-06-05 22:43
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cfp', '0004_talk_token'),
]
operations = [
migrations.AlterModelOptions(
name='talkcategory',
options={'ordering': ('pk',), 'verbose_name': 'category', 'verbose_name_plural': 'categories'},
),
migrations.AddField(
model_name='conference',
name='contact_email',
field=models.CharField(blank=True, max_length=100),
),
migrations.AddField(
model_name='conference',
name='name',
field=models.CharField(blank=True, max_length=100),
),
migrations.AlterField(
model_name='talk',
name='category',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cfp.TalkCategory', verbose_name='Talk Category'),
),
migrations.AlterField(
model_name='talk',
name='video_licence',
field=models.CharField(choices=[('CC-Zero CC-BY', 'CC-Zero CC-BY'), ('CC-BY-SA', 'CC-BY-SA'), ('CC-BY-ND', 'CC-BY-ND'), ('CC-BY-NC', 'CC-BY-NC'), ('CC-BY-NC-SA', 'CC-BY-NC-SA'), ('CC-BY-NC-ND', 'CC-BY-NC-ND')], default='CC-BY-SA', max_length=10, verbose_name='Video licence'),
),
]

View File

@ -1,6 +1,7 @@
from enum import IntEnum
import uuid
from datetime import timedelta
from os.path import join, basename
from django.contrib.auth.models import User
from django.contrib.sites.models import Site
@ -12,29 +13,12 @@ from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext
from django.utils import timezone
from ponyconf.utils import PonyConfModel, enum_to_choices
from enum import IntEnum
from datetime import timedelta
from os.path import join, basename
from django.contrib.auth.models import User
from django.contrib.sites.models import Site
from django.core.urlresolvers import reverse
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext
from django.utils import timezone
from ponyconf.utils import PonyConfModel
from autoslug import AutoSlugField
from colorful.fields import RGBColorField
from .utils import query_sum
from .utils import generate_user_uid
from enum import IntEnum
from django.contrib.auth.models import User
from django.contrib.sites.models import Site
@ -43,6 +27,7 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext
#from ponyconf.utils import PonyConfModel, enum_to_choices
@ -50,9 +35,11 @@ from django.utils.translation import ugettext
class Conference(models.Model):
site = models.OneToOneField(Site, on_delete=models.CASCADE)
name = models.CharField(blank=True, max_length=100)
home = models.TextField(blank=True, default="")
venue = models.TextField(blank=True, default="")
city = models.CharField(max_length=64, blank=True, default="")
contact_email = models.CharField(max_length=100, blank=True)
#subscriptions_open = models.BooleanField(default=False) # workshop subscription
#def cfp_is_open(self):
@ -66,6 +53,9 @@ 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))
def from_email(self):
return self.name+' <'+self.contact_email+'>'
def __str__(self):
return str(self.site)
@ -76,13 +66,12 @@ class Participant(PonyConfModel):
site = models.ForeignKey(Site, on_delete=models.CASCADE)
name = models.CharField(max_length=128)
name = models.CharField(max_length=128, blank=True)
email = models.EmailField()
phone_number = models.CharField(max_length=16, blank=True, default='', verbose_name=_('Phone number'))
biography = models.TextField(blank=True, verbose_name=_('Biography'))
#email_token = models.CharField(max_length=12, default=generate_user_uid, unique=True)
token = models.UUIDField(default=uuid.uuid4, editable=False)
# TALK
#videotaped = models.BooleanField(_("I'm ok to be recorded on video"), default=True)
@ -178,6 +167,8 @@ class TalkCategory(models.Model): # type of talk (conf 30min, 1h, stand, …)
class Meta:
unique_together = ('site', 'name')
ordering = ('pk',)
verbose_name = "category"
verbose_name_plural = "categories"
def __str__(self):
return ugettext(self.name)
@ -216,21 +207,28 @@ class TalkCategory(models.Model): # type of talk (conf 30min, 1h, stand, …)
class Talk(PonyConfModel):
LICENCES = IntEnum('Video licence', 'CC-Zero CC-BY CC-BY-SA CC-BY-ND CC-BY-NC CC-BY-NC-SA CC-BY-NC-ND')
LICENCES = (
('CC-Zero CC-BY', 'CC-Zero CC-BY'),
('CC-BY-SA', 'CC-BY-SA'),
('CC-BY-ND', 'CC-BY-ND'),
('CC-BY-NC', 'CC-BY-NC'),
('CC-BY-NC-SA','CC-BY-NC-SA'),
('CC-BY-NC-ND', 'CC-BY-NC-ND'),
)
site = models.ForeignKey(Site, on_delete=models.CASCADE)
#proposer = models.ForeignKey(User, related_name='+')
speakers = models.ManyToManyField(Participant, verbose_name=_('Speakers'))
title = models.CharField(max_length=128, verbose_name=_('Title'), help_text=_('After submission, title can only be changed by the staff.'))
title = models.CharField(max_length=128, verbose_name=_('Talk Title'))
slug = AutoSlugField(populate_from='title', unique=True)
#abstract = models.CharField(max_length=255, blank=True, verbose_name=_('Abstract'))
description = models.TextField(blank=True, verbose_name=_('Description'))
description = models.TextField(blank=True, verbose_name=_('Description of your talk'))
track = models.ForeignKey(Track, blank=True, null=True, verbose_name=_('Track'))
notes = models.TextField(blank=True, verbose_name=_('Message to organizers'))
category = models.ForeignKey(TalkCategory, verbose_name=_('Intervention kind'))
notes = models.TextField(blank=True, verbose_name=_('Message to organizers'), help_text=_('If you have any constraint or if you have anything that may help you to select your talk, like a video or slides of your talk, please write it down here'))
category = models.ForeignKey(TalkCategory, blank=True, null=True, verbose_name=_('Talk Category'))
videotaped = models.BooleanField(_("I'm ok to be recorded on video"), default=True)
video_licence = models.IntegerField(choices=enum_to_choices(LICENCES), default=2, verbose_name=_("Video licence"))
video_licence = models.CharField(choices=LICENCES, default='CC-BY-SA', max_length=10, verbose_name=_("Video licence"))
sound = models.BooleanField(_("I need sound"), default=False)
accepted = models.NullBooleanField(default=None)
#start_date = models.DateTimeField(null=True, blank=True, default=None)
@ -240,6 +238,8 @@ class Talk(PonyConfModel):
#materials = models.FileField(null=True, upload_to=talk_materials_destination, verbose_name=_('Materials'),
# help_text=_('You can use this field to share some materials related to your intervention.'))
token = models.UUIDField(default=uuid.uuid4, editable=False)
class Meta:
ordering = ('title',)

View File

@ -13,7 +13,11 @@
<div class="row">
<div class="col-md-12">
<p>Merci pour votre participation !</p>
<p>{% trans "Thanks for your proposal" %}</p>
<p>{% trans "You can edit your talk at anytime:" %} <a href="{% url 'talk-proposal-edit' talk.token participant.token %}">{% if request.is_secure %}https{% else %}http{% endif %}://{{ site.domain }}{% url 'talk-proposal-edit' talk.token participant.token %}</a></p>
<p>{% trans "You can add an additionnal speaker:" %} <a href="{% url 'talk-proposal-speaker-add' talk.token %}">{% if request.is_secure %}https{% else %}http{% endif %}://{{ site.domain }}{% url 'talk-proposal-speaker-add' talk.token %}</a></p>
<p>{% trans "You can edit your profile:" %} <a href="{% url 'talk-proposal-speaker-edit' talk.token participant.token %}">{% if request.is_secure %}https{% else %}http{% endif %}://{{ site.domain }}{% url 'talk-proposal-speaker-edit' talk.token participant.token %}</a></p>
<p>{% trans "An email has been sent to you with those URLs" %}
</div>
</div>
{% endblock %}

View File

@ -1,4 +1,5 @@
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% load ponyconf_tags i18n %}
@ -13,7 +14,14 @@
<div class="row">
<div class="col-md-12">
{% include "_form.html" %}
<form method="POST" class="form-horizontal col-md-8 col-md-offset-2">
{% csrf_token %}
{{ participant_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 %}

View File

@ -0,0 +1,26 @@
{% 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 %}

View File

@ -3,8 +3,10 @@ from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^propose/$', views.ProposeView.as_view(), name='propose'),
url(r'^thanks/$', views.CompleteView.as_view(), name='propose-complete'),
url(r'^$', views.talk_proposal, name='talk-proposal'),
url(r'^(?P<talk_id>[\w\-]+)/speaker/add/$', views.talk_proposal_speaker_edit, name='talk-proposal-speaker-add'),
url(r'^(?P<talk_id>[\w\-]+)/speaker/(?P<speaker_id>[\w\-]+)/$', views.talk_proposal_speaker_edit, name='talk-proposal-speaker-edit'),
url(r'^(?P<talk_id>[\w\-]+)/(?P<participant_id>[\w\-]+)/$', views.talk_proposal, name='talk-proposal-edit'),
#url(r'^markdown/$', views.markdown_preview, name='markdown'),
#url(r'^$', views.home, name='home'),
#url(r'^staff/$', views.staff, name='staff'),

View File

@ -1,36 +1,111 @@
from django.views.generic import FormView, TemplateView
from django.contrib.sites.shortcuts import get_current_site
from django.core.mail import send_mail
from django.core.urlresolvers import reverse_lazy
from django.forms.models import modelform_factory
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
from django.views.generic import FormView, TemplateView
from .forms import ProposeForm
from .models import Participant, Talk
class ProposeView(FormView):
form_class = ProposeForm
template_name = 'cfp/propose.html'
success_url = reverse_lazy('propose-complete')
def talk_proposal(request, talk_id=None, participant_id=None):
def propose(request):
TalkForm = modelform_factory(Talk)
ParticipantForm = modelform_factory(Participant)
talk_form = TalkForm(request.POST or None)
participant_form = ParticipantForm(request.POST or None)
forms = [talk_form, participant_form]
if request.method == 'POST' and all([form.is_valid() for form in forms]):
site = get_current_site(request)
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)
ParticipantForm = modelform_factory(Participant, fields=('name','email', 'biography'))
participant_form = ParticipantForm(request.POST or None, instance=participant)
TalkForm = modelform_factory(Talk, fields=('category', 'title', 'description','notes'))
talk_form = TalkForm(request.POST or None, instance=talk)
if request.method == 'POST' and talk_form.is_valid() and participant_form.is_valid():
talk = talk_form.save(commit=False)
talk.site = get_current_site(request)
email = participant.cleaned_data['email']
try:
participant = Participant.objects.get(email=email)
except Participant.DoesNoExist:
participant = participant_form.save()
talk.participant = participant
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.save()
talk.save()
return redirect(reverse('propose-complete'))
return render('cfp/propose.html', {
'talk_form': talk_form,
talk.speakers.add(participant)
protocol = 'http' 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])
msg_title = _('Your talk "{}" has been submitted for {}').format(talk.title, site.conference.name)
msg_body = _("""Hi {},
Your talk has been submitted for {}.
Here are the details of your talk:
Title: {}
Description {}
You can edit your talk at anytume: {}
You can add a new co-speaker here: {}
You can edit your profile here: {}
If you have any question, your can answer to this email.
Thanks!
{}
""").format(participant.name, site.conference.name, talk.title, talk.description, url_talk_proposal_edit, url_talk_proposal_speaker_add, url_talk_proposal_speaker_edit, site.conference.name)
send_mail(
msg_title,
msg_body,
site.conference.from_email(),
[participant.email],
fail_silently=False,
)
return render(request, 'cfp/complete.html', {'talk': talk, 'participant': participant})
return render(request, 'cfp/propose.html', {
'participant_form': participant_form,
'site': site,
'talk_form': talk_form,
})
class CompleteView(TemplateView):
template_name = 'cfp/complete.html'
def talk_proposal_speaker_edit(request, talk_id, participant_id=None):
site = get_current_site(request)
talk = get_object_or_404(Talk, token=talk_id, site=site)
participant = None
if participant_id:
participant = get_object_or_404(Participant, token=participant_id, site=site)
ParticipantForm = modelform_factory(Participant, fields=('name','email', 'biography'))
participant_form = ParticipantForm(request.POST or None, instance=participant)
if request.method == 'POST' and participant_form.is_valid():
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.save()
talk.speakers.add(participant)
return render(request,'cfp/complete.html', {'talk': talk, 'participant': participant})
return render(request, 'cfp/speaker.html', {
'participant_form': participant_form,
'site': site,
})

View File

@ -51,6 +51,7 @@ INSTALLED_APPS = [
#'registration',
#'django_select2',
#'avatar',
'crispy_forms',
# build-in apps
'django.contrib.admin',
@ -209,6 +210,8 @@ SELECT2_CSS = 'select2/dist/css/select2.min.css'
#AUTHENTICATION_BACKENDS = ['yeouia.backends.YummyEmailOrUsernameInsensitiveAuth']
LOGOUT_REDIRECT_URL = 'home'
CRISPY_TEMPLATE_PACK='bootstrap3'
# django-registration
ACCOUNT_ACTIVATION_DAYS = 7
INCLUDE_REGISTER_URL = True
@ -218,3 +221,9 @@ CACHES = {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
}
}
SERVER_EMAIL = 'ponyconf@example.com'
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'localhost'
EMAIL_PORT = 1025

View File

@ -18,7 +18,7 @@
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li{% block hometab %}{% endblock %}><a href="{% url 'home' %}"><span class="glyphicon glyphicon-home"></span>&nbsp;{% trans "Home" %}</a></li>
<li{% block proposetab %}{% endblock %}><a href="{% url 'propose' %}"><span class="glyphicon glyphicon-bullhorn"></span>&nbsp;{% trans "Call for participation" %}</a></li>
<li{% block proposetab %}{% endblock %}><a href="{% url 'talk-proposal' %}"><span class="glyphicon glyphicon-bullhorn"></span>&nbsp;{% trans "Call for participation" %}</a></li>
{% comment %}
{% if request.user.is_authenticated %}
<li{% block exhibitortab %}{% endblock %}><a href="{% url 'participate-as-speaker' %}"><span class="glyphicon glyphicon-bullhorn"></span>&nbsp;{% trans "Exhibitor" %}</a></li>