diff --git a/_1327/documents/views.py b/_1327/documents/views.py index c7aa87da..6ab46f22 100644 --- a/_1327/documents/views.py +++ b/_1327/documents/views.py @@ -213,7 +213,12 @@ def publish(request, title, next_state_id): raise PermissionDenied() document.publish(next_state_id) - messages.success(request, _("Minutes document has been published.")) + if isinstance(document, MinutesDocument): + messages.success(request, _("Minutes document has been published.")) + elif isinstance(document, Poll): + messages.success(request, _("Poll has been published.")) + else: + raise SuspiciousOperation return HttpResponseRedirect(reverse(document.get_view_url_name(), args=[document.url_title])) diff --git a/_1327/polls/forms.py b/_1327/polls/forms.py index 7cacb434..0c714004 100644 --- a/_1327/polls/forms.py +++ b/_1327/polls/forms.py @@ -14,7 +14,7 @@ class PollForm(DocumentForm): class Meta: model = Poll - fields = ['title_de', 'title_en', 'url_title', 'text_de', 'text_en', 'start_date', 'end_date', 'max_allowed_number_of_answers', 'show_results_immediately', 'comment', 'group', 'vote_groups'] + fields = ['title_de', 'title_en', 'url_title', 'text_de', 'text_en', 'start_date', 'end_date', 'max_allowed_number_of_answers', 'state', 'comment', 'group', 'vote_groups'] def __init__(self, *args, **kwargs): creation = kwargs.get('creation', None) diff --git a/_1327/polls/migrations/0005_auto_20200318_1928.py b/_1327/polls/migrations/0005_auto_20200318_1928.py new file mode 100644 index 00000000..c8ec81e6 --- /dev/null +++ b/_1327/polls/migrations/0005_auto_20200318_1928.py @@ -0,0 +1,22 @@ +# Generated by Django 2.2.11 on 2020-03-18 18:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('polls', '0004_auto_20200302_1915'), + ] + + operations = [ + migrations.RemoveField( + model_name='poll', + name='show_results_immediately', + ), + migrations.AddField( + model_name='poll', + name='state', + field=models.IntegerField(choices=[(0, 'Unpublished'), (1, 'Published After End of Poll'), (2, 'Published')], default=0, verbose_name='State'), + ), + ] diff --git a/_1327/polls/models.py b/_1327/polls/models.py index 747049b9..3d5e99c1 100644 --- a/_1327/polls/models.py +++ b/_1327/polls/models.py @@ -1,8 +1,13 @@ from datetime import date, datetime +from django.conf import settings +from django.contrib.auth.models import Group from django.contrib.contenttypes.models import ContentType +from django.core.exceptions import SuspiciousOperation from django.db import models from django.db.models import Sum +from django.db.models.signals import post_save +from django.dispatch import receiver from django.template import loader from django.urls import reverse from django.utils.translation import gettext_lazy as _ @@ -21,6 +26,15 @@ class Poll(Document): + UNPUBLISHED = 0 + PUBLISHED = 1 + # AFTER_END is marked differently so that it does not clash with the numeration scheme minutes + AFTER_END = 5 # TODO: keep this numeration scheme so that it doesn't conflict with the other options? + PUBLISH_CHOICES = ( + (UNPUBLISHED, _('Unpublished')), + (AFTER_END, _('Published After End of Poll')), + (PUBLISHED, _('Published')), + ) def can_be_changed_by(self, user): permission_name = self.edit_permission_name @@ -34,7 +48,8 @@ def can_be_reverted(self): end_date = models.DateField(default=datetime.now, verbose_name=_("End Date")) max_allowed_number_of_answers = models.PositiveIntegerField(default=1) participants = models.ManyToManyField(UserProfile, related_name="polls", blank=True) - show_results_immediately = models.BooleanField(default=True, verbose_name=_("show results immediately after vote")) + # show_results_immediately = models.BooleanField(default=True, verbose_name=_("show results immediately after vote")) + state = models.IntegerField(choices=PUBLISH_CHOICES, default=UNPUBLISHED, verbose_name=_("State")) VIEW_PERMISSION_NAME = POLL_VIEW_PERMISSION_NAME VOTE_PERMISSION_NAME = POLL_VOTE_PERMISSION_NAME @@ -131,6 +146,17 @@ def has_choice_descriptions(self): return True return False + def show_publish_button(self): + return not self.is_in_creation and self.state == Poll.UNPUBLISHED + + # TODO: publish button might be a bit ugly because it is close to permission button + def publish(self, next_state_id): + if next_state_id == Poll.PUBLISHED: + self.state = int(next_state_id) + self.save() + else: + raise SuspiciousOperation + revisions.register(Poll, follow=["document_ptr"]) diff --git a/_1327/polls/templatetags/poll_tags.py b/_1327/polls/templatetags/poll_tags.py index 990bebe9..4add2885 100644 --- a/_1327/polls/templatetags/poll_tags.py +++ b/_1327/polls/templatetags/poll_tags.py @@ -2,15 +2,24 @@ from django import template +from _1327.polls.models import Poll + register = template.Library() @register.filter def can_see_results(poll): - if not poll.show_results_immediately and poll.end_date >= datetime.date.today(): + if poll.state == Poll.UNPUBLISHED: return False - else: + elif poll.state == Poll.AFTER_END: + if poll.end_date >= datetime.date.today(): + return False + else: + return True + elif poll.state == Poll.PUBLISHED: return True + else: + return False # TODO: raise Exception? @register.filter diff --git a/_1327/polls/tests.py b/_1327/polls/tests.py index 0da10a91..2e989bcc 100644 --- a/_1327/polls/tests.py +++ b/_1327/polls/tests.py @@ -255,10 +255,12 @@ def test_edit_poll_delete_choice(self): def test_edit_poll_user_has_no_permission(self): user = baker.make(UserProfile) - response = self.app.get(reverse(self.poll.get_edit_url_name(), args=[self.poll.url_title]), user=user, expect_errors=True) + response = self.app.get(reverse(self.poll.get_edit_url_name(), args=[self.poll.url_title]), user=user, + expect_errors=True) self.assertEqual(response.status_code, 403) - response = self.app.post(reverse(self.poll.get_edit_url_name(), args=[self.poll.url_title]), user=user, expect_errors=True) + response = self.app.post(reverse(self.poll.get_edit_url_name(), args=[self.poll.url_title]), user=user, + expect_errors=True) self.assertEqual(response.status_code, 403) def test_deletion_no_superuser(self): @@ -296,7 +298,8 @@ def test_result_preview_non_superuser(self): user = baker.make(UserProfile) assign_perm(self.poll.vote_permission_name, user, self.poll) - response = self.app.get(reverse('polls:results_for_admin', args=[self.poll.url_title]), user=user, expect_errors=True) + response = self.app.get(reverse('polls:results_for_admin', args=[self.poll.url_title]), user=user, + expect_errors=True) self.assertEqual(response.status_code, 403) def test_result_preview_superuser(self): @@ -410,14 +413,16 @@ def test_view_before_poll_has_started(self): def test_view_poll_without_vote_permission(self): self.assign_view_perm(self.user, self.poll) - response = self.app.get(reverse(self.poll.get_view_url_name(), args=[self.poll.url_title]), user=self.user, expect_errors=True) + response = self.app.get(reverse(self.poll.get_view_url_name(), args=[self.poll.url_title]), user=self.user, + expect_errors=True) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'polls_results.html') def test_vote_poll_without_vote_permission(self): self.assign_view_perm(self.user, self.poll) - response = self.app.get(reverse(self.poll.get_view_url_name(), args=[self.poll.url_title]), user=self.user, expect_errors=True) + response = self.app.get(reverse(self.poll.get_view_url_name(), args=[self.poll.url_title]), user=self.user, + expect_errors=True) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'polls_results.html') @@ -456,7 +461,8 @@ def test_vote_with_insufficient_permissions(self): user = baker.make(UserProfile) assign_perm(Poll.VIEW_PERMISSION_NAME, user, self.poll) - response = self.app.get(reverse(self.poll.get_view_url_name(), args=[self.poll.url_title]), user=user, expect_errors=True) + response = self.app.get(reverse(self.poll.get_view_url_name(), args=[self.poll.url_title]), user=user, + expect_errors=True) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'polls_results.html') @@ -492,7 +498,8 @@ def test_start_vote_multiple_choice_poll(self): self.poll.max_allowed_number_of_answers = 2 self.poll.save() - response = self.app.get(reverse(self.poll.get_view_url_name(), args=[self.poll.url_title]) + '/', user=self.user) + response = self.app.get(reverse(self.poll.get_view_url_name(), args=[self.poll.url_title]) + '/', + user=self.user) self.assertEqual(response.status_code, 301) response = self.app.get(reverse(self.poll.get_view_url_name(), args=[self.poll.url_title]), user=self.user) @@ -525,7 +532,8 @@ def test_vote_single_choice_submitting_more_than_one_choice(self): data = [('choice', choice.id) for choice in self.poll.choices.all()] - response = self.app.post(reverse(self.poll.get_view_url_name(), args=[self.poll.url_title]), params=data, user=self.user) + response = self.app.post(reverse(self.poll.get_view_url_name(), args=[self.poll.url_title]), params=data, + user=self.user) self.assertRedirects(response, reverse(self.poll.get_view_url_name(), args=[self.poll.url_title])) def test_vote_single_choice_correctly(self): @@ -536,7 +544,8 @@ def test_vote_single_choice_correctly(self): data = [('choice', choice.id)] votes = choice.votes - response = self.app.post(reverse(self.poll.get_view_url_name(), args=[self.poll.url_title]), params=data, user=self.user) + response = self.app.post(reverse(self.poll.get_view_url_name(), args=[self.poll.url_title]), params=data, + user=self.user) self.assertEqual(response.status_code, 302) choice = self.poll.choices.first() @@ -555,7 +564,8 @@ def test_vote_multiple_choice_correctly(self): data.append(('choice', choice.id)) votes.append(choice.votes) - response = self.app.post(reverse(self.poll.get_view_url_name(), args=[self.poll.url_title]), params=data, user=self.user) + response = self.app.post(reverse(self.poll.get_view_url_name(), args=[self.poll.url_title]), params=data, + user=self.user) self.assertEqual(response.status_code, 302) for i, choice in enumerate(self.poll.choices.all()): self.assertEqual(choice.votes, votes[i] + 1) @@ -572,23 +582,54 @@ def test_view_before_poll_has_started(self): self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'polls_index.html') + # TODO def test_view_poll_before_end(self): self.poll.participants.add(self.user) - self.poll.show_results_immediately = True + self.poll.state = Poll.UNPUBLISHED + self.poll.save() + + response = self.app.get(reverse(self.poll.get_view_url_name(), args=[self.poll.url_title]), user=self.user) + self.assertRedirects(response, reverse('polls:index')) + + self.poll.state = Poll.AFTER_END + self.poll.save() + + response = self.app.get(reverse(self.poll.get_view_url_name(), args=[self.poll.url_title]), user=self.user) + self.assertRedirects(response, reverse('polls:index')) + + self.poll.state = Poll.PUBLISHED self.poll.save() response = self.app.get(reverse(self.poll.get_view_url_name(), args=[self.poll.url_title]), user=self.user) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'polls_results.html') - self.poll.show_results_immediately = False + # TODO + def test_view_poll_after_end(self): + self.poll.participants.add(self.user) + self.poll.state = Poll.UNPUBLISHED self.poll.save() response = self.app.get(reverse(self.poll.get_view_url_name(), args=[self.poll.url_title]), user=self.user) self.assertRedirects(response, reverse('polls:index')) + self.poll.state = Poll.AFTER_END + self.poll.save() + + response = self.app.get(reverse(self.poll.get_view_url_name(), args=[self.poll.url_title]), user=self.user) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'polls_results.html') + + self.poll.state = Poll.PUBLISHED + self.poll.save() + + response = self.app.get(reverse(self.poll.get_view_url_name(), args=[self.poll.url_title]), user=self.user) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'polls_results.html') + + # TODO def test_vote_poll_with_results_that_can_not_be_seen_immediately(self): - self.poll.show_results_immediately = False + self.poll.state = Poll.AFTER_END self.poll.save() response = self.app.get(reverse(self.poll.get_view_url_name(), args=[self.poll.url_title]), user=self.user) @@ -602,6 +643,8 @@ def test_vote_poll_with_results_that_can_not_be_seen_immediately(self): self.assertRedirects(response, reverse('polls:index')) +# TODO ? +# - check states when creating class PollEditTests(WebTest): csrf_checks = False @@ -646,7 +689,8 @@ def test_create_two_polls_without_changing_url_title(self): self.assertEqual(response.status_code, 200) def test_submit_with_too_few_forms(self): - response = self.app.get(reverse(self.poll.get_edit_url_name(), args=[self.poll.url_title]) + '/', user=self.user) + response = self.app.get(reverse(self.poll.get_edit_url_name(), args=[self.poll.url_title]) + '/', + user=self.user) self.assertEqual(response.status_code, 301) response = self.app.get(reverse(self.poll.get_edit_url_name(), args=[self.poll.url_title]), user=self.user) diff --git a/_1327/polls/views.py b/_1327/polls/views.py index 70551ee4..d718323c 100644 --- a/_1327/polls/views.py +++ b/_1327/polls/views.py @@ -52,7 +52,7 @@ def results(request, poll, url_title): and poll.end_date >= datetime.date.today(): return vote(request, poll, url_title) - if not poll.show_results_immediately and poll.end_date >= datetime.date.today(): + if poll.state == Poll.AFTER_END and poll.end_date >= datetime.date.today(): messages.info( request, _("You can not see the results of this poll right now. You have to wait until {} to see the results of this poll.").format( @@ -126,7 +126,7 @@ def vote(request, poll, url_title): poll.participants.add(request.user) messages.success(request, _("We've received your vote!")) - if not poll.show_results_immediately: + if poll.state == Poll.AFTER_END: messages.info(request, _("The results of this poll will be available as from {}").format((poll.end_date + datetime.timedelta(days=1)).strftime("%d. %B %Y"))) return HttpResponseRedirect(reverse('polls:index')) return HttpResponseRedirect(reverse(poll.get_view_url_name(), args=[url_title]))