Add initial speaker/talk models, expand Category and Topic to they can be used for this, add a user's default City to their profile

This commit is contained in:
Michael Hall 2018-04-21 10:28:04 -04:00
parent 84e5f17cc9
commit 437134991d
12 changed files with 312 additions and 24 deletions

View file

@ -24,6 +24,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)
@ -101,12 +102,16 @@ 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)

View file

@ -262,15 +262,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:

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,113 @@
# Generated by Django 2.0 on 2018-04-21 14:26
from django.db import migrations, models
import django.db.models.deletion
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')),
('start_time', models.DateTimeField(db_index=True, verbose_name='Start Time')),
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='events.Event')),
],
),
migrations.CreateModel(
name='Speaker',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('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, to='events.Talk'),
),
]

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)
@ -391,4 +376,12 @@ 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):
event = models.ForeignKey(Event, on_delete=models.CASCADE)
talk = models.ForeignKey(Talk, on_delete=models.SET_NULL, blank=False, null=True)
start_time = models.DateTimeField(verbose_name=_('Start Time'), db_index=True)

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)
@ -275,17 +277,60 @@ 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)
class Speaker(models.Model):
user = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
bio = models.TextField(blank=True)
categories = models.ManyToManyField('Category', blank=True)
topics = models.ManyToManyField('Topic', blank=True)
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, 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)

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

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

@ -24,7 +24,7 @@
</div>
<div class="col-md-3">
{% if teams %}
<h4>Your Teams</h4>
<h4>Teams</h4>
<ul>
{% for t in teams %}
<li>
@ -33,6 +33,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

@ -72,6 +72,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