diff --git a/proposals/admin.py b/proposals/admin.py index a6eb6c6..da03187 100644 --- a/proposals/admin.py +++ b/proposals/admin.py @@ -8,7 +8,7 @@ class TalkAdmin(admin.ModelAdmin): # (it is easy to obtain incoherent data due to site framework) def has_add_permission(self, request): return False - # Filter for 'on site' topics and event + # Filter for 'on site' topics, tracks and events def get_form(self, request, obj=None, **kwargs): form = super(TalkAdmin, self).get_form(request, obj, **kwargs) # in fact, obj should never be none as 'add' button is disabled @@ -18,8 +18,17 @@ class TalkAdmin(admin.ModelAdmin): form.base_fields['event'].queryset = Event.objects.filter(site=obj.site) return form + +class TopicAdmin(admin.ModelAdmin): + # Filter for 'on site' tracks + def get_form(self, request, obj=None, **kwargs): + form = super().get_form(request, obj, **kwargs) + if obj: + form.base_fields['track'].queryset = Track.objects.filter(site=obj.site) + return form + admin.site.register(Conference) -admin.site.register(Topic) +admin.site.register(Topic, TopicAdmin) admin.site.register(Track) admin.site.register(Talk, TalkAdmin) admin.site.register(Event) diff --git a/proposals/forms.py b/proposals/forms.py index dc49a6a..e905cc9 100644 --- a/proposals/forms.py +++ b/proposals/forms.py @@ -102,24 +102,22 @@ class SpeakerFilterForm(forms.Form): self.fields['topic'].choices = topics.values_list('slug', 'name') -class TopicCreateForm(forms.ModelForm): +class TopicForm(forms.ModelForm): def __init__(self, *args, **kwargs): - self.site_id = kwargs.pop('site_id') - super(TopicCreateForm, self).__init__(*args, **kwargs) + self.site = kwargs.pop('site') + super().__init__(*args, **kwargs) + self.fields['track'].queryset = Track.objects.filter(site=self.site) class Meta: model = Topic - fields = ['name', 'description', 'reviewers'] + fields = ['name', 'description', 'reviewers', 'track'] widgets = {'reviewers': Select2TagWidget()} def clean_name(self): name = self.cleaned_data['name'] - if name != self.instance.name and Topic.objects.filter(site__id=self.site_id, name=name).exists(): + if self.instance and name != self.instance.name and Topic.objects.filter(site=self.site, name=name).exists(): raise self.instance.unique_error_message(self._meta.model, ['name']) return name -TopicUpdateForm = modelform_factory(Topic, fields=['reviewers'], - widgets={'reviewers': Select2TagWidget()}) - ConferenceForm = modelform_factory(Conference, fields=['home']) diff --git a/proposals/migrations/0012_topic_track.py b/proposals/migrations/0012_topic_track.py new file mode 100644 index 0000000..431ff69 --- /dev/null +++ b/proposals/migrations/0012_topic_track.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.1 on 2016-09-22 09:46 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('proposals', '0011_auto_20160921_2236'), + ] + + operations = [ + migrations.AddField( + model_name='topic', + name='track', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='proposals.Track', verbose_name='Destination track'), + ), + ] diff --git a/proposals/models.py b/proposals/models.py index 830b3c1..d7cb8ec 100644 --- a/proposals/models.py +++ b/proposals/models.py @@ -25,26 +25,6 @@ class Conference(models.Model): return str(self.site) -class Topic(PonyConfModel): - - site = models.ForeignKey(Site, on_delete=models.CASCADE) - - name = models.CharField(max_length=128, verbose_name=_('Name')) - slug = AutoSlugField(populate_from='name', unique=True) - description = models.TextField(blank=True, verbose_name=_('Description')) - - reviewers = models.ManyToManyField(User, blank=True, verbose_name=_('Reviewers')) - - class Meta: - unique_together = ('site', 'name') - - def __str__(self): - return self.name - - def get_absolute_url(self): - return reverse('list-talks') + '?topic=%s' % self.slug - - class Track(PonyConfModel): site = models.ForeignKey(Site, on_delete=models.CASCADE) @@ -60,6 +40,27 @@ class Track(PonyConfModel): return self.name +class Topic(PonyConfModel): + + site = models.ForeignKey(Site, on_delete=models.CASCADE) + + name = models.CharField(max_length=128, verbose_name=_('Name')) + slug = AutoSlugField(populate_from='name', unique=True) + description = models.TextField(blank=True, verbose_name=_('Description')) + track = models.ForeignKey(Track, blank=True, null=True, verbose_name=_('Destination track')) + + reviewers = models.ManyToManyField(User, blank=True, verbose_name=_('Reviewers')) + + class Meta: + unique_together = ('site', 'name') + + def __str__(self): + return self.name + + def get_absolute_url(self): + return reverse('list-talks') + '?topic=%s' % self.slug + + class Event(models.Model): site = models.ForeignKey(Site, on_delete=models.CASCADE) diff --git a/proposals/templates/proposals/talk_detail.html b/proposals/templates/proposals/talk_detail.html index b40db2c..a0b58c7 100644 --- a/proposals/templates/proposals/talk_detail.html +++ b/proposals/templates/proposals/talk_detail.html @@ -48,7 +48,18 @@

