Add user management of their speaker bios and talks
This commit is contained in:
parent
e45c0370a1
commit
5f16e176f9
18 changed files with 625 additions and 15 deletions
|
@ -133,7 +133,17 @@ class TopicAdmin(admin.ModelAdmin):
|
||||||
exclude = ('slug', )
|
exclude = ('slug', )
|
||||||
admin.site.register(Topic, TopicAdmin)
|
admin.site.register(Topic, TopicAdmin)
|
||||||
|
|
||||||
admin.site.register(Speaker)
|
class SpeakerAdmin(admin.ModelAdmin):
|
||||||
admin.site.register(Talk)
|
list_display = ('title', 'user', 'avatar')
|
||||||
admin.site.register(Presentation)
|
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)
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,17 @@ from django.utils import timezone
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from .models.locale import Country, SPR, City
|
from .models.locale import Country, SPR, City
|
||||||
from .models.profiles import Team, UserProfile
|
from .models.profiles import Team, UserProfile, Speaker, Talk
|
||||||
from .models.events import Event, EventComment ,CommonEvent, EventSeries, Place, EventPhoto
|
from .models.events import (
|
||||||
|
Event,
|
||||||
|
EventComment,
|
||||||
|
CommonEvent,
|
||||||
|
EventSeries,
|
||||||
|
Place,
|
||||||
|
EventPhoto,
|
||||||
|
Presentation,
|
||||||
|
SpeakerRequest,
|
||||||
|
)
|
||||||
import recurrence
|
import recurrence
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
|
@ -329,3 +338,21 @@ class NewCommonEventForm(forms.ModelForm):
|
||||||
'end_time': DateTimeWidget
|
'end_time': DateTimeWidget
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SpeakerBioForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Speaker
|
||||||
|
fields = ['avatar', 'title', 'bio', 'categories']
|
||||||
|
|
||||||
|
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']
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
# Generated by Django 2.0 on 2018-04-21 14:26
|
# Generated by Django 2.0 on 2018-04-22 21:23
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import imagekit.models.fields
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -15,14 +17,19 @@ class Migration(migrations.Migration):
|
||||||
name='Presentation',
|
name='Presentation',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('start_time', models.DateTimeField(db_index=True, verbose_name='Start Time')),
|
('status', models.SmallIntegerField(choices=[(-1, 'Declined'), (0, 'Proposed'), (1, 'Accepted')], db_index=True, default=0)),
|
||||||
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='events.Event')),
|
('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(
|
migrations.CreateModel(
|
||||||
name='Speaker',
|
name='Speaker',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('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)),
|
('bio', models.TextField(blank=True)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -108,6 +115,6 @@ class Migration(migrations.Migration):
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='presentation',
|
model_name='presentation',
|
||||||
name='talk',
|
name='talk',
|
||||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='events.Talk'),
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='presentations', to='events.Talk'),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -381,9 +381,22 @@ class SpeakerRequest(models.Model):
|
||||||
topics = models.ManyToManyField('Topic', blank=True)
|
topics = models.ManyToManyField('Topic', blank=True)
|
||||||
|
|
||||||
class Presentation(models.Model):
|
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)
|
event = models.ForeignKey(Event, related_name='presentations', on_delete=models.CASCADE)
|
||||||
talk = models.ForeignKey(Talk, related_name='presentations', on_delete=models.SET_NULL, blank=False, null=True)
|
talk = models.ForeignKey(Talk, related_name='presentations', on_delete=models.SET_NULL, blank=False, null=True)
|
||||||
start_time = models.DateTimeField(verbose_name=_('Start Time'), db_index=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):
|
def __str__(self):
|
||||||
return '%s at %s' % (self.talk.title, self.event.name)
|
return '%s at %s' % (self.talk.title, self.event.name)
|
||||||
|
|
|
@ -86,6 +86,10 @@ class UserProfile(models.Model):
|
||||||
def moderating(self):
|
def moderating(self):
|
||||||
return [member.team for member in Member.objects.filter(user=self, role__in=(Member.ADMIN, Member.MODERATOR))]
|
return [member.team for member in Member.objects.filter(user=self, role__in=(Member.ADMIN, Member.MODERATOR))]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def talks(self):
|
||||||
|
return Talk.objects.filter(speaker__user=self)
|
||||||
|
|
||||||
def can_create_event(self, team):
|
def can_create_event(self, team):
|
||||||
try:
|
try:
|
||||||
if self.user.is_superuser:
|
if self.user.is_superuser:
|
||||||
|
@ -304,12 +308,26 @@ class Topic(models.Model):
|
||||||
|
|
||||||
class Speaker(models.Model):
|
class Speaker(models.Model):
|
||||||
user = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
|
user = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
|
||||||
bio = models.TextField(blank=True)
|
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)
|
categories = models.ManyToManyField('Category', blank=True)
|
||||||
topics = models.ManyToManyField('Topic', 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):
|
def __str__(self):
|
||||||
|
if self.title:
|
||||||
|
return self.title
|
||||||
return self.user.__str__()
|
return self.user.__str__()
|
||||||
|
|
||||||
class Talk(models.Model):
|
class Talk(models.Model):
|
||||||
|
@ -327,7 +345,7 @@ class Talk(models.Model):
|
||||||
(QANDA, _("Q & A")),
|
(QANDA, _("Q & A")),
|
||||||
(DEMO, _("Demonstration")),
|
(DEMO, _("Demonstration")),
|
||||||
]
|
]
|
||||||
speaker = models.ForeignKey(Speaker, on_delete=models.CASCADE)
|
speaker = models.ForeignKey(Speaker, verbose_name=_('Speaker Bio'), on_delete=models.CASCADE)
|
||||||
title = models.CharField(max_length=256)
|
title = models.CharField(max_length=256)
|
||||||
abstract = models.TextField()
|
abstract = models.TextField()
|
||||||
talk_type = models.SmallIntegerField(_("Type"), choices=TYPES, default=PRESENTATION)
|
talk_type = models.SmallIntegerField(_("Type"), choices=TYPES, default=PRESENTATION)
|
||||||
|
@ -336,6 +354,14 @@ class Talk(models.Model):
|
||||||
category = models.ForeignKey('Category', on_delete=models.SET_NULL, blank=False, null=True)
|
category = models.ForeignKey('Category', on_delete=models.SET_NULL, blank=False, null=True)
|
||||||
topics = models.ManyToManyField('Topic', blank=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):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
|
|
|
@ -94,6 +94,7 @@
|
||||||
</button>
|
</button>
|
||||||
<div class="dropdown-menu" aria-labelledby="editMenuButton">
|
<div class="dropdown-menu" aria-labelledby="editMenuButton">
|
||||||
<a href="{% url 'edit-event' event.id %}" class="dropdown-item">Event Details</a>
|
<a href="{% url 'edit-event' event.id %}" class="dropdown-item">Event Details</a>
|
||||||
|
<a href="{% url 'edit-event' event.id %}" class="dropdown-item">Speakers</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -146,6 +147,15 @@
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<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-profile' presentation.talk.speaker.user.id %}">{{presentation.talk.speaker}}</a></div>
|
||||||
|
{% endfor %}
|
||||||
|
<a class="btn btn-primary btn-sm" href="{% url 'propose-event-talk' event.id %}">Propose a talk</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div class="container mt-3">
|
<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,31 @@
|
||||||
|
{% extends "get_together/base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h2>New Talk</h2>
|
||||||
|
|
||||||
|
|
||||||
|
<form action="{% url "add-talk" %}" 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>
|
||||||
|
{% 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 talk.future_presentations.count > 0 %}
|
||||||
|
<div class="alerts">
|
||||||
|
<div class="alert alert-danger">This talk has {{ talk.future_presentations.count }} pending event!</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,46 @@
|
||||||
|
{% 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>
|
||||||
|
{% 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-user-talk' %}" 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 %}
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
{% extends "get_together/base.html" %}
|
{% extends "get_together/base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block styles %}
|
||||||
|
<link href="{% static 'css/bootstrap-album.css' %}" rel="stylesheet"/>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
@ -6,11 +12,19 @@
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-2"></div>
|
<div class="col-md-2"></div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-7">
|
||||||
<div class="h2">
|
<div class="h2">
|
||||||
<img class="align-bottom" border="1" src="{{user.avatar_url}}" height="128px"/> {{user.user}}
|
<img class="align-bottom" border="1" src="{{user.avatar_url}}" height="128px"/> {{user.user}}
|
||||||
{% if user.user.id == request.user.id %}
|
{% 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>
|
<a href="{% url 'user-event-ical' request.user.profile.secret_key %}" class="btn btn-success btn-sm">iCal</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
@ -21,6 +35,25 @@
|
||||||
{% if user.weburl %}
|
{% if user.weburl %}
|
||||||
<p>Homepage: {{user.weburl}}</p>
|
<p>Homepage: {{user.weburl}}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if user.talks %}
|
||||||
|
<h3>Talks</h3>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
{% for talk in user.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>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
{% if teams %}
|
{% if teams %}
|
||||||
|
|
|
@ -49,6 +49,16 @@ urlpatterns = [
|
||||||
path('profile/<int:user_id>/', views.show_profile, name='show-profile'),
|
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/<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>/+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/', views.events_list, name='events'),
|
||||||
path('events/all/', views.events_list_all, name='all-events'),
|
path('events/all/', views.events_list_all, name='all-events'),
|
||||||
path('teams/', views.teams_list, name='teams'),
|
path('teams/', views.teams_list, name='teams'),
|
||||||
|
@ -70,7 +80,10 @@ urlpatterns = [
|
||||||
path('events/<int:event_id>/+add_place/', views.add_place_to_event, name='add-place'),
|
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>/+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>/+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('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>/+edit/', views.edit_series, name='edit-series'),
|
||||||
path('series/<int:series_id>/+delete/', views.delete_series, name='delete-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'),
|
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 .user import *
|
||||||
from .new_user import *
|
from .new_user import *
|
||||||
from .new_team import *
|
from .new_team import *
|
||||||
|
from .speakers import *
|
||||||
from .utils import *
|
from .utils import *
|
||||||
|
|
||||||
KM_PER_DEGREE_LAT = 110.574
|
KM_PER_DEGREE_LAT = 110.574
|
||||||
|
|
|
@ -65,7 +65,7 @@ def show_event(request, event_id, event_slug):
|
||||||
'comment_form': comment_form,
|
'comment_form': comment_form,
|
||||||
'is_attending': request.user.profile in event.attendees.all(),
|
'is_attending': request.user.profile in event.attendees.all(),
|
||||||
'attendee_list': Attendee.objects.filter(event=event),
|
'attendee_list': Attendee.objects.filter(event=event),
|
||||||
'presentation_list': event.presentations.all().order_by('start_time'),
|
'presentation_list': event.presentations.filter(status=Presentation.ACCEPTED).order_by('start_time'),
|
||||||
'can_edit_event': request.user.profile.can_edit_event(event),
|
'can_edit_event': request.user.profile.can_edit_event(event),
|
||||||
}
|
}
|
||||||
return render(request, 'get_together/events/show_event.html', context)
|
return render(request, 'get_together/events/show_event.html', context)
|
||||||
|
|
200
get_together/views/speakers.py
Normal file
200
get_together/views/speakers.py
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
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, Speaker, Talk
|
||||||
|
from events.forms import (
|
||||||
|
SpeakerBioForm,
|
||||||
|
UserTalkForm,
|
||||||
|
DeleteTalkForm,
|
||||||
|
SchedulePresentationForm,
|
||||||
|
)
|
||||||
|
|
||||||
|
from events.models.events import Event, Presentation
|
||||||
|
|
||||||
|
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 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('show-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):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def show_talk(request, talk_id):
|
||||||
|
pass
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
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()
|
||||||
|
return redirect('user-talks')
|
||||||
|
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('user-talks')
|
||||||
|
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 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):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue