Skip to content
Snippets Groups Projects
Commit fdb2e577 authored by Felix Blanke's avatar Felix Blanke
Browse files

Feature: allow explicit 'publishing' of preference poll

parent 8d6f5b3e
No related branches found
No related tags found
No related merge requests found
......@@ -64,6 +64,7 @@
</div>
</a>
{% if 'AKPreferencePoll'|check_app_installed and event.active %}
{% if not event.poll_hidden or user.is_staff %}
<a class="dashboard-box btn btn-primary"
href="{% url 'poll:poll' event_slug=event.slug %}">
<div class="col-sm-12 col-md-3 col-lg-2 dashboard-button">
......@@ -72,6 +73,7 @@
</div>
</a>
{% endif %}
{% endif %}
{% for button in event.dashboardbutton_set.all %}
<a class="dashboard-box btn btn-{{ button.get_color_display }}"
href="{{ button.url }}">
......
......@@ -29,6 +29,7 @@ class DashboardTests(TestCase):
end=now(),
active=True,
plan_hidden=False,
poll_hidden=False,
)
cls.default_category = AKCategory.objects.create(
name="Test Category",
......@@ -146,6 +147,26 @@ class DashboardTests(TestCase):
self.assertContains(response, "Current AKs")
self.assertContains(response, "AK Wall")
def test_poll_hidden(self):
"""
Test visibility of poll buttons with regard to poll visibility status for a given event
"""
url_event_dashboard = reverse('dashboard:dashboard_event', kwargs={"slug": self.event.slug})
if apps.is_installed('AKPreferencePoll'):
# Poll hidden? No buttons should show up
self.event.poll_hidden = True
self.event.save()
response = self.client.get(url_event_dashboard)
self.assertNotContains(response, "AK Preferences")
# Poll not hidden?
# Buttons to preference poll should be on the page
self.event.poll_hidden = False
self.event.save()
response = self.client.get(url_event_dashboard)
self.assertContains(response, "AK Preferences")
def test_dashboard_buttons(self):
"""
Make sure manually added buttons are displayed correctly
......
......@@ -51,12 +51,16 @@ class EventAdmin(admin.ModelAdmin):
wizard.
"""
model = Event
list_display = ['name', 'status_url', 'place', 'start', 'end', 'active', 'plan_hidden']
list_display = ['name', 'status_url', 'place', 'start', 'end', 'active', 'plan_hidden', 'poll_hidden']
list_filter = ['active']
list_editable = ['active']
ordering = ['-start']
readonly_fields = ['status_url', 'plan_hidden', 'plan_published_at', 'toggle_plan_visibility']
actions = ['publish', 'unpublish']
readonly_fields = [
'status_url',
'plan_hidden', 'plan_published_at', 'toggle_plan_visibility',
'poll_hidden', 'poll_published_at', 'toggle_poll_visibility',
]
actions = ['publish_plan', 'unpublish_plan', 'publish_poll', 'unpublish_poll']
def add_view(self, request, form_url='', extra_context=None):
# Override
......@@ -115,13 +119,31 @@ class EventAdmin(admin.ModelAdmin):
text = _('Unpublish plan')
return format_html("<a href='{url}'>{text}</a>", url=url, text=text)
@display(description=_("Toggle poll visibility"))
def toggle_poll_visibility(self, obj):
"""
Define a read-only field to toggle the visibility of the preference poll of this event
This will choose from two different link targets/views depending on the current visibility status
:param obj: event to change the visibility of the plan for
:return: toggling link (HTML)
:rtype: str
"""
if obj.poll_hidden:
url = f"{reverse_lazy('admin:poll-publish')}?pks={obj.pk}"
text = _('Publish preference poll')
else:
url = f"{reverse_lazy('admin:poll-unpublish')}?pks={obj.pk}"
text = _('Unpublish preference poll')
return format_html("<a href='{url}'>{text}</a>", url=url, text=text)
def get_form(self, request, obj=None, change=False, **kwargs):
# Override (update) form rendering to make sure the timezone of the event is used
timezone.activate(obj.timezone)
return super().get_form(request, obj, change, **kwargs)
@action(description=_('Publish plan'))
def publish(self, request, queryset):
def publish_plan(self, request, queryset):
"""
Admin action to publish the plan
"""
......@@ -129,7 +151,7 @@ class EventAdmin(admin.ModelAdmin):
return HttpResponseRedirect(f"{reverse_lazy('admin:plan-publish')}?pks={','.join(str(pk) for pk in selected)}")
@action(description=_('Unpublish plan'))
def unpublish(self, request, queryset):
def unpublish_plan(self, request, queryset):
"""
Admin action to hide the plan
"""
......@@ -137,6 +159,23 @@ class EventAdmin(admin.ModelAdmin):
return HttpResponseRedirect(
f"{reverse_lazy('admin:plan-unpublish')}?pks={','.join(str(pk) for pk in selected)}")
@action(description=_('Publish preference poll'))
def publish_poll(self, request, queryset):
"""
Admin action to publish the preference poll
"""
selected = queryset.values_list('pk', flat=True)
return HttpResponseRedirect(f"{reverse_lazy('admin:poll-publish')}?pks={','.join(str(pk) for pk in selected)}")
@action(description=_('Unpublish preference poll'))
def unpublish_poll(self, request, queryset):
"""
Admin action to hide the preference poll
"""
selected = queryset.values_list('pk', flat=True)
return HttpResponseRedirect(
f"{reverse_lazy('admin:poll-unpublish')}?pks={','.join(str(pk) for pk in selected)}")
class PrepopulateWithNextActiveEventMixin:
"""
......
......@@ -38,9 +38,10 @@ class NewEventWizardStartForm(forms.ModelForm):
"""
class Meta:
model = Event
fields = ['name', 'slug', 'timezone', 'plan_hidden']
fields = ['name', 'slug', 'timezone', 'plan_hidden', 'poll_hidden']
widgets = {
'plan_hidden': forms.HiddenInput(),
'poll_hidden': forms.HiddenInput(),
}
# Special hidden field for wizard state handling
......@@ -57,7 +58,7 @@ class NewEventWizardSettingsForm(forms.ModelForm):
class Meta:
model = Event
fields = "__all__"
exclude = ['plan_published_at']
exclude = ['plan_published_at', 'poll_published_at']
widgets = {
'name': forms.HiddenInput(),
'slug': forms.HiddenInput(),
......@@ -69,6 +70,7 @@ class NewEventWizardSettingsForm(forms.ModelForm):
'interest_end': DateTimeInput(),
'reso_deadline': DateTimeInput(),
'plan_hidden': forms.HiddenInput(),
'poll_hidden': forms.HiddenInput(),
}
......
# Generated by Django 5.1.6 on 2025-04-04 11:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("AKModel", "0069_alter_akpreference_preference"),
]
operations = [
migrations.AddField(
model_name="event",
name="poll_hidden",
field=models.BooleanField(
default=True,
help_text="Hides preference poll for non-staff users",
verbose_name="Poll Hidden",
),
),
migrations.AddField(
model_name="event",
name="poll_published_at",
field=models.DateTimeField(
blank=True,
help_text="Timestamp at which the preference poll was published",
null=True,
verbose_name="Poll published at",
),
),
]
......@@ -158,6 +158,12 @@ class Event(models.Model):
plan_published_at = models.DateTimeField(verbose_name=_('Plan published at'), blank=True, null=True,
help_text=_('Timestamp at which the plan was published'))
poll_hidden = models.BooleanField(verbose_name=_('Poll Hidden'),
help_text=_('Hides preference poll for non-staff users'),
default=True)
poll_published_at = models.DateTimeField(verbose_name=_('Poll published at'), blank=True, null=True,
help_text=_('Timestamp at which the preference poll was published'))
base_url = models.URLField(verbose_name=_("Base URL"), help_text=_("Prefix for wiki link construction"), blank=True)
wiki_export_template_name = models.CharField(verbose_name=_("Wiki Export Template Name"), blank=True, max_length=50)
default_slot = models.DecimalField(max_digits=4, decimal_places=2, default=2, verbose_name=_('Default Slot Length'),
......
......@@ -4,8 +4,8 @@ from django.urls import include, path
from rest_framework.routers import DefaultRouter
import AKModel.views.api
from AKModel.views.manage import ExportSlidesView, PlanPublishView, PlanUnpublishView, DefaultSlotEditorView, \
AKsByUserView, AKScheduleJSONImportView
from AKModel.views.manage import ExportSlidesView, PlanPublishView, PlanUnpublishView, PollPublishView, PollUnpublishView, \
DefaultSlotEditorView, AKsByUserView, AKScheduleJSONImportView
from AKModel.views.ak import AKRequirementOverview, AKCSVExportView, AKJSONExportView, AKWikiExportView, \
AKMessageDeleteView
from AKModel.views.event_wizard import NewEventWizardStartView, NewEventWizardPrepareImportView, \
......@@ -108,6 +108,8 @@ def get_admin_urls_event(admin_site):
path('<slug:event_slug>/ak-slide-export/', admin_site.admin_view(ExportSlidesView.as_view()), name="ak_slide_export"),
path('plan/publish/', admin_site.admin_view(PlanPublishView.as_view()), name="plan-publish"),
path('plan/unpublish/', admin_site.admin_view(PlanUnpublishView.as_view()), name="plan-unpublish"),
path('poll/publish/', admin_site.admin_view(PollPublishView.as_view()), name="poll-publish"),
path('poll/unpublish/', admin_site.admin_view(PollUnpublishView.as_view()), name="poll-unpublish"),
path('<slug:event_slug>/defaultSlots/', admin_site.admin_view(DefaultSlotEditorView.as_view()),
name="default-slots-editor"),
path('<slug:event_slug>/importRooms/', admin_site.admin_view(RoomBatchCreationView.as_view()),
......
......@@ -159,6 +159,31 @@ class PlanUnpublishView(IntermediateAdminActionView):
self.entities.update(plan_published_at=None, plan_hidden=True)
class PollPublishView(IntermediateAdminActionView):
"""
Admin action view: Publish the preference poll of one or multitple event(s)
"""
title = _('Publish preference poll')
model = Event
confirmation_message = _('Publish the plan(s) of:')
success_message = _('Preference poll published')
def action(self, form):
self.entities.update(poll_published_at=Now(), poll_hidden=False)
class PollUnpublishView(IntermediateAdminActionView):
"""
Admin action view: Unpublish the preference poll of one or multitple event(s)
"""
title = _('Unpublish preference poll')
model = Event
confirmation_message = _('Unpublish the preference poll(s) of:')
success_message = _('Preference poll unpublished')
def action(self, form):
self.entities.update(poll_published_at=None, poll_hidden=True)
class DefaultSlotEditorView(EventSlugMixin, IntermediateAdminView):
"""
Admin view: Allow to edit the default slots of an event
......
from django.urls import reverse
from django.test import TestCase
from AKModel.models import Event
from AKModel.tests.test_views import BasicViewTests
class PollViewTests(BasicViewTests, TestCase):
"""
Tests for AKPreferencePoll
"""
fixtures = ['model.json']
APP_NAME = 'poll'
def test_poll_redirect(self):
"""
Test: Make sure that user is redirected from poll to dashboard when poll is hidden
"""
event = Event.objects.get(slug='kif42')
_, url_poll = self._name_and_url(('poll', {'event_slug': event.slug}))
url_dashboard = reverse("dashboard:dashboard_event", kwargs={"slug": event.slug})
event.poll_hidden = True
event.save()
self.client.logout()
response = self.client.get(url_poll)
self.assertRedirects(response, url_dashboard,
msg_prefix=f"Redirect away from poll not working ({url_poll} -> {url_dashboard})")
self.client.force_login(self.staff_user)
response = self.client.get(url_poll)
self.assertEqual(
response.status_code,
200,
msg=f"{url_poll} broken",
)
self.assertTemplateUsed(response, "AKPreferencePoll/poll.html", msg_prefix="Poll is not visible for staff user")
from django import forms
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from django.http import HttpResponseRedirect
from django.shortcuts import redirect
from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _
from django.views.generic import FormView
......@@ -39,6 +39,13 @@ class PreferencePollCreateView(EventSlugMixin, SuccessMessageMixin, FormView):
extra=0,
)
def get(self, request, *args, **kwargs):
s = super().get(request, *args, **kwargs)
# Don't show preference form when event is not active or poll is hidden -> redirect to dashboard
if not self.event.active or (self.event.poll_hidden and not request.user.is_staff):
return redirect(self.get_success_url())
return s
def get_success_url(self):
return reverse_lazy(
"dashboard:dashboard_event", kwargs={"slug": self.event.slug}
......@@ -96,4 +103,4 @@ class PreferencePollCreateView(EventSlugMixin, SuccessMessageMixin, FormView):
success_message = self.get_success_message(participant_form.cleaned_data)
if success_message:
messages.success(self.request, success_message)
return HttpResponseRedirect(self.get_success_url())
return redirect(self.get_success_url())
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment