Refactor Speaker/Talk/Presentation models into their own file, add views and templates for deleting speaker and talk, add view and template for showing a talk, improve the workflow for proposing a talk that wasn't previously defined
This commit is contained in:
parent
5f16e176f9
commit
8e7c955928
18 changed files with 251 additions and 106 deletions
|
@ -10,8 +10,6 @@ from .models.profiles import (
|
|||
Member,
|
||||
Category,
|
||||
Topic,
|
||||
Speaker,
|
||||
Talk,
|
||||
)
|
||||
from .models.search import Searchable
|
||||
from .models.events import (
|
||||
|
@ -22,7 +20,12 @@ from .models.events import (
|
|||
EventPhoto,
|
||||
CommonEvent,
|
||||
Attendee,
|
||||
)
|
||||
from .models.speakers import (
|
||||
Speaker,
|
||||
Talk,
|
||||
Presentation,
|
||||
SpeakerRequest,
|
||||
)
|
||||
|
||||
admin.site.register(Language)
|
||||
|
|
|
@ -6,7 +6,7 @@ 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, Speaker, Talk
|
||||
from .models.profiles import Team, UserProfile
|
||||
from .models.events import (
|
||||
Event,
|
||||
EventComment,
|
||||
|
@ -14,6 +14,10 @@ from .models.events import (
|
|||
EventSeries,
|
||||
Place,
|
||||
EventPhoto,
|
||||
)
|
||||
from .models.speakers import (
|
||||
Speaker,
|
||||
Talk,
|
||||
Presentation,
|
||||
SpeakerRequest,
|
||||
)
|
||||
|
@ -343,6 +347,9 @@ class SpeakerBioForm(forms.ModelForm):
|
|||
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
|
||||
|
|
|
@ -2,4 +2,4 @@ from .profiles import *
|
|||
from .locale import *
|
||||
from .search import *
|
||||
from .events import *
|
||||
|
||||
from .speakers import *
|
||||
|
|
|
@ -376,28 +376,4 @@ class EventSeries(models.Model):
|
|||
def __str__(self):
|
||||
return u'%s by %s at %s' % (self.name, self.team.name, self.start_time)
|
||||
|
||||
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.SET_NULL, 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):
|
||||
return '%s at %s' % (self.talk.title, self.event.name)
|
||||
|
||||
|
|
|
@ -86,10 +86,6 @@ class UserProfile(models.Model):
|
|||
def moderating(self):
|
||||
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):
|
||||
try:
|
||||
if self.user.is_superuser:
|
||||
|
@ -306,62 +302,3 @@ class Topic(models.Model):
|
|||
self.slug = slugify(self.name)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
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'), 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
|
||||
|
||||
|
|
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"
|
||||
|
|
@ -151,7 +151,7 @@
|
|||
<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>
|
||||
<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.user}}</a>, {{presentation.talk.speaker.title}}</div>
|
||||
{% endfor %}
|
||||
<a class="btn btn-primary btn-sm" href="{% url 'propose-event-talk' event.id %}">Propose a talk</a>
|
||||
</td>
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
<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}}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
<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 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>?
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
<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 %}
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
<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>
|
||||
<a href="{% url 'add-talk' %}?event={{event.id}}" class="btn btn-success">New Talk</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
50
get_together/templates/get_together/speakers/show_talk.html
Normal file
50
get_together/templates/get_together/speakers/show_talk.html
Normal file
|
@ -0,0 +1,50 @@
|
|||
{% 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>{{ talk.speaker }}</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 class="text-muted">{{ presentation.event.local_start_time }}</small>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -35,11 +35,11 @@
|
|||
{% if user.weburl %}
|
||||
<p>Homepage: {{user.weburl}}</p>
|
||||
{% endif %}
|
||||
{% if user.talks %}
|
||||
{% if talks %}
|
||||
<h3>Talks</h3>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
{% for talk in user.talks %}
|
||||
{% for talk in talks %}
|
||||
<div class="mr-3 mb-3 col-md-5">
|
||||
<div class="card box-shadow" >
|
||||
<div class="card-body">
|
||||
|
|
|
@ -14,10 +14,10 @@ from events.models.events import (
|
|||
EventPhoto,
|
||||
Place,
|
||||
Attendee,
|
||||
Presentation,
|
||||
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,
|
||||
|
|
|
@ -6,16 +6,17 @@ 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.models.profiles import UserProfile
|
||||
from events.forms import (
|
||||
SpeakerBioForm,
|
||||
DeleteSpeakerForm,
|
||||
UserTalkForm,
|
||||
DeleteTalkForm,
|
||||
SchedulePresentationForm,
|
||||
)
|
||||
|
||||
from events.models.events import Event, Presentation
|
||||
|
||||
from events.models.events import Event
|
||||
from events.models.speakers import Speaker, Talk, Presentation, SpeakerRequest
|
||||
import datetime
|
||||
import simplejson
|
||||
|
||||
|
@ -46,7 +47,7 @@ def add_speaker(request):
|
|||
speaker_form = SpeakerBioForm(request.POST, request.FILES, instance=new_speaker)
|
||||
if speaker_form.is_valid():
|
||||
new_speaker = speaker_form.save()
|
||||
return redirect('show-talks')
|
||||
return redirect('user-talks')
|
||||
else:
|
||||
context = {
|
||||
'speaker': new_speaker,
|
||||
|
@ -78,10 +79,41 @@ def edit_speaker(request, speaker_id):
|
|||
return redirect('home')
|
||||
|
||||
def delete_speaker(request, speaker_id):
|
||||
pass
|
||||
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):
|
||||
pass
|
||||
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()
|
||||
|
@ -92,13 +124,24 @@ def add_talk(request):
|
|||
'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()
|
||||
return redirect('user-talks')
|
||||
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,
|
||||
|
@ -126,7 +169,7 @@ def edit_talk(request, talk_id):
|
|||
talk_form.fields['speaker'].queryset = request.user.profile.speaker_set
|
||||
if talk_form.is_valid():
|
||||
talk = talk_form.save()
|
||||
return redirect('user-talks')
|
||||
return redirect('show-talk', talk.id)
|
||||
else:
|
||||
context = {
|
||||
'talk': talk,
|
||||
|
|
|
@ -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