diff --git a/client/src/app/core/translate/marked-translations.ts b/client/src/app/core/translate/marked-translations.ts index 43ff8c29a..66b1808ae 100644 --- a/client/src/app/core/translate/marked-translations.ts +++ b/client/src/app/core/translate/marked-translations.ts @@ -172,6 +172,7 @@ _('Number of all delegates'); _('Number of all participants'); _('Use the following custom number'); _('Custom number of ballot papers'); +_('Voting'); // subgroup PDF export _('PDF export'); _('Title for PDF documents of motions'); @@ -278,8 +279,8 @@ _('Next states'); // other translations _('Searching for candidates'); -_('Voting'); _('Finished'); +_('In the election process'); // ** Users ** // permission strings (see models.py of each Django app) diff --git a/client/src/app/core/ui-services/voting-banner.service.ts b/client/src/app/core/ui-services/voting-banner.service.ts index a50cea015..b6f112244 100644 --- a/client/src/app/core/ui-services/voting-banner.service.ts +++ b/client/src/app/core/ui-services/voting-banner.service.ts @@ -74,9 +74,9 @@ export class VotingBannerService { private getTextForPoll(poll: ViewBasePoll): string { return poll instanceof ViewMotionPoll ? `${this.translate.instant('Motion') + ' ' + poll.motion.getIdentifierOrTitle()}: ${this.translate.instant( - 'Voting started' - )}!` - : `${poll.getTitle()}: ${this.translate.instant('Ballots opened')}!`; + 'Voting is open' + )}` + : `${poll.getTitle()}: ${this.translate.instant('Ballot is open')}`; } /** diff --git a/client/src/app/shared/models/poll/base-poll.ts b/client/src/app/shared/models/poll/base-poll.ts index 6dbe9fd3d..0c059cb02 100644 --- a/client/src/app/shared/models/poll/base-poll.ts +++ b/client/src/app/shared/models/poll/base-poll.ts @@ -27,7 +27,6 @@ export enum PercentBase { YN = 'YN', YNA = 'YNA', Valid = 'valid', - Votes = 'votes', Cast = 'cast', Disabled = 'disabled' } @@ -68,6 +67,10 @@ export abstract class BasePoll = any> extends return this.state === PollState.Published; } + public get isPercentBaseValidOrCast(): boolean { + return this.onehundred_percent_base === PercentBase.Valid || this.onehundred_percent_base === PercentBase.Cast; + } + /** * If the state is finished. */ diff --git a/client/src/app/shared/pipes/poll-percent-base.pipe.spec.ts b/client/src/app/shared/pipes/poll-percent-base.pipe.spec.ts new file mode 100644 index 000000000..f10efe859 --- /dev/null +++ b/client/src/app/shared/pipes/poll-percent-base.pipe.spec.ts @@ -0,0 +1,8 @@ +import { PollPercentBasePipe } from './poll-percent-base.pipe'; + +describe('PollPercentBasePipe', () => { + it('create an instance', () => { + const pipe = new PollPercentBasePipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/client/src/app/shared/pipes/poll-percent-base.pipe.ts b/client/src/app/shared/pipes/poll-percent-base.pipe.ts new file mode 100644 index 000000000..6054e94d9 --- /dev/null +++ b/client/src/app/shared/pipes/poll-percent-base.pipe.ts @@ -0,0 +1,36 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +import { ViewBasePoll } from 'app/site/polls/models/view-base-poll'; + +/** + * Uses a number and a ViewPoll-object. + * Converts the number to the voting percent base using the + * given 100%-Base option in the poll object + * + * returns null if a percent calculation is not possible + * or the result is 0 + * + * @example + * ```html + * {{ voteYes | pollPercentBase: poll }} + * ``` + */ +@Pipe({ + name: 'pollPercentBase' +}) +export class PollPercentBasePipe implements PipeTransform { + private decimalPlaces = 3; + + public transform(value: number, viewPoll: ViewBasePoll): string | null { + const totalByBase = viewPoll.getPercentBase(); + + if (totalByBase) { + const percentNumber = (value / totalByBase) * 100; + if (percentNumber > 0) { + const result = percentNumber % 1 === 0 ? percentNumber : percentNumber.toFixed(this.decimalPlaces); + return `(${result}%)`; + } + } + return null; + } +} diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts index bc5f4ccd7..be7c92fd9 100644 --- a/client/src/app/shared/shared.module.ts +++ b/client/src/app/shared/shared.module.ts @@ -122,6 +122,7 @@ import { AssignmentPollDialogComponent } from 'app/site/assignments/components/a import { ParsePollNumberPipe } from './pipes/parse-poll-number.pipe'; import { ReversePipe } from './pipes/reverse.pipe'; import { PollKeyVerbosePipe } from './pipes/poll-key-verbose.pipe'; +import { PollPercentBasePipe } from './pipes/poll-percent-base.pipe'; /** * Share Module for all "dumb" components and pipes. @@ -285,7 +286,8 @@ import { PollKeyVerbosePipe } from './pipes/poll-key-verbose.pipe'; AssignmentPollDialogComponent, ParsePollNumberPipe, ReversePipe, - PollKeyVerbosePipe + PollKeyVerbosePipe, + PollPercentBasePipe ], declarations: [ PermsDirective, @@ -342,7 +344,8 @@ import { PollKeyVerbosePipe } from './pipes/poll-key-verbose.pipe'; AssignmentPollDialogComponent, ParsePollNumberPipe, ReversePipe, - PollKeyVerbosePipe + PollKeyVerbosePipe, + PollPercentBasePipe ], providers: [ { @@ -361,7 +364,8 @@ import { PollKeyVerbosePipe } from './pipes/poll-key-verbose.pipe'; LocalizedDatePipe, ParsePollNumberPipe, ReversePipe, - PollKeyVerbosePipe + PollKeyVerbosePipe, + PollPercentBasePipe ], entryComponents: [ SortBottomSheetComponent, diff --git a/client/src/app/site/assignments/models/view-assignment-poll.ts b/client/src/app/site/assignments/models/view-assignment-poll.ts index c53157653..f38823c01 100644 --- a/client/src/app/site/assignments/models/view-assignment-poll.ts +++ b/client/src/app/site/assignments/models/view-assignment-poll.ts @@ -86,6 +86,10 @@ export class ViewAssignmentPoll extends ViewBasePoll implements return data; } + + public getPercentBase(): number { + return 0; + } } export interface ViewAssignmentPoll extends AssignmentPoll { diff --git a/client/src/app/site/assignments/models/view-assignment.ts b/client/src/app/site/assignments/models/view-assignment.ts index 75aaaddcf..d6cdeb264 100644 --- a/client/src/app/site/assignments/models/view-assignment.ts +++ b/client/src/app/site/assignments/models/view-assignment.ts @@ -27,7 +27,7 @@ export const AssignmentPhases: { name: string; value: number; display_name: stri { name: 'PHASE_VOTING', value: 1, - display_name: 'Voting' + display_name: 'In the election process' }, { name: 'PHASE_FINISHED', diff --git a/client/src/app/site/motions/models/view-motion-option.ts b/client/src/app/site/motions/models/view-motion-option.ts index b380648cb..64880b50a 100644 --- a/client/src/app/site/motions/models/view-motion-option.ts +++ b/client/src/app/site/motions/models/view-motion-option.ts @@ -9,6 +9,19 @@ export class ViewMotionOption extends BaseViewModel { } public static COLLECTIONSTRING = MotionOption.COLLECTIONSTRING; protected _collectionString = MotionOption.COLLECTIONSTRING; + + public sumYN(): number { + let sum = 0; + sum += this.yes > 0 ? this.yes : 0; + sum += this.no > 0 ? this.no : 0; + return sum; + } + + public sumYNA(): number { + let sum = this.sumYN(); + sum += this.abstain > 0 ? this.abstain : 0; + return sum; + } } interface TIMotionOptionRelations { diff --git a/client/src/app/site/motions/models/view-motion-poll.ts b/client/src/app/site/motions/models/view-motion-poll.ts index bd0c1ba73..7b5164032 100644 --- a/client/src/app/site/motions/models/view-motion-poll.ts +++ b/client/src/app/site/motions/models/view-motion-poll.ts @@ -1,6 +1,6 @@ import { ChartData } from 'app/shared/components/charts/charts.component'; import { MotionPoll, MotionPollMethods } from 'app/shared/models/motions/motion-poll'; -import { PollColor, PollState } from 'app/shared/models/poll/base-poll'; +import { PercentBase, PollColor, PollState } from 'app/shared/models/poll/base-poll'; import { BaseViewModel } from 'app/site/base/base-view-model'; import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable'; import { ViewMotionOption } from 'app/site/motions/models/view-motion-option'; @@ -16,14 +16,57 @@ export const MotionPollMethodsVerbose = { YNA: 'Yes/No/Abstain' }; +interface TableKey { + vote: string; + icon?: string; + canHide: boolean; + showPercent: boolean; +} + export class ViewMotionPoll extends ViewBasePoll implements MotionPollTitleInformation { public static COLLECTIONSTRING = MotionPoll.COLLECTIONSTRING; protected _collectionString = MotionPoll.COLLECTIONSTRING; public readonly pollClassType: 'assignment' | 'motion' = 'motion'; - private tableKeys = ['yes', 'no', 'abstain']; - private voteKeys = ['votesvalid', 'votesinvalid', 'votescast']; + private tableKeys: TableKey[] = [ + { + vote: 'yes', + icon: 'thumb_up', + canHide: false, + showPercent: true + }, + { + vote: 'no', + icon: 'thumb_down', + canHide: false, + showPercent: true + }, + { + vote: 'abstain', + icon: 'trip_origin', + canHide: false, + showPercent: true + } + ]; + + private voteKeys: TableKey[] = [ + { + vote: 'votesvalid', + canHide: true, + showPercent: this.poll.isPercentBaseValidOrCast + }, + { + vote: 'votesinvalid', + canHide: true, + showPercent: this.poll.isPercentBaseValidOrCast + }, + { + vote: 'votescast', + canHide: true, + showPercent: this.poll.isPercentBaseValidOrCast + } + ]; public get hasVotes(): boolean { return !!this.options[0].votes.length; @@ -38,9 +81,19 @@ export class ViewMotionPoll extends ViewBasePoll implements MotionPo } public generateTableData(): PollData[] { - let tableData = this.options.flatMap(vote => this.tableKeys.map(key => ({ key: key, value: vote[key] }))); - tableData.push(...this.voteKeys.map(key => ({ key: key, value: this[key] }))); - tableData = tableData.map(entry => (entry.value >= 0 ? entry : { key: entry.key, value: null })); + let tableData = this.options.flatMap(vote => + this.tableKeys.map(key => ({ + key: key.vote, + value: vote[key.vote], + canHide: key.canHide, + icon: key.icon, + showPercent: key.showPercent + })) + ); + tableData.push( + ...this.voteKeys.map(key => ({ key: key.vote, value: this[key.vote], showPercent: key.showPercent })) + ); + tableData = tableData.filter(entry => entry.canHide === false || entry.value || entry.value !== -2); return tableData; } @@ -76,6 +129,10 @@ export class ViewMotionPoll extends ViewBasePoll implements MotionPo return MotionPollMethodsVerbose[this.pollmethod]; } + public anySpecialVotes(): boolean { + return this.options[0].yes < 0 || this.options[0].no < 0 || this.options[0].abstain < 0; + } + /** * Override from base poll to skip started state in analog poll type */ @@ -85,6 +142,40 @@ export class ViewMotionPoll extends ViewBasePoll implements MotionPo } return super.getNextStates(); } + + public getPercentBase(): number { + const base: PercentBase = this.poll.onehundred_percent_base; + const options = this.options[0]; + + let totalByBase: number; + switch (base) { + case PercentBase.YN: + if (options.yes >= 0 && options.no >= 0) { + totalByBase = options.sumYN(); + } + break; + case PercentBase.YNA: + if (options.yes >= 0 && options.no >= 0 && options.abstain >= 0) { + totalByBase = options.sumYNA(); + } + break; + case PercentBase.Valid: + // auslagern + if (options.yes >= 0 && options.no >= 0 && options.abstain >= 0) { + totalByBase = this.poll.votesvalid; + } + break; + case PercentBase.Cast: + totalByBase = this.poll.votescast; + break; + case PercentBase.Disabled: + break; + default: + throw new Error('The given poll has no percent base: ' + this); + } + + return totalByBase; + } } export interface ViewMotionPoll extends MotionPoll { diff --git a/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.html b/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.html index 6cd120436..c6c0322f6 100644 --- a/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.html +++ b/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.html @@ -43,34 +43,51 @@ Votes - {{ row.key | pollKeyVerbose | translate }} - {{ row.value }} + + + {{ row.key | pollKeyVerbose | translate }} + + + {{ row.key | pollKeyVerbose | translate }} + + + + {{ row.value | parsePollNumber }} + + {{ row.value | pollPercentBase: poll }} + + -
+

