Adds speaker management feature, fixes #36

This commit is contained in:
Michael Hall 2018-04-27 22:54:49 -04:00
commit d654bbb1ca
33 changed files with 1398 additions and 50 deletions

View file

@ -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)

View file

@ -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']

View 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),
),
]

View 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'),
),
]

View 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'),
),
]

View 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'),
),
]

View file

@ -2,4 +2,4 @@ from .profiles import *
from .locale import *
from .search import *
from .events import *
from .speakers import *

View file

@ -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)

View file

@ -32,6 +32,7 @@ class Country(models.Model):
class Meta:
ordering = ('name',)
verbose_name_plural = 'Countries'
def __str__(self):
return u'%s' % (self.name)

View file

@ -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
View 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
View 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

View file

@ -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)

View file

@ -29,6 +29,7 @@ ALLOWED_HOSTS = []
SITE_ID=1
ADMINS = [ 'mhall119' ]
EVENTS_TEAMS_DEFAULT_PREMIUM=False
# Application definition

View file

@ -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 %}

View file

@ -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">

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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 %}

View 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 %}

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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 %}

View 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 %}

View file

@ -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>

View file

@ -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>

View file

@ -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'),

View file

@ -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

View file

@ -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:

View 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)

View file

@ -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)