Adds speaker management feature, fixes #36
This commit is contained in:
commit
d654bbb1ca
33 changed files with 1398 additions and 50 deletions
|
@ -3,9 +3,30 @@ from django.utils.safestring import mark_safe
|
|||
|
||||
# Register your models here.
|
||||
from .models.locale import Language, Continent, Country, SPR, City
|
||||
from .models.profiles import UserProfile, Organization, Team, Member, Category, Topic
|
||||
from .models.profiles import (
|
||||
UserProfile,
|
||||
Organization,
|
||||
Team,
|
||||
Member,
|
||||
Category,
|
||||
Topic,
|
||||
)
|
||||
from .models.search import Searchable
|
||||
from .models.events import Place, Event, EventComment, EventSeries, EventPhoto, CommonEvent, Attendee
|
||||
from .models.events import (
|
||||
Place,
|
||||
Event,
|
||||
EventComment,
|
||||
EventSeries,
|
||||
EventPhoto,
|
||||
CommonEvent,
|
||||
Attendee,
|
||||
)
|
||||
from .models.speakers import (
|
||||
Speaker,
|
||||
Talk,
|
||||
Presentation,
|
||||
SpeakerRequest,
|
||||
)
|
||||
|
||||
admin.site.register(Language)
|
||||
admin.site.register(Continent)
|
||||
|
@ -24,6 +45,7 @@ class CityAdmin(admin.ModelAdmin):
|
|||
admin.site.register(City, CityAdmin)
|
||||
|
||||
class ProfileAdmin(admin.ModelAdmin):
|
||||
raw_id_fields = ('city',)
|
||||
list_display = ('user', 'realname', 'avatar', 'web_url')
|
||||
admin.site.register(UserProfile, ProfileAdmin)
|
||||
|
||||
|
@ -33,11 +55,15 @@ admin.site.register(Organization, OrgAdmin)
|
|||
|
||||
class TeamAdmin(admin.ModelAdmin):
|
||||
raw_id_fields = ('country', 'spr', 'city', 'owner_profile', 'admin_profiles', 'contact_profiles')
|
||||
list_display = ('__str__', 'member_count', 'owner_profile', 'created_date')
|
||||
list_display = ('__str__', 'active', 'member_count', 'event_count', 'owner_profile', 'created_date', 'is_premium', 'premium_expires')
|
||||
list_filter = ('active', 'is_premium', 'organization', 'country',)
|
||||
ordering = ('-created_date',)
|
||||
def member_count(self, team):
|
||||
return team.members.all().count()
|
||||
member_count.short_description = 'Members'
|
||||
def event_count(self, team):
|
||||
return team.event_set.all().count()
|
||||
event_count.short_description = 'Events'
|
||||
admin.site.register(Team, TeamAdmin)
|
||||
|
||||
class SearchableAdmin(admin.ModelAdmin):
|
||||
|
@ -101,12 +127,30 @@ class AttendeeAdmin(admin.ModelAdmin):
|
|||
admin.site.register(Attendee, AttendeeAdmin)
|
||||
|
||||
class CategoryAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'image')
|
||||
list_display = ('name', 'slug', 'image')
|
||||
exclude = ('slug', )
|
||||
def image(self, obj):
|
||||
return (mark_safe('<img src="%s" title="%s" height="64px" />' % (obj.img_url, obj.name)))
|
||||
image.short_description = 'Image'
|
||||
admin.site.register(Category, CategoryAdmin)
|
||||
|
||||
class TopicAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'category')
|
||||
list_display = ('name', 'slug', 'category')
|
||||
list_filter = ('category',)
|
||||
exclude = ('slug', )
|
||||
admin.site.register(Topic, TopicAdmin)
|
||||
|
||||
class SpeakerAdmin(admin.ModelAdmin):
|
||||
list_display = ('title', 'user', 'avatar')
|
||||
admin.site.register(Speaker, SpeakerAdmin)
|
||||
|
||||
class TalkAdmin(admin.ModelAdmin):
|
||||
list_display = ('title', 'speaker', 'category')
|
||||
list_filter = ('category',)
|
||||
admin.site.register(Talk, TalkAdmin)
|
||||
|
||||
class PresentationAdmin(admin.ModelAdmin):
|
||||
list_display = ('talk', 'status', 'event')
|
||||
list_filter = ('status',)
|
||||
admin.site.register(Presentation, PresentationAdmin)
|
||||
|
||||
|
|
|
@ -7,7 +7,20 @@ from django.utils import timezone
|
|||
from django.contrib.auth.models import User
|
||||
from .models.locale import Country, SPR, City
|
||||
from .models.profiles import Team, UserProfile
|
||||
from .models.events import Event, EventComment ,CommonEvent, EventSeries, Place, EventPhoto
|
||||
from .models.events import (
|
||||
Event,
|
||||
EventComment,
|
||||
CommonEvent,
|
||||
EventSeries,
|
||||
Place,
|
||||
EventPhoto,
|
||||
)
|
||||
from .models.speakers import (
|
||||
Speaker,
|
||||
Talk,
|
||||
Presentation,
|
||||
SpeakerRequest,
|
||||
)
|
||||
import recurrence
|
||||
|
||||
import pytz
|
||||
|
@ -262,15 +275,27 @@ class UserForm(forms.ModelForm):
|
|||
class UserProfileForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = UserProfile
|
||||
fields = ['avatar', 'realname', 'tz', 'send_notifications']
|
||||
fields = ['avatar', 'realname', 'city', 'tz', 'send_notifications']
|
||||
labels = {
|
||||
'send_notifications': _('Send me notification emails'),
|
||||
}
|
||||
widgets = {
|
||||
'city': Lookup(source=City),
|
||||
}
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['city'].required = True
|
||||
|
||||
class ConfirmProfileForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = UserProfile
|
||||
fields = ['avatar', 'realname', 'tz']
|
||||
fields = ['avatar', 'realname', 'city']
|
||||
widgets = {
|
||||
'city': Lookup(source=City),
|
||||
}
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['city'].required = True
|
||||
|
||||
class SendNotificationsForm(forms.ModelForm):
|
||||
class Meta:
|
||||
|
@ -317,3 +342,24 @@ class NewCommonEventForm(forms.ModelForm):
|
|||
'end_time': DateTimeWidget
|
||||
}
|
||||
|
||||
class SpeakerBioForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Speaker
|
||||
fields = ['avatar', 'title', 'bio', 'categories']
|
||||
|
||||
class DeleteSpeakerForm(forms.Form):
|
||||
confirm = forms.BooleanField(label="Yes, delete series", required=True)
|
||||
|
||||
class UserTalkForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Talk
|
||||
fields = ['speaker', 'title', 'abstract', 'talk_type', 'web_url', 'category']
|
||||
|
||||
class DeleteTalkForm(forms.Form):
|
||||
confirm = forms.BooleanField(label="Yes, delete series", required=True)
|
||||
|
||||
class SchedulePresentationForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Presentation
|
||||
fields = ['start_time']
|
||||
|
||||
|
|
34
events/migrations/0026_add_user_city.py
Normal file
34
events/migrations/0026_add_user_city.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
# Generated by Django 2.0 on 2018-04-21 13:45
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('events', '0025_add_event_series'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='userprofile',
|
||||
name='city',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='events.City'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='eventseries',
|
||||
name='end_time',
|
||||
field=models.TimeField(db_index=True, help_text='Local time that the event ends', verbose_name='End Time'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='eventseries',
|
||||
name='start_time',
|
||||
field=models.TimeField(db_index=True, help_text='Local time that the event starts', verbose_name='Start Time'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='team',
|
||||
name='description',
|
||||
field=models.TextField(blank=True, null=True),
|
||||
),
|
||||
]
|
31
events/migrations/0027_add_category_topic_slug.py
Normal file
31
events/migrations/0027_add_category_topic_slug.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
# Generated by Django 2.0 on 2018-04-21 14:13
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('events', '0026_add_user_city'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='topic',
|
||||
name='slug',
|
||||
field=models.CharField(default='-', max_length=256),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='category',
|
||||
name='slug',
|
||||
field=models.CharField(default='-', max_length=256),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='userprofile',
|
||||
name='city',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='events.City', verbose_name='Home city'),
|
||||
),
|
||||
]
|
120
events/migrations/0028_add_speaker_models.py
Normal file
120
events/migrations/0028_add_speaker_models.py
Normal file
|
@ -0,0 +1,120 @@
|
|||
# Generated by Django 2.0 on 2018-04-22 21:23
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import imagekit.models.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('events', '0027_add_category_topic_slug'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Presentation',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('status', models.SmallIntegerField(choices=[(-1, 'Declined'), (0, 'Proposed'), (1, 'Accepted')], db_index=True, default=0)),
|
||||
('start_time', models.DateTimeField(blank=True, db_index=True, null=True, verbose_name='Start Time')),
|
||||
('created_time', models.DateTimeField(db_index=True, default=django.utils.timezone.now)),
|
||||
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='events.UserProfile')),
|
||||
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='presentations', to='events.Event')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Speaker',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('avatar', imagekit.models.fields.ProcessedImageField(blank=True, upload_to='avatars', verbose_name='Photo Image')),
|
||||
('title', models.CharField(blank=True, max_length=256, null=True)),
|
||||
('bio', models.TextField(blank=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SpeakerRequest',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='events.Event')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Talk',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=256)),
|
||||
('abstract', models.TextField()),
|
||||
('talk_type', models.SmallIntegerField(choices=[(0, 'Presentation'), (1, 'Workshop'), (2, 'Panel'), (3, 'Roundtable'), (4, 'Q & A'), (5, 'Demonstration')], default=0, verbose_name='Type')),
|
||||
('web_url', models.URLField(blank=True, null=True, verbose_name='Website')),
|
||||
],
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='category',
|
||||
options={'verbose_name_plural': 'Categories'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='country',
|
||||
options={'ordering': ('name',), 'verbose_name_plural': 'Countries'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='eventseries',
|
||||
options={'verbose_name_plural': 'Event series'},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='category',
|
||||
name='slug',
|
||||
field=models.CharField(blank=True, max_length=256),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='topic',
|
||||
name='description',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='topic',
|
||||
name='slug',
|
||||
field=models.CharField(blank=True, max_length=256),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='talk',
|
||||
name='category',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='events.Category'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='talk',
|
||||
name='speaker',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='events.Speaker'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='talk',
|
||||
name='topics',
|
||||
field=models.ManyToManyField(blank=True, to='events.Topic'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='speakerrequest',
|
||||
name='topics',
|
||||
field=models.ManyToManyField(blank=True, to='events.Topic'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='speaker',
|
||||
name='categories',
|
||||
field=models.ManyToManyField(blank=True, to='events.Category'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='speaker',
|
||||
name='topics',
|
||||
field=models.ManyToManyField(blank=True, to='events.Topic'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='speaker',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='events.UserProfile'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='presentation',
|
||||
name='talk',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='presentations', to='events.Talk'),
|
||||
),
|
||||
]
|
49
events/migrations/0029_add_team_premium_fields.py
Normal file
49
events/migrations/0029_add_team_premium_fields.py
Normal file
|
@ -0,0 +1,49 @@
|
|||
# Generated by Django 2.0 on 2018-04-27 21:59
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('events', '0028_add_speaker_models'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='team',
|
||||
name='is_premium',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='team',
|
||||
name='premium_by',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='premium_teams', to='events.UserProfile'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='team',
|
||||
name='premium_expires',
|
||||
field=models.DateTimeField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='team',
|
||||
name='premium_started',
|
||||
field=models.DateTimeField(blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='presentation',
|
||||
name='talk',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='presentations', to='events.Talk'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='speaker',
|
||||
name='bio',
|
||||
field=models.TextField(blank=True, verbose_name='Biography'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='talk',
|
||||
name='speaker',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='talks', to='events.Speaker', verbose_name='Speaker Bio'),
|
||||
),
|
||||
]
|
|
@ -2,4 +2,4 @@ from .profiles import *
|
|||
from .locale import *
|
||||
from .search import *
|
||||
from .events import *
|
||||
|
||||
from .speakers import *
|
||||
|
|
|
@ -11,6 +11,7 @@ from recurrence.fields import RecurrenceField
|
|||
from imagekit.models import ImageSpecField
|
||||
from imagekit.processors import ResizeToFill
|
||||
|
||||
from ..utils import slugify
|
||||
from .locale import *
|
||||
from .profiles import *
|
||||
from .search import *
|
||||
|
@ -19,11 +20,8 @@ from .. import location
|
|||
import re
|
||||
import pytz
|
||||
import datetime
|
||||
import unicodedata
|
||||
import hashlib
|
||||
|
||||
SLUG_OK = '-_~'
|
||||
|
||||
class Place(models.Model):
|
||||
name = models.CharField(help_text=_('Name of the Place'), max_length=150)
|
||||
city = models.ForeignKey(City, verbose_name=_('City'), on_delete=models.CASCADE)
|
||||
|
@ -192,21 +190,6 @@ def delete_event_searchable(event):
|
|||
pass
|
||||
|
||||
|
||||
def slugify(s, ok=SLUG_OK, lower=True, spaces=False):
|
||||
# L and N signify letter/number.
|
||||
# http://www.unicode.org/reports/tr44/tr44-4.html#GC_Values_Table
|
||||
rv = []
|
||||
for c in unicodedata.normalize('NFKC', s):
|
||||
cat = unicodedata.category(c)[0]
|
||||
if cat in 'LN' or c in ok:
|
||||
rv.append(c)
|
||||
if cat == 'Z': # space
|
||||
rv.append(' ')
|
||||
new = ''.join(rv).strip()
|
||||
if not spaces:
|
||||
new = re.sub('[-\s]+', '-', new)
|
||||
return new.lower() if lower else new
|
||||
|
||||
class Attendee(models.Model):
|
||||
NORMAL=0
|
||||
CREW=1
|
||||
|
@ -311,6 +294,8 @@ class CommonEvent(models.Model):
|
|||
return self.name
|
||||
|
||||
class EventSeries(models.Model):
|
||||
class Meta:
|
||||
verbose_name_plural = 'Event series'
|
||||
name = models.CharField(max_length=150, verbose_name=_('Event Name'))
|
||||
team = models.ForeignKey(Team, on_delete=models.CASCADE)
|
||||
parent = models.ForeignKey('CommonEvent', related_name='planned_events', null=True, blank=True, on_delete=models.SET_NULL)
|
||||
|
|
|
@ -32,6 +32,7 @@ class Country(models.Model):
|
|||
|
||||
class Meta:
|
||||
ordering = ('name',)
|
||||
verbose_name_plural = 'Countries'
|
||||
|
||||
def __str__(self):
|
||||
return u'%s' % (self.name)
|
||||
|
|
|
@ -10,6 +10,7 @@ from imagekit.processors import ResizeToFill
|
|||
|
||||
from .locale import *
|
||||
from .. import location
|
||||
from ..utils import slugify
|
||||
|
||||
import uuid
|
||||
import pytz
|
||||
|
@ -27,6 +28,7 @@ class UserProfile(models.Model):
|
|||
processors=[ResizeToFill(128, 128)],
|
||||
format='PNG',
|
||||
blank=True)
|
||||
city = models.ForeignKey(City, verbose_name=_('Home city'), null=True, blank=True, on_delete=models.CASCADE)
|
||||
|
||||
web_url = models.URLField(verbose_name=_('Website URL'), blank=True, null=True)
|
||||
twitter = models.CharField(verbose_name=_('Twitter Name'), max_length=32, blank=True, null=True)
|
||||
|
@ -222,6 +224,11 @@ class Team(models.Model):
|
|||
category = models.ForeignKey('Category', on_delete=models.SET_NULL, blank=False, null=True)
|
||||
topics = models.ManyToManyField('Topic', blank=True)
|
||||
|
||||
is_premium = models.BooleanField(default=settings.EVENTS_TEAMS_DEFAULT_PREMIUM)
|
||||
premium_by = models.ForeignKey(UserProfile, related_name='premium_teams', null=True, on_delete=models.SET_NULL)
|
||||
premium_started = models.DateTimeField(blank=True, null=True)
|
||||
premium_expires = models.DateTimeField(blank=True, null=True)
|
||||
|
||||
@property
|
||||
def location_name(self):
|
||||
if self.city:
|
||||
|
@ -275,17 +282,28 @@ class Member(models.Model):
|
|||
class Category(models.Model):
|
||||
name = models.CharField(max_length=256)
|
||||
description = models.TextField()
|
||||
slug = models.CharField(max_length=256, blank=True)
|
||||
img_url = models.URLField(blank=False, null=False)
|
||||
|
||||
class Meta:
|
||||
verbose_name_plural = 'Categories'
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.slug = slugify(self.name)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
class Topic(models.Model):
|
||||
category = models.ForeignKey(Category, on_delete=models.CASCADE, null=False, blank=False)
|
||||
name = models.CharField(max_length=256)
|
||||
description = models.TextField()
|
||||
slug = models.CharField(max_length=256, blank=True)
|
||||
description = models.TextField(blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.slug = slugify(self.name)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
|
104
events/models/speakers.py
Normal file
104
events/models/speakers.py
Normal file
|
@ -0,0 +1,104 @@
|
|||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils import timezone
|
||||
|
||||
from imagekit.models import ImageSpecField
|
||||
from imagekit.processors import ResizeToFill
|
||||
|
||||
from ..utils import slugify
|
||||
from .locale import *
|
||||
from .profiles import *
|
||||
from .events import *
|
||||
from .search import *
|
||||
from .. import location
|
||||
|
||||
import pytz
|
||||
import datetime
|
||||
|
||||
class Speaker(models.Model):
|
||||
user = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
|
||||
avatar = ProcessedImageField(verbose_name=_("Photo Image"),
|
||||
upload_to='avatars',
|
||||
processors=[ResizeToFill(128, 128)],
|
||||
format='PNG',
|
||||
blank=True)
|
||||
title = models.CharField(max_length=256, blank=True, null=True)
|
||||
bio = models.TextField(verbose_name=_('Biography'), blank=True)
|
||||
|
||||
categories = models.ManyToManyField('Category', blank=True)
|
||||
topics = models.ManyToManyField('Topic', blank=True)
|
||||
|
||||
def headshot(self):
|
||||
if self.avatar:
|
||||
return self.avatar
|
||||
else:
|
||||
return self.user.avatar
|
||||
|
||||
def __str__(self):
|
||||
if self.title:
|
||||
return self.title
|
||||
return self.user.__str__()
|
||||
|
||||
class Talk(models.Model):
|
||||
PRESENTATION=0
|
||||
WORKSHOP=1
|
||||
PANEL=2
|
||||
ROUNDTABLE=3
|
||||
QANDA=4
|
||||
DEMO=5
|
||||
TYPES = [
|
||||
(PRESENTATION, _("Presentation")),
|
||||
(WORKSHOP, _("Workshop")),
|
||||
(PANEL, _("Panel")),
|
||||
(ROUNDTABLE, _("Roundtable")),
|
||||
(QANDA, _("Q & A")),
|
||||
(DEMO, _("Demonstration")),
|
||||
]
|
||||
speaker = models.ForeignKey(Speaker, verbose_name=_('Speaker Bio'), related_name='talks', on_delete=models.CASCADE)
|
||||
title = models.CharField(max_length=256)
|
||||
abstract = models.TextField()
|
||||
talk_type = models.SmallIntegerField(_("Type"), choices=TYPES, default=PRESENTATION)
|
||||
web_url = models.URLField(_("Website"), null=True, blank=True)
|
||||
|
||||
category = models.ForeignKey('Category', on_delete=models.SET_NULL, blank=False, null=True)
|
||||
topics = models.ManyToManyField('Topic', blank=True)
|
||||
|
||||
@property
|
||||
def future_presentations(self):
|
||||
return self.presentations.filter(status__gte=0, event__start_time__gt=timezone.now())
|
||||
|
||||
@property
|
||||
def past_presentations(self):
|
||||
return self.presentations.filter(status=1, event__start_time__lte=timezone.now())
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
class SpeakerRequest(models.Model):
|
||||
event = models.ForeignKey(Event, on_delete=models.CASCADE)
|
||||
topics = models.ManyToManyField('Topic', blank=True)
|
||||
|
||||
class Presentation(models.Model):
|
||||
DECLINED=-1
|
||||
PROPOSED=0
|
||||
ACCEPTED=1
|
||||
|
||||
STATUSES = [
|
||||
(DECLINED, _("Declined")),
|
||||
(PROPOSED, _("Proposed")),
|
||||
(ACCEPTED, _("Accepted")),
|
||||
]
|
||||
event = models.ForeignKey(Event, related_name='presentations', on_delete=models.CASCADE)
|
||||
talk = models.ForeignKey(Talk, related_name='presentations', on_delete=models.CASCADE, blank=False, null=True)
|
||||
status = models.SmallIntegerField(choices=STATUSES, default=PROPOSED, db_index=True)
|
||||
start_time = models.DateTimeField(verbose_name=_('Start Time'), db_index=True, null=True, blank=True)
|
||||
|
||||
created_by = models.ForeignKey(UserProfile, on_delete=models.SET_NULL, null=True, blank=False)
|
||||
created_time = models.DateTimeField(default=timezone.now, db_index=True)
|
||||
|
||||
def __str__(self):
|
||||
try:
|
||||
return '%s at %s' % (self.talk.title, self.event.name)
|
||||
except:
|
||||
return "No talk"
|
||||
|
21
events/utils.py
Normal file
21
events/utils.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
import re
|
||||
import unicodedata
|
||||
|
||||
SLUG_OK = '-_~'
|
||||
|
||||
def slugify(s, ok=SLUG_OK, lower=True, spaces=False):
|
||||
# L and N signify letter/number.
|
||||
# http://www.unicode.org/reports/tr44/tr44-4.html#GC_Values_Table
|
||||
rv = []
|
||||
s = re.sub('\s*&\s*', ' and ', s)
|
||||
for c in unicodedata.normalize('NFKC', s):
|
||||
cat = unicodedata.category(c)[0]
|
||||
if cat in 'LN' or c in ok:
|
||||
rv.append(c)
|
||||
if cat == 'Z': # space
|
||||
rv.append(' ')
|
||||
new = ''.join(rv).strip()
|
||||
if not spaces:
|
||||
new = re.sub('[-\s]+', '-', new)
|
||||
return new.lower() if lower else new
|
||||
|
|
@ -15,3 +15,4 @@ DATABASES['default'].update(dj_database_url.config())
|
|||
MEDIA_URL = os.environ.get('MEDIA_URL', '/media/')
|
||||
STATIC_URL = os.environ.get('STATIC_URL', '/static/')
|
||||
|
||||
EVENTS_TEAMS_DEFAULT_PREMIUM = os.environ.get('EVENTS_TEAMS_DEFAULT_PREMIUM', False)
|
||||
|
|
|
@ -29,6 +29,7 @@ ALLOWED_HOSTS = []
|
|||
SITE_ID=1
|
||||
ADMINS = [ 'mhall119' ]
|
||||
|
||||
EVENTS_TEAMS_DEFAULT_PREMIUM=False
|
||||
|
||||
# Application definition
|
||||
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
{% extends "get_together/base.html" %}
|
||||
{% load static tz %}
|
||||
|
||||
{% block styles %}
|
||||
<link href="{% static 'css/bootstrap-album.css' %}" rel="stylesheet"/>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if talks_count == 0 %}
|
||||
<div class="alerts">
|
||||
<div class="alert alert-info">No talks have been proposed for this event.</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="container">
|
||||
<h2>Manage talks for: {{event.name}}</h2>
|
||||
{% if pending_talks %}
|
||||
<hr/>
|
||||
<div class="row">
|
||||
{% for presentation in pending_talks %}
|
||||
<form action="{% url 'schedule-event-talks' event.id %}" method="POST">
|
||||
<input type="hidden" name="presentation_id" value="{{presentation.id}}"/>
|
||||
{% csrf_token %}
|
||||
<div class="col-md-4">
|
||||
<div class="card mb-4 box-shadow" style="width: 18rem;">
|
||||
<div class="card-body">
|
||||
<p class="card-title" style="height: 2rem;"><strong><a href="{% url 'show-talk' presentation.talk.id %}">{{presentation.talk.title}}</a></strong></p>
|
||||
<div class="card-text">
|
||||
<small class="text-muted mb-1">{{ presentation.talk.speaker }}</small>
|
||||
<div>
|
||||
<button class="btn btn-sm btn-success" type="submit" name="action" value="accept">Accept</button>
|
||||
<button class="btn btn-sm btn-danger" type="submit" name="action" value="decline">Decline</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if accepted_talks %}
|
||||
<hr/>
|
||||
<div class="row">
|
||||
{% for presentation in accepted_talks %}
|
||||
<form action="{% url 'schedule-event-talks' event.id %}" method="POST">
|
||||
<input type="hidden" name="presentation_id" value="{{presentation.id}}"/>
|
||||
{% csrf_token %}
|
||||
<div class="col-md-4">
|
||||
<div class="card mb-4 box-shadow" style="width: 18rem;">
|
||||
<div class="card-body">
|
||||
<p class="card-title" style="height: 2rem;">
|
||||
<strong><a href="{% url 'show-talk' presentation.talk.id %}">{{presentation.talk.title}}</a></strong>
|
||||
{% if presentation.status == -1 %}
|
||||
<span class="badge badge-danger" >Declined</span>
|
||||
{% elif presentation.status == 1 %}
|
||||
<span class="badge badge-success" >Accepted</span>
|
||||
{% else %}
|
||||
<span class="badge badge-info" >Submitted</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
<div class="card-text">
|
||||
<small class="text-muted mb-1">{{ presentation.talk.speaker }}</small>
|
||||
<div>
|
||||
<button class="btn btn-sm btn-dark" type="submit" name="action" value="propose">Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if declined_talks %}
|
||||
<hr/>
|
||||
<div class="row">
|
||||
{% for presentation in declined_talks %}
|
||||
<form action="{% url 'schedule-event-talks' event.id %}" method="POST">
|
||||
<input type="hidden" name="presentation_id" value="{{presentation.id}}"/>
|
||||
{% csrf_token %}
|
||||
<div class="col-md-4">
|
||||
<div class="card mb-4 box-shadow" style="width: 18rem;">
|
||||
<div class="card-body">
|
||||
<p class="card-title" style="height: 2rem;">
|
||||
<strong><a href="{% url 'show-talk' presentation.talk.id %}">{{presentation.talk.title}}</a></strong>
|
||||
{% if presentation.status == -1 %}
|
||||
<span class="badge badge-danger" >Declined</span>
|
||||
{% elif presentation.status == 1 %}
|
||||
<span class="badge badge-success" >Accepted</span>
|
||||
{% else %}
|
||||
<span class="badge badge-info" >Submitted</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
<div class="card-text">
|
||||
<small class="text-muted mb-1">{{ presentation.talk.speaker }}</small>
|
||||
<div>
|
||||
<button class="btn btn-sm btn-dark" type="submit" name="action" value="propose">Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<a href="{{event.get_absolute_url}}" class="btn btn-primary">Done</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -94,6 +94,7 @@
|
|||
</button>
|
||||
<div class="dropdown-menu" aria-labelledby="editMenuButton">
|
||||
<a href="{% url 'edit-event' event.id %}" class="dropdown-item">Event Details</a>
|
||||
<a href="{% url 'schedule-event-talks' event.id %}" class="dropdown-item">Talks</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
@ -146,6 +147,20 @@
|
|||
</tr>
|
||||
{% endif %}
|
||||
|
||||
{% if event.team.is_premium %}
|
||||
<tr>
|
||||
<td width="120px"><b>Presentations:</b></td>
|
||||
<td>
|
||||
{% for presentation in presentation_list %}
|
||||
<div><a href="{% url 'show-talk' presentation.talk.id %}">{{presentation.talk.title}}</a> by <a href="{% url 'show-speaker' presentation.talk.speaker.id %}">{{presentation.talk.speaker.user}}, {{presentation.talk.speaker.title}}</a></div>
|
||||
{% endfor %}
|
||||
<a class="btn btn-primary btn-sm" href="{% url 'propose-event-talk' event.id %}">Propose a talk</a>
|
||||
{% if pending_presentations %}
|
||||
<a class="btn btn-success btn-sm" href="{% url 'schedule-event-talks' event.id %}">{{pending_presentations}} proposed talks</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
|
||||
<div class="container mt-3">
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
{% extends "get_together/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<h2>New Speaker</h2>
|
||||
|
||||
|
||||
<form enctype="multipart/form-data" action="{% url "add-speaker" %}" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<table class="table">
|
||||
<tr><th></th><td><img src="{{speaker.headshot.url}}"/></td></tr>
|
||||
{{speaker_form}}
|
||||
</table>
|
||||
<br />
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1,33 @@
|
|||
{% extends "get_together/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<h2>New Talk</h2>
|
||||
|
||||
|
||||
<form action="{% url "add-talk" %}" method="post">
|
||||
{% csrf_token %}
|
||||
{% if event %}<input type="hidden" name="event" value="{{event.id}}" />{% endif %}
|
||||
|
||||
<div class="form-group">
|
||||
<table class="table">
|
||||
{{talk_form}}
|
||||
</table>
|
||||
<br />
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block javascript %}
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function(){
|
||||
|
||||
$("#id_speaker").selectmenu()
|
||||
$("#id_talk_type").selectmenu()
|
||||
$("#id_category").selectmenu()
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -0,0 +1,20 @@
|
|||
{% extends "get_together/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Confirm deletion</h2>
|
||||
{% if speaker.talks.count > 0 %}
|
||||
<div class="alerts">
|
||||
<div class="alert alert-danger">This speaker profile has {{ speaker.talks.count }} talks!</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
Are you sure you want to delete <strong>{{speaker.title}}</strong>?
|
||||
<form action="{% url "delete-speaker" speaker.id %}" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="form-group">
|
||||
{{ delete_form }}
|
||||
<br />
|
||||
<button type="submit" class="btn btn-danger">Delete Talk</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{% extends "get_together/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Confirm deletion</h2>
|
||||
{% if talk.future_presentations.count > 0 %}
|
||||
<div class="alerts">
|
||||
<div class="alert alert-danger">This talk has {{ talk.future_presentations.count }} pending events!</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
Are you sure you want to delete <strong>{{talk.title}}</strong>?
|
||||
<form action="{% url "delete-talk" talk.id %}" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="form-group">
|
||||
{{ delete_form }}
|
||||
<br />
|
||||
<button type="submit" class="btn btn-danger">Delete Talk</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
{% extends "get_together/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<h2>Edit Speaker</h2>
|
||||
|
||||
|
||||
<form enctype="multipart/form-data" action="{% url "edit-speaker" speaker.id %}" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<table class="table">
|
||||
<tr><th></th><td><img src="{{speaker.headshot.url}}"/></td></tr>
|
||||
{{speaker_form}}
|
||||
</table>
|
||||
<br />
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
<a href="{% url 'delete-speaker' speaker.id %}" class="btn btn-danger">Delete</a>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javascript %}
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function(){
|
||||
$("#city_select").lookup({
|
||||
search: function(searchText, callback) {
|
||||
if (searchText.length < 3) return callback(searchText, []);
|
||||
$.getJSON("/api/cities/?q="+searchText, function(data) {
|
||||
var m = this.url.match(/q=([^&]+)/);
|
||||
var q = "";
|
||||
if (m && m.length > 0) {
|
||||
q = this.url.match(/q=([^&]+)/)[1]
|
||||
}
|
||||
|
||||
return callback(q, data);
|
||||
});
|
||||
},
|
||||
select: function( event, ui ) {
|
||||
$("#id_tz").val(ui.data.tz);
|
||||
$("#id_tz").selectmenu("refresh");
|
||||
}
|
||||
});
|
||||
$("#id_categories").selectmenu()
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
32
get_together/templates/get_together/speakers/edit_talk.html
Normal file
32
get_together/templates/get_together/speakers/edit_talk.html
Normal file
|
@ -0,0 +1,32 @@
|
|||
{% extends "get_together/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<h2>Edit Talk</h2>
|
||||
|
||||
|
||||
<form action="{% url "edit-talk" talk.id %}" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<table class="table">
|
||||
{{talk_form}}
|
||||
</table>
|
||||
<br />
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
<a href="{% url 'delete-talk' talk.id %}" class="btn btn-danger">Delete</a>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javascript %}
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function(){
|
||||
|
||||
$("#id_speaker").selectmenu()
|
||||
$("#id_talk_type").selectmenu()
|
||||
$("#id_category").selectmenu()
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -0,0 +1,64 @@
|
|||
{% extends "get_together/base.html" %}
|
||||
{% load static tz %}
|
||||
|
||||
{% block styles %}
|
||||
<link href="{% static 'css/bootstrap-album.css' %}" rel="stylesheet"/>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
{% if proposed_talks %}
|
||||
<div class="row">
|
||||
{% for presentation in proposed_talks %}
|
||||
<div class="col-md-4">
|
||||
<div class="card mb-4 box-shadow" style="width: 18rem;">
|
||||
<div class="card-body">
|
||||
<p class="card-title" style="height: 2rem;">
|
||||
<strong><a href="{% url 'show-talk' presentation.talk.id %}">{{presentation.talk.title}}</a></strong>
|
||||
{% if presentation.status == -1 %}
|
||||
<span class="badge badge-danger" >Declined</span>
|
||||
{% elif presentation.status == 1 %}
|
||||
<span class="badge badge-success" >Accepted</span>
|
||||
{% else %}
|
||||
<span class="badge badge-info" >Submitted</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
<div class="card-text">
|
||||
<small class="text-muted mb-1">{{ presentation.talk.speaker }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<hr/>
|
||||
{% endif %}
|
||||
<div class="row">
|
||||
{% for talk in available_talks %}
|
||||
<form action="{% url 'propose-event-talk' event.id %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<div class="col-md-4">
|
||||
<div class="card mb-4 box-shadow" style="width: 18rem;">
|
||||
<div class="card-body">
|
||||
<p class="card-title" style="height: 2rem;"><strong><a href="{% url 'show-talk' talk.id %}">{{talk.title}}</a></strong></p>
|
||||
<div class="card-text">
|
||||
<small class="text-muted mb-1">{{ talk.speaker }}</small>
|
||||
<button class="btn btn-primary" type="submit" name="talk_id" value="{{talk.id}}">Propose</a></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<a href="{{event.get_absolute_url}}" class="btn btn-secondary">Cancel</a>
|
||||
<a href="{% url 'add-talk' %}?event={{event.id}}" class="btn btn-success">New Talk</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
{% extends "get_together/base.html" %}
|
||||
{% load static tz %}
|
||||
|
||||
{% block styles %}
|
||||
<link href="{% static 'css/bootstrap-album.css' %}" rel="stylesheet"/>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
{% for speaker in speaker_bios %}
|
||||
<div class="col-md-4">
|
||||
<div class="card mb-4 box-shadow" style="width: 18rem;">
|
||||
<div class="card-img-top mt-1" style="text-align: center"><img src="{{speaker.headshot.url}}"/></div>
|
||||
<div class="card-body">
|
||||
<p class="card-title" style="height: 2rem;">
|
||||
<strong><a href="{% url 'edit-speaker' speaker.id %}">{{speaker.title}}</a></strong>
|
||||
</p>
|
||||
<div class="card-text">
|
||||
<small class="text-muted mb-1">{{ speaker.bio }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<a href="{% url 'add-speaker' %}" class="btn btn-success"><i class="fa fa-plus"></i> Speaker Bio</a>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
|
||||
<div class="row">
|
||||
{% for talk in talks %}
|
||||
<div class="col-md-4">
|
||||
<div class="card mb-4 box-shadow" style="width: 18rem;">
|
||||
<div class="card-body">
|
||||
<p class="card-title" style="height: 2rem;"><strong><a href="{% url 'edit-talk' talk.id %}">{{talk.title}}</a></strong></p>
|
||||
<div class="card-text">
|
||||
<small class="text-muted mb-1">{{ talk.abstract }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<a href="{% url 'add-talk' %}" class="btn btn-success"><i class="fa fa-plus"></i> Talk</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
{% extends "get_together/base.html" %}
|
||||
{% load markup static %}
|
||||
|
||||
{% block styles %}
|
||||
<link href="{% static 'css/bootstrap-album.css' %}" rel="stylesheet"/>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
<img src="{{speaker.headshot.url}}"/><hr/>
|
||||
<h3>{{ speaker.user }}</h3>
|
||||
<h5 class="text-muted">{{ speaker.title }}</h5>
|
||||
<p>
|
||||
{{ speaker.bio|markdown }}
|
||||
</p>
|
||||
|
||||
{% if talks %}
|
||||
<br/>
|
||||
<h4>Talks</h4>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
{% for talk in talks %}
|
||||
<div class="col-md-4">
|
||||
<div class="card box-shadow" >
|
||||
<div class="card-body">
|
||||
<p class="card-title"><strong><a href="{% url 'show-talk' talk.id %}">{{talk.title}}</a></strong></p>
|
||||
<div class="card-text">
|
||||
<small class="text-muted mb-1">{{ talk.speaker }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col"><h4>Events ({{presentations.count}})</h4><hr/></div>
|
||||
</div>
|
||||
{% for presentation in presentations %}
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
<h6 class="mt-2 mb-0"><a href="{{presentation.event.get_absolute_url}}">{{presentation.event.name}}</a></h6>
|
||||
<small>{{ presentation.event.team }}</small><br/>
|
||||
<small class="text-muted">{{ presentation.event.local_start_time }}</small>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
51
get_together/templates/get_together/speakers/show_talk.html
Normal file
51
get_together/templates/get_together/speakers/show_talk.html
Normal file
|
@ -0,0 +1,51 @@
|
|||
{% extends "get_together/base.html" %}
|
||||
{% load markup static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
<h2>{{ talk.title }}
|
||||
{% if talk.speaker.user == request.user.profile %}
|
||||
<a href="{% url 'edit-talk' talk.id %}" class="btn btn-secondary btn-sm">Edit Talk</a>
|
||||
{% endif %}
|
||||
</h2>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td><b>Speaker:</b></td><td><a href="{% url 'show-speaker' talk.speaker.id %}">{{ talk.speaker.user }}, {{ talk.speaker.title }}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Category:</b></td><td>{{ talk.category }}</td>
|
||||
</tr>
|
||||
{% if talk.web_url %}
|
||||
<tr>
|
||||
<td><b>Website:</b></td><td><a href="{{ talk.web_url }}" target="_blank">{{ talk.web_url }}</a></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td><b>Abstract:</b></td><td>{{ talk.abstract|markdown }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col"><h4>Events ({{presentations.count}})</h4><hr/></div>
|
||||
</div>
|
||||
{% for presentation in presentations %}
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
<h6 class="mt-2 mb-0"><a href="{{presentation.event.get_absolute_url}}">{{presentation.event.name}}</a></h6>
|
||||
<small>{{ presentation.event.team }}</small><br/>
|
||||
<small class="text-muted">{{ presentation.event.local_start_time }}</small>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
{% extends "get_together/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
|
@ -30,8 +31,27 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block javascript %}
|
||||
<script src="{% static 'js/jquery-ui-lookup.js' %}"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function(){
|
||||
$("#city_select").lookup({
|
||||
search: function(searchText, callback) {
|
||||
if (searchText.length < 3) return callback(searchText, []);
|
||||
$.getJSON("/api/cities/?q="+searchText, function(data) {
|
||||
var m = this.url.match(/q=([^&]+)/);
|
||||
var q = "";
|
||||
if (m && m.length > 0) {
|
||||
q = this.url.match(/q=([^&]+)/)[1]
|
||||
}
|
||||
|
||||
return callback(q, data);
|
||||
});
|
||||
},
|
||||
select: function( event, ui ) {
|
||||
$("#id_tz").val(ui.data.tz);
|
||||
$("#id_tz").selectmenu("refresh");
|
||||
}
|
||||
});
|
||||
$("#id_tz").selectmenu()
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
{% extends "get_together/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block styles %}
|
||||
<link href="{% static 'css/bootstrap-album.css' %}" rel="stylesheet"/>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
|
@ -6,11 +12,19 @@
|
|||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-2"></div>
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-7">
|
||||
<div class="h2">
|
||||
<img class="align-bottom" border="1" src="{{user.avatar_url}}" height="128px"/> {{user.user}}
|
||||
{% if user.user.id == request.user.id %}
|
||||
<a href="{% url 'edit-profile' %}" class="btn btn-secondary btn-sm">Edit Profile</a>
|
||||
<div class="btn-group dropdown">
|
||||
<button class="btn btn-sm btn-secondary dropdown-toggle" type="button" id="editMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Edit
|
||||
</button>
|
||||
<div class="dropdown-menu" aria-labelledby="editMenuButton">
|
||||
<a href="{% url 'edit-profile' %}" class="dropdown-item">Profile</a>
|
||||
<a href="{% url 'user-talks' %}" class="dropdown-item">Talks</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="{% url 'user-event-ical' request.user.profile.secret_key %}" class="btn btn-success btn-sm">iCal</a>
|
||||
{% endif %}
|
||||
|
||||
|
@ -21,10 +35,29 @@
|
|||
{% if user.weburl %}
|
||||
<p>Homepage: {{user.weburl}}</p>
|
||||
{% endif %}
|
||||
{% if talks %}
|
||||
<h3>Talks</h3>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
{% for talk in talks %}
|
||||
<div class="mr-3 mb-3 col-md-5">
|
||||
<div class="card box-shadow" >
|
||||
<div class="card-body">
|
||||
<p class="card-title"><strong><a href="{% url 'show-talk' talk.id %}">{{talk.title}}</a></strong></p>
|
||||
<div class="card-text">
|
||||
<small class="text-muted mb-1">{{ talk.speaker }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
{% if teams %}
|
||||
<h4>Your Teams</h4>
|
||||
<h4>Teams</h4>
|
||||
<ul>
|
||||
{% for t in teams %}
|
||||
<li>
|
||||
|
@ -33,6 +66,14 @@
|
|||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
<h4>Categories</h4>
|
||||
<ul>
|
||||
{% for c in user.categories.all %}
|
||||
<li>
|
||||
{{c.name}}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -49,6 +49,17 @@ urlpatterns = [
|
|||
path('profile/<int:user_id>/', views.show_profile, name='show-profile'),
|
||||
path('profile/<str:account_secret>.ics', feeds.UserEventsCalendar(), name='user-event-ical'),
|
||||
|
||||
path('profile/+add-speaker', views.add_speaker, name='add-speaker'),
|
||||
path('speaker/<int:speaker_id>/', views.show_speaker, name='show-speaker'),
|
||||
path('speaker/<int:speaker_id>/+edit', views.edit_speaker, name='edit-speaker'),
|
||||
path('speaker/<int:speaker_id>/+delete', views.delete_speaker, name='delete-speaker'),
|
||||
|
||||
path('profile/+talks', views.list_user_talks, name='user-talks'),
|
||||
path('profile/+add-talk', views.add_talk, name='add-talk'),
|
||||
path('talk/<int:talk_id>/', views.show_talk, name='show-talk'),
|
||||
path('talk/<int:talk_id>/+edit', views.edit_talk, name='edit-talk'),
|
||||
path('talk/<int:talk_id>/+delete', views.delete_talk, name='delete-talk'),
|
||||
|
||||
path('events/', views.events_list, name='events'),
|
||||
path('events/all/', views.events_list_all, name='all-events'),
|
||||
path('teams/', views.teams_list, name='teams'),
|
||||
|
@ -70,7 +81,10 @@ urlpatterns = [
|
|||
path('events/<int:event_id>/+add_place/', views.add_place_to_event, name='add-place'),
|
||||
path('events/<int:event_id>/+comment/', event_views.comment_event, name='comment-event'),
|
||||
path('events/<int:event_id>/+photo/', views.add_event_photo, name='add-event-photo'),
|
||||
path('events/<int:event_id>/+propose-talk/', views.propose_event_talk, name='propose-event-talk'),
|
||||
path('events/<int:event_id>/+schedule-talks/', views.schedule_event_talks, name='schedule-event-talks'),
|
||||
path('events/<int:event_id>/<str:event_slug>/', views.show_event, name='show-event'),
|
||||
|
||||
path('series/<int:series_id>/+edit/', views.edit_series, name='edit-series'),
|
||||
path('series/<int:series_id>/+delete/', views.delete_series, name='delete-series'),
|
||||
path('series/<int:series_id>/+add_place/', views.add_place_to_series, name='add-place-to-series'),
|
||||
|
|
|
@ -27,6 +27,7 @@ from .places import *
|
|||
from .user import *
|
||||
from .new_user import *
|
||||
from .new_team import *
|
||||
from .speakers import *
|
||||
from .utils import *
|
||||
|
||||
KM_PER_DEGREE_LAT = 110.574
|
||||
|
@ -72,6 +73,11 @@ def home(request, *args, **kwards):
|
|||
city_distance += 1
|
||||
else:
|
||||
city = sorted(nearby_cities, key=lambda city: location.city_distance_from(ll, city))[0]
|
||||
|
||||
if request.user.profile.city is None:
|
||||
profile = request.user.profile
|
||||
profile.city = city
|
||||
profile.save()
|
||||
except:
|
||||
pass # City lookup failed
|
||||
|
||||
|
|
|
@ -7,8 +7,17 @@ from django.shortcuts import render, redirect, reverse, get_object_or_404
|
|||
from django.http import HttpResponse, JsonResponse
|
||||
from django.utils import timezone
|
||||
|
||||
from events.models.events import Event, CommonEvent, EventSeries, EventPhoto, Place, Attendee, update_event_searchable, \
|
||||
delete_event_searchable
|
||||
from events.models.events import (
|
||||
Event,
|
||||
CommonEvent,
|
||||
EventSeries,
|
||||
EventPhoto,
|
||||
Place,
|
||||
Attendee,
|
||||
update_event_searchable,
|
||||
delete_event_searchable,
|
||||
)
|
||||
from events.models.speakers import Speaker, Talk, SpeakerRequest, Presentation
|
||||
from events.models.profiles import Team, Organization, UserProfile, Member
|
||||
from events.forms import (
|
||||
TeamEventForm,
|
||||
|
@ -20,7 +29,7 @@ from events.forms import (
|
|||
NewPlaceForm,
|
||||
UploadEventPhotoForm,
|
||||
NewCommonEventForm
|
||||
)
|
||||
)
|
||||
from events import location
|
||||
|
||||
import datetime
|
||||
|
@ -56,6 +65,8 @@ def show_event(request, event_id, event_slug):
|
|||
'comment_form': comment_form,
|
||||
'is_attending': request.user.profile in event.attendees.all(),
|
||||
'attendee_list': Attendee.objects.filter(event=event),
|
||||
'presentation_list': event.presentations.filter(status=Presentation.ACCEPTED).order_by('start_time'),
|
||||
'pending_presentations': event.presentations.filter(status=Presentation.PROPOSED).count(),
|
||||
'can_edit_event': request.user.profile.can_edit_event(event),
|
||||
}
|
||||
return render(request, 'get_together/events/show_event.html', context)
|
||||
|
@ -220,13 +231,6 @@ def add_place_to_series(request, series_id):
|
|||
else:
|
||||
return redirect('home')
|
||||
|
||||
def share_event(request, event_id):
|
||||
event = get_object_or_404(Event, id=event_id)
|
||||
context = {
|
||||
'event': event,
|
||||
}
|
||||
return render(request, 'get_together/events/share_event.html', context)
|
||||
|
||||
def edit_event(request, event_id):
|
||||
event = get_object_or_404(Event, id=event_id)
|
||||
|
||||
|
@ -391,14 +395,6 @@ def create_common_event(request, org_slug):
|
|||
else:
|
||||
return redirect('home')
|
||||
|
||||
def share_common_event(request, event_id):
|
||||
event = get_object_or_404(CommonEvent, id=event_id)
|
||||
context = {
|
||||
'event': event,
|
||||
}
|
||||
return render(request, 'get_together/orgs/share_common_event.html', context)
|
||||
|
||||
@login_required
|
||||
def create_common_event_team_select(request, event_id):
|
||||
teams = request.user.profile.moderating
|
||||
if len(teams) == 1:
|
||||
|
|
278
get_together/views/speakers.py
Normal file
278
get_together/views/speakers.py
Normal file
|
@ -0,0 +1,278 @@
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import logout as logout_user
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.http import HttpResponse, JsonResponse
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
|
||||
from events.models.profiles import UserProfile
|
||||
from events.forms import (
|
||||
SpeakerBioForm,
|
||||
DeleteSpeakerForm,
|
||||
UserTalkForm,
|
||||
DeleteTalkForm,
|
||||
SchedulePresentationForm,
|
||||
)
|
||||
|
||||
from events.models.events import Event
|
||||
from events.models.speakers import Speaker, Talk, Presentation, SpeakerRequest
|
||||
import datetime
|
||||
import simplejson
|
||||
|
||||
from .teams import *
|
||||
from .events import *
|
||||
|
||||
@login_required
|
||||
def list_user_talks(request):
|
||||
profile = request.user.profile
|
||||
speaker_bios = Speaker.objects.filter(user=profile)
|
||||
talks = list(Talk.objects.filter(speaker__user=profile))
|
||||
context = {
|
||||
'speaker_bios': speaker_bios,
|
||||
'talks': talks,
|
||||
}
|
||||
return render(request, 'get_together/speakers/list_user_talks.html', context)
|
||||
|
||||
def show_speaker(request, speaker_id):
|
||||
speaker = get_object_or_404(Speaker, id=speaker_id)
|
||||
|
||||
context = {
|
||||
'speaker': speaker,
|
||||
'talks': Talk.objects.filter(speaker=speaker),
|
||||
'presentations': Presentation.objects.filter(talk__speaker=speaker, status=Presentation.ACCEPTED),
|
||||
}
|
||||
return render(request, 'get_together/speakers/show_speaker.html', context)
|
||||
|
||||
def add_speaker(request):
|
||||
new_speaker = Speaker(user=request.user.profile)
|
||||
if request.method == 'GET':
|
||||
speaker_form = SpeakerBioForm(instance=new_speaker)
|
||||
context = {
|
||||
'speaker': new_speaker,
|
||||
'speaker_form': speaker_form,
|
||||
}
|
||||
return render(request, 'get_together/speakers/create_speaker.html', context)
|
||||
elif request.method == 'POST':
|
||||
speaker_form = SpeakerBioForm(request.POST, request.FILES, instance=new_speaker)
|
||||
if speaker_form.is_valid():
|
||||
new_speaker = speaker_form.save()
|
||||
return redirect('user-talks')
|
||||
else:
|
||||
context = {
|
||||
'speaker': new_speaker,
|
||||
'speaker_form': speaker_form,
|
||||
}
|
||||
return render(request, 'get_together/speakers/create_speaker.html', context)
|
||||
return redirect('home')
|
||||
|
||||
def edit_speaker(request, speaker_id):
|
||||
speaker = get_object_or_404(Speaker, id=speaker_id)
|
||||
if request.method == 'GET':
|
||||
speaker_form = SpeakerBioForm(instance=speaker)
|
||||
context = {
|
||||
'speaker': speaker,
|
||||
'speaker_form': speaker_form,
|
||||
}
|
||||
return render(request, 'get_together/speakers/edit_speaker.html', context)
|
||||
elif request.method == 'POST':
|
||||
speaker_form = SpeakerBioForm(request.POST, request.FILES, instance=speaker)
|
||||
if speaker_form.is_valid():
|
||||
speaker = speaker_form.save()
|
||||
return redirect('user-talks')
|
||||
else:
|
||||
context = {
|
||||
'speaker': speaker,
|
||||
'speaker_form': speaker_form,
|
||||
}
|
||||
return render(request, 'get_together/speakers/edit_speaker.html', context)
|
||||
return redirect('home')
|
||||
|
||||
def delete_speaker(request, speaker_id):
|
||||
speaker = get_object_or_404(Speaker, id=speaker_id)
|
||||
if not speaker.user == request.user.profile:
|
||||
messages.add_message(request, messages.WARNING, message=_('You can not make changes to this speaker bio.'))
|
||||
return redirect('show-speaker', speaker_id)
|
||||
|
||||
if request.method == 'GET':
|
||||
form = DeleteSpeakerForm()
|
||||
|
||||
context = {
|
||||
'speaker': speaker,
|
||||
'delete_form': form,
|
||||
}
|
||||
return render(request, 'get_together/speakers/delete_speaker.html', context)
|
||||
elif request.method == 'POST':
|
||||
form = DeleteSpeakerForm(request.POST)
|
||||
if form.is_valid() and form.cleaned_data['confirm']:
|
||||
speaker.delete()
|
||||
return redirect('user-talks')
|
||||
else:
|
||||
context = {
|
||||
'speaker': speaker,
|
||||
'delete_form': form,
|
||||
}
|
||||
return render(request, 'get_together/speakers/delete_speaker.html', context)
|
||||
else:
|
||||
return redirect('home')
|
||||
|
||||
def show_talk(request, talk_id):
|
||||
talk = get_object_or_404(Talk, id=talk_id)
|
||||
presentations = Presentation.objects.filter(talk=talk, status=Presentation.ACCEPTED).order_by('-event__start_time')
|
||||
context = {
|
||||
'talk': talk,
|
||||
'presentations': presentations,
|
||||
}
|
||||
return render(request, 'get_together/speakers/show_talk.html', context)
|
||||
|
||||
def add_talk(request):
|
||||
new_talk = Talk()
|
||||
if request.method == 'GET':
|
||||
talk_form = UserTalkForm(instance=new_talk)
|
||||
talk_form.fields['speaker'].queryset = request.user.profile.speaker_set
|
||||
context = {
|
||||
'talk': new_talk,
|
||||
'talk_form': talk_form,
|
||||
}
|
||||
if 'event' in request.GET and request.GET['event']:
|
||||
context['event'] = get_object_or_404(Event, id=request.GET['event'])
|
||||
return render(request, 'get_together/speakers/create_talk.html', context)
|
||||
elif request.method == 'POST':
|
||||
talk_form = UserTalkForm(request.POST, instance=new_talk)
|
||||
talk_form.fields['speaker'].queryset = request.user.profile.speaker_set
|
||||
if talk_form.is_valid():
|
||||
new_talk = talk_form.save()
|
||||
if 'event' in request.POST and request.POST['event']:
|
||||
event = Event.objects.get(id=request.POST['event'])
|
||||
Presentation.objects.create(
|
||||
event=event,
|
||||
talk=new_talk,
|
||||
status=Presentation.PROPOSED,
|
||||
created_by=request.user.profile
|
||||
)
|
||||
return redirect(event.get_absolute_url())
|
||||
return redirect('show-talk', new_talk.id)
|
||||
else:
|
||||
context = {
|
||||
'talk': new_talk,
|
||||
'talk_form': talk_form,
|
||||
}
|
||||
return render(request, 'get_together/speakers/create_talk.html', context)
|
||||
return redirect('home')
|
||||
|
||||
def edit_talk(request, talk_id):
|
||||
talk = get_object_or_404(Talk, id=talk_id)
|
||||
if not talk.speaker.user == request.user.profile:
|
||||
messages.add_message(request, messages.WARNING, message=_('You can not make changes to this talk.'))
|
||||
return redirect('show-talk', talk_id)
|
||||
|
||||
if request.method == 'GET':
|
||||
talk_form = UserTalkForm(instance=talk)
|
||||
talk_form.fields['speaker'].queryset = request.user.profile.speaker_set
|
||||
context = {
|
||||
'talk': talk,
|
||||
'talk_form': talk_form,
|
||||
}
|
||||
return render(request, 'get_together/speakers/edit_talk.html', context)
|
||||
elif request.method == 'POST':
|
||||
talk_form = UserTalkForm(request.POST, instance=talk)
|
||||
talk_form.fields['speaker'].queryset = request.user.profile.speaker_set
|
||||
if talk_form.is_valid():
|
||||
talk = talk_form.save()
|
||||
return redirect('show-talk', talk.id)
|
||||
else:
|
||||
context = {
|
||||
'talk': talk,
|
||||
'talk_form': talk_form,
|
||||
}
|
||||
return render(request, 'get_together/speakers/edit_talk.html', context)
|
||||
return redirect('home')
|
||||
|
||||
def delete_talk(request, talk_id):
|
||||
talk = get_object_or_404(Talk, id=talk_id)
|
||||
if not talk.speaker.user == request.user.profile:
|
||||
messages.add_message(request, messages.WARNING, message=_('You can not make changes to this talk.'))
|
||||
return redirect('show-talk', talk_id)
|
||||
|
||||
if request.method == 'GET':
|
||||
form = DeleteTalkForm()
|
||||
|
||||
context = {
|
||||
'talk': talk,
|
||||
'delete_form': form,
|
||||
}
|
||||
return render(request, 'get_together/speakers/delete_talk.html', context)
|
||||
elif request.method == 'POST':
|
||||
form = DeleteTalkForm(request.POST)
|
||||
if form.is_valid() and form.cleaned_data['confirm']:
|
||||
talk.delete()
|
||||
return redirect('user-talks')
|
||||
else:
|
||||
context = {
|
||||
'talk': talk,
|
||||
'delete_form': form,
|
||||
}
|
||||
return render(request, 'get_together/speakers/delete_talk.html', context)
|
||||
else:
|
||||
return redirect('home')
|
||||
|
||||
@login_required
|
||||
def propose_event_talk(request, event_id):
|
||||
event = get_object_or_404(Event, id=event_id)
|
||||
if not event.team.is_premium:
|
||||
messages.add_message(request, messages.ERROR, message=_("You can not manage talks for this event."))
|
||||
return redirect(event.get_absolute_url())
|
||||
|
||||
if request.method == 'GET':
|
||||
profile = request.user.profile
|
||||
talks = list(Talk.objects.filter(speaker__user=profile))
|
||||
presentations = event.presentations.all().order_by('-status')
|
||||
for presentation in presentations:
|
||||
if presentation.talk in talks:
|
||||
talks.remove(presentation.talk)
|
||||
|
||||
context = {
|
||||
'event': event,
|
||||
'available_talks': talks,
|
||||
'proposed_talks': presentations,
|
||||
}
|
||||
return render(request, 'get_together/speakers/list_user_presentations.html', context)
|
||||
elif request.method == 'POST':
|
||||
talk = get_object_or_404(Talk, id=request.POST.get('talk_id'))
|
||||
new_proposal = Presentation.objects.create(
|
||||
event=event,
|
||||
talk=talk,
|
||||
status=Presentation.PROPOSED,
|
||||
start_time=event.local_start_time,
|
||||
created_by=request.user.profile,
|
||||
)
|
||||
messages.add_message(request, messages.SUCCESS, message=_('Your talk has been submitted to the event organizer.'))
|
||||
return redirect(event.get_absolute_url())
|
||||
else:
|
||||
redirect('home')
|
||||
|
||||
def schedule_event_talks(request, event_id):
|
||||
event = get_object_or_404(Event, id=event_id)
|
||||
if not event.team.is_premium:
|
||||
messages.add_message(request, messages.ERROR, message=mark_safe(_('Upgrade this team to a <a href="/about/premium">Premium</a> account to use this feature.')))
|
||||
return redirect(event.get_absolute_url())
|
||||
|
||||
if request.method == 'POST':
|
||||
presentation = get_object_or_404(Presentation, id=request.POST.get('presentation_id'))
|
||||
if request.POST.get('action') == 'accept':
|
||||
presentation.status = Presentation.ACCEPTED
|
||||
elif request.POST.get('action') == 'decline':
|
||||
presentation.status = Presentation.DECLINED
|
||||
elif request.POST.get('action') == 'propose':
|
||||
presentation.status = Presentation.PROPOSED
|
||||
presentation.save()
|
||||
|
||||
context = {
|
||||
'event': event,
|
||||
'talks_count': event.presentations.count(),
|
||||
'accepted_talks': event.presentations.filter(status=Presentation.ACCEPTED).order_by('start_time'),
|
||||
'pending_talks': event.presentations.filter(status=Presentation.PROPOSED).order_by('start_time'),
|
||||
'declined_talks': event.presentations.filter(status=Presentation.DECLINED).order_by('start_time'),
|
||||
}
|
||||
return render(request, 'get_together/events/schedule_event_talks.html', context)
|
||||
|
|
@ -33,10 +33,11 @@ def show_profile(request, user_id):
|
|||
user = get_object_or_404(UserProfile, id=user_id)
|
||||
|
||||
teams = user.memberships.all()
|
||||
|
||||
talks = Talk.objects.filter(speaker__user=user)
|
||||
context = {
|
||||
'user': user,
|
||||
'teams': teams,
|
||||
'talks': talks,
|
||||
}
|
||||
|
||||
return render(request, 'get_together/users/show_profile.html', context)
|
||||
|
|
Loading…
Reference in a new issue