{{ 'Single votes' | translate }}

- - - - - - {{ 'User' | translate }} - -
{{ vote.user.getFullName() }}
-
{{ 'Unknown user' | translate }}
-
-
- - {{ 'Vote' | translate }} - {{ vote.valueVerbose }} - +
+ + + + + + {{ 'Participant' | translate }} + +
{{ vote.user.getFullName() }}
+
{{ 'Anonymous' | translate }}
+
+
+ + {{ 'Vote' | translate }} + {{ vote.valueVerbose }} + - - -
+ + + +
+
+ {{ 'The individual votes were made anonymous.' | translate }} +
diff --git a/client/src/app/site/motions/modules/motion-poll/motion-poll-dialog/motion-poll-dialog.component.html b/client/src/app/site/motions/modules/motion-poll/motion-poll-dialog/motion-poll-dialog.component.html index dbf278737..fac323c53 100644 --- a/client/src/app/site/motions/modules/motion-poll/motion-poll-dialog/motion-poll-dialog.component.html +++ b/client/src/app/site/motions/modules/motion-poll/motion-poll-dialog/motion-poll-dialog.component.html @@ -42,7 +42,7 @@ -
+
Publish immediately diff --git a/client/src/app/site/motions/modules/motion-poll/motion-poll/motion-poll.component.html b/client/src/app/site/motions/modules/motion-poll/motion-poll/motion-poll.component.html index 4acb0e736..99e4ddb62 100644 --- a/client/src/app/site/motions/modules/motion-poll/motion-poll/motion-poll.component.html +++ b/client/src/app/site/motions/modules/motion-poll/motion-poll/motion-poll.component.html @@ -55,53 +55,60 @@
- -
- +
{{ voteYes | parsePollNumber }} + {{ voteYes | pollPercentBase: poll }}
{{ voteNo | parsePollNumber }} + {{ voteNo | pollPercentBase: poll }}
{{ voteAbstain | parsePollNumber }} + {{ voteAbstain | pollPercentBase: poll }}
-
+
- {{ 'An empty poll - you have to enter votes.' | translate }} + {{ 'Edit to enter votes.' | translate }}
- +
diff --git a/client/src/app/site/motions/modules/motion-poll/motion-poll/motion-poll.component.scss b/client/src/app/site/motions/modules/motion-poll/motion-poll/motion-poll.component.scss index 0b4aba82b..8bcae1954 100644 --- a/client/src/app/site/motions/modules/motion-poll/motion-poll/motion-poll.component.scss +++ b/client/src/app/site/motions/modules/motion-poll/motion-poll/motion-poll.component.scss @@ -51,15 +51,20 @@ .poll-chart-wrapper { cursor: pointer; display: grid; + grid-gap: 10px; + grid-template-areas: 'placeholder chart legend'; grid-template-columns: auto minmax(50px, 20%) auto; .doughnut-chart { + grid-area: chart; margin-top: auto; margin-bottom: auto; } .vote-legend { - margin: auto 10px; + grid-area: legend; + margin-top: auto; + margin-bottom: auto; div + div { margin-top: 10px; diff --git a/client/src/app/site/polls/components/poll-form/poll-form.component.html b/client/src/app/site/polls/components/poll-form/poll-form.component.html index e6966d0dd..779a6190f 100644 --- a/client/src/app/site/polls/components/poll-form/poll-form.component.html +++ b/client/src/app/site/polls/components/poll-form/poll-form.component.html @@ -1,23 +1,25 @@
+
-

+

- + {{ value[0] }} - + {{ value[1] }}
-
+ + @@ -26,7 +28,9 @@ This field is required - + + + + + - + {{ option.value }} @@ -46,29 +56,37 @@ + - + {{ option.value | translate }} - - - - {{ option.value | translate }} - - - - - - - - - {{ PollPropertyVerbose.global_no | translate }} - {{ PollPropertyVerbose.global_abstain | translate }} - + + + + + + {{ PollPropertyVerbose.global_no | translate }} + {{ + PollPropertyVerbose.global_abstain | translate + }}
diff --git a/client/src/app/site/polls/components/poll-form/poll-form.component.scss b/client/src/app/site/polls/components/poll-form/poll-form.component.scss index 15a5b0dd2..cfd7e2958 100644 --- a/client/src/app/site/polls/components/poll-form/poll-form.component.scss +++ b/client/src/app/site/polls/components/poll-form/poll-form.component.scss @@ -1,4 +1,4 @@ -.poll-preview--title { +.poll-preview-title { margin: 0; } @@ -14,13 +14,13 @@ span { display: block; } - &--label { + &-label { font-size: 75%; } } } -.poll-preview--meta-info-form { +.poll-preview-meta-info-form { display: flex; align-items: center; flex-wrap: wrap; diff --git a/client/src/app/site/polls/components/poll-form/poll-form.component.ts b/client/src/app/site/polls/components/poll-form/poll-form.component.ts index d88fd9a83..695f1119a 100644 --- a/client/src/app/site/polls/components/poll-form/poll-form.component.ts +++ b/client/src/app/site/polls/components/poll-form/poll-form.component.ts @@ -114,9 +114,10 @@ export class PollFormComponent extends BaseViewComponent implements OnInit { this.data.groups_id = this.configService.instant('motion_poll_default_groups'); } } + Object.keys(this.contentForm.controls).forEach(key => { if (this.data[key]) { - this.contentForm.get(key).setValue(this.data[key]); + this.contentForm.get(key).patchValue(this.data[key]); } }); } @@ -132,9 +133,9 @@ export class PollFormComponent extends BaseViewComponent implements OnInit { this.contentForm.get('pollmethod').valueChanges.subscribe(method => { let forbiddenBases: string[]; if (method === 'YN') { - forbiddenBases = [PercentBase.YNA, PercentBase.Votes]; + forbiddenBases = [PercentBase.YNA, PercentBase.Cast]; } else if (method === 'YNA') { - forbiddenBases = [PercentBase.Votes]; + forbiddenBases = [PercentBase.Cast]; } else if (method === 'votes') { forbiddenBases = [PercentBase.YN, PercentBase.YNA]; } diff --git a/client/src/app/site/polls/components/poll-progress/poll-progress.component.html b/client/src/app/site/polls/components/poll-progress/poll-progress.component.html index 13089e6f5..7718a161e 100644 --- a/client/src/app/site/polls/components/poll-progress/poll-progress.component.html +++ b/client/src/app/site/polls/components/poll-progress/poll-progress.component.html @@ -1,3 +1,3 @@ -{{ poll.voted_id.length }} von {{ max }} haben abgestimmt. + {{ 'Casted votes' }}: {{ poll.voted_id.length }} / {{ max }} diff --git a/client/src/app/site/polls/components/poll-progress/poll-progress.component.ts b/client/src/app/site/polls/components/poll-progress/poll-progress.component.ts index fdfb7f221..642fdee2e 100644 --- a/client/src/app/site/polls/components/poll-progress/poll-progress.component.ts +++ b/client/src/app/site/polls/components/poll-progress/poll-progress.component.ts @@ -40,7 +40,11 @@ export class PollProgressComponent extends BaseViewComponent implements OnInit { public ngOnInit(): void { this.userRepo .getViewModelListObservable() - .pipe(map(users => users.filter(user => this.poll.groups_id.intersect(user.groups_id).length))) + .pipe( + map(users => + users.filter(user => user.is_present && this.poll.groups_id.intersect(user.groups_id).length) + ) + ) .subscribe(users => { this.max = users.length; }); diff --git a/client/src/app/site/polls/models/view-base-poll.ts b/client/src/app/site/polls/models/view-base-poll.ts index c48c35197..d6a3dba0d 100644 --- a/client/src/app/site/polls/models/view-base-poll.ts +++ b/client/src/app/site/polls/models/view-base-poll.ts @@ -21,8 +21,11 @@ export interface PollData { value?: number; yes?: number; no?: number; - abstain: number; + abstain?: number; user?: string; + canHide?: boolean; + icon?: string; + showPercent?: boolean; } export const PollClassTypeVerbose = { @@ -40,7 +43,7 @@ export const PollStateVerbose = { export const PollStateChangeActionVerbose = { 1: 'Reset', 2: 'Start voting', - 3: 'End voting', + 3: 'Stop voting', 4: 'Publish' }; @@ -51,7 +54,7 @@ export const PollTypeVerbose = { }; export const PollPropertyVerbose = { - majority_method: 'Majority method', + majority_method: 'Required majority', onehundred_percent_base: '100% base', type: 'Poll type', pollmethod: 'Poll method', @@ -69,10 +72,12 @@ export const MajorityMethodVerbose = { disabled: 'Disabled' }; +/** + * TODO: These need to be in order + */ export const PercentBaseVerbose = { YN: 'Yes/No', YNA: 'Yes/No/Abstain', - votes: 'All votes', valid: 'Valid votes', cast: 'Total votes cast', disabled: 'Disabled' @@ -145,6 +150,8 @@ export abstract class ViewBasePoll = any> extends Bas public abstract generateChartData(): ChartData; public abstract generateTableData(): PollData[]; + + public abstract getPercentBase(): number; } export interface ViewBasePoll = any> extends BasePoll { diff --git a/openslides/assignments/config_variables.py b/openslides/assignments/config_variables.py index 7b9a45a4a..e4b9f3f6d 100644 --- a/openslides/assignments/config_variables.py +++ b/openslides/assignments/config_variables.py @@ -1,3 +1,5 @@ +from django.core.validators import MinValueValidator + from openslides.assignments.models import AssignmentPoll from openslides.core.config import ConfigVariable @@ -19,8 +21,8 @@ def get_config_variables(): for base in AssignmentPoll.PERCENT_BASES ), weight=400, - group="Voting", - subgroup="Elections", + group="Elections", + subgroup="Voting", ) yield ConfigVariable( @@ -35,18 +37,8 @@ def get_config_variables(): help_text="Default method to check whether a candidate has reached the required majority.", weight=405, hidden=True, - group="Voting", - subgroup="Elections", - ) - - yield ConfigVariable( - name="assignment_poll_add_candidates_to_list_of_speakers", - default_value=True, - input_type="boolean", - label="Put all candidates on the list of speakers", - weight=410, - group="Voting", - subgroup="Elections", + group="Elections", + subgroup="Voting", ) yield ConfigVariable( @@ -54,9 +46,52 @@ def get_config_variables(): default_value=[], input_type="groups", label="Default groups for named and pseudoanonymous assignment polls", + weight=410, + group="Elections", + subgroup="Voting", + ) + + yield ConfigVariable( + name="assignment_poll_add_candidates_to_list_of_speakers", + default_value=True, + input_type="boolean", + label="Put all candidates on the list of speakers", weight=415, - group="Voting", - subgroup="Elections", + group="Elections", + subgroup="Voting", + ) + + # Ballot Paper + yield ConfigVariable( + name="assignments_pdf_ballot_papers_selection", + default_value="CUSTOM_NUMBER", + input_type="choice", + label="Number of ballot papers (selection)", + choices=( + {"value": "NUMBER_OF_DELEGATES", "display_name": "Number of all delegates"}, + { + "value": "NUMBER_OF_ALL_PARTICIPANTS", + "display_name": "Number of all participants", + }, + { + "value": "CUSTOM_NUMBER", + "display_name": "Use the following custom number", + }, + ), + weight=430, + group="Elections", + subgroup="Ballot papers", + ) + + yield ConfigVariable( + name="assignments_pdf_ballot_papers_number", + default_value=8, + input_type="integer", + label="Custom number of ballot papers", + weight=435, + group="Elections", + subgroup="Ballot papers", + validators=(MinValueValidator(1),), ) # PDF diff --git a/openslides/motions/config_variables.py b/openslides/motions/config_variables.py index 7105939c2..c20abb303 100644 --- a/openslides/motions/config_variables.py +++ b/openslides/motions/config_variables.py @@ -332,22 +332,77 @@ def get_config_variables(): # Voting and ballot papers yield ConfigVariable( - name="motions_poll_100_percent_base", - default_value="YES_NO_ABSTAIN", + name="motion_poll_default_100_percent_base", + default_value="YNA", input_type="choice", label="The 100 % base of a voting result consists of", - choices=( - {"value": "YES_NO_ABSTAIN", "display_name": "Yes/No/Abstain"}, - {"value": "YES_NO", "display_name": "Yes/No"}, - {"value": "VALID", "display_name": "All valid ballots"}, - {"value": "CAST", "display_name": "All casted ballots"}, - {"value": "DISABLED", "display_name": "Disabled (no percents)"}, + choices=tuple( + {"value": base[0], "display_name": base[1]} + for base in MotionPoll.PERCENT_BASES ), weight=370, group="Motions", subgroup="Voting and ballot papers", ) + yield ConfigVariable( + name="motion_poll_default_majority_method", + default_value="simple", + input_type="choice", + choices=tuple( + {"value": method[0], "display_name": method[1]} + for method in MotionPoll.MAJORITY_METHODS + ), + label="Required majority", + help_text="Default method to check whether a motion has reached the required majority.", + weight=371, + hidden=True, + group="Motions", + subgroup="Voting and ballot papers", + ) + + yield ConfigVariable( + name="motion_poll_default_groups", + default_value=[], + input_type="groups", + label="Default groups for named and pseudoanonymous motion polls", + weight=372, + group="Motions", + subgroup="Voting and ballot papers", + ) + + yield ConfigVariable( + name="motions_pdf_ballot_papers_selection", + default_value="CUSTOM_NUMBER", + input_type="choice", + label="Number of ballot papers (selection)", + choices=( + {"value": "NUMBER_OF_DELEGATES", "display_name": "Number of all delegates"}, + { + "value": "NUMBER_OF_ALL_PARTICIPANTS", + "display_name": "Number of all participants", + }, + { + "value": "CUSTOM_NUMBER", + "display_name": "Use the following custom number", + }, + ), + weight=373, + group="Motions", + subgroup="Voting and ballot papers", + ) + + yield ConfigVariable( + name="motions_pdf_ballot_papers_number", + default_value=8, + input_type="integer", + label="Custom number of ballot papers", + weight=374, + group="Motions", + subgroup="Voting and ballot papers", + validators=(MinValueValidator(1),), + ) + # PDF export yield ConfigVariable( @@ -387,44 +442,3 @@ def get_config_variables(): group="Motions", subgroup="PDF export", ) - - # Voting - yield ConfigVariable( - name="motion_poll_default_100_percent_base", - default_value="YNA", - input_type="choice", - label="The 100 % base of a voting result consists of", - choices=tuple( - {"value": base[0], "display_name": base[1]} - for base in MotionPoll.PERCENT_BASES - ), - weight=420, - group="Voting", - subgroup="Motions", - ) - - yield ConfigVariable( - name="motion_poll_default_majority_method", - default_value="simple", - input_type="choice", - choices=tuple( - {"value": method[0], "display_name": method[1]} - for method in MotionPoll.MAJORITY_METHODS - ), - label="Required majority", - help_text="Default method to check whether a motion has reached the required majority.", - weight=425, - hidden=True, - group="Voting", - subgroup="Motions", - ) - - yield ConfigVariable( - name="motion_poll_default_groups", - default_value=[], - input_type="groups", - label="Default groups for named and pseudoanonymous motion polls", - weight=430, - group="Voting", - subgroup="Motions", - )