talks can be tagged

This commit is contained in:
Élie Bouttier 2017-10-09 19:48:33 +02:00
parent 5e276d9c1a
commit 4e7ae4eca9
7 changed files with 101 additions and 12 deletions

View File

@ -2,7 +2,8 @@ from django.contrib import admin
from django.contrib.sites.models import Site
from .mixins import OnSiteAdminMixin
from .models import Conference, Participant, Talk, TalkCategory, Track, Vote, Volunteer, Activity
from .models import Conference, Participant, Talk, TalkCategory, Track, \
Vote, Volunteer, Activity, Tag
class ConferenceAdmin(OnSiteAdminMixin, admin.ModelAdmin):
@ -41,11 +42,7 @@ class VoteAdmin(admin.ModelAdmin):
return super().get_queryset(request).filter(talk__site=request.conference.site)
class VolunteerAdmin(OnSiteAdminMixin, admin.ModelAdmin):
pass
class ActivityAdmin(OnSiteAdminMixin, admin.ModelAdmin):
class OnSiteModelAdmin(OnSiteAdminMixin, admin.ModelAdmin):
pass
@ -54,5 +51,6 @@ admin.site.register(Participant, ParticipantAdmin)
admin.site.register(Talk, TalkAdmin)
admin.site.register(TalkCategory, TalkCategoryAdmin)
admin.site.register(Vote, VoteAdmin)
admin.site.register(Volunteer, VolunteerAdmin)
admin.site.register(Activity, ActivityAdmin)
admin.site.register(Tag, OnSiteModelAdmin)
admin.site.register(Volunteer, OnSiteModelAdmin)
admin.site.register(Activity, OnSiteModelAdmin)

View File

@ -9,7 +9,7 @@ from django.utils.crypto import get_random_string
from django_select2.forms import ModelSelect2MultipleWidget
from .models import Participant, Talk, TalkCategory, Track, Conference, Room, Volunteer
from .models import Participant, Talk, TalkCategory, Track, Tag, Conference, Room, Volunteer
ACCEPTATION_CHOICES = [
@ -61,7 +61,10 @@ class TalkStaffForm(forms.ModelForm):
self.fields['duration'].help_text = _('Default duration: %(duration)d min') % {'duration': self.instance.duration}
class Meta(TalkForm.Meta):
fields = ('category', 'track', 'title', 'description', 'notes', 'start_date', 'duration', 'room', 'materials', 'video',)
fields = ('category', 'track', 'title', 'description', 'notes', 'tags', 'start_date', 'duration', 'room', 'materials', 'video',)
widgets = {
'tags': forms.CheckboxSelectMultiple,
}
labels = {
'category': _('Category'),
'title': _('Title'),
@ -98,6 +101,12 @@ class TalkFilterForm(forms.Form):
widget=forms.CheckboxSelectMultiple,
choices=[],
)
tag = forms.MultipleChoiceField(
label=_('Tag'),
required=False,
widget=forms.CheckboxSelectMultiple,
choices=[],
)
vote = forms.NullBooleanField(
label=_('Vote'),
help_text=_('Filter talks you already / not yet voted for'),
@ -126,6 +135,7 @@ class TalkFilterForm(forms.Form):
self.fields['category'].choices = categories.values_list('pk', 'name')
tracks = Track.objects.filter(site=site)
self.fields['track'].choices = [('none', _('Not assigned'))] + list(tracks.values_list('slug', 'name'))
self.fields['tag'].choices = Tag.objects.filter(site=site).values_list('slug', 'name')
class TalkActionForm(forms.Form):

View File

@ -0,0 +1,33 @@
from __future__ import unicode_literals
import autoslug.fields
import colorful.fields
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('sites', '0002_alter_domain_unique'),
('cfp', '0012_talk_confirmed'),
]
operations = [
migrations.CreateModel(
name='Tag',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=256, verbose_name='Name')),
('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='name')),
('color', colorful.fields.RGBColorField(default='#ffffff', verbose_name='Color')),
('inverted', models.BooleanField(default=False)),
('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sites.Site')),
],
),
migrations.AddField(
model_name='talk',
name='tags',
field=models.ManyToManyField(to='cfp.Tag'),
),
]

View File

