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:
parent
84e5f17cc9
commit
437134991d
12 changed files with 312 additions and 24 deletions
|
@ -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)
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
34
events/migrations/0026_add_user_city.py
Normal file
34
events/migrations/0026_add_user_city.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
# Generated by Django 2.0 on 2018-04-21 13:45
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('events', '0025_add_event_series'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='userprofile',
|
||||
name='city',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='events.City'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='eventseries',
|
||||
name='end_time',
|
||||
field=models.TimeField(db_index=True, help_text='Local time that the event ends', verbose_name='End Time'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='eventseries',
|
||||
name='start_time',
|
||||
field=models.TimeField(db_index=True, help_text='Local time that the event starts', verbose_name='Start Time'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='team',
|
||||
name='description',
|
||||
field=models.TextField(blank=True, null=True),
|
||||
),
|
||||
]
|
31
events/migrations/0027_add_category_topic_slug.py
Normal file
31
events/migrations/0027_add_category_topic_slug.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
# Generated by Django 2.0 on 2018-04-21 14:13
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('events', '0026_add_user_city'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='topic',
|
||||
name='slug',
|
||||
field=models.CharField(default='-', max_length=256),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='category',
|
||||
name='slug',
|
||||
field=models.CharField(default='-', max_length=256),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='userprofile',
|
||||
name='city',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='events.City', verbose_name='Home city'),
|
||||
),
|
||||
]
|
113
events/migrations/0028_add_speaker_models.py
Normal file
113
events/migrations/0028_add_speaker_models.py
Normal 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'),
|
||||
),
|
||||
]
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ class Country(models.Model):
|
|||
|
||||
class Meta:
|
||||
ordering = ('name',)
|
||||
verbose_name_plural = 'Countries'
|
||||
|
||||
def __str__(self):
|
||||
return u'%s' % (self.name)
|
||||
|
|
|
@ -10,6 +10,7 @@ from imagekit.processors import ResizeToFill
|
|||
|
||||
from .locale import *
|
||||
from .. import location
|
||||
from ..utils import slugify
|
||||
|
||||
import uuid
|
||||
import pytz
|
||||
|
@ -27,6 +28,7 @@ class UserProfile(models.Model):
|
|||
processors=[ResizeToFill(128, 128)],
|
||||
format='PNG',
|
||||
blank=True)
|
||||
city = models.ForeignKey(City, verbose_name=_('Home city'), null=True, blank=True, on_delete=models.CASCADE)
|
||||
|
||||
web_url = models.URLField(verbose_name=_('Website URL'), blank=True, null=True)
|
||||
twitter = models.CharField(verbose_name=_('Twitter Name'), max_length=32, blank=True, null=True)
|
||||
|
@ -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
21
events/utils.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
import re
|
||||
import unicodedata
|
||||
|
||||
SLUG_OK = '-_~'
|
||||
|
||||
def slugify(s, ok=SLUG_OK, lower=True, spaces=False):
|
||||
# L and N signify letter/number.
|
||||
# http://www.unicode.org/reports/tr44/tr44-4.html#GC_Values_Table
|
||||
rv = []
|
||||
s = re.sub('\s*&\s*', ' and ', s)
|
||||
for c in unicodedata.normalize('NFKC', s):
|
||||
cat = unicodedata.category(c)[0]
|
||||
if cat in 'LN' or c in ok:
|
||||
rv.append(c)
|
||||
if cat == 'Z': # space
|
||||
rv.append(' ')
|
||||
new = ''.join(rv).strip()
|
||||
if not spaces:
|
||||
new = re.sub('[-\s]+', '-', new)
|
||||
return new.lower() if lower else new
|
||||
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in a new issue