diff --git a/ponyconf/settings.py b/ponyconf/settings.py index f6664dd..7f34d22 100644 --- a/ponyconf/settings.py +++ b/ponyconf/settings.py @@ -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 diff --git a/proposals/__init__.py b/proposals/__init__.py deleted file mode 100644 index b83842a..0000000 --- a/proposals/__init__.py +++ /dev/null @@ -1 +0,0 @@ -default_app_config = 'proposals.apps.ProposalsConfig' diff --git a/proposals/admin.py b/proposals/admin.py deleted file mode 100644 index 7e2253e..0000000 --- a/proposals/admin.py +++ /dev/null @@ -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) diff --git a/proposals/apps.py b/proposals/apps.py deleted file mode 100644 index 6245495..0000000 --- a/proposals/apps.py +++ /dev/null @@ -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) diff --git a/proposals/context_processors.py b/proposals/context_processors.py deleted file mode 100644 index 8871920..0000000 --- a/proposals/context_processors.py +++ /dev/null @@ -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} diff --git a/proposals/forms.py b/proposals/forms.py deleted file mode 100644 index 40db163..0000000 --- a/proposals/forms.py +++ /dev/null @@ -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}), - }) diff --git a/proposals/migrations/0001_initial.py b/proposals/migrations/0001_initial.py deleted file mode 100644 index 8cb8222..0000000 --- a/proposals/migrations/0001_initial.py +++ /dev/null @@ -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')]), - ), - ] diff --git a/proposals/migrations/0002_talk_video.py b/proposals/migrations/0002_talk_video.py deleted file mode 100644 index 5c085cb..0000000 --- a/proposals/migrations/0002_talk_video.py +++ /dev/null @@ -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'), - ), - ] diff --git a/proposals/migrations/__init__.py b/proposals/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/proposals/models.py b/proposals/models.py deleted file mode 100644 index 9ee3d53..0000000 --- a/proposals/models.py +++ /dev/null @@ -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() diff --git a/proposals/signals.py b/proposals/signals.py deleted file mode 100644 index a7e8b16..0000000 --- a/proposals/signals.py +++ /dev/null @@ -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) diff --git a/proposals/static/js/markdown-preview.js b/proposals/static/js/markdown-preview.js deleted file mode 100644 index b40abe9..0000000 --- a/proposals/static/js/markdown-preview.js +++ /dev/null @@ -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.'); - }); -}) diff --git a/proposals/templates/proposals/_talk_list.html b/proposals/templates/proposals/_talk_list.html deleted file mode 100644 index acbbd6c..0000000 --- a/proposals/templates/proposals/_talk_list.html +++ /dev/null @@ -1,24 +0,0 @@ -{% load i18n %} - {% regroup talk_list by event as event_list %} - {% for event in event_list %} -

{{ event.list.0.event }}

- - {% empty %}{% trans "No talks" %} - {% endfor %} diff --git a/proposals/templates/proposals/conference.html b/proposals/templates/proposals/conference.html deleted file mode 100644 index 56e6453..0000000 --- a/proposals/templates/proposals/conference.html +++ /dev/null @@ -1,53 +0,0 @@ -{% extends 'staff.html' %} - -{% load bootstrap3 staticfiles i18n %} - -{% block conferencetab %} class=active{% endblock %} - -{% block content %} - -
-
-

{% trans "Home page" %}

-
-
-
- {% csrf_token %} - {% bootstrap_form form exclude='home' %} - -
-
-
- -
-
-
-
-
-
- -
-
-
-
-
- -{% endblock %} - -{% block css %} -{{ block.super }} -{{ form.media.css }} -{% endblock css %} - -{% block js_end %} -{{ block.super }} -{{ form.media.js }} - - -{% endblock js_end %} diff --git a/proposals/templates/proposals/home.html b/proposals/templates/proposals/home.html deleted file mode 100644 index b91fd71..0000000 --- a/proposals/templates/proposals/home.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends 'base.html' %} - -{% load proposals_tags i18n %} - -{% block hometab %} class="active"{% endblock %} - -{% block content %} - -{% markdown site.conference.home %} - -{% endblock %} diff --git a/proposals/templates/proposals/participate.html b/proposals/templates/proposals/participate.html deleted file mode 100644 index ccc93ac..0000000 --- a/proposals/templates/proposals/participate.html +++ /dev/null @@ -1,35 +0,0 @@ -{% extends 'base.html' %} - -{% load accounts_tags i18n %} - -{% block exhibitortab %} class="active"{% endblock %} - -{% block content %} - -