@ -8,6 +8,8 @@ from django.db.models import Q, Count, Avg, Case, When
from django.db.models.functions import Coalesce
from django.utils import timezone
from django.utils.translation import ugettext, ugettext_lazy as _
from django.utils.safestring import mark_safe
from django.utils.html import escape, format_html
from autoslug import AutoSlugField
from colorful.fields import RGBColorField
@ -183,6 +185,39 @@ class Room(models.Model):
return self.talks.filter(Q(start_date__isnull=True) | Q(duration=0, category__duration=0)).all()
class Tag(models.Model):
site = models.ForeignKey(Site, on_delete=models.CASCADE)
name = models.CharField(max_length=256, verbose_name=_('Name'))
slug = AutoSlugField(populate_from='name')
color = RGBColorField(default='#ffffff', verbose_name=_("Color"))
inverted = models.BooleanField(default=False)
@property
def link(self):
return format_html('<a href="{url}?tag={tag}">{content}</a>', **{
'url': reverse('talk-list'),
'tag': self.slug,
'content': self.label,
})
@property
def label(self):
return format_html('<span class="label" style="{style}">{name}</span>', **{
'style': self.style,
'name': self.name,
})
@property
def style(self):
return mark_safe('background-color: {bg}; color: {fg}; vertical-align: middle;'.format(**{
'fg': '#fff' if self.inverted else '#000',
'bg': self.color,
}))
def __str__(self):
return self.name
class TalkCategory(models.Model): # type of talk (conf 30min, 1h, stand, …)
site = models.ForeignKey(Site, on_delete=models.CASCADE)
name = models.CharField(max_length=64)
@ -266,6 +301,7 @@ class Talk(PonyConfModel):
description = models.TextField(verbose_name=_('Description of your talk'),
help_text=_('This field is only visible by organizers.'))
track = models.ForeignKey(Track, blank=True, null=True, verbose_name=_('Track'))
tags = models.ManyToManyField(Tag)
notes = models.TextField(blank=True, verbose_name=_('Message to organizers'),
help_text=_('If you have any constraint or if you have anything that may '
'help you to select your talk, like a video or slides of your'
@ -330,6 +366,9 @@ class Talk(PonyConfModel):
else:
return 'warning'
def get_tags_html(self):
return mark_safe(' '.join(map(lambda tag: tag.link, self.tags.all())))
@property
def estimated_duration(self):
return self.duration or self.category.duration

View File

@ -22,9 +22,12 @@
<dd>{% if talk.track %}
<a href="{{ talk.track.get_absolute_url }}">{{ talk.track }}</a>
{% else %}
<em>{% trans "No assigned yet." context "session" %}</em>
<em>{% trans "not defined" context "session" %}</em>
{% endif %}</dd>
<dt>{% trans "Tags" %}</dt>
<dd>{% if talk.tags.exist %}{{ talk.get_tags_html }}{% else %}<em>{% trans "none" context "tag" %}</em>{% endif %}</dd>
<dt>{% trans "Timeslot" %}</dt>
<dd>{% if talk.start_date %}
<span class="date">{{ talk.start_date|date:"l d b" }}</span>,

View File

@ -24,6 +24,7 @@
{% bootstrap_field filter_form.video layout="horizontal" %}
</div>
<div class="col-md-6 col-xs-6">
{% bootstrap_field filter_form.tag layout="horizontal" %}
{% bootstrap_field filter_form.track layout="horizontal" %}
</div>
</div>
@ -43,6 +44,7 @@
<th class="text-center">{% trans "Intervention kind" %} <a href="?{{ sort_urls.category }}"><span class="glyphicon glyphicon-{{ sort_glyphicons.category }} pull-right"></span></a></th>
<th class="text-center">{% trans "Speakers" %}</th>
<th class="text-center">{% trans "Track" %}</th>
<th class="text-center">{% trans "Tags" %}</th>
<th class="text-center">{% trans "Status" %} <a href="?{{ sort_urls.status }}"><span class="glyphicon glyphicon-{{ sort_glyphicons.status }} pull-right"></span></a></th>
</tr>
</thead>
@ -62,6 +64,7 @@
{% endfor %}
</td>
<td>{{ talk.track|default:"" }}</td>
<td>{{ talk.get_tags_html }}</td>
<td>
{{ talk.get_status_str }}
</td>

View File

@ -247,6 +247,9 @@ def talk_list(request):
if data['scheduled'] != None:
show_filters = True
talks = talks.filter(start_date__isnull=not data['scheduled'])
if len(data['tag']):
show_filters = True
talks = talks.filter(tags__slug__in=data['tag'])
if len(data['track']):
show_filters = True
q = Q()
@ -323,7 +326,7 @@ def talk_list(request):
glyphicon = 'sort'
sort_urls[c] = url.urlencode()
sort_glyphicons[c] = glyphicon
talks = talks.prefetch_related('category', 'speakers', 'track')
talks = talks.prefetch_related('category', 'speakers', 'track', 'tags')
return render(request, 'cfp/staff/talk_list.html', {
'show_filters': show_filters,
'talk_list': talks,