diff --git a/CHANGELOG b/CHANGELOG index 2b4ce95e3..29d9f02e3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -22,6 +22,7 @@ Other: - Changed api for main menu entries. - Enhanced http error pages. - Moved dashboard and select widgets view from projector to core app. +- Created a poll description field for each assignment-poll. Version 1.5.1 (unreleased) diff --git a/openslides/assignment/forms.py b/openslides/assignment/forms.py index a2812216f..8787c7a5a 100644 --- a/openslides/assignment/forms.py +++ b/openslides/assignment/forms.py @@ -9,7 +9,7 @@ from openslides.utils.person import PersonFormField from .models import Assignment -class AssignmentForm(forms.ModelForm, CssClassMixin): +class AssignmentForm(CssClassMixin, forms.ModelForm): posts = forms.IntegerField( min_value=1, initial=1, label=ugettext_lazy("Number of available posts")) @@ -18,8 +18,7 @@ class AssignmentForm(forms.ModelForm, CssClassMixin): exclude = ('status', 'elected') -class AssignmentRunForm(forms.Form, CssClassMixin): +class AssignmentRunForm(CssClassMixin, forms.Form): candidate = PersonFormField( widget=forms.Select(attrs={'class': 'medium-input'}), - label=ugettext_lazy("Nominate a participant"), - ) + label=ugettext_lazy("Nominate a participant")) diff --git a/openslides/assignment/models.py b/openslides/assignment/models.py index 204e1ef31..f666f0608 100644 --- a/openslides/assignment/models.py +++ b/openslides/assignment/models.py @@ -50,9 +50,9 @@ class Assignment(SlideMixin, AbsoluteUrlMixin, models.Model): name = models.CharField(max_length=100, verbose_name=ugettext_lazy("Name")) description = models.TextField(null=True, blank=True, verbose_name=ugettext_lazy("Description")) posts = models.PositiveSmallIntegerField(verbose_name=ugettext_lazy("Number of available posts")) - polldescription = models.CharField( - max_length=100, null=True, blank=True, - verbose_name=ugettext_lazy("Comment on the ballot paper")) + poll_description_default = models.CharField( + max_length=79, null=True, blank=True, + verbose_name=ugettext_lazy("Default comment on the ballot paper")) status = models.CharField(max_length=3, choices=STATUS, default='sea') class Meta: @@ -192,8 +192,8 @@ class Assignment(SlideMixin, AbsoluteUrlMixin, models.Model): return person in self.elected def gen_poll(self): - poll = AssignmentPoll(assignment=self) - poll.save() + poll = AssignmentPoll.objects.create( + assignment=self, description=self.poll_description_default) poll.set_options([{'candidate': person} for person in self.candidates]) return poll @@ -255,9 +255,11 @@ class AssignmentOption(BaseOption): class AssignmentPoll(RelatedModelMixin, CollectInvalid, CollectVotesCast, PublishPollMixin, AbsoluteUrlMixin, BasePoll): option_class = AssignmentOption - assignment = models.ForeignKey(Assignment, related_name='poll_set') yesnoabstain = models.NullBooleanField() + description = models.CharField( + max_length=79, null=True, blank=True, + verbose_name=ugettext_lazy("Comment on the ballot paper")) def __unicode__(self): return _("Ballot %d") % self.get_ballot() @@ -297,3 +299,6 @@ class AssignmentPoll(RelatedModelMixin, CollectInvalid, CollectVotesCast, def get_ballot(self): return self.assignment.poll_set.filter(id__lte=self.id).count() + + def append_pollform_fields(self, fields): + fields.append('description') diff --git a/openslides/assignment/templates/assignment/poll_view.html b/openslides/assignment/templates/assignment/poll_view.html index f170daeaf..5de2d10cf 100644 --- a/openslides/assignment/templates/assignment/poll_view.html +++ b/openslides/assignment/templates/assignment/poll_view.html @@ -25,14 +25,13 @@ -{% if assignment.polldescription %} -

{% trans "Short description (for ballot paper)" %}: {{ assignment.polldescription }}

-{% endif %} - -

-{% trans "Special values" %}: -1 = {% trans 'majority' %} | -2 = {% trans 'undocumented' %} -

{% csrf_token %} +

+ {% trans "Special values" %}: + -1 = {% trans 'majority' %}| + -2 = {% trans 'undocumented' %} +

+ @@ -73,10 +72,12 @@
{% trans "Candidates" %}
+

{% trans "Short description (for ballot paper)" %}:

+

{{ pollform.description }}

- - {% trans 'Ballot paper as PDF' %} - + + {% trans 'Ballot paper as PDF' %} +

diff --git a/openslides/assignment/views.py b/openslides/assignment/views.py index 941f6f65f..2bd349076 100644 --- a/openslides/assignment/views.py +++ b/openslides/assignment/views.py @@ -197,10 +197,11 @@ class AssignmentRunOtherDeleteView(SingleObjectMixin, QuestionView): class PollCreateView(SingleObjectMixin, RedirectView): model = Assignment permission_required = 'assignment.can_manage_assignment' - url_name = 'assignment_poll_view' + url_name = 'assignment_detail' def pre_redirect(self, *args, **kwargs): - self.object = self.get_object().gen_poll() + self.object = self.get_object() + self.object.gen_poll() messages.success(self.request, _("New ballot was successfully created.")) @@ -213,7 +214,7 @@ class PollUpdateView(PollFormView): self.assignment = self.poll.get_assignment() context['assignment'] = self.assignment context['poll'] = self.poll - context['polls'] = self.assignment.poll_set.filter(assignment=self.assignment) + context['polls'] = self.assignment.poll_set.all() context['ballotnumber'] = self.poll.get_ballot() return context @@ -513,7 +514,7 @@ class AssignmentPollPDF(PDFView): _("Election") + ": " + self.poll.assignment.name, stylesheet['Ballot_title'])) cell.append(Paragraph( - self.poll.assignment.polldescription, + self.poll.description or '', stylesheet['Ballot_subtitle'])) options = self.poll.get_options() diff --git a/openslides/poll/forms.py b/openslides/poll/forms.py index 96ca9f4b5..fa3221ccf 100644 --- a/openslides/poll/forms.py +++ b/openslides/poll/forms.py @@ -5,7 +5,7 @@ from django import forms from openslides.utils.forms import CssClassMixin -class OptionForm(forms.Form, CssClassMixin): +class OptionForm(CssClassMixin, forms.Form): def __init__(self, *args, **kwargs): extra = kwargs.pop('extra') formid = kwargs.pop('formid') @@ -20,5 +20,4 @@ class OptionForm(forms.Form, CssClassMixin): label=value, initial=weight, min_value=-2, - required=False, - ) + required=False) diff --git a/openslides/poll/views.py b/openslides/poll/views.py index 13a2f5fab..f16806734 100644 --- a/openslides/poll/views.py +++ b/openslides/poll/views.py @@ -30,19 +30,19 @@ class PollFormView(FormMixin, TemplateView): error = True if error: - return self.render_to_response(self.get_context_data( + response = self.render_to_response(self.get_context_data( forms=option_forms, - pollform=pollform, - )) + pollform=pollform)) + else: + for form in option_forms: + data = {} + for value in self.poll.get_vote_values(): + data[value] = form.cleaned_data[value] + self.poll.set_vote_objects_with_values(form.option, data) - for form in option_forms: - data = {} - for value in self.poll.get_vote_values(): - data[value] = form.cleaned_data[value] - self.poll.set_vote_objects_with_values(form.option, data) - - pollform.save() - return HttpResponseRedirect(self.get_success_url()) + pollform.save() + response = HttpResponseRedirect(self.get_success_url()) + return response def get_poll_class(self): if self.poll_class is not None: @@ -71,4 +71,4 @@ class PollFormView(FormMixin, TemplateView): def get_modelform_class(self): fields = [] self.poll.append_pollform_fields(fields) - return modelform_factory(self.poll.__class__, fields=fields) + return modelform_factory(type(self.poll), fields=fields) diff --git a/openslides/static/styles/base.css b/openslides/static/styles/base.css index e979774b2..019a7ddbe 100644 --- a/openslides/static/styles/base.css +++ b/openslides/static/styles/base.css @@ -142,6 +142,9 @@ input, textarea { .small-form input { width: 55px; } +.normal-form input { + width: 320px; +} textarea { height: 100px; } @@ -379,7 +382,7 @@ legend + .control-group { margin: 0 5px 0 45px; width: auto; } - /* hide optional column */ + /* hide optional column */ .optional { display: none; } diff --git a/tests/utils/test_main_menu.py b/tests/utils/test_main_menu.py index 7bcae5b5e..713627573 100644 --- a/tests/utils/test_main_menu.py +++ b/tests/utils/test_main_menu.py @@ -10,6 +10,13 @@ from openslides.utils.main_menu import MainMenuEntry class MainMenuEntryObject(TestCase): request_factory = RequestFactory() + def tearDown(self): + """ + Remove all receivers of the MainMenuEntry-signal, so it is not called in + other tests. + """ + MainMenuEntry.signal.receivers = [] + def get_entry(self, cls): request = self.request_factory.get('/') request.user = AnonymousUser()