{% trans "Participate" %}

- -{% if my_talks %} -

{% trans "My talks:" %}

-{% include "proposals/_talk_list.html" with talk_list=my_talks %} -{% endif %} - -{% if proposed_talks %} -

{% trans "Proposed talks for others speakers:" %}

-{% include "proposals/_talk_list.html" with talk_list=proposed_talks %} -{% endif %} - -
-{% 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 %}{% endif %} -{% endfor %} -
-{% trans "Propose a talk" %} -{% else %} -{% trans "Sorry, the Call for Participation is closed." %} -{% endif %} - -{% endblock %} diff --git a/proposals/templates/proposals/speaker_list.html b/proposals/templates/proposals/speaker_list.html deleted file mode 100644 index 055d72c..0000000 --- a/proposals/templates/proposals/speaker_list.html +++ /dev/null @@ -1,117 +0,0 @@ -{% extends 'staff.html' %} - -{% load bootstrap3 i18n %} - -{% block speakerstab %} class="active"{% endblock %} - -{% block content %} - -

{% trans "Speakers" %}

- -{% trans "Show filtering options…" %} - -

- -
-
-
-
-
- {% bootstrap_field filter_form.transport layout="horizontal" %} - {% bootstrap_field filter_form.transport_booked layout="horizontal" %} - {% bootstrap_field filter_form.sound layout="horizontal" %} -
-
- {% bootstrap_field filter_form.accommodation layout="horizontal" %} - {% bootstrap_field filter_form.accommodation_booked layout="horizontal" %} - {% bootstrap_field filter_form.status layout="horizontal" %} -
-
-
-
- {% bootstrap_field filter_form.topic layout="horizontal" %} -
-
- {% bootstrap_field filter_form.track layout="horizontal" %} -
-
- -
-
-
- - - - - - - - - - - - - - - - - - - - {% for speaker in speaker_list %} - {% if forloop.first %} - - {% endif %} - - - - - {% if speaker.need_transport %} - - {% elif speaker.need_transport is None %} - - {% else %} - - {% endif %} - - {% if speaker.accommodation is None %} - ? - {% else %} - {{ speaker.get_accommodation_display }} - {% endif %} - - {% if speaker.sound %} - - {% else %} - - {% endif %} - - - {% if forloop.last %} - - {% endif %} - {% endfor %} -
{% trans "Total:" %} {{ speaker_list|length }} {% trans "speaker" %}{{ speaker_list|length|pluralize }} -
{% trans "Username" %}{% trans "Fullname" %}{% trans "Talk count" %}{% blocktrans context "table column title" %}Need transport?{% endblocktrans %}{% blocktrans context "table column title" %}Need accommodation?{% endblocktrans %}{% trans "Need sound?" %}
{% trans "Contact:" %} {% trans "link" %}
{{ speaker.user.username }}{{ speaker.user.get_full_name }}{{ speaker.not_refused_talk_set.count }}{% if speaker.pending_talk_set.count %} ({{ speaker.pending_talk_set.count }} pending){% endif %} - {% for transport in speaker.transport.all %} - {% if not forloop.first %}, {% endif %} - {{ transport }} - {% empty %} - Yes - {% endfor %} - ?NoYesNo - {% trans "Contact" %} -
- -{% endblock %} - -{% block js_end %} - -{% endblock %} diff --git a/proposals/templates/proposals/talk_decide.html b/proposals/templates/proposals/talk_decide.html deleted file mode 100644 index 5df73f6..0000000 --- a/proposals/templates/proposals/talk_decide.html +++ /dev/null @@ -1,26 +0,0 @@ -{% extends 'base.html' %} - -{% load i18n %} - -{% block listingtab %} active{% endblock %} - -{% block content %} - -

{% if accept %}{% trans "Are you sure to accept this proposals?" %}{% else %}{% trans "Are you sure to decline this proposals?" %}{% endif %}

- -

{% trans "Information about the proposals" %}

-{% trans "Title:" %} {{ talk.title }}
-{% trans "Kind:" %} {{ talk.event }}
- -

{% trans "Information for the proposer" %}

-
- {% csrf_token %} -
- - -
- - {% trans "Cancel" %} -
- -{% endblock %} diff --git a/proposals/templates/proposals/talk_detail.html b/proposals/templates/proposals/talk_detail.html deleted file mode 100644 index e95e19d..0000000 --- a/proposals/templates/proposals/talk_detail.html +++ /dev/null @@ -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 %} - -