{% trans "Track:" %}

-

{% if talk.track %}{{ talk.track }}{% else %}{% trans "No assigned yet." %}{% endif %}

+{% if talk.track %} +

{{ talk.track }}

+{% else %} +

{% trans "No assigned yet." %}

+{% for topic in talk.topics.distinct %} + {% if forloop.first %}

{% endif %} + {% if topic.track %} + {% trans "Assign to" %} {{ topic.track }} + {% endif %} + {% if forloop.last %}

{% endif %} +{% endfor %} +{% endif %} {% endif %} diff --git a/proposals/urls.py b/proposals/urls.py index a5361ad..13851d2 100644 --- a/proposals/urls.py +++ b/proposals/urls.py @@ -14,6 +14,7 @@ urlpatterns = [ url(r'^talk/details/(?P[-\w]+)$', views.TalkDetail.as_view(), name='show-talk'), url(r'^talk/accept/(?P[-\w]+)/$', views.talk_decide, {'accepted': True}, name='accept-talk'), url(r'^talk/decline/(?P[-\w]+)/$', views.talk_decide, {'accepted': False}, name='decline-talk'), + url(r'^talk/assign-to-track/(?P[-\w]+)/(?P[-\w]+)/$', views.talk_assign_to_track, name='assign-talk-to-track'), url(r'^topic/$', views.TopicList.as_view(), name='list-topics'), url(r'^topic/add/$', views.TopicCreate.as_view(), name='add-topic'), url(r'^topic/(?P[-\w]+)/edit/$', views.TopicUpdate.as_view(), name='edit-topic'), diff --git a/proposals/views.py b/proposals/views.py index 5641b0a..0adb7ce 100644 --- a/proposals/views.py +++ b/proposals/views.py @@ -21,8 +21,8 @@ from accounts.utils import is_staff from conversations.models import ConversationWithParticipant, ConversationAboutTalk, Message -from .forms import TalkForm, TopicCreateForm, TopicUpdateForm, ConferenceForm, TalkFilterForm, STATUS_VALUES, SpeakerFilterForm -from .models import Talk, Topic, Vote, Conference +from .forms import TalkForm, TopicForm, ConferenceForm, TalkFilterForm, STATUS_VALUES, SpeakerFilterForm +from .models import Talk, Track, Topic, Vote, Conference from .signals import talk_added, talk_edited from .utils import allowed_talks, markdown_to_html @@ -164,6 +164,17 @@ def talk_edit(request, talk=None): }) +@orga_required +def talk_assign_to_track(request, talk, track): + talk = get_object_or_404(Talk, slug=talk, site=get_current_site(request)) + track = get_object_or_404(Track, slug=track, site=get_current_site(request)) + talk.track = track + talk.save() + messages.success(request, _('Talk assigned to track successfully!')) + next_url = request.GET.get('next') or reverse('show-talk', kwargs={'slug': talk.slug}) + return redirect(next_url) + + class TalkDetail(LoginRequiredMixin, DetailView): def get_queryset(self): return Talk.objects.filter(site=get_current_site(self.request)).all() @@ -184,12 +195,17 @@ class TopicMixin(object): class TopicFormMixin(object): + form_class = TopicForm + def get_form_kwargs(self): - kwargs = super(TopicFormMixin, self).get_form_kwargs() - if self.get_form_class() == TopicCreateForm: - kwargs.update({'site_id': get_current_site(self.request).id}) + kwargs = super().get_form_kwargs() + kwargs.update({'site': get_current_site(self.request)}) return kwargs + def form_valid(self, form): + form.instance.site = get_current_site(self.request) + return super().form_valid(form) + class TopicList(LoginRequiredMixin, TopicMixin, ListView): pass @@ -197,16 +213,10 @@ class TopicList(LoginRequiredMixin, TopicMixin, ListView): class TopicCreate(OrgaRequiredMixin, TopicMixin, TopicFormMixin, CreateView): model = Topic - form_class = TopicCreateForm - - def form_valid(self, form): - form.instance.site = get_current_site(self.request) - return super().form_valid(form) class TopicUpdate(OrgaRequiredMixin, TopicMixin, TopicFormMixin, UpdateView): - def get_form_class(self): - return TopicCreateForm if self.request.user.is_superuser else TopicUpdateForm + pass @login_required