From 64cef6a9c8b65f950126c1e1da353785e6bfe703 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Sun, 8 Oct 2023 12:32:47 +0200 Subject: [PATCH] Allow pending talks to be public, by TalkType Add a new flag to TalkType to control whether pending talks are publicly visible. Closes: #252 --- docs/talks.rst | 5 ++++ wafer/talks/admin.py | 7 ++++-- .../0023_talktype_show_pending_submissions.py | 23 ++++++++++++++++++ wafer/talks/models.py | 8 ++++++- wafer/talks/tests/fixtures.py | 4 ++-- wafer/talks/tests/test_views.py | 16 +++++++++++++ wafer/talks/views.py | 24 +++++++++++++------ wafer/users/views.py | 11 +++++++-- 8 files changed, 84 insertions(+), 14 deletions(-) create mode 100644 wafer/talks/migrations/0023_talktype_show_pending_submissions.py diff --git a/docs/talks.rst b/docs/talks.rst index ca05cba5..0ddbe207 100644 --- a/docs/talks.rst +++ b/docs/talks.rst @@ -113,6 +113,11 @@ If for some reason, an ``Accepted`` talk cannot be given, it can be marked as ``Cancelled``. ``Cancelled`` talks are still public, so that cancelled a scheduled talk does not invalidate the schedule. +If the talk has a type that has ``show_pending_submissions`` enabled, +then it will be public immediately after submission, in any of the +``Submitted``, ``Under Consideration``, and ``Provisionally Accepted`` +states. + Talk tracks =========== diff --git a/wafer/talks/admin.py b/wafer/talks/admin.py index 8e4c9bfb..d534e3ae 100644 --- a/wafer/talks/admin.py +++ b/wafer/talks/admin.py @@ -157,9 +157,12 @@ def review_score(self, obj): class TalkTypeAdmin(VersionAdmin): - list_display = ('name', 'order', 'disable_submission', 'css_class', 'submission_deadline', 'accept_late_submissions') + list_display = ('name', 'order', 'disable_submission', 'css_class', + 'submission_deadline', 'accept_late_submissions', + 'show_pending_submissions') readonly_fields = ('css_class',) - list_editable = ('submission_deadline', 'accept_late_submissions') + list_editable = ('submission_deadline', 'accept_late_submissions', + 'show_pending_submissions') class TrackAdmin(VersionAdmin): diff --git a/wafer/talks/migrations/0023_talktype_show_pending_submissions.py b/wafer/talks/migrations/0023_talktype_show_pending_submissions.py new file mode 100644 index 00000000..82c36af1 --- /dev/null +++ b/wafer/talks/migrations/0023_talktype_show_pending_submissions.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.6 on 2023-10-08 10:31 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("talks", "0022_talk_language"), + ] + + operations = [ + migrations.AddField( + model_name="talktype", + name="show_pending_submissions", + field=models.BooleanField( + default=False, + help_text="Whether to publicly show pending submissions, before acceptance", + verbose_name="show pending submissions", + ), + ), + ] diff --git a/wafer/talks/models.py b/wafer/talks/models.py index a020a531..235f19c8 100644 --- a/wafer/talks/models.py +++ b/wafer/talks/models.py @@ -85,12 +85,16 @@ class TalkType(models.Model): default=False, help_text=_("Whether submissions after the deadline should be accepted") ) - show_speakers = models.BooleanField( _('Show authors in speakers list'), default=True, help_text=_("Whether to show the authors for this talk type in the speakers list") ) + show_pending_submissions = models.BooleanField( + _('show pending submissions'), + default=False, + help_text=_("Whether to publicly show pending submissions, before acceptance") + ) objects = TalkTypeManager() @@ -343,6 +347,8 @@ def can_view(self, user): return True if self.accepted or self.cancelled: return True + if self.talk_type and self.talk_type.show_pending_submissions: + return True return False @classmethod diff --git a/wafer/talks/tests/fixtures.py b/wafer/talks/tests/fixtures.py index b402afa2..01235db1 100644 --- a/wafer/talks/tests/fixtures.py +++ b/wafer/talks/tests/fixtures.py @@ -2,9 +2,9 @@ from wafer.tests.utils import create_user -def create_talk_type(name): +def create_talk_type(name, **kwargs): """Create a talk type""" - return TalkType.objects.create(name=name) + return TalkType.objects.create(name=name, **kwargs) def create_talk(title, status, username=None, user=None, talk_type=None): diff --git a/wafer/talks/tests/test_views.py b/wafer/talks/tests/test_views.py index 698fa7b3..467f0474 100644 --- a/wafer/talks/tests/test_views.py +++ b/wafer/talks/tests/test_views.py @@ -54,11 +54,18 @@ def test_user_with_view_all(self): class TalkViewTests(TestCase): def setUp(self): + public_type = create_talk_type(name="BoF", show_pending_submissions=True) self.talk_a = create_talk("Talk A", ACCEPTED, "author_a") self.talk_r = create_talk("Talk R", REJECTED, "author_r") self.talk_s = create_talk("Talk S", SUBMITTED, "author_s") + self.talk_sp = create_talk("Talk SP", SUBMITTED, "author_sp", + talk_type=public_type) self.talk_u = create_talk("Talk U", UNDER_CONSIDERATION, "author_u") + self.talk_up = create_talk("Talk UP", UNDER_CONSIDERATION, "author_up", + talk_type=public_type) self.talk_p = create_talk("Talk P", PROVISIONAL, "author_p") + self.talk_pp = create_talk("Talk PP", PROVISIONAL, "author_pp", + talk_type=public_type) self.talk_c = create_talk("Talk C", CANCELLED, "author_c") self.client = Client() @@ -77,15 +84,24 @@ def test_view_rejected_not_logged_in(self): def test_view_cancelled_not_logged_in(self): self.check_talk_view(self.talk_c, 200) + def test_view_public_submitted_not_logged_in(self): + self.check_talk_view(self.talk_sp, 200) + def test_view_submitted_not_logged_in(self): self.check_talk_view(self.talk_s, 403) def test_view_consideration_not_logged_in(self): self.check_talk_view(self.talk_u, 403) + def test_view_public_consideration_not_logged_in(self): + self.check_talk_view(self.talk_up, 200) + def test_view_provisional_not_logged_in(self): self.check_talk_view(self.talk_p, 403) + def test_view_public_provisional_not_logged_in(self): + self.check_talk_view(self.talk_pp, 200) + def test_view_accepted_author(self): self.check_talk_view(self.talk_a, 200, auth={ 'username': 'author_a', 'password': 'author_a_password', diff --git a/wafer/talks/views.py b/wafer/talks/views.py index 70fd29f1..ffc9640a 100644 --- a/wafer/talks/views.py +++ b/wafer/talks/views.py @@ -21,7 +21,8 @@ from wafer.talks.models import ( Review, Talk, TalkType, TalkUrl, Track, - ACCEPTED, CANCELLED, SUBMITTED, UNDER_CONSIDERATION, WITHDRAWN) + ACCEPTED, CANCELLED, PROVISIONAL, SUBMITTED, UNDER_CONSIDERATION, + WITHDRAWN) from wafer.talks.forms import ReviewForm, get_talk_form_class from wafer.talks.serializers import TalkSerializer, TalkUrlSerializer from wafer.users.models import UserProfile @@ -51,7 +52,11 @@ def get_queryset(self): if self.request and Talk.can_view_all(self.request.user): talks = Talk.objects.all() else: - talks = Talk.objects.filter(Q(status=ACCEPTED) | Q(status=CANCELLED)) + talks = Talk.objects.filter( + Q(status__in=(ACCEPTED, CANCELLED)) + | Q(status__in=(SUBMITTED, UNDER_CONSIDERATION, PROVISIONAL), + talk_type__show_pending_submissions=True) + ) return talks.prefetch_related( "talk_type", "corresponding_author", "authors", "authors__userprofile" ) @@ -313,8 +318,11 @@ def get_queryset(self): # to people who aren't part of the management group if self.request.user.id is None: # Anonymous user, so just accepted or cancelled talks - return Talk.objects.filter(Q(status=ACCEPTED) | - Q(status=CANCELLED)) + return Talk.objects.filter( + Q(status__in=(ACCEPTED, CANCELLED)) + | Q(status__in=(SUBMITTED, UNDER_CONSIDERATION, PROVISIONAL), + talk_type__show_pending_submissions=True) + ) elif Talk.can_view_all(self.request.user): return Talk.objects.all() else: @@ -322,9 +330,11 @@ def get_queryset(self): # XXX: Should this be all authors rather than just # the corresponding author? return Talk.objects.filter( - Q(status=ACCEPTED) | - Q(status=CANCELLED) | - Q(corresponding_author=self.request.user)) + Q(status__in=(ACCEPTED, CANCELLED)) + | Q(status__in=(SUBMITTED, UNDER_CONSIDERATION, PROVISIONAL), + talk_type__show_pending_submissions=True) + | Q(corresponding_author=self.request.user) + ) class TalkExistsPermission(BasePermission): diff --git a/wafer/users/views.py b/wafer/users/views.py index 08f71d5c..ec8523ac 100644 --- a/wafer/users/views.py +++ b/wafer/users/views.py @@ -4,6 +4,7 @@ from django.contrib.auth import get_user_model from django.contrib.auth.models import AnonymousUser, Group from django.core.exceptions import PermissionDenied +from django.db.models import Q from django.http import Http404 from django.urls import reverse from django.views.generic import UpdateView @@ -12,7 +13,8 @@ from rest_framework import viewsets from rest_framework.permissions import IsAdminUser -from wafer.talks.models import ACCEPTED, CANCELLED +from wafer.talks.models import ( + ACCEPTED, CANCELLED, PROVISIONAL, SUBMITTED, UNDER_CONSIDERATION) from wafer.users.forms import UserForm, UserProfileForm from wafer.users.serializers import UserSerializer from wafer.users.models import UserProfile, PROFILE_GROUP @@ -30,7 +32,12 @@ class UsersView(PaginatedBuildableListView): def get_queryset(self, *args, **kwargs): qs = super().get_queryset(*args, **kwargs) if not settings.WAFER_PUBLIC_ATTENDEE_LIST: - qs = qs.filter(talks__status__in=(ACCEPTED, CANCELLED)).distinct() + qs = qs.filter( + Q(talks__status__in=(ACCEPTED, CANCELLED)) + | Q(talks__status__in=(SUBMITTED, UNDER_CONSIDERATION, + PROVISIONAL), + talks__talk_type__show_pending_submissions=True) + ).distinct() qs = qs.order_by('first_name', 'last_name', 'username') return qs