{{ talk.title }}

- -{% if edit_perm %} -{% trans "Edit" %}
-{% endif %} - -

{% if talk.abstract %}{{ talk.abstract }}{% else %}{% trans "No abstract provided." %}{% endif %}

- -{% if moderate_perm %} - -
-
{% trans "Format" %}
-
{{ talk.event }}
-
{% trans "Topics" %}
-
{% for topic in talk.topics.all %} - {{ topic }}{% if not forloop.last %}, {% endif %} - {% empty %} - {% trans "No topics." %} - {% endfor %}
- -
{% trans "Track" %}
-
{% if talk.track %} - {{ talk.track }} - {% else %} - {% trans "No assigned yet." %} - {% endif %}
- -
Horaire
-
{% if talk.start_date %} - {{ talk.start_date|date:"l d b" }}, - {{ talk.start_date|date:"H:i" }} – {% if talk.end_date %}{{ talk.end_date|date:"H:i" }}{% else %}?{% endif %} - {% else %}{% trans "not defined" %} - {% endif %} -
-
Salle
-
{% if talk.room %} - - {{ talk.room }} - - {% else %}{% trans "not defined" %} - {% endif %} -
- {% if talk.registration_required %} -
{% trans "Registrations" %}
-
{% if talk.attendees_limit %}{{ talk.attendees.count }} / {{ talk.attendees_limit }}{% else %}{% trans "required but unlimited" %}{% endif %}
- {% endif %} - {% if talk.materials %} -
{% trans "Materials" %}
-
{{ talk.materials_name }}
- {% endif %} - {% if talk.video %} -
{% trans "Video" %}
-
{% trans "download" %}
- {% endif %} - -
- -{% endif %} - -

{% trans "Description" %}

- -

{% if talk.description %}{{ talk.description|linebreaksbr }}{% else %}{% trans "No description provided." %}{% endif %}

- -

{% trans "Speakers" %}

- -{% for speaker in talk.speakers.all %} -{% if forloop.first %}{% endif %} -{% empty %} -{% trans "No speakers." %} -{% endfor %} - -{% if moderate_perm %} - -{% if not talk.track %} -

{% trans "Track" %}

-

{% trans "No assigned yet." %}

- {% for topic in talk.topics.distinct %} - {% if forloop.first %}

{% endif %} - {% if topic.track %} - {% trans "Assign to" %} {{ topic.track }} - {% endif %} - {% if forloop.last %}

{% endif %} - {% endfor %} -{% endif %} - -{% endif %} - -

{% trans "Notes" %}

- -

{% if talk.notes %}{{ talk.notes|linebreaksbr }}{% else %}{% trans "No notes." %}{% endif %}

- -{% if moderate_perm %} - -

{% trans "Moderation" %}

- -

{% trans "Status" %}

- -{{ talk.accepted|yesno:"Accepted,Declined,Pending decision" }}
- -{% if talk.accepted == None %} -

{% trans "Vote" %}

-
- -2 - -1 - 0 - +1 - +2 -
-

-

{{ talk.vote_set.all|length }} {% trans "vote" %}{{ talk.vote_set.all|length|pluralize }}, {% trans "average:" %} {{ talk.score|floatformat:1 }}

- -Accept -Decline -{% endif %} - -{% if talk.registration_required %} -

{% trans "Attendees" %}

- -{% for attendee in talk.attendees.all %} -{% if forloop.first %}
    {% endif %} -
  1. {{ attendee.get_name }}
  2. -{% if forloop.last %}
{% endif %} -{% empty %} -{% trans "No attendees yet." %} -{% endfor %} - -{% endif %} - -

{% trans "Messages" %}

-{% trans "These messages are for organization team only." %}

-{% for message in talk.conversation.messages.all %} -{% include 'conversations/_message_detail.html' %} -{% endfor %} - -{% include 'conversations/_message_form.html' %} - -{% endif %} - -{% endblock %} diff --git a/proposals/templates/proposals/talk_edit.html b/proposals/templates/proposals/talk_edit.html deleted file mode 100644 index 4091a7b..0000000 --- a/proposals/templates/proposals/talk_edit.html +++ /dev/null @@ -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 %} - -

{% if talk %}{% trans "Edit a talk" %}{% else %}{% trans "Propose a talk" %}{% endif %}

