talks can be tagged
This commit is contained in:
parent
5e276d9c1a
commit
4e7ae4eca9
14
cfp/admin.py
14
cfp/admin.py
|
@ -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)
|
||||
|
|
14
cfp/forms.py
14
cfp/forms.py
|
@ -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):
|
||||
|
|
|
@ -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'),
|
||||
),
|
||||
]
|
|
@ -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
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue