removing old proposals app
This commit is contained in:
parent
623b94fccd
commit
06deb53ef9
|
@ -41,16 +41,13 @@ INSTALLED_APPS = [
|
|||
'ponyconf',
|
||||
'cfp',
|
||||
'mailing',
|
||||
#'proposals',
|
||||
#'planning',
|
||||
#'volunteers',
|
||||
|
||||
# external apps
|
||||
'djangobower',
|
||||
'bootstrap3',
|
||||
#'registration',
|
||||
'django_select2',
|
||||
#'avatar',
|
||||
'crispy_forms',
|
||||
|
||||
# build-in apps
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
default_app_config = 'proposals.apps.ProposalsConfig'
|
|
@ -1,47 +0,0 @@
|
|||
from django.contrib import admin
|
||||
from django.contrib.sites.shortcuts import get_current_site
|
||||
|
||||
from proposals.models import Conference, Talk, Topic, Track, Event, Attendee
|
||||
from planning.models import Room
|
||||
|
||||
from ponyconf.admin import SiteAdminMixin
|
||||
|
||||
|
||||
|
||||
class TalkAdmin(SiteAdminMixin, admin.ModelAdmin):
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
form = super(TalkAdmin, self).get_form(request, obj, **kwargs)
|
||||
site = get_current_site(request)
|
||||
form.base_fields['topics'].queryset = Topic.objects.filter(site=site)
|
||||
form.base_fields['track'].queryset = Track.objects.filter(site=site)
|
||||
form.base_fields['event'].queryset = Event.objects.filter(site=site)
|
||||
form.base_fields['room'].queryset = Room.objects.filter(site=site)
|
||||
return form
|
||||
|
||||
|
||||
class TopicAdmin(SiteAdminMixin, admin.ModelAdmin):
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
form = super().get_form(request, obj, **kwargs)
|
||||
site = get_current_site(request)
|
||||
form.base_fields['track'].queryset = Track.objects.filter(site=site)
|
||||
return form
|
||||
|
||||
|
||||
class TrackAdmin(SiteAdminMixin, admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
class EventAdmin(SiteAdminMixin, admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
class AttendeeAdmin(admin.ModelAdmin):
|
||||
list_display = ('get_name', 'get_email')
|
||||
|
||||
|
||||
admin.site.register(Conference)
|
||||
admin.site.register(Topic, TopicAdmin)
|
||||
admin.site.register(Track, TrackAdmin)
|
||||
admin.site.register(Talk, TalkAdmin)
|
||||
admin.site.register(Event, EventAdmin)
|
||||
admin.site.register(Attendee, AttendeeAdmin)
|
|
@ -1,10 +0,0 @@
|
|||
from django.apps import AppConfig
|
||||
from django.db.models.signals import post_migrate
|
||||
|
||||
|
||||
class ProposalsConfig(AppConfig):
|
||||
name = 'proposals'
|
||||
|
||||
def ready(self):
|
||||
import proposals.signals # noqa
|
||||
post_migrate.connect(proposals.signals.call_first_site_post_save, sender=self)
|
|
@ -1,9 +0,0 @@
|
|||
from django.conf import settings
|
||||
from django.contrib.sites.shortcuts import get_current_site
|
||||
|
||||
from .models import Conference
|
||||
|
||||
|
||||
def conference(request):
|
||||
conference = Conference.objects.get(site=get_current_site(request))
|
||||
return {'conference': conference}
|
|
@ -1,209 +0,0 @@
|
|||
from django import forms
|
||||
from django.db.utils import OperationalError
|
||||
from django.forms.models import modelform_factory
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from django_select2.forms import Select2TagWidget
|
||||
|
||||
from accounts.models import User, Participation, Transport
|
||||
from proposals.models import Conference, Event, Talk, Topic, Track
|
||||
from planning.models import Room
|
||||
|
||||
STATUS_CHOICES = [
|
||||
('pending', 'Pending decision'),
|
||||
('accepted', 'Accepted'),
|
||||
('declined', 'Declined'),
|
||||
]
|
||||
STATUS_VALUES = [
|
||||
('pending', None),
|
||||
('accepted', True),
|
||||
('declined', False),
|
||||
]
|
||||
|
||||
|
||||
class TalkForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
site = kwargs.pop('site')
|
||||
staff = kwargs.pop('staff')
|
||||
super(TalkForm, self).__init__(*args, **kwargs)
|
||||
self.fields['topics'].queryset = Topic.objects.filter(site=site)
|
||||
self.fields['track'].queryset = Track.objects.filter(site=site)
|
||||
self.fields['materials'].required = False
|
||||
if staff:
|
||||
self.fields['event'].queryset = Event.objects.filter(site=site)
|
||||
else:
|
||||
self.fields['event'].queryset = Conference.objects.get(site=site).opened_events
|
||||
for field in ['track', 'duration', 'start_date', 'room', 'registration_required', 'attendees_limit']:
|
||||
self.fields.pop(field)
|
||||
if self.instance.pk is not None:
|
||||
self.fields['title'].disabled = True
|
||||
|
||||
class Meta:
|
||||
model = Talk
|
||||
fields = ['title', 'abstract', 'description', 'topics', 'track', 'notes', 'event', 'speakers', 'materials', 'video', 'duration', 'start_date', 'room', 'registration_required', 'attendees_limit']
|
||||
widgets = {'topics': forms.CheckboxSelectMultiple(), 'speakers': Select2TagWidget()}
|
||||
help_texts = {
|
||||
'abstract': _('Should be less than 255 characters'),
|
||||
'notes': _('If you want to add some precisions for the organizers.'),
|
||||
}
|
||||
|
||||
|
||||
class TalkFilterForm(forms.Form):
|
||||
kind = forms.MultipleChoiceField(
|
||||
required=False,
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
choices=[],
|
||||
)
|
||||
status = forms.MultipleChoiceField(
|
||||
required=False,
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
choices=STATUS_CHOICES,
|
||||
)
|
||||
topic = forms.MultipleChoiceField(
|
||||
required=False,
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
choices=[],
|
||||
)
|
||||
track = forms.MultipleChoiceField(
|
||||
required=False,
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
choices=[],
|
||||
)
|
||||
vote = forms.NullBooleanField(help_text=_('Filter talks you already / not yet voted for'))
|
||||
room = forms.NullBooleanField(help_text=_('Filter talks already / not yet affected to a room'))
|
||||
scheduled = forms.NullBooleanField(help_text=_('Filter talks already / not yet scheduled'))
|
||||
materials = forms.NullBooleanField(help_text=_('Filter talks with / without materials'))
|
||||
video = forms.NullBooleanField(help_text=_('Filter talks with / without video'))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
site = kwargs.pop('site')
|
||||
super().__init__(*args, **kwargs)
|
||||
events = Event.objects.filter(site=site)
|
||||
self.fields['kind'].choices = events.values_list('pk', 'name')
|
||||
topics = Topic.objects.filter(site=site)
|
||||
self.fields['topic'].choices = topics.values_list('slug', 'name')
|
||||
tracks = Track.objects.filter(site=site)
|
||||
self.fields['track'].choices = [('none', 'Not assigned')] + list(tracks.values_list('slug', 'name'))
|
||||
|
||||
|
||||
class TalkActionForm(forms.Form):
|
||||
talks = forms.MultipleChoiceField(choices=[])
|
||||
decision = forms.NullBooleanField(label=_('Accept talk?'))
|
||||
track = forms.ChoiceField(required=False, choices=[], label=_('Assign to a track'))
|
||||
room = forms.ChoiceField(required=False, choices=[], label=_('Put in a room'))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
site = kwargs.pop('site')
|
||||
talks = kwargs.pop('talks')
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['talks'].choices = [(talk.slug, None) for talk in talks.all()]
|
||||
tracks = Track.objects.filter(site=site)
|
||||
self.fields['track'].choices = [(None, "---------")] + list(tracks.values_list('slug', 'name'))
|
||||
rooms = Room.objects.filter(site=site)
|
||||
self.fields['room'].choices = [(None, "---------")] + list(rooms.values_list('slug', 'name'))
|
||||
|
||||
|
||||
def get_options(option):
|
||||
try:
|
||||
options = list(option.objects.values_list('pk', 'name'))
|
||||
except OperationalError:
|
||||
# happens when db doesn't exist yet
|
||||
options = []
|
||||
return options
|
||||
|
||||
|
||||
class SpeakerFilterForm(forms.Form):
|
||||
status = forms.MultipleChoiceField(
|
||||
required=False,
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
choices=STATUS_CHOICES,
|
||||
)
|
||||
topic = forms.MultipleChoiceField(
|
||||
required=False,
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
choices=[],
|
||||
)
|
||||
track = forms.MultipleChoiceField(
|
||||
required=False,
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
choices=[],
|
||||
)
|
||||
transport = forms.MultipleChoiceField(
|
||||
required=False,
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
choices=[('unanswered', 'Not answered'), ('unspecified', 'Not specified')] + get_options(Transport),
|
||||
)
|
||||
transport_booked = forms.NullBooleanField()
|
||||
accommodation= forms.MultipleChoiceField(
|
||||
required=False,
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
choices=[('unknown', 'Not specified')] + list(Participation.ACCOMMODATION_CHOICES),
|
||||
)
|
||||
accommodation_booked = forms.NullBooleanField()
|
||||
sound = forms.NullBooleanField()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
site = kwargs.pop('site')
|
||||
super().__init__(*args, **kwargs)
|
||||
topics = Topic.objects.filter(site=site)
|
||||
self.fields['topic'].choices = topics.values_list('slug', 'name')
|
||||
tracks = Track.objects.filter(site=site)
|
||||
self.fields['track'].choices = [('none', 'Not assigned')] + list(tracks.values_list('slug', 'name'))
|
||||
|
||||
|
||||
class TopicForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.site = kwargs.pop('site')
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['track'].queryset = Track.objects.filter(site=self.site)
|
||||
|
||||
class Meta:
|
||||
model = Topic
|
||||
fields = ['name', 'description', 'reviewers', 'track']
|
||||
widgets = {'reviewers': Select2TagWidget()}
|
||||
|
||||
def clean_name(self):
|
||||
name = self.cleaned_data['name']
|
||||
if self.instance and name != self.instance.name and Topic.objects.filter(site=self.site, name=name).exists():
|
||||
raise self.instance.unique_error_message(self._meta.model, ['name'])
|
||||
return name
|
||||
|
||||
|
||||
class TrackForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.site = kwargs.pop('site')
|
||||
super().__init__(*args, **kwargs)
|
||||
if 'instance' in kwargs:
|
||||
reviewers = User.objects.filter(topic__track=kwargs['instance'])
|
||||
if reviewers.exists():
|
||||
self.fields['managers'].help_text = 'Suggestion: ' + ', '.join([str(u) for u in reviewers.all()])
|
||||
|
||||
class Meta:
|
||||
model = Track
|
||||
fields = ['name', 'description', 'managers']
|
||||
widgets = {'managers': Select2TagWidget()}
|
||||
|
||||
def clean_name(self):
|
||||
name = self.cleaned_data['name']
|
||||
if self.instance and name != self.instance.name and Track.objects.filter(site=self.site, name=name).exists():
|
||||
raise self.instance.unique_error_message(self._meta.model, ['name'])
|
||||
return name
|
||||
|
||||
|
||||
class SubscribeForm(forms.Form):
|
||||
email = forms.EmailField()
|
||||
name = forms.CharField(max_length=128, label=_('Name or nickname'))
|
||||
captcha = forms.IntegerField(label=_('How much is 3+4?'), help_text=_('Anti-bot'))
|
||||
|
||||
def clean_captcha(self):
|
||||
value = self.cleaned_data['captcha']
|
||||
if value != 7:
|
||||
raise forms.ValidationError(_("Please re-do the maths."))
|
||||
return value
|
||||
|
||||
|
||||
ConferenceForm = modelform_factory(Conference,
|
||||
fields=['subscriptions_open', 'venue', 'city', 'home'],
|
||||
widgets={
|
||||
'venue': forms.Textarea(attrs={'rows': 4}),
|
||||
})
|
|
@ -1,163 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.3 on 2017-01-13 10:49
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import autoslug.fields
|
||||
import colorful.fields
|
||||
from django.conf import settings
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import proposals.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('planning', '0001_initial'),
|
||||
('sites', '0002_alter_domain_unique'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Attendee',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('updated', models.DateTimeField(auto_now=True)),
|
||||
('name', models.CharField(blank=True, default='', max_length=64)),
|
||||
('email', models.EmailField(blank=True, default='', max_length=254)),
|
||||
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Conference',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('home', models.TextField(blank=True, default='')),
|
||||
('venue', models.TextField(blank=True, default='')),
|
||||
('city', models.CharField(blank=True, default='', max_length=64)),
|
||||
('subscriptions_open', models.BooleanField(default=False)),
|
||||
('site', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='sites.Site')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Event',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=64)),
|
||||
('duration', models.PositiveIntegerField(default=0, verbose_name='Default duration (min)')),
|
||||
('color', colorful.fields.RGBColorField(default='#ffffff', verbose_name='Color on program')),
|
||||
('label', models.CharField(blank=True, default='', max_length=64, verbose_name='Label on program')),
|
||||
('opening_date', models.DateTimeField(blank=True, default=None, null=True)),
|
||||
('closing_date', models.DateTimeField(blank=True, default=None, null=True)),
|
||||
('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sites.Site')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('pk',),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Talk',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('updated', models.DateTimeField(auto_now=True)),
|
||||
('title', models.CharField(help_text='After submission, title can only be changed by the staff.', max_length=128, verbose_name='Title')),
|
||||
('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='title', unique=True)),
|
||||
('abstract', models.CharField(blank=True, max_length=255, verbose_name='Abstract')),
|
||||
('description', models.TextField(blank=True, verbose_name='Description')),
|
||||
('notes', models.TextField(blank=True, verbose_name='Notes')),
|
||||
('accepted', models.NullBooleanField(default=None)),
|
||||
('start_date', models.DateTimeField(blank=True, default=None, null=True)),
|
||||
('duration', models.PositiveIntegerField(default=0, verbose_name='Duration (min)')),
|
||||
('plenary', models.BooleanField(default=False)),
|
||||
('registration_required', models.BooleanField(default=False)),
|
||||
('attendees_limit', models.PositiveIntegerField(default=0, verbose_name='Max. number of attendees')),
|
||||
('materials', models.FileField(help_text='You can use this field to share some materials related to your intervention.', null=True, upload_to=proposals.models.talk_materials_destination, verbose_name='Materials')),
|
||||
('attendees', models.ManyToManyField(to='proposals.Attendee', verbose_name='Attendees')),
|
||||
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='proposals.Event', verbose_name='Intervention kind')),
|
||||
('proposer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL)),
|
||||
('room', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='planning.Room')),
|
||||
('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sites.Site')),
|
||||
('speakers', models.ManyToManyField(to=settings.AUTH_USER_MODEL, verbose_name='Speakers')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('event__id',),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Topic',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('updated', models.DateTimeField(auto_now=True)),
|
||||
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||
('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='name', unique=True)),
|
||||
('description', models.TextField(blank=True, verbose_name='Description')),
|
||||
('reviewers', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Reviewers')),
|
||||
('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sites.Site')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Track',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('updated', models.DateTimeField(auto_now=True)),
|
||||
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||
('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='name')),
|
||||
('description', models.TextField(blank=True, verbose_name='Description')),
|
||||
('managers', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Managers')),
|
||||
('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sites.Site')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Vote',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('updated', models.DateTimeField(auto_now=True)),
|
||||
('vote', models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(-2), django.core.validators.MaxValueValidator(2)])),
|
||||
('talk', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='proposals.Talk')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='topic',
|
||||
name='track',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='proposals.Track', verbose_name='Destination track'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='talk',
|
||||
name='topics',
|
||||
field=models.ManyToManyField(blank=True, help_text='The topics can not be changed after submission.', to='proposals.Topic', verbose_name='Topics'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='talk',
|
||||
name='track',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='proposals.Track', verbose_name='Track'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='vote',
|
||||
unique_together=set([('talk', 'user')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='track',
|
||||
unique_together=set([('site', 'name')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='topic',
|
||||
unique_together=set([('site', 'name')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='event',
|
||||
unique_together=set([('site', 'name')]),
|
||||
),
|
||||
]
|
|
@ -1,20 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.5 on 2017-01-16 21:47
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('proposals', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='talk',
|
||||
name='video',
|
||||
field=models.URLField(blank=True, default='', max_length=1000, verbose_name='URL vidéo'),
|
||||
),
|
||||
]
|
|
@ -1,264 +0,0 @@
|
|||
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 autoslug import AutoSlugField
|
||||
from colorful.fields import RGBColorField
|
||||
|
||||
from accounts.models import Participation
|
||||
from ponyconf.utils import PonyConfModel, enum_to_choices
|
||||
from planning.models import Room
|
||||
|
||||
from .utils import query_sum
|
||||
|
||||
|
||||
class Conference(models.Model):
|
||||
|
||||
site = models.OneToOneField(Site, on_delete=models.CASCADE)
|
||||
home = models.TextField(blank=True, default="")
|
||||
venue = models.TextField(blank=True, default="")
|
||||
city = models.CharField(max_length=64, blank=True, default="")
|
||||
subscriptions_open = models.BooleanField(default=False) # workshop subscription
|
||||
|
||||
def cfp_is_open(self):
|
||||
events = Event.objects.filter(site=self.site)
|
||||
return any(map(lambda x: x.is_open(), events))
|
||||
|
||||
@property
|
||||
def opened_events(self):
|
||||
now = timezone.now()
|
||||
return Event.objects.filter(site=self.site)\
|
||||
.filter(Q(opening_date__isnull=True) | Q(opening_date__lte=now))\
|
||||
.filter(Q(closing_date__isnull=True) | Q(closing_date__gte=now))
|
||||
|
||||
def __str__(self):
|
||||
return str(self.site)
|
||||
|
||||
|
||||
class Track(PonyConfModel):
|
||||
|
||||
site = models.ForeignKey(Site, on_delete=models.CASCADE)
|
||||
|
||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||
slug = AutoSlugField(populate_from='name')
|
||||
description = models.TextField(blank=True, verbose_name=_('Description'))
|
||||
|
||||
managers = models.ManyToManyField(User, blank=True, verbose_name=_('Managers'))
|
||||
|
||||
class Meta:
|
||||
unique_together = ('site', 'name')
|
||||
|
||||
def estimated_duration(self):
|
||||
return sum([talk.estimated_duration for talk in self.talk_set.all()])
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('list-talks') + '?track=%s' % self.slug
|
||||
|
||||
|
||||
class Topic(PonyConfModel):
|
||||
|
||||
site = models.ForeignKey(Site, on_delete=models.CASCADE)
|
||||
|
||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||
slug = AutoSlugField(populate_from='name', unique=True)
|
||||
description = models.TextField(blank=True, verbose_name=_('Description'))
|
||||
track = models.ForeignKey(Track, blank=True, null=True, verbose_name=_('Destination track'))
|
||||
|
||||
reviewers = models.ManyToManyField(User, blank=True, verbose_name=_('Reviewers'))
|
||||
|
||||
class Meta:
|
||||
unique_together = ('site', 'name')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('list-talks') + '?topic=%s' % self.slug
|
||||
|
||||
|
||||
class Event(models.Model):
|
||||
|
||||
site = models.ForeignKey(Site, on_delete=models.CASCADE)
|
||||
name = models.CharField(max_length=64)
|
||||
duration = models.PositiveIntegerField(default=0, verbose_name=_('Default duration (min)'))
|
||||
color = RGBColorField(default='#ffffff', verbose_name=_("Color on program"))
|
||||
label = models.CharField(max_length=64, verbose_name=_("Label on program"), blank=True, default="")
|
||||
opening_date = models.DateTimeField(null=True, blank=True, default=None)
|
||||
closing_date = models.DateTimeField(null=True, blank=True, default=None)
|
||||
|
||||
def is_open(self):
|
||||
now = timezone.now()
|
||||
if self.opening_date and now < self.opening_date:
|
||||
return False
|
||||
if self.closing_date and now > self.closing_date:
|
||||
return False
|
||||
return True
|
||||
|
||||
class Meta:
|
||||
unique_together = ('site', 'name')
|
||||
ordering = ('pk',)
|
||||
|
||||
def __str__(self):
|
||||
return ugettext(self.name)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('list-talks') + '?kind=%d' % self.pk
|
||||
|
||||
|
||||
class Attendee(PonyConfModel):
|
||||
|
||||
user = models.ForeignKey(User, null=True)
|
||||
name = models.CharField(max_length=64, blank=True, default="")
|
||||
email = models.EmailField(blank=True, default="")
|
||||
|
||||
def get_name(self):
|
||||
if self.user:
|
||||
return str(self.user.profile)
|
||||
else:
|
||||
return self.name
|
||||
get_name.short_description = _('Name')
|
||||
|
||||
def get_email(self):
|
||||
if self.user:
|
||||
return self.user.email
|
||||
else:
|
||||
return self.email
|
||||
get_email.short_description = _('Email')
|
||||
|
||||
def __str__(self):
|
||||
return self.get_name()
|
||||
|
||||
|
||||
def talk_materials_destination(talk, filename):
|
||||
return join(talk.site.name, talk.slug, filename)
|
||||
|
||||
|
||||
class Talk(PonyConfModel):
|
||||
|
||||
site = models.ForeignKey(Site, on_delete=models.CASCADE)
|
||||
|
||||
proposer = models.ForeignKey(User, related_name='+')
|
||||
speakers = models.ManyToManyField(User, verbose_name=_('Speakers'))
|
||||
title = models.CharField(max_length=128, verbose_name=_('Title'), help_text=_('After submission, title can only be changed by the staff.'))
|
||||
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'))
|
||||
topics = models.ManyToManyField(Topic, blank=True, verbose_name=_('Topics'), help_text=_('The topics can not be changed after submission.'))
|
||||
track = models.ForeignKey(Track, blank=True, null=True, verbose_name=_('Track'))
|
||||
notes = models.TextField(blank=True, verbose_name=_('Notes'))
|
||||
event = models.ForeignKey(Event, verbose_name=_('Intervention kind'))
|
||||
accepted = models.NullBooleanField(default=None)
|
||||
start_date = models.DateTimeField(null=True, blank=True, default=None)
|
||||
duration = models.PositiveIntegerField(default=0, verbose_name=_('Duration (min)'))
|
||||
room = models.ForeignKey(Room, blank=True, null=True, default=None)
|
||||
plenary = models.BooleanField(default=False)
|
||||
registration_required = models.BooleanField(default=False)
|
||||
attendees = models.ManyToManyField(Attendee, verbose_name=_('Attendees'))
|
||||
attendees_limit = models.PositiveIntegerField(default=0, verbose_name=_('Max. number of attendees'))
|
||||
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.'))
|
||||
video = models.URLField(max_length=1000, blank=True, default='', verbose_name='URL vidéo')
|
||||
|
||||
class Meta:
|
||||
ordering = ('title',)
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
def get_speakers_str(self):
|
||||
speakers = [str(Participation.objects.get(site=self.site, user=speaker)) for speaker in self.speakers.all()]
|
||||
if len(speakers) == 0:
|
||||
return 'superman'
|
||||
elif len(speakers) == 1:
|
||||
return speakers[0]
|
||||
else:
|
||||
return ', '.join(speakers[:-1]) + ' & ' + str(speakers[-1])
|
||||
|
||||
@property
|
||||
def estimated_duration(self):
|
||||
return self.duration or self.event.duration
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('show-talk', kwargs={'slug': self.slug})
|
||||
|
||||
def is_moderable_by(self, user):
|
||||
if user.is_superuser:
|
||||
return True
|
||||
try:
|
||||
participation = Participation.objects.get(site=self.site, user=user)
|
||||
except Participation.DoesNotExists:
|
||||
return False
|
||||
if participation.orga:
|
||||
return True
|
||||
if self.topics.filter(reviewers=user).exists():
|
||||
return True
|
||||
if self.track and user in self.track.managers.all():
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_editable_by(self, user):
|
||||
return user == self.proposer or user in self.speakers.all() or self.is_moderable_by(user)
|
||||
|
||||
def score(self):
|
||||
if self.vote_set.exists():
|
||||
return query_sum(self.vote_set, 'vote') / len(self.vote_set.all())
|
||||
else:
|
||||
return 0
|
||||
|
||||
@property
|
||||
def end_date(self):
|
||||
if self.estimated_duration:
|
||||
return self.start_date + timedelta(minutes=self.estimated_duration)
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def remaining_attendees(self):
|
||||
if self.registration_required and self.attendees_limit:
|
||||
return self.attendees_limit - self.attendees.count()
|
||||
else:
|
||||
return None # = infinity \o/
|
||||
|
||||
@property
|
||||
def dtstart(self):
|
||||
return self.start_date.strftime('%Y%m%dT%H%M%SZ')
|
||||
|
||||
@property
|
||||
def dtend(self):
|
||||
return self.end_date.strftime('%Y%m%dT%H%M%SZ')
|
||||
|
||||
@property
|
||||
def materials_name(self):
|
||||
return basename(self.materials.name)
|
||||
|
||||
class Meta:
|
||||
ordering = ('event__id',)
|
||||
|
||||
|
||||
class Vote(PonyConfModel):
|
||||
|
||||
talk = models.ForeignKey(Talk)
|
||||
user = models.ForeignKey(User)
|
||||
vote = models.IntegerField(validators=[MinValueValidator(-2), MaxValueValidator(2)], default=0)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('talk', 'user')
|
||||
|
||||
def __str__(self):
|
||||
return "%+i by %s for %s" % (self.vote, self.user, self.talk)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return self.talk.get_absolute_url()
|
|
@ -1,55 +0,0 @@
|
|||
from django.db.models.signals import m2m_changed, post_save
|
||||
from django.dispatch import Signal, receiver
|
||||
from django.contrib.sites.models import Site
|
||||
from django.utils.translation import ugettext_noop
|
||||
from django.conf import settings
|
||||
|
||||
from ponyconf.decorators import disable_for_loaddata
|
||||
from accounts.models import Participation
|
||||
|
||||
from .models import Conference, Talk, Topic, Event
|
||||
|
||||
|
||||
talk_added = Signal(providing_args=["sender", "instance", "author"])
|
||||
talk_edited = Signal(providing_args=["sender", "instance", "author"])
|
||||
|
||||
|
||||
@receiver(post_save, sender=Site, dispatch_uid="Create Conference for Site")
|
||||
@disable_for_loaddata
|
||||
def create_conference(sender, instance, **kwargs):
|
||||
Conference.objects.get_or_create(site=instance)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Site, dispatch_uid="Create default events type for Site")
|
||||
@disable_for_loaddata
|
||||
def create_events(sender, instance, **kwargs):
|
||||
if not Event.objects.filter(site=instance).exists():
|
||||
Event.objects.bulk_create([
|
||||
Event(site=instance, name=ugettext_noop('conference (short)')),
|
||||
Event(site=instance, name=ugettext_noop('conference (long)')),
|
||||
Event(site=instance, name=ugettext_noop('workshop')),
|
||||
Event(site=instance, name=ugettext_noop('stand')),
|
||||
Event(site=instance, name=ugettext_noop('other')),
|
||||
])
|
||||
|
||||
|
||||
def call_first_site_post_save(apps, **kwargs):
|
||||
site = Site.objects.filter(id=getattr(settings, 'SITE_ID', 1))
|
||||
if site.exists():
|
||||
site.first().save()
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=Talk.speakers.through, dispatch_uid="Create Participation for speakers")
|
||||
def create_participation_for_speakers(sender, instance, action, reverse, model, pk_set, using, **kwargs):
|
||||
if action != "pre_add":
|
||||
pass
|
||||
for speaker in instance.speakers.all():
|
||||
Participation.objects.get_or_create(user=speaker, site=instance.site)
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=Topic.reviewers.through, dispatch_uid="Create Participation for reviewers")
|
||||
def create_participation_for_reviewers(sender, instance, action, reverse, model, pk_set, using, **kwargs):
|
||||
if action != "pre_add":
|
||||
pass
|
||||
for reviewer in instance.reviewers.all():
|
||||
Participation.objects.get_or_create(user=reviewer, site=instance.site)
|
|
@ -1,25 +0,0 @@
|
|||
var csrftoken = $.cookie('csrftoken');
|
||||
|
||||
function csrfSafeMethod(method) {
|
||||
// these HTTP methods do not require CSRF protection
|
||||
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
|
||||
}
|
||||
$.ajaxSetup({
|
||||
beforeSend: function(xhr, settings) {
|
||||
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
|
||||
xhr.setRequestHeader("X-CSRFToken", csrftoken);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$('a[href="#preview"]').on("show.bs.tab", function (e) {
|
||||
$('#preview-content').html('Loading preview...');
|
||||
var data = $('#markdown-content').val();
|
||||
$.post(markdown_preview_url, {'data': data})
|
||||
.done(function(data, textStatus) {
|
||||
$('#preview-content').html(data);
|
||||
})
|
||||
.fail(function () {
|
||||
$('#preview-content').html('Sorry, an error occured.');
|
||||
});
|
||||
})
|
|
@ -1,24 +0,0 @@
|
|||
{% load i18n %}
|
||||
{% regroup talk_list by event as event_list %}
|
||||
{% for event in event_list %}
|
||||
<h3>{{ event.list.0.event }}</h3>
|
||||
<ul>{% for talk in event.list %}
|
||||
<li>
|
||||
{{ talk.get_link }}
|
||||
<i>{% trans "by" %}</i>
|
||||
{% for speaker in talk.speakers.all %}
|
||||
<a href="{% url 'show-participant' speaker.username %}">{{ speaker }}</a>
|
||||
{% if forloop.revcounter == 2 %} {% trans "and" %} {% elif not forloop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
{% if talk.topics.exists %}
|
||||
<i>{% trans "in" %}</i>
|
||||
{% for topic in talk.topics.all %}
|
||||
<a href="{{ topic.get_absolute_url }}">{{ topic }}</a>
|
||||
{% if forloop.revcounter == 2 %} {% trans "and" %} {% elif not forloop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% empty %}{% trans "No talks" %}
|
||||
{% endfor %}
|
|
@ -1,53 +0,0 @@
|
|||
{% extends 'staff.html' %}
|
||||
|
||||
{% load bootstrap3 staticfiles i18n %}
|
||||
|
||||
{% block conferencetab %} class=active{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3>{% trans "Home page" %}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form method="post" role="form">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form exclude='home' %}
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li class="active"><a href="#editor" role="tab" data-toggle="tab">Editor</a></li>
|
||||
<li><a href="#preview" role="tab" data-toggle="tab">Preview</a></li>
|
||||
<li><a href="https://daringfireball.net/projects/markdown/syntax" target="blank" role="tab">Syntaxe</a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="editor" style="max-height: 220px; margin-bottom: 20px;">
|
||||
<div class="form-group">
|
||||
<textarea style="width: 100%; height: 220px;" id="markdown-content" name="home">{{ conference.home }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane well" id="preview" style="min-height: 220px; magin-bottom: 20px;">
|
||||
<div id="preview-content"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-pencil"></span> Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ block.super }}
|
||||
{{ form.media.css }}
|
||||
{% endblock css %}
|
||||
|
||||
{% block js_end %}
|
||||
{{ block.super }}
|
||||
{{ form.media.js }}
|
||||
<script type="text/javascript">
|
||||
var markdown_preview_url = "{% url 'markdown' %}";
|
||||
</script>
|
||||
<script src="{% static 'js/markdown-preview.js' %}"></script>
|
||||
{% endblock js_end %}
|
|
@ -1,11 +0,0 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% load proposals_tags i18n %}
|
||||
|
||||
{% block hometab %} class="active"{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% markdown site.conference.home %}
|
||||
|
||||
{% endblock %}
|
|
@ -1,35 +0,0 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% load accounts_tags i18n %}
|
||||
|
||||
{% block exhibitortab %} class="active"{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>{% trans "Participate" %}</h1>
|
||||
|
||||
{% if my_talks %}
|
||||
<h2>{% trans "My talks:" %}</h2>
|
||||
{% include "proposals/_talk_list.html" with talk_list=my_talks %}
|
||||
{% endif %}
|
||||
|
||||
{% if proposed_talks %}
|
||||
<h2>{% trans "Proposed talks for others speakers:" %}</h2>
|
||||
{% include "proposals/_talk_list.html" with talk_list=proposed_talks %}
|
||||
{% endif %}
|
||||
|
||||
<br />
|
||||
{% if conference.cfp_is_open %}
|
||||
{% trans "The Call for Participation is currently open for following categories:" %}
|
||||
{% for event in conference.opened_events.all %}
|
||||
{% if forloop.first %}<ul>{% endif %}
|
||||
<li>{{ event }}{% if event.closing_date %} ({% blocktrans with closing_date=event.closing_date|date:"DATETIME_FORMAT" %}until {{ closing_date }}{% endblocktrans %}){% endif %}</li>
|
||||
{% if forloop.last %}</ul>{% endif %}
|
||||
{% endfor %}
|
||||
<br />
|
||||
<a class="btn btn-{% if conference.cfp_is_open %}success{% else %}danger{% endif %}" href="{% url 'add-talk' %}">{% trans "Propose a talk" %}</a>
|
||||
{% else %}
|
||||
{% trans "Sorry, the Call for Participation is closed." %}
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
|
@ -1,117 +0,0 @@
|
|||
{% extends 'staff.html' %}
|
||||
|
||||
{% load bootstrap3 i18n %}
|
||||
|
||||
{% block speakerstab %} class="active"{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>{% trans "Speakers" %}</h1>
|
||||
|
||||
<a class="btn btn-primary" role="button" data-toggle="collapse" href="#filter" aria-expanded="{{ show_filters|yesno:"true,false" }}" aria-controles="filter">{% trans "Show filtering options…" %}</a>
|
||||
|
||||
<br /><br />
|
||||
|
||||
<div class="collapse{{ show_filters|yesno:" in," }}" id="filter">
|
||||
<div class="well">
|
||||
<form class="form-horizontal" method="get">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
{% bootstrap_field filter_form.transport layout="horizontal" %}
|
||||
{% bootstrap_field filter_form.transport_booked layout="horizontal" %}
|
||||
{% bootstrap_field filter_form.sound layout="horizontal" %}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{% bootstrap_field filter_form.accommodation layout="horizontal" %}
|
||||
{% bootstrap_field filter_form.accommodation_booked layout="horizontal" %}
|
||||
{% bootstrap_field filter_form.status layout="horizontal" %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
{% bootstrap_field filter_form.topic layout="horizontal" %}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{% bootstrap_field filter_form.track layout="horizontal" %}
|
||||
</div>
|
||||
</div>
|
||||
<input type="submit" class="btn btn-success" value="{% trans "Filter" %}">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table table-bordered table-hover">
|
||||
<caption>{% trans "Total:" %} {{ speaker_list|length }} {% trans "speaker" %}{{ speaker_list|length|pluralize }}
|
||||
</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">{% trans "Username" %}</th>
|
||||
<th class="text-center">{% trans "Fullname" %}</th>
|
||||
<th class="text-center">{% trans "Talk count" %}</th>
|
||||
<th class="text-center">{% blocktrans context "table column title" %}Need transport?{% endblocktrans %}</th>
|
||||
<th class="text-center">{% blocktrans context "table column title" %}Need accommodation?{% endblocktrans %}</th>
|
||||
<th class="text-center">{% trans "Need sound?" %}</th>
|
||||
<th class="text-center"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="7">{% trans "Contact:" %} <a href="{{ contact_link }}">{% trans "link" %}</a></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
{% for speaker in speaker_list %}
|
||||
{% if forloop.first %}
|
||||
<tbody>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td><a href="{% url 'show-participant' username=speaker.user.username %}">{{ speaker.user.username }}</a></td>
|
||||
<td>{{ speaker.user.get_full_name }}</td>
|
||||
<td class="text-right">{{ speaker.not_refused_talk_set.count }}{% if speaker.pending_talk_set.count %} ({{ speaker.pending_talk_set.count }} pending){% endif %}</td>
|
||||
{% if speaker.need_transport %}
|
||||
<td class="{% if speaker.transport_booked %}success{% else %}warning{% endif %}">
|
||||
{% for transport in speaker.transport.all %}
|
||||
{% if not forloop.first %}, {% endif %}
|
||||
{{ transport }}
|
||||
{% empty %}
|
||||
Yes
|
||||
{% endfor %}
|
||||
</td>
|
||||
{% elif speaker.need_transport is None %}
|
||||
<td>?</td>
|
||||
{% else %}
|
||||
<td>No</td>
|
||||
{% endif %}
|
||||
<td{% if speaker.accommodation is not None and speaker.accommodation != speaker.ACCOMMODATION_NO %} class="{% if speaker.accommodation_booked %}success{% else %}warning{% endif %}"{% endif %}>
|
||||
{% if speaker.accommodation is None %}
|
||||
?
|
||||
{% else %}
|
||||
{{ speaker.get_accommodation_display }}
|
||||
{% endif %}
|
||||
</td>
|
||||
{% if speaker.sound %}
|
||||
<td class="warning">Yes</td>
|
||||
{% else %}
|
||||
<td>No</td>
|
||||
{% endif %}
|
||||
<td>
|
||||
<a class="btn btn-{% if speaker.conversation.messages.last.author == speaker.user %}primary{% else %}default{% endif %}" href="{% url 'user-conversation' speaker.user.username %}">{% trans "Contact" %}</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% if forloop.last %}
|
||||
</tbody>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_end %}
|
||||
<script type="text/javascript">
|
||||
jQuery(document).ready(function($) {
|
||||
var anchor = window.location.hash.replace("#", "");
|
||||
if (anchor == "filter") {
|
||||
$("#filter").collapse('show');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -1,26 +0,0 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block listingtab %} active{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>{% if accept %}{% trans "Are you sure to accept this proposals?" %}{% else %}{% trans "Are you sure to decline this proposals?" %}{% endif %}</h1>
|
||||
|
||||
<h3>{% trans "Information about the proposals" %}</h3>
|
||||
<b>{% trans "Title:" %}</b> {{ talk.title }}<br />
|
||||
<b>{% trans "Kind:" %}</b> {{ talk.event }}<br />
|
||||
|
||||
<h3>{% trans "Information for the proposer" %}</h3>
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<label for="message">{% trans "If you want to send a message to the proposer, please enter it below. Remember to indicate which talk your message is reffering." %}</label>
|
||||
<textarea name="message" class="form-control" rows="5"></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-{% if accept %}success{% else %}danger{% endif %}">{% if accept %}{% trans "Accept the proposal" %}{% else %}{% trans "Decline the proposal" %}{% endif %}</button>
|
||||
<a class="btn btn-default" href="{% url 'show-talk' talk.slug %}">{% trans "Cancel" %}</a>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
|
@ -1,153 +0,0 @@
|
|||
{% extends base_template %}
|
||||
|
||||
{% if staff %}
|
||||
{% block talkstab %} class="active"{% endblock %}
|
||||
{% else %}
|
||||
{% block exhibitortab %} class="active"{% endblock %}
|
||||
{% endif %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>{{ talk.title }}</h1>
|
||||
|
||||
{% if edit_perm %}
|
||||
<a class="btn btn-success" href="{% url 'edit-talk' talk.slug %}">{% trans "Edit" %}</a><br />
|
||||
{% endif %}
|
||||
|
||||
<p>{% if talk.abstract %}{{ talk.abstract }}{% else %}<i>{% trans "No abstract provided." %}</i>{% endif %}</p>
|
||||
|
||||
{% if moderate_perm %}
|
||||
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Format" %}</dt>
|
||||
<dd><a href="{{ talk.event.get_absolute_url }}">{{ talk.event }}</a></dd>
|
||||
<dt>{% trans "Topics" %}</dt>
|
||||
<dd>{% for topic in talk.topics.all %}
|
||||
<a href="{{ topic.get_absolute_url }}">{{ topic }}</a>{% if not forloop.last %}, {% endif %}
|
||||
{% empty %}
|
||||
<i>{% trans "No topics." %}</i>
|
||||
{% endfor %}</dd>
|
||||
|
||||
<dt>{% trans "Track" %}</dt>
|
||||
<dd>{% if talk.track %}
|
||||
<a href="{{ talk.track.get_absolute_url }}">{{ talk.track }}</a>
|
||||
{% else %}
|
||||
<em>{% trans "No assigned yet." %}</em>
|
||||
{% endif %}</dd>
|
||||
|
||||
<dt>Horaire</dt>
|
||||
<dd>{% if talk.start_date %}
|
||||
<span class="date">{{ talk.start_date|date:"l d b" }}</span>,
|
||||
<span class="time">{{ talk.start_date|date:"H:i" }} – {% if talk.end_date %}{{ talk.end_date|date:"H:i" }}{% else %}?{% endif %}</span>
|
||||
{% else %}<em>{% trans "not defined" %}</em>
|
||||
{% endif %}
|
||||
</dd>
|
||||
<dt>Salle</dt>
|
||||
<dd>{% if talk.room %}
|
||||
<a href="{{ talk.room.get_absolute_url }}">
|
||||
<span class="label label-info">{{ talk.room }}</span>
|
||||
</a>
|
||||
{% else %}<em>{% trans "not defined" %}</em>
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% if talk.registration_required %}
|
||||
<dt>{% trans "Registrations" %}</dt>
|
||||
<dd>{% if talk.attendees_limit %}{{ talk.attendees.count }} / {{ talk.attendees_limit }}{% else %}{% trans "required but unlimited" %}{% endif %}</dd>
|
||||
{% endif %}
|
||||
{% if talk.materials %}
|
||||
<dt>{% trans "Materials" %}</dt>
|
||||
<dd><a href="{{ talk.materials.url }}">{{ talk.materials_name }}</a></dd>
|
||||
{% endif %}
|
||||
{% if talk.video %}
|
||||
<dt>{% trans "Video" %}</dt>
|
||||
<dd><a href="{{ talk.video }}">{% trans "download" %}</a></dd>
|
||||
{% endif %}
|
||||
|
||||
</dl>
|
||||
|
||||
{% endif %}
|
||||
|
||||
<h3>{% trans "Description" %}</h3>
|
||||
|
||||
<p>{% if talk.description %}{{ talk.description|linebreaksbr }}{% else %}<i>{% trans "No description provided." %}</i>{% endif %}</p>
|
||||
|
||||
<h3>{% trans "Speakers" %}</h3>
|
||||
|
||||
{% for speaker in talk.speakers.all %}
|
||||
{% if forloop.first %}<ul>{% endif %}
|
||||
<li><a href="{% url 'show-participant' speaker.username %}">{{ speaker.profile }}</a></li>
|
||||
{% if forloop.last %}</ul>{% endif %}
|
||||
{% empty %}
|
||||
<i>{% trans "No speakers." %}</i>
|
||||
{% endfor %}
|
||||
|
||||
{% if moderate_perm %}
|
||||
|
||||
{% if not talk.track %}
|
||||
<h3>{% trans "Track" %}</h3>
|
||||
<p><em>{% trans "No assigned yet." %}</em></p>
|
||||
{% for topic in talk.topics.distinct %}
|
||||
{% if forloop.first %}<p>{% endif %}
|
||||
{% if topic.track %}
|
||||
<a class="btn btn-primary" href="{% url 'assign-talk-to-track' talk.slug topic.track.slug %}">{% trans "Assign to" %} {{ topic.track }}</a>
|
||||
{% endif %}
|
||||
{% if forloop.last %}</p>{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
<h3>{% trans "Notes" %}</h3>
|
||||
|
||||
<p>{% if talk.notes %}{{ talk.notes|linebreaksbr }}{% else %}<i>{% trans "No notes." %}</i>{% endif %}</p>
|
||||
|
||||
{% if moderate_perm %}
|
||||
|
||||
<h2>{% trans "Moderation" %}</h2>
|
||||
|
||||
<h3>{% trans "Status" %}</h3>
|
||||
|
||||
<span class="label label-{{ talk.accepted|yesno:"success,danger,warning" }}">{{ talk.accepted|yesno:"Accepted,Declined,Pending decision" }}</span><br />
|
||||
|
||||
{% if talk.accepted == None %}
|
||||
<h3>{% trans "Vote" %}</h3>
|
||||
<div class="btn-group" role="group" aria-label="vote">
|
||||
<a class="btn {% if vote.vote == -2 %} active {% endif %}btn-danger" href="{% url 'vote' talk=talk.slug score='-2' %}">-2</a>
|
||||
<a class="btn {% if vote.vote == -1 %} active {% endif %}btn-warning" href="{% url 'vote' talk=talk.slug score='-1' %}">-1</a>
|
||||
<a class="btn {% if vote.vote == 0 %} active {% endif %}btn-default" href="{% url 'vote' talk=talk.slug score='0' %}"> 0</a>
|
||||
<a class="btn {% if vote.vote == 1 %} active {% endif %}btn-info" href="{% url 'vote' talk=talk.slug score='1' %}">+1</a>
|
||||
<a class="btn {% if vote.vote == 2 %} active {% endif %}btn-success" href="{% url 'vote' talk=talk.slug score='2' %}">+2</a>
|
||||
</div>
|
||||
<br /><br />
|
||||
<p>{{ talk.vote_set.all|length }} {% trans "vote" %}{{ talk.vote_set.all|length|pluralize }}, {% trans "average:" %} {{ talk.score|floatformat:1 }}</p>
|
||||
|
||||
<a href="{% url 'accept-talk' talk.slug %}" class="btn btn-success">Accept</a>
|
||||
<a href="{% url 'decline-talk' talk.slug %}" class="btn btn-danger">Decline</a>
|
||||
{% endif %}
|
||||
|
||||
{% if talk.registration_required %}
|
||||
<h3>{% trans "Attendees" %}</h3>
|
||||
|
||||
{% for attendee in talk.attendees.all %}
|
||||
{% if forloop.first %}<ol>{% endif %}
|
||||
<li><a href="mailto:{{ attendee.get_email }}">{{ attendee.get_name }}</a></li>
|
||||
{% if forloop.last %}</ol>{% endif %}
|
||||
{% empty %}
|
||||
<em>{% trans "No attendees yet." %}</em>
|
||||
{% endfor %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
<h3>{% trans "Messages" %}</h3>
|
||||
{% trans "These messages are for organization team only." %}<br /><br />
|
||||
{% for message in talk.conversation.messages.all %}
|
||||
{% include 'conversations/_message_detail.html' %}
|
||||
{% endfor %}
|
||||
|
||||
{% include 'conversations/_message_form.html' %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
|
@ -1,37 +0,0 @@
|
|||
{% extends base_template %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block talkstab %}{% if base_template == 'staff.html' %} class="active"{% endif %}{% endblock %}
|
||||
{% block exhibitortab %}{% if base_template == 'base.html' %} class="active"{% endif %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>{% if talk %}{% trans "Edit a talk" %}{% else %}{% trans "Propose a talk" %}{% endif %}</h1>
|
||||
|
||||
{% include "_form.html" with multipart=True %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ block.super }}
|
||||
{{ form.media.css }}
|
||||
{% endblock css %}
|
||||
|
||||
{% block js_end %}
|
||||
{{ block.super }}
|
||||
{{ form.media.js }}
|
||||
<script type="text/javascript">
|
||||
var update_attendees_limit = function() {
|
||||
if ($("#id_registration_required").is(":checked")) {
|
||||
$(".form-group:has(#id_attendees_limit)").show();
|
||||
} else {
|
||||
$(".form-group:has(#id_attendees_limit)").hide();
|
||||
}
|
||||
}
|
||||
$(function() {
|
||||
update_attendees_limit();
|
||||
$("#id_registration_required").change(update_attendees_limit);
|
||||
})
|
||||
</script>
|
||||
{% endblock js_end %}
|
|
@ -1,122 +0,0 @@
|
|||
{% extends 'staff.html' %}
|
||||
|
||||
{% load bootstrap3 i18n accounts_tags %}
|
||||
|
||||
{% block talkstab %} class="active"{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>{% trans "Talks" %}</h1>
|
||||
|
||||
<a class="btn btn-primary" role="button" data-toggle="collapse" href="#filter" aria-expanded="{{ show_filters|yesno:"true,false" }}" aria-controls="filter">{% trans "Show filtering options…" %}</a>
|
||||
|
||||
<br /><br />
|
||||
|
||||
<div class="collapse{{ show_filters|yesno:" in," }}" id="filter">
|
||||
<div class="well">
|
||||
<form class="form-horizontal" method="get">
|
||||
<div class="row">
|
||||
<div class="col-md-4 col-xs-6">
|
||||
{% bootstrap_field filter_form.status layout="horizontal" %}
|
||||
{% bootstrap_field filter_form.kind layout="horizontal" %}
|
||||
{% bootstrap_field filter_form.vote layout="horizontal" %}
|
||||
{% bootstrap_field filter_form.room layout="horizontal" %}
|
||||
{% bootstrap_field filter_form.scheduled layout="horizontal" %}
|
||||
{% bootstrap_field filter_form.materials layout="horizontal" %}
|
||||
{% bootstrap_field filter_form.video layout="horizontal" %}
|
||||
</div>
|
||||
<div class="col-md-4 col-xs-6">
|
||||
{% bootstrap_field filter_form.topic layout="horizontal" %}
|
||||
</div>
|
||||
<div class="col-md-4 col-xs-6">
|
||||
{% bootstrap_field filter_form.track layout="horizontal" %}
|
||||
</div>
|
||||
</div>
|
||||
<input type="submit" class="btn btn-success" value="{% trans "Filter" %}">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form action="" method="post">
|
||||
|
||||
<table class="table table-bordered table-hover">
|
||||
<caption>{% trans "Total:" %} {{ talk_list|length }} {% trans "talk" %}{{ talk_list|length|pluralize }}</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th class="text-center">{% trans "Title" %} <a href="?{{ sort_urls.title }}"><span class="glyphicon glyphicon-{{ sort_glyphicons.title }} pull-right"></span></a></th>
|
||||
<th class="text-center">{% trans "Intervention kind" %} <a href="?{{ sort_urls.kind }}"><span class="glyphicon glyphicon-{{ sort_glyphicons.kind }} pull-right"></span></a></th>
|
||||
<th class="text-center">{% trans "Speakers" %}</th>
|
||||
<th class="text-center">{% trans "Topics" %}</th>
|
||||
<th class="text-center">{% trans "Track" %}</th>
|
||||
<th class="text-center">{% trans "Status" %} <a href="?{{ sort_urls.status }}"><span class="glyphicon glyphicon-{{ sort_glyphicons.status }} pull-right"></span></a></th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for talk in talk_list %}
|
||||
{% if forloop.first %}
|
||||
<tbody>
|
||||
{% endif %}
|
||||
<tr class="{{ talk.accepted|yesno:"success,danger,warning" }}">
|
||||
<td><input type="checkbox" name="talks" value="{{ talk.slug }}"></td>
|
||||
<td><a href="{% url 'show-talk' talk.slug %}">{{ talk.title }}</a></td>
|
||||
<td>{{ talk.event }}</td>
|
||||
<td>
|
||||
{% for speaker in talk.speakers.all %}
|
||||
<a href="{% url 'show-participant' speaker.username %}">{{ speaker.profile }}</a>
|
||||
{% if forloop.revcounter == 2 %} {% trans "and" %} {% elif not forloop.last %}, {% endif %}
|
||||
{% empty %}–
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>
|
||||
{% for topic in talk.topics.all %}
|
||||
<span class="badge">{{ topic }}</span>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>{{ talk.track|default:"–" }}</td>
|
||||
<td>
|
||||
{% if talk.accepted == True %}
|
||||
{% trans "Accepted" %}
|
||||
{% elif talk.accepted == False %}
|
||||
{% trans "Declined" %}
|
||||
{% else %}
|
||||
{% blocktrans with score=talk.score|floatformat:1 %}Pending, score: {{ score }}{% endblocktrans %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% if forloop.last%}
|
||||
</tbody>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% if action_form %}
|
||||
<div id="filter">
|
||||
<div class="well">
|
||||
<h4>{% trans "For selected talks:" %}</h4>
|
||||
{% csrf_token %}
|
||||
{% bootstrap_field action_form.decision %}
|
||||
{% if request|orga %}
|
||||
{% bootstrap_field action_form.track %}
|
||||
{% bootstrap_field action_form.room %}
|
||||
{% endif %}
|
||||
{% buttons %}
|
||||
<button type="submit" class="btn btn-primary">{% trans "Apply" %}</button>
|
||||
{% endbuttons %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_end %}
|
||||
<script type="text/javascript">
|
||||
jQuery(document).ready(function($) {
|
||||
var anchor = window.location.hash.replace("#", "");
|
||||
if (anchor == "filter") {
|
||||
$("#filter").collapse('show');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -1,29 +0,0 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% load bootstrap3 i18n %}
|
||||
|
||||
{% block wsregtab %} class="active"{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ block.super }}
|
||||
{{ form.media.css }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>{% trans "Register for a workshop" %}<br /><small>{{ talk.title }}</small></h1>
|
||||
|
||||
<hr>
|
||||
|
||||
{% include "_form.html" %}
|
||||
|
||||
<p class="text-center">
|
||||
<a href="{% url "login" %}?next={% url "register-for-a-talk" talk.slug %}">{% trans "Login to register with your account" %}</a>
|
||||
</p>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_end %}
|
||||
{{ block.super }}
|
||||
{{ form.media.js }}
|
||||
{% endblock %}
|
|
@ -1,36 +0,0 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% load accounts_tags i18n %}
|
||||
|
||||
{% block wsregtab %} class="active"{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>{% trans "Register to a workshop" %}</h1>
|
||||
|
||||
{% for talk in talks %}
|
||||
{% if forloop.first %}<div class="list-group">{% endif %}
|
||||
<div class="list-group-item{% if attendee in talk.attendees.all %} list-group-item-info{% endif %}">
|
||||
<h4 clas="list-group-item-heading">{{ talk.title }}</h4>
|
||||
<p class="list-group-item-text">
|
||||
<p>{{ talk.description }}</p>
|
||||
{% if talk.attendees_limit %}
|
||||
<p><em>{% blocktrans count remaining=talk.remaining_attendees %}{{ remaining }} remaining seat{% plural %}{{ remaining }} remaining seats{% endblocktrans %}</em></p>
|
||||
{% endif %}
|
||||
{% if talk.remaining_attendees != 0 or attendee in talk.attendees.all %}
|
||||
<p>
|
||||
{% if attendee in talk.attendees.all %}
|
||||
<a class="btn btn-danger" href="{% url 'register-for-a-talk' talk=talk.slug %}">{% trans "Unregister" %}</a>
|
||||
{% else %}
|
||||
<a class="btn btn-primary" href="{% url 'register-for-a-talk' talk=talk.slug %}">{% trans "Register" %}</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
{% if forloop.last %}</div>{% endif %}
|
||||
{% empty %}
|
||||
<em>{% trans "There are no workshops requiring registration for now … come back later!" %}</em>
|
||||
{% endfor %}
|
||||
|
||||
{% endblock %}
|
|
@ -1,23 +0,0 @@
|
|||
{% extends 'staff.html' %}
|
||||
|
||||
{% load bootstrap3 i18n %}
|
||||
|
||||
{% block topicstab %} class="active"{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ block.super }}
|
||||
{{ form.media.css }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>{% trans "Topic" %}</h1>
|
||||
|
||||
{% include "_form.html" %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_end %}
|
||||
{{ block.super }}
|
||||
{{ form.media.js }}
|
||||
{% endblock %}
|
|
@ -1,35 +0,0 @@
|
|||
{% extends 'staff.html' %}
|
||||
|
||||
{% load bootstrap3 accounts_tags i18n %}
|
||||
|
||||
{% block topicstab %} class="active"{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>{% trans "Topics" %}</h1>
|
||||
|
||||
{% if request|orga %}
|
||||
<p><a href="{% url 'add-topic' %}" class="btn btn-success">{% trans "Add a topic" %}</a><p>
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
{% for topic in topic_list %}
|
||||
<div class="col-xs-6 col-sm-4">
|
||||
<h2>{{ topic }}</h2>
|
||||
<p>{{ topic.description }}</p>
|
||||
{% if request|staff %}
|
||||
{{ topic.reviewers.count }} {% trans "reviewer" %}{{ topic.reviewers.count|pluralize }}
|
||||
|
|
||||
<a href="{{ topic.get_absolute_url }}">{{ topic.talk_set.count }} {% trans "talk" %}{{ topic.talk_set.count|pluralize }}</a>
|
||||
|
|
||||
<a href="{% url 'edit-topic' topic.slug %}">{% bootstrap_icon "pencil" %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% cycle '' '<div class="clearfix visible-xs"></div>' %}
|
||||
{% cycle '' '' '<div class="clearfix hidden-xs"></div>' %}
|
||||
{% empty %}
|
||||
<div class="col-xs-12"><em>{% trans "No topics." %}</em></div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
|
@ -1,23 +0,0 @@
|
|||
{% extends 'staff.html' %}
|
||||
|
||||
{% load bootstrap3 i18n %}
|
||||
|
||||
{% block trackstab %} class="active"{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ block.super }}
|
||||
{{ form.media.css }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>{% trans "Track" %}</h1>
|
||||
|
||||
{% include "_form.html" %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_end %}
|
||||
{{ block.super }}
|
||||
{{ form.media.js }}
|
||||
{% endblock %}
|
|
@ -1,37 +0,0 @@
|
|||
{% extends 'staff.html' %}
|
||||
|
||||
{% load bootstrap3 accounts_tags proposals_tags i18n %}
|
||||
|
||||
{% block trackstab %} class="active"{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>{% trans "Tracks" %}</h1>
|
||||
|
||||
{% if request|orga %}
|
||||
<p><a href="{% url 'add-track' %}" class="btn btn-success">{% trans "Add a track" %}</a><p>
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
{% for track in track_list %}
|
||||
<div class="col-xs-6 col-sm-4">
|
||||
<h2>{{ track }}</h2>
|
||||
<p>{{ track.description }}</p>
|
||||
{% if request|staff %}
|
||||
{{ track.managers.count }} {% trans "manager" %}{{ track.managers.count|pluralize }}
|
||||
|
|
||||
<a href="{{ track.get_absolute_url }}">{{ track.talk_set.count }} {% trans "talk" %}{{ track.talk_set.count|pluralize }}</a>
|
||||
|
|
||||
{{ track.estimated_duration|duration_format }}
|
||||
|
|
||||
<a href="{% url 'edit-track' track.slug %}">{% bootstrap_icon "pencil" %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% cycle '' '<div class="clearfix visible-xs"></div>' %}
|
||||
{% cycle '' '' '<div class="clearfix hidden-xs"></div>' %}
|
||||
{% empty %}
|
||||
<div class="col-xs-12"><em>{% trans "No tracks." %}</em></div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
|
@ -1,18 +0,0 @@
|
|||
from django import template
|
||||
|
||||
from proposals.utils import markdown_to_html
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def markdown(value):
|
||||
return markdown_to_html(value)
|
||||
|
||||
@register.filter('duration_format')
|
||||
def duration_format(value):
|
||||
value = int(value)
|
||||
hours = int(value/60)
|
||||
minutes = value%60
|
||||
return '%d h %02d' % (hours, minutes)
|
|
@ -1,95 +0,0 @@
|
|||
from django.contrib.auth.models import User
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import TestCase
|
||||
|
||||
from accounts.models import Participation
|
||||
|
||||
from .models import Talk, Topic, Vote
|
||||
|
||||
|
||||
class ProposalsTests(TestCase):
|
||||
def setUp(self):
|
||||
a, b, c = (User.objects.create_user(guy, email='%s@example.org' % guy, password=guy) for guy in 'abc')
|
||||
Topic.objects.create(name='topipo', description='super topic', site=Site.objects.first())
|
||||
c.is_superuser = True
|
||||
c.save()
|
||||
|
||||
def test_everything(self):
|
||||
# talk-edit
|
||||
self.client.login(username='a', password='a')
|
||||
self.client.post(reverse('add-talk'),
|
||||
{'title': 'super talk',
|
||||
'abstract': 'super',
|
||||
'description': 'this is my super talk',
|
||||
'notes': 'you can watch my previous talk videos',
|
||||
'event': 1,
|
||||
'topics': 1,
|
||||
'speakers': 1})
|
||||
talk = Talk.objects.first()
|
||||
self.assertEqual(str(talk), 'super talk')
|
||||
self.assertEqual(talk.abstract, 'super')
|
||||
self.assertEqual(talk.description, 'this is my super talk')
|
||||
self.assertEqual(talk.notes, 'you can watch my previous talk videos')
|
||||
response = self.client.post(reverse('edit-talk', kwargs={'talk': 'super-talk'}),
|
||||
{'title': 'mega talk', 'description': 'mega',
|
||||
'event': 1, 'speakers': 1, 'duration': 60,
|
||||
'registration_required': False, 'attendees_limit': 0})
|
||||
self.assertEqual(str(talk), 'super talk') # title is read only there
|
||||
talk = Talk.objects.first()
|
||||
self.assertEqual(talk.description, 'mega')
|
||||
|
||||
# Status Code
|
||||
for view in ['home', 'participate-as-speaker', 'add-talk', 'list-topics']:
|
||||
self.assertEqual(self.client.get(reverse(view)).status_code, 200)
|
||||
for view in ['list-talks', 'list-speakers']:
|
||||
self.assertEqual(self.client.get(reverse(view)).status_code, 403)
|
||||
self.assertEqual(self.client.get(reverse('list-speakers')).status_code, 403)
|
||||
self.assertEqual(self.client.get(reverse('edit-talk', kwargs={'talk': talk.slug})).status_code, 200)
|
||||
self.assertEqual(self.client.get(reverse('show-talk', kwargs={'slug': talk.slug})).status_code, 200)
|
||||
self.assertEqual(self.client.get(reverse('show-participant', kwargs={'username': 'a'})).status_code, 200)
|
||||
|
||||
self.client.login(username='b', password='b')
|
||||
self.assertEqual(self.client.post(reverse('edit-talk', kwargs={'talk': 'super-talk'}),
|
||||
{'title': 'mega talk', 'description': 'mega', 'event': 1}).status_code, 403)
|
||||
self.assertEqual(self.client.get(reverse('participate-as-speaker')).status_code, 200)
|
||||
|
||||
# Vote
|
||||
self.assertEqual(talk.score(), 0)
|
||||
self.assertEqual(self.client.get(reverse('vote', kwargs={'talk': talk.slug, 'score': 2})).status_code, 403)
|
||||
self.client.login(username='c', password='c')
|
||||
self.assertEqual(self.client.get(reverse('vote', kwargs={'talk': talk.slug, 'score': 2})).status_code, 302)
|
||||
self.assertEqual(talk.score(), 2)
|
||||
|
||||
# Models str & get_asbolute_url
|
||||
for model in [Talk, Topic, Vote]:
|
||||
item = model.objects.first()
|
||||
self.assertEqual(self.client.get(item.get_absolute_url()).status_code, 200)
|
||||
self.assertTrue(str(item))
|
||||
|
||||
# Talk.is_{editable,moderable}_by
|
||||
a, b, c = User.objects.all()
|
||||
self.assertTrue(talk.is_editable_by(c)) # c is superuser
|
||||
self.assertTrue(talk.is_moderable_by(c)) # c is superuser
|
||||
self.assertFalse(talk.is_editable_by(b)) # b is not speaker
|
||||
self.assertFalse(talk.is_moderable_by(b)) # b is not orga
|
||||
self.client.login(username='a', password='a')
|
||||
self.client.post(reverse('edit-talk', kwargs={'talk': 'super-talk'}),
|
||||
{'title': 'mega talk', 'description': 'mega', 'event': 1,
|
||||
'speakers': (a.pk, b.pk), 'duration': 60,
|
||||
'registration_required': False, 'attendees_limit': 0})
|
||||
talk = Talk.objects.get(slug='super-talk')
|
||||
self.assertTrue(b in talk.speakers.all())
|
||||
self.assertTrue(talk.is_editable_by(b)) # b is speaker now
|
||||
self.assertFalse(talk.is_moderable_by(b)) # b is not orga
|
||||
|
||||
def test_topic_edition_permissions(self):
|
||||
# Only orga and superuser can edit topics
|
||||
self.client.login(username='b', password='b')
|
||||
self.assertFalse(Participation.objects.get(user__username='b').orga)
|
||||
self.assertEqual(self.client.get(reverse('edit-topic', kwargs={'slug': 'topipo'})).status_code, 302)
|
||||
Participation.objects.filter(user__username='b').update(orga=True)
|
||||
self.assertEqual(self.client.get(reverse('edit-topic', kwargs={'slug': 'topipo'})).status_code, 200)
|
||||
self.client.login(username='c', password='c') # superuser
|
||||
self.assertEqual(self.client.get(reverse('edit-topic', kwargs={'slug': 'topipo'})).status_code, 200)
|
||||
self.assertEqual(self.client.get(reverse('list-topics')).status_code, 200)
|
|
@ -1,28 +0,0 @@
|
|||
from django.conf.urls import url
|
||||
|
||||
from proposals import views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^markdown/$', views.markdown_preview, name='markdown'),
|
||||
url(r'^$', views.home, name='home'),
|
||||
url(r'^staff/$', views.staff, name='staff'),
|
||||
url(r'^conference/$', views.conference, name='edit-conference'),
|
||||
url(r'^talk/propose/$', views.participate, name='participate-as-speaker'),
|
||||
url(r'^talk/$', views.talk_list, name='list-talks'),
|
||||
url(r'^talk/add/$', views.talk_edit, name='add-talk'),
|
||||
url(r'^talk/edit/(?P<talk>[-\w]+)$', views.talk_edit, name='edit-talk'),
|
||||
url(r'^talk/vote/(?P<talk>[-\w]+)/(?P<score>[-0-2]+)$', views.vote, name='vote'),
|
||||
url(r'^talk/details/(?P<slug>[-\w]+)$', views.TalkDetail.as_view(), name='show-talk'),
|
||||
url(r'^talk/accept/(?P<talk>[-\w]+)/$', views.talk_decide, {'accepted': True}, name='accept-talk'),
|
||||
url(r'^talk/decline/(?P<talk>[-\w]+)/$', views.talk_decide, {'accepted': False}, name='decline-talk'),
|
||||
url(r'^talk/assign-to-track/(?P<talk>[-\w]+)/(?P<track>[-\w]+)/$', views.talk_assign_to_track, name='assign-talk-to-track'),
|
||||
url(r'^topic/$', views.TopicList.as_view(), name='list-topics'),
|
||||
url(r'^topic/add/$', views.TopicCreate.as_view(), name='add-topic'),
|
||||
url(r'^topic/(?P<slug>[-\w]+)/edit/$', views.TopicUpdate.as_view(), name='edit-topic'),
|
||||
url(r'^track/$', views.TrackList.as_view(), name='list-tracks'),
|
||||
url(r'^track/add/$', views.TrackCreate.as_view(), name='add-track'),
|
||||
url(r'^track/(?P<slug>[-\w]+)/edit/$', views.TrackUpdate.as_view(), name='edit-track'),
|
||||
url(r'^speakers/$', views.speaker_list, name='list-speakers'),
|
||||
url(r'^register/$', views.talk_registrable_list, name='list-registrable-talks'),
|
||||
url(r'^register/(?P<talk>[-\w]+)$', views.talk_register, name='register-for-a-talk'),
|
||||
]
|
|
@ -1,25 +0,0 @@
|
|||
from django.contrib.sites.shortcuts import get_current_site
|
||||
from django.db.models import Q, Sum
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from accounts.models import Participation
|
||||
|
||||
from markdown import markdown
|
||||
import bleach
|
||||
|
||||
|
||||
def query_sum(queryset, field):
|
||||
return queryset.aggregate(s=Coalesce(Sum(field), 0))['s']
|
||||
|
||||
|
||||
def allowed_talks(talks, request):
|
||||
if not Participation.objects.get(site=get_current_site(request), user=request.user).is_orga():
|
||||
talks = talks.filter(Q(topics__reviewers=request.user) | Q(speakers=request.user) | Q(proposer=request.user))
|
||||
return talks.distinct()
|
||||
|
||||
def markdown_to_html(md):
|
||||
html = markdown(md)
|
||||
allowed_tags = bleach.ALLOWED_TAGS + ['p', 'pre', 'span' ] + ['h%d' % i for i in range(1, 7) ]
|
||||
html = bleach.clean(html, tags=allowed_tags)
|
||||
return mark_safe(html)
|
|
@ -1,459 +0,0 @@
|
|||
from functools import reduce
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.sites.shortcuts import get_current_site
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db.models import Q, Count
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.views.generic import CreateView, DetailView, ListView, UpdateView
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext_lazy
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from django.http import HttpResponse, Http404
|
||||
|
||||
from ponyconf.mixins import OnSiteFormMixin
|
||||
|
||||
from accounts.models import Participation
|
||||
from accounts.mixins import OrgaRequiredMixin, StaffRequiredMixin
|
||||
from accounts.decorators import orga_required, staff_required
|
||||
from accounts.utils import is_staff, is_orga
|
||||
|
||||
from conversations.models import ConversationWithParticipant, ConversationAboutTalk, Message
|
||||
|
||||
from planning.models import Room
|
||||
|
||||
from .forms import TalkForm, TopicForm, TrackForm, ConferenceForm, TalkFilterForm, STATUS_VALUES, SpeakerFilterForm, TalkActionForm, SubscribeForm
|
||||
from .models import Talk, Track, Topic, Vote, Conference, Attendee
|
||||
from .signals import talk_added, talk_edited
|
||||
from .utils import markdown_to_html
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(["POST"])
|
||||
def markdown_preview(request):
|
||||
content = request.POST.get('data', '')
|
||||
return HttpResponse(markdown_to_html(content))
|
||||
|
||||
|
||||
def home(request):
|
||||
return render(request, 'proposals/home.html')
|
||||
|
||||
|
||||
@staff_required
|
||||
def staff(request):
|
||||
return render(request, 'staff.html')
|
||||
|
||||
|
||||
@orga_required
|
||||
def conference(request):
|
||||
conference = Conference.objects.get(site=get_current_site(request))
|
||||
form = ConferenceForm(request.POST or None, instance=conference)
|
||||
if request.method == 'POST' and form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, 'Conference updated!')
|
||||
return redirect(reverse('edit-conference'))
|
||||
return render(request, 'proposals/conference.html', {
|
||||
'form': form,
|
||||
})
|
||||
|
||||
@login_required
|
||||
def participate(request):
|
||||
site = get_current_site(request)
|
||||
talks = Talk.objects.filter(site=site)
|
||||
my_talks = talks.filter(speakers=request.user)
|
||||
proposed_talks = talks.exclude(speakers=request.user).filter(proposer=request.user)
|
||||
return render(request, 'proposals/participate.html', {
|
||||
'my_talks': my_talks,
|
||||
'proposed_talks': proposed_talks,
|
||||
})
|
||||
|
||||
@staff_required
|
||||
def talk_list(request):
|
||||
show_filters = False
|
||||
talks = Talk.objects.filter(site=get_current_site(request))
|
||||
filter_form = TalkFilterForm(request.GET or None, site=get_current_site(request))
|
||||
# Filtering
|
||||
if filter_form.is_valid():
|
||||
data = filter_form.cleaned_data
|
||||
if len(data['kind']):
|
||||
show_filters = True
|
||||
talks = talks.filter(reduce(lambda x, y: x | y, [Q(event__pk=pk) for pk in data['kind']]))
|
||||
if len(data['status']):
|
||||
show_filters = True
|
||||
talks = talks.filter(reduce(lambda x, y: x | y, [Q(accepted=dict(STATUS_VALUES)[status]) for status in data['status']]))
|
||||
if len(data['topic']):
|
||||
show_filters = True
|
||||
talks = talks.filter(reduce(lambda x, y: x | y, [Q(topics__slug=topic) for topic in data['topic']]))
|
||||
if len(data['track']):
|
||||
show_filters = True
|
||||
q = Q()
|
||||
if 'none' in data['track']:
|
||||
data['track'].remove('none')
|
||||
q |= Q(track__isnull=True)
|
||||
if len(data['track']):
|
||||
q |= Q(track__slug__in=data['track'])
|
||||
talks = talks.filter(q)
|
||||
if data['vote'] != None:
|
||||
show_filters = True
|
||||
if data['vote']:
|
||||
talks = talks.filter(vote__user=request.user)
|
||||
else:
|
||||
talks = talks.exclude(vote__user=request.user)
|
||||
if data['room'] != None:
|
||||
show_filters = True
|
||||
talks = talks.filter(room__isnull=not data['room'])
|
||||
if data['scheduled'] != None:
|
||||
show_filters = True
|
||||
talks = talks.filter(start_date__isnull=not data['scheduled'])
|
||||
if data['materials'] != None:
|
||||
show_filters = True
|
||||
talks = talks.filter(materials__isnull=not data['materials'])
|
||||
if data['video'] != None:
|
||||
show_filters = True
|
||||
if data['video']:
|
||||
talks = talks.exclude(video__exact='')
|
||||
else:
|
||||
talks = talks.filter(video__exact='')
|
||||
# Action
|
||||
action_form = TalkActionForm(request.POST or None, talks=talks, site=get_current_site(request))
|
||||
if not is_orga(request, request.user):
|
||||
action_form.fields.pop('track')
|
||||
action_form.fields.pop('room')
|
||||
if request.method == 'POST':
|
||||
if action_form.is_valid():
|
||||
data = action_form.cleaned_data
|
||||
permission_error = False
|
||||
for talk in data['talks']:
|
||||
talk = Talk.objects.get(site=get_current_site(request), slug=talk)
|
||||
if data['decision'] != None:
|
||||
if not talk.is_moderable_by(request.user):
|
||||
permission_error = True
|
||||
continue
|
||||
# TODO: merge with talk_decide code
|
||||
conversation = ConversationAboutTalk.objects.get(talk=talk)
|
||||
if data['decision']:
|
||||
note = "The talk has been accepted."
|
||||
else:
|
||||
note = "The talk has been declined."
|
||||
Message.objects.create(conversation=conversation, author=request.user, content=note)
|
||||
talk.accepted = data['decision']
|
||||
if data['track']:
|
||||
talk.track = Track.objects.get(site=get_current_site(request), slug=data['track'])
|
||||
if data['room']:
|
||||
talk.room = Room.objects.get(site=get_current_site(request), slug=data['room'])
|
||||
talk.save()
|
||||
if permission_error:
|
||||
messages.warning(request, 'Some actions were ignored due to missing permissions.')
|
||||
return redirect(request.get_full_path())
|
||||
# Sorting
|
||||
if request.GET.get('order') == 'desc':
|
||||
reverse = True
|
||||
else:
|
||||
reverse = False
|
||||
SORT_MAPPING = {
|
||||
'title': 'title',
|
||||
'kind': 'event',
|
||||
'status': 'accepted',
|
||||
}
|
||||
sort = request.GET.get('sort')
|
||||
if sort in SORT_MAPPING.keys():
|
||||
if reverse:
|
||||
talks = talks.order_by('-' + SORT_MAPPING[sort])
|
||||
else:
|
||||
talks = talks.order_by(SORT_MAPPING[sort])
|
||||
# Sorting URLs
|
||||
sort_urls = dict()
|
||||
sort_glyphicons = dict()
|
||||
for c in SORT_MAPPING.keys():
|
||||
url = request.GET.copy()
|
||||
url['sort'] = c
|
||||
if c == sort:
|
||||
if reverse:
|
||||
del url['order']
|
||||
glyphicon = 'sort-by-attributes-alt'
|
||||
else:
|
||||
url['order'] = 'desc'
|
||||
glyphicon = 'sort-by-attributes'
|
||||
else:
|
||||
glyphicon = 'sort'
|
||||
sort_urls[c] = url.urlencode()
|
||||
sort_glyphicons[c] = glyphicon
|
||||
return render(request, 'proposals/talk_list.html', {
|
||||
'show_filters': show_filters,
|
||||
'talk_list': talks,
|
||||
'filter_form': filter_form,
|
||||
'action_form': action_form,
|
||||
'sort_urls': sort_urls,
|
||||
'sort_glyphicons': sort_glyphicons,
|
||||
})
|
||||
|
||||
@login_required
|
||||
def talk_edit(request, talk=None):
|
||||
site = get_current_site(request)
|
||||
if talk: # edit existing talk
|
||||
talk = get_object_or_404(Talk, slug=talk, site=site)
|
||||
if not talk.is_editable_by(request.user):
|
||||
raise PermissionDenied
|
||||
else: # add new talk
|
||||
conf = Conference.objects.get(site=site)
|
||||
if not is_orga(request, request.user) and not conf.cfp_is_open():
|
||||
raise PermissionDenied
|
||||
staff = talk.is_moderable_by(request.user) if talk else is_orga(request, request.user)
|
||||
form = TalkForm(request.POST or None, request.FILES or None, instance=talk, site=site, staff=staff)
|
||||
if talk:
|
||||
form.fields['topics'].disabled = True
|
||||
if 'duration' in form.fields and talk.event.duration:
|
||||
form.fields['duration'].help_text = 'Default value if zero: %d min' % talk.duration
|
||||
if 'attendees_limit' in form.fields and talk.is_editable_by(request.user) and talk.room and talk.room.capacity:
|
||||
form.fields['attendees_limit'].help_text=ungettext_lazy(
|
||||
"Note: the room %(room)s has %(capacity)s seat.",
|
||||
"Note: the room %(room)s has %(capacity)s seats.",
|
||||
talk.room.capacity) % {'room': talk.room.name, 'capacity': talk.room.capacity}
|
||||
else:
|
||||
form.fields.pop('materials')
|
||||
form.fields.pop('video')
|
||||
form.fields['speakers'].initial = [request.user]
|
||||
if request.method == 'POST' and form.is_valid():
|
||||
if hasattr(talk, 'id'):
|
||||
talk = form.save()
|
||||
if request.user == talk.proposer or request.user in talk.speakers.all():
|
||||
talk_edited.send(talk.__class__, instance=talk, author=request.user)
|
||||
messages.success(request, _('Talk modified successfully!'))
|
||||
else:
|
||||
form.instance.site = get_current_site(request)
|
||||
form.instance.proposer = request.user
|
||||
talk = form.save()
|
||||
talk_added.send(talk.__class__, instance=talk, author=request.user)
|
||||
messages.success(request, _('Talk proposed successfully!'))
|
||||
return redirect(talk.get_absolute_url())
|
||||
return render(request, 'proposals/talk_edit.html', {
|
||||
'base_template': 'staff.html' if staff else 'base.html',
|
||||
'talk': talk,
|
||||
'form': form,
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
def talk_assign_to_track(request, talk, track):
|
||||
talk = get_object_or_404(Talk, slug=talk, site=get_current_site(request))
|
||||
if not talk.is_moderable_by(request.user):
|
||||
raise PermissionDenied
|
||||
track = get_object_or_404(Track, slug=track, site=get_current_site(request))
|
||||
talk.track = track
|
||||
talk.save()
|
||||
messages.success(request, _('Talk assigned to track successfully!'))
|
||||
next_url = request.GET.get('next') or reverse('show-talk', kwargs={'slug': talk.slug})
|
||||
return redirect(next_url)
|
||||
|
||||
|
||||
class TalkDetail(LoginRequiredMixin, DetailView):
|
||||
def get_queryset(self):
|
||||
return Talk.objects.filter(site=get_current_site(self.request)).all()
|
||||
|
||||
def get_context_data(self, **ctx):
|
||||
if self.object.is_moderable_by(self.request.user):
|
||||
vote = Vote.objects.filter(talk=self.object, user=self.request.user).first()
|
||||
ctx.update(edit_perm=True, moderate_perm=True, vote=vote,
|
||||
form_url=reverse('talk-conversation', kwargs={'talk': self.object.slug}))
|
||||
else:
|
||||
ctx['edit_perm'] = self.object.is_editable_by(self.request.user)
|
||||
if is_staff(self.request, self.request.user):
|
||||
ctx.update(base_template='staff.html')
|
||||
else:
|
||||
ctx.update(base_template='base.html')
|
||||
return super().get_context_data(**ctx)
|
||||
|
||||
|
||||
class TopicMixin(object):
|
||||
def get_queryset(self):
|
||||
return Topic.objects.filter(site=get_current_site(self.request)).all()
|
||||
|
||||
|
||||
class TopicFormMixin(OnSiteFormMixin):
|
||||
form_class = TopicForm
|
||||
|
||||
|
||||
class TopicList(LoginRequiredMixin, TopicMixin, ListView):
|
||||
pass
|
||||
|
||||
|
||||
class TopicCreate(OrgaRequiredMixin, TopicMixin, TopicFormMixin, CreateView):
|
||||
model = Topic
|
||||
|
||||
|
||||
class TopicUpdate(OrgaRequiredMixin, TopicMixin, TopicFormMixin, UpdateView):
|
||||
pass
|
||||
|
||||
|
||||
class TrackFormMixin(OnSiteFormMixin):
|
||||
form_class = TrackForm
|
||||
|
||||
|
||||
class TrackMixin(object):
|
||||
def get_queryset(self):
|
||||
return Track.objects.filter(site=get_current_site(self.request)).all()
|
||||
|
||||
|
||||
class TrackList(LoginRequiredMixin, TrackMixin, ListView):
|
||||
pass
|
||||
|
||||
|
||||
class TrackCreate(OrgaRequiredMixin, TrackMixin, TrackFormMixin, CreateView):
|
||||
model = Track
|
||||
|
||||
|
||||
class TrackUpdate(OrgaRequiredMixin, TrackMixin, TrackFormMixin, UpdateView):
|
||||
pass
|
||||
|
||||
|
||||
@login_required
|
||||
def vote(request, talk, score):
|
||||
talk = get_object_or_404(Talk, site=get_current_site(request), slug=talk)
|
||||
if not talk.is_moderable_by(request.user):
|
||||
raise PermissionDenied
|
||||
vote, created = Vote.objects.get_or_create(talk=talk, user=request.user)
|
||||
vote.vote = int(score)
|
||||
vote.save()
|
||||
messages.success(request, _('Vote successfully created') if created else _('Vote successfully updated'))
|
||||
return redirect(talk.get_absolute_url())
|
||||
|
||||
|
||||
@login_required
|
||||
def talk_decide(request, talk, accepted):
|
||||
site = get_current_site(request)
|
||||
talk = get_object_or_404(Talk, site=site, slug=talk)
|
||||
if not talk.is_moderable_by(request.user):
|
||||
raise PermissionDenied
|
||||
if request.method == 'POST':
|
||||
# Does we need to send a notification to the proposer?
|
||||
m = request.POST.get('message', '').strip()
|
||||
if m:
|
||||
participation = Participation.objects.get(site=site, user=talk.proposer)
|
||||
conversation = ConversationWithParticipant.objects.get(participation=participation)
|
||||
Message.objects.create(conversation=conversation, author=request.user, content=m)
|
||||
# Save the decision in the talk's conversation
|
||||
conversation = ConversationAboutTalk.objects.get(talk=talk)
|
||||
if accepted:
|
||||
note = "The talk has been accepted."
|
||||
else:
|
||||
note = "The talk has been declined."
|
||||
Message.objects.create(conversation=conversation, author=request.user, content=note)
|
||||
talk.accepted = accepted
|
||||
talk.save()
|
||||
messages.success(request, _('Decision taken in account'))
|
||||
return redirect('show-talk', slug=talk.slug)
|
||||
return render(request, 'proposals/talk_decide.html', {
|
||||
'talk': talk,
|
||||
'accept': accepted,
|
||||
})
|
||||
|
||||
|
||||
@staff_required
|
||||
def speaker_list(request):
|
||||
show_filters = False
|
||||
site = get_current_site(request)
|
||||
filter_form = SpeakerFilterForm(request.GET or None, site=site)
|
||||
talks = Talk.objects.filter(site=site)
|
||||
# Filtering
|
||||
if filter_form.is_valid():
|
||||
data = filter_form.cleaned_data
|
||||
if len(data['status']):
|
||||
show_filters = True
|
||||
talks = talks.filter(reduce(lambda x, y: x | y, [Q(accepted=dict(STATUS_VALUES)[status]) for status in data['status']]))
|
||||
if len(data['topic']):
|
||||
show_filters = True
|
||||
talks = talks.filter(reduce(lambda x, y: x | y, [Q(topics__slug=topic) for topic in data['topic']]))
|
||||
if len(data['track']):
|
||||
show_filters = True
|
||||
q = Q()
|
||||
if 'none' in data['track']:
|
||||
data['track'].remove('none')
|
||||
q |= Q(track__isnull=True)
|
||||
if len(data['track']):
|
||||
q |= Q(track__slug__in=data['track'])
|
||||
talks = talks.filter(q)
|
||||
speakers = Participation.objects.filter(site=site,user__talk__in=talks).order_by('pk').distinct()
|
||||
if filter_form.is_valid():
|
||||
data = filter_form.cleaned_data
|
||||
if len(data['transport']):
|
||||
show_filters = True
|
||||
q = Q()
|
||||
if 'unanswered' in data['transport']:
|
||||
data['transport'].remove('unanswered')
|
||||
q |= Q(need_transport=None)
|
||||
if 'unspecified' in data['transport']:
|
||||
data['transport'].remove('unspecified')
|
||||
speakers = speakers.annotate(transport_count=Count('transport'))
|
||||
q |= Q(need_transport=True, transport_count=0)
|
||||
if len(data['transport']):
|
||||
q |= (Q(need_transport=True) & Q(reduce(lambda x, y: x | y, [Q(transport__pk=pk) for pk in data['transport']])))
|
||||
speakers = speakers.filter(q)
|
||||
if len(data['accommodation']):
|
||||
show_filters = True
|
||||
accommodations = list(map(lambda x: None if x == 'unknown' else x, data['accommodation']))
|
||||
speakers = speakers.filter(reduce(lambda x, y: x | y, [Q(accommodation=value) for value in accommodations]))
|
||||
if data['sound'] != None:
|
||||
show_filters = True
|
||||
speakers = speakers.filter(sound=data['sound'])
|
||||
if data['transport_booked'] != None:
|
||||
show_filters = True
|
||||
speakers = speakers.filter(need_transport=True).filter(transport_booked=data['transport_booked'])
|
||||
if data['accommodation_booked'] != None:
|
||||
show_filters = True
|
||||
speakers = speakers.exclude(accommodation=Participation.ACCOMMODATION_NO).filter(accommodation_booked=data['accommodation_booked'])
|
||||
contact_link = 'mailto:' + ','.join([speaker.user.email for speaker in speakers.all() if speaker.user.email])
|
||||
return render(request, 'proposals/speaker_list.html', {
|
||||
'speaker_list': speakers,
|
||||
'filter_form': filter_form,
|
||||
'show_filters': show_filters,
|
||||
'contact_link': contact_link,
|
||||
})
|
||||
|
||||
|
||||
def talk_registrable_list(request):
|
||||
site = get_current_site(request)
|
||||
if not Conference.objects.get(site=site).subscriptions_open:
|
||||
raise Http404
|
||||
talks = Talk.objects.filter(site=site, registration_required=True)
|
||||
if request.user.is_authenticated():
|
||||
attendee = Attendee.objects.filter(user=request.user).first() # None if it does not exists
|
||||
else:
|
||||
attendee = None
|
||||
return render(request, 'proposals/talk_registrable_list.html', {
|
||||
'talks': talks,
|
||||
'attendee': attendee,
|
||||
})
|
||||
|
||||
|
||||
def talk_register(request, talk):
|
||||
talk = get_object_or_404(Talk, site=get_current_site(request), registration_required=True, slug=talk)
|
||||
|
||||
form = SubscribeForm(request.POST or None)
|
||||
|
||||
if request.user.is_authenticated() or (request.method == 'POST' and form.is_valid()):
|
||||
if request.user.is_authenticated():
|
||||
attendee, created = Attendee.objects.get_or_create(user=request.user)
|
||||
else:
|
||||
attendee, created = Attendee.objects.get_or_create(email=form.cleaned_data['email'], name=form.cleaned_data['name'])
|
||||
if attendee in talk.attendees.all():
|
||||
if request.user.is_authenticated():
|
||||
talk.attendees.remove(attendee)
|
||||
messages.success(request, _("Unregistered :-("))
|
||||
else:
|
||||
messages.error(request, _("Already registered!"))
|
||||
elif talk.remaining_attendees == 0:
|
||||
raise PermissionDenied
|
||||
else:
|
||||
talk.attendees.add(attendee)
|
||||
messages.success(request, _("Registered!"))
|
||||
talk.save()
|
||||
return redirect('list-registrable-talks')
|
||||
|
||||
return render(request, 'proposals/talk_register.html', {
|
||||
'talk': talk,
|
||||
'form': form,
|
||||
})
|
Loading…
Reference in New Issue