- -{% include "_form.html" with multipart=True %} - -{% endblock %} - -{% block css %} -{{ block.super }} -{{ form.media.css }} -{% endblock css %} - -{% block js_end %} -{{ block.super }} -{{ form.media.js }} - -{% endblock js_end %} diff --git a/proposals/templates/proposals/talk_list.html b/proposals/templates/proposals/talk_list.html deleted file mode 100644 index 4a12d8f..0000000 --- a/proposals/templates/proposals/talk_list.html +++ /dev/null @@ -1,122 +0,0 @@ -{% extends 'staff.html' %} - -{% load bootstrap3 i18n accounts_tags %} - -{% block talkstab %} class="active"{% endblock %} - -{% block content %} - -

{% trans "Talks" %}

- -{% trans "Show filtering options…" %} - -

- -
-
-
-
-
- {% 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" %} -
-
- {% bootstrap_field filter_form.topic layout="horizontal" %} -
-
- {% bootstrap_field filter_form.track layout="horizontal" %} -
-
- -
-
-
- -
- - - - - - - - - - - - - - - {% for talk in talk_list %} - {% if forloop.first %} - - {% endif %} - - - - - - - - - - {% if forloop.last%} - - {% endif %} - {% endfor %} -
{% trans "Total:" %} {{ talk_list|length }} {% trans "talk" %}{{ talk_list|length|pluralize }}
{% trans "Title" %} {% trans "Intervention kind" %} {% trans "Speakers" %}{% trans "Topics" %}{% trans "Track" %}{% trans "Status" %}
{{ talk.title }}{{ talk.event }} - {% for speaker in talk.speakers.all %} - {{ speaker.profile }} - {% if forloop.revcounter == 2 %} {% trans "and" %} {% elif not forloop.last %}, {% endif %} - {% empty %}– - {% endfor %} - - {% for topic in talk.topics.all %} - {{ topic }} - {% endfor %} - {{ talk.track|default:"–" }} - {% 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 %} -
- -{% if action_form %} -
-
-

{% trans "For selected talks:" %}

- {% csrf_token %} - {% bootstrap_field action_form.decision %} - {% if request|orga %} - {% bootstrap_field action_form.track %} - {% bootstrap_field action_form.room %} - {% endif %} - {% buttons %} - - {% endbuttons %} -
-
-{% endif %} - -
- -{% endblock %} - -{% block js_end %} - -{% endblock %} diff --git a/proposals/templates/proposals/talk_register.html b/proposals/templates/proposals/talk_register.html deleted file mode 100644 index 5c1e535..0000000 --- a/proposals/templates/proposals/talk_register.html +++ /dev/null @@ -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 %} - -

{% trans "Register for a workshop" %}
{{ talk.title }}

- -
- -{% include "_form.html" %} - -

- {% trans "Login to register with your account" %} -

- -{% endblock %} - -{% block js_end %} -{{ block.super }} -{{ form.media.js }} -{% endblock %} diff --git a/proposals/templates/proposals/talk_registrable_list.html b/proposals/templates/proposals/talk_registrable_list.html deleted file mode 100644 index b38d26c..0000000 --- a/proposals/templates/proposals/talk_registrable_list.html +++ /dev/null @@ -1,36 +0,0 @@ -{% extends 'base.html' %} - -{% load accounts_tags i18n %} - -{% block wsregtab %} class="active"{% endblock %} - -{% block content %} - -

{% trans "Register to a workshop" %}

- -{% for talk in talks %} -{% if forloop.first %}
{% endif %} -
-

{{ talk.title }}

-

-

{{ talk.description }}

- {% if talk.attendees_limit %} -

{% blocktrans count remaining=talk.remaining_attendees %}{{ remaining }} remaining seat{% plural %}{{ remaining }} remaining seats{% endblocktrans %}

- {% endif %} - {% if talk.remaining_attendees != 0 or attendee in talk.attendees.all %} -

- {% if attendee in talk.attendees.all %} - {% trans "Unregister" %} - {% else %} - {% trans "Register" %} - {% endif %} -

- {% endif %} -

-
-{% if forloop.last %}
{% endif %} -{% empty %} -{% trans "There are no workshops requiring registration for now … come back later!" %} -{% endfor %} - -{% endblock %} diff --git a/proposals/templates/proposals/topic_form.html b/proposals/templates/proposals/topic_form.html deleted file mode 100644 index 5e50057..0000000 --- a/proposals/templates/proposals/topic_form.html +++ /dev/null @@ -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 %} - -

{% trans "Topic" %}

- -{% include "_form.html" %} - -{% endblock %} - -{% block js_end %} -{{ block.super }} -{{ form.media.js }} -{% endblock %} diff --git a/proposals/templates/proposals/topic_list.html b/proposals/templates/proposals/topic_list.html deleted file mode 100644 index d55caa7..0000000 --- a/proposals/templates/proposals/topic_list.html +++ /dev/null @@ -1,35 +0,0 @@ -{% extends 'staff.html' %} - -{% load bootstrap3 accounts_tags i18n %} - -{% block topicstab %} class="active"{% endblock %} - -{% block content %} - -

{% trans "Topics" %}

- -{% if request|orga %} -

{% trans "Add a topic" %}

-{% endif %} - -

- {% for topic in topic_list %} -
-

{{ topic }}

-

{{ topic.description }}

- {% if request|staff %} - {{ topic.reviewers.count }} {% trans "reviewer" %}{{ topic.reviewers.count|pluralize }} - | - {{ topic.talk_set.count }} {% trans "talk" %}{{ topic.talk_set.count|pluralize }} - | - {% bootstrap_icon "pencil" %} - {% endif %} -
- {% cycle '' '
' %} - {% cycle '' '' '' %} - {% empty %} -
{% trans "No topics." %}
- {% endfor %} -
- -{% endblock %} diff --git a/proposals/templates/proposals/track_form.html b/proposals/templates/proposals/track_form.html deleted file mode 100644 index 8fc12db..0000000 --- a/proposals/templates/proposals/track_form.html +++ /dev/null @@ -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 %} - -

{% trans "Track" %}

- -{% include "_form.html" %} - -{% endblock %} - -{% block js_end %} -{{ block.super }} -{{ form.media.js }} -{% endblock %} diff --git a/proposals/templates/proposals/track_list.html b/proposals/templates/proposals/track_list.html deleted file mode 100644 index 924cc19..0000000 --- a/proposals/templates/proposals/track_list.html +++ /dev/null @@ -1,37 +0,0 @@ -{% extends 'staff.html' %} - -{% load bootstrap3 accounts_tags proposals_tags i18n %} - -{% block trackstab %} class="active"{% endblock %} - -{% block content %} - -

{% trans "Tracks" %}

- -{% if request|orga %} -

{% trans "Add a track" %}

-{% endif %} - -

- {% for track in track_list %} -
-

{{ track }}

-

{{ track.description }}

- {% if request|staff %} - {{ track.managers.count }} {% trans "manager" %}{{ track.managers.count|pluralize }} - | - {{ track.talk_set.count }} {% trans "talk" %}{{ track.talk_set.count|pluralize }} - | - {{ track.estimated_duration|duration_format }} - | - {% bootstrap_icon "pencil" %} - {% endif %} -
- {% cycle '' '
' %} - {% cycle '' '' '' %} - {% empty %} -
{% trans "No tracks." %}
- {% endfor %} -
- -{% endblock %} diff --git a/proposals/templatetags/proposals_tags.py b/proposals/templatetags/proposals_tags.py deleted file mode 100644 index 2656151..0000000 --- a/proposals/templatetags/proposals_tags.py +++ /dev/null @@ -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) diff --git a/proposals/tests.py b/proposals/tests.py deleted file mode 100644 index 12885ac..0000000 --- a/proposals/tests.py +++ /dev/null @@ -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) diff --git a/proposals/urls.py b/proposals/urls.py deleted file mode 100644 index 9d25dce..0000000 --- a/proposals/urls.py +++ /dev/null @@ -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[-\w]+)$', views.talk_edit, name='edit-talk'), - url(r'^talk/vote/(?P[-\w]+)/(?P[-0-2]+)$', views.vote, name='vote'), - url(r'^talk/details/(?P[-\w]+)$', views.TalkDetail.as_view(), name='show-talk'), - url(r'^talk/accept/(?P[-\w]+)/$', views.talk_decide, {'accepted': True}, name='accept-talk'), - url(r'^talk/decline/(?P[-\w]+)/$', views.talk_decide, {'accepted': False}, name='decline-talk'), - url(r'^talk/assign-to-track/(?P[-\w]+)/(?P[-\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[-\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[-\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[-\w]+)$', views.talk_register, name='register-for-a-talk'), -] diff --git a/proposals/utils.py b/proposals/utils.py deleted file mode 100644 index a6a44c5..0000000 --- a/proposals/utils.py +++ /dev/null @@ -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) diff --git a/proposals/views.py b/proposals/views.py deleted file mode 100644 index e6bcfea..0000000 --- a/proposals/views.py +++ /dev/null @@ -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, - })