From 97c2299aec50891fa02f980d736433afc9c338ce Mon Sep 17 00:00:00 2001 From: Sean Date: Wed, 22 Apr 2020 16:54:50 +0200 Subject: [PATCH] Implement vote weight in client Implements vote weight in client The user detail page has a new property change deserialize to parse floats change "yes"-voting to send "Y" and "0" instead of "1" and "0" add vote weight to user list, filter, sort add vote weight to single voting result votesvalid and votescast respect the individual vote weight fix parse-poll pipe and null in pdf --- .../shared/models/base/base-decimal-model.ts | 2 +- client/src/app/shared/models/users/user.ts | 4 ++ .../shared/pipes/parse-poll-number.pipe.ts | 5 +- .../assignment-poll-detail.component.html | 9 +++- .../assignment-poll-detail.component.ts | 7 +++ .../motion-poll-detail.component.html | 15 +++++- .../motion-poll-detail.component.ts | 7 +++ .../motions/services/motion-pdf.service.ts | 7 ++- .../user-detail/user-detail.component.html | 34 +++++++++++++- .../user-detail/user-detail.component.ts | 16 ++++++- .../user-import-list.component.html | 28 +++++------ .../user-import/user-import-list.component.ts | 46 ++++++++++--------- .../user-list/user-list.component.html | 28 +++++++---- .../user-list/user-list.component.scss | 1 + .../user-list/user-list.component.ts | 11 ++++- .../services/user-filter-list.service.ts | 8 ++++ .../users/services/user-import.service.ts | 10 +++- .../users/services/user-sort-list.service.ts | 1 + openslides/assignments/views.py | 21 ++++++--- openslides/motions/views.py | 15 ++++-- openslides/poll/models.py | 12 +++-- openslides/users/config_variables.py | 9 ++++ tests/integration/assignments/test_polls.py | 41 ++++++++++++++++- tests/integration/motions/test_polls.py | 36 +++++++++++++-- 24 files changed, 295 insertions(+), 78 deletions(-) diff --git a/client/src/app/shared/models/base/base-decimal-model.ts b/client/src/app/shared/models/base/base-decimal-model.ts index 5a893f5c7..86ea4c11c 100644 --- a/client/src/app/shared/models/base/base-decimal-model.ts +++ b/client/src/app/shared/models/base/base-decimal-model.ts @@ -7,7 +7,7 @@ export abstract class BaseDecimalModel extends BaseModel { if (input && typeof input === 'object') { this.getDecimalFields().forEach(field => { if (input[field] !== undefined) { - input[field] = parseInt(input[field], 10); + input[field] = parseFloat(input[field]); } }); } diff --git a/client/src/app/shared/models/users/user.ts b/client/src/app/shared/models/users/user.ts index 00179e534..174c5b5aa 100644 --- a/client/src/app/shared/models/users/user.ts +++ b/client/src/app/shared/models/users/user.ts @@ -37,6 +37,10 @@ export class User extends BaseDecimalModel { public auth_type?: UserAuthType; public vote_weight: number; + public get isVoteWeightOne(): boolean { + return this.vote_weight === 1; + } + public constructor(input?: Partial) { super(User.COLLECTIONSTRING, input); } diff --git a/client/src/app/shared/pipes/parse-poll-number.pipe.ts b/client/src/app/shared/pipes/parse-poll-number.pipe.ts index 1f49249ae..c14763755 100644 --- a/client/src/app/shared/pipes/parse-poll-number.pipe.ts +++ b/client/src/app/shared/pipes/parse-poll-number.pipe.ts @@ -11,14 +11,13 @@ export class ParsePollNumberPipe implements PipeTransform { public constructor(private translate: TranslateService) {} public transform(value: number): number | string { - const input = Math.trunc(value); - switch (input) { + switch (value) { case VOTE_MAJORITY: return this.translate.instant('majority'); case VOTE_UNDOCUMENTED: return this.translate.instant('undocumented'); default: - return input; + return value; } } } diff --git a/client/src/app/site/assignments/components/assignment-poll-detail/assignment-poll-detail.component.html b/client/src/app/site/assignments/components/assignment-poll-detail/assignment-poll-detail.component.html index ccd9afe98..483d891b2 100644 --- a/client/src/app/site/assignments/components/assignment-poll-detail/assignment-poll-detail.component.html +++ b/client/src/app/site/assignments/components/assignment-poll-detail/assignment-poll-detail.component.html @@ -70,8 +70,13 @@
{{ vote.user.getShortName() }} -
- {{ vote.user.getLevelAndNumber() }} +
+
+ {{ vote.user.getLevelAndNumber() }} +
+
+ {{ 'Vote weight' | translate }}: {{ vote.user.vote_weight }} +
diff --git a/client/src/app/site/assignments/components/assignment-poll-detail/assignment-poll-detail.component.ts b/client/src/app/site/assignments/components/assignment-poll-detail/assignment-poll-detail.component.ts index 954f1b0a1..2f4c28a00 100644 --- a/client/src/app/site/assignments/components/assignment-poll-detail/assignment-poll-detail.component.ts +++ b/client/src/app/site/assignments/components/assignment-poll-detail/assignment-poll-detail.component.ts @@ -10,6 +10,7 @@ import { OperatorService } from 'app/core/core-services/operator.service'; import { AssignmentPollRepositoryService } from 'app/core/repositories/assignments/assignment-poll-repository.service'; import { AssignmentVoteRepositoryService } from 'app/core/repositories/assignments/assignment-vote-repository.service'; import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service'; +import { ConfigService } from 'app/core/ui-services/config.service'; import { PromptService } from 'app/core/ui-services/prompt.service'; import { VoteValue } from 'app/shared/models/poll/base-vote'; import { BasePollDetailComponent } from 'app/site/polls/components/base-poll-detail.component'; @@ -32,6 +33,8 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponent('users_activate_vote_weight') + .subscribe(active => (this.isVoteWeightActive = active)); } protected createVotesData(): void { 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 6e13dca24..4f587078a 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 @@ -45,7 +45,7 @@ [filterProps]="filterProps" [allowProjector]="false" [fullScreen]="true" - [vScrollFixed]="60" + [vScrollFixed]="-1" listStorageKey="motion-poll-vote" [cssClasses]="{ 'single-votes-table': true }" > @@ -56,7 +56,18 @@
-
{{ vote.user.getFullName() }}
+
+ {{ vote.user.getShortName() }} +
+
+ {{ vote.user.getLevelAndNumber() }} +
+ +
+ {{ 'Vote weight' | translate }}: {{ vote.user.vote_weight }} +
+
+
{{ 'Anonymous' | translate }}
diff --git a/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.ts b/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.ts index b30ec81c3..cdd6072d8 100644 --- a/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.ts +++ b/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.ts @@ -10,6 +10,7 @@ import { OperatorService } from 'app/core/core-services/operator.service'; import { MotionPollRepositoryService } from 'app/core/repositories/motions/motion-poll-repository.service'; import { MotionVoteRepositoryService } from 'app/core/repositories/motions/motion-vote-repository.service'; import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service'; +import { ConfigService } from 'app/core/ui-services/config.service'; import { PromptService } from 'app/core/ui-services/prompt.service'; import { ViewMotion } from 'app/site/motions/models/view-motion'; import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll'; @@ -41,6 +42,8 @@ export class MotionPollDetailComponent extends BasePollDetailComponent('users_activate_vote_weight') + .subscribe(active => (this.isVoteWeightActive = active)); } protected createVotesData(): void { diff --git a/client/src/app/site/motions/services/motion-pdf.service.ts b/client/src/app/site/motions/services/motion-pdf.service.ts index b9997c685..6c751db0b 100644 --- a/client/src/app/site/motions/services/motion-pdf.service.ts +++ b/client/src/app/site/motions/services/motion-pdf.service.ts @@ -381,7 +381,12 @@ export class MotionPdfService { column1.push(`${votingOption}:`); if (value.showPercent) { const resultInPercent = this.motionPollService.getVoteValueInPercent(value.amount, poll); - column2.push(`(${resultInPercent})`); + // hard check for "null" since 0 is a valid number in this case + if (resultInPercent !== null) { + column2.push(`(${resultInPercent})`); + } else { + column2.push(''); + } } else { column2.push(''); } diff --git a/client/src/app/site/users/components/user-detail/user-detail.component.html b/client/src/app/site/users/components/user-detail/user-detail.component.html index 920b15fa1..27658f3c2 100644 --- a/client/src/app/site/users/components/user-detail/user-detail.component.html +++ b/client/src/app/site/users/components/user-detail/user-detail.component.html @@ -111,7 +111,13 @@
- + + - + + + + + + +
@@ -275,6 +299,12 @@
+ +
+

{{ 'Vote weight' | translate }}

+ {{ user.vote_weight }} +
+

{{ 'Initial password' | translate }}

diff --git a/client/src/app/site/users/components/user-detail/user-detail.component.ts b/client/src/app/site/users/components/user-detail/user-detail.component.ts index 94a5ca3be..89dcbff92 100644 --- a/client/src/app/site/users/components/user-detail/user-detail.component.ts +++ b/client/src/app/site/users/components/user-detail/user-detail.component.ts @@ -11,10 +11,12 @@ import { ConstantsService } from 'app/core/core-services/constants.service'; import { OperatorService } from 'app/core/core-services/operator.service'; import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service'; import { UserRepositoryService } from 'app/core/repositories/users/user-repository.service'; +import { ConfigService } from 'app/core/ui-services/config.service'; import { PromptService } from 'app/core/ui-services/prompt.service'; import { genders } from 'app/shared/models/users/user'; import { OneOfValidator } from 'app/shared/validators/one-of-validator'; import { BaseViewComponent } from 'app/site/base/base-view'; +import { PollService } from 'app/site/polls/services/poll.service'; import { UserPdfExportService } from '../../services/user-pdf-export.service'; import { ViewGroup } from '../../models/view-group'; import { ViewUser } from '../../models/view-user'; @@ -76,6 +78,12 @@ export class UserDetailComponent extends BaseViewComponent implements OnInit { private userBackends: UserBackends | null = null; + private isVoteWeightActive: boolean; + + public get showVoteWeight(): boolean { + return this.pollService.isElectronicVotingEnabled && this.isVoteWeightActive; + } + /** * Constructor for user * @@ -103,12 +111,17 @@ export class UserDetailComponent extends BaseViewComponent implements OnInit { private promptService: PromptService, private pdfService: UserPdfExportService, private groupRepo: GroupRepositoryService, - private constantsService: ConstantsService + private constantsService: ConstantsService, + private pollService: PollService, + configService: ConfigService ) { super(title, translate, matSnackBar); this.createForm(); this.constantsService.get('UserBackends').subscribe(backends => (this.userBackends = backends)); + configService + .get('users_activate_vote_weight') + .subscribe(active => (this.isVoteWeightActive = active)); this.groupRepo.getViewModelListObservableWithoutDefaultGroup().subscribe(this.groups); } @@ -157,6 +170,7 @@ export class UserDetailComponent extends BaseViewComponent implements OnInit { gender: [''], structure_level: [''], number: [''], + vote_weight: [], about_me: [''], groups_id: [''], is_present: [true], diff --git a/client/src/app/site/users/components/user-import/user-import-list.component.html b/client/src/app/site/users/components/user-import/user-import-list.component.html index 7af5c7bb3..841c2e618 100644 --- a/client/src/app/site/users/components/user-import/user-import-list.component.html +++ b/client/src/app/site/users/components/user-import/user-import-list.component.html @@ -51,20 +51,9 @@
- {{ 'Title' | translate }}, {{ 'Given name' | translate }}, {{ 'Surname' | translate }}, {{ 'Structure level' | translate }}, {{ 'Participant number' | translate }}, {{ 'Groups' | translate }}, {{ 'Comment' | translate }}, {{ 'Is active' | translate }}, {{ 'Is present' | translate }}, {{ 'Is committee' | translate }}, {{ 'Initial password' | translate }}, {{ 'Email' | translate }}, {{ 'Username' | translate }}, {{ 'Gender' | translate }} + + {{ entry | translate }}, +
  • @@ -282,35 +271,46 @@ + {{ 'Is present' | translate }} + {{ 'Is committee' | translate }} + {{ 'Initial password' | translate }} {{ entry.newEntry.default_password }} + {{ 'Email' | translate }} {{ entry.newEntry.email }} + {{ 'Username' | translate }} {{ entry.newEntry.username }} + {{ 'Gender' | translate }} {{ entry.newEntry.gender }} + + {{ 'Vote weight' | translate }} + {{ entry.newEntry.vote_weight }} + + diff --git a/client/src/app/site/users/components/user-import/user-import-list.component.ts b/client/src/app/site/users/components/user-import/user-import-list.component.ts index 488d2c065..be4987692 100644 --- a/client/src/app/site/users/components/user-import/user-import-list.component.ts +++ b/client/src/app/site/users/components/user-import/user-import-list.component.ts @@ -21,6 +21,24 @@ import { UserImportService } from '../../services/user-import.service'; export class UserImportListComponent extends BaseImportListComponentDirective { public textAreaForm: FormGroup; + public headerRow = [ + 'Title', + 'Given name', + 'Surname', + 'Structure level', + 'Participant number', + 'Groups', + 'Comment', + 'Is active', + 'Is present', + 'Is a committee', + 'Initial password', + 'Email', + 'Username', + 'Gender', + 'Vote weight' + ]; + /** * Constructor for list view bases * @@ -47,22 +65,6 @@ export class UserImportListComponent extends BaseImportListComponentDirective -
    +
    - {{ name }} -
    -
    - account_balance - block +
    +
    {{ user.short_name }}
    +
    + {{ 'Vote weight' | translate }}: {{ user.vote_weight }} +
    +
    +
    + + account_balance + + + block + +
    diff --git a/client/src/app/site/users/components/user-list/user-list.component.scss b/client/src/app/site/users/components/user-list/user-list.component.scss index 60e0ed38d..ed3df07ec 100644 --- a/client/src/app/site/users/components/user-list/user-list.component.scss +++ b/client/src/app/site/users/components/user-list/user-list.component.scss @@ -29,5 +29,6 @@ } .icon-group { + margin-left: 1em; z-index: 3; } diff --git a/client/src/app/site/users/components/user-list/user-list.component.ts b/client/src/app/site/users/components/user-list/user-list.component.ts index afb61c40d..7350fb726 100644 --- a/client/src/app/site/users/components/user-list/user-list.component.ts +++ b/client/src/app/site/users/components/user-list/user-list.component.ts @@ -19,6 +19,7 @@ import { PromptService } from 'app/core/ui-services/prompt.service'; import { genders } from 'app/shared/models/users/user'; import { infoDialogSettings } from 'app/shared/utils/dialog-settings'; import { BaseListViewComponent } from 'app/site/base/base-list-view'; +import { PollService } from 'app/site/polls/services/poll.service'; import { UserFilterListService } from '../../services/user-filter-list.service'; import { UserPdfExportService } from '../../services/user-pdf-export.service'; import { UserSortListService } from '../../services/user-sort-list.service'; @@ -98,6 +99,8 @@ export class UserListComponent extends BaseListViewComponent implement return this._presenceViewConfigured && this.operator.hasPerms('users.can_manage'); } + private isVoteWeightActive: boolean; + /** * Helper to check for main button permissions * @@ -107,6 +110,10 @@ export class UserListComponent extends BaseListViewComponent implement return this.operator.hasPerms('users.can_manage'); } + public get showVoteWeight(): boolean { + return this.pollService.isElectronicVotingEnabled && this.isVoteWeightActive; + } + /** * Define the columns to show */ @@ -173,13 +180,15 @@ export class UserListComponent extends BaseListViewComponent implement public sortService: UserSortListService, config: ConfigService, private userPdf: UserPdfExportService, - private dialog: MatDialog + private dialog: MatDialog, + private pollService: PollService ) { super(titleService, translate, matSnackBar, storage); // enable multiSelect for this listView this.canMultiSelect = true; config.get('users_enable_presence_view').subscribe(state => (this._presenceViewConfigured = state)); + config.get('users_activate_vote_weight').subscribe(active => (this.isVoteWeightActive = active)); config.get(this.selfPresentConfStr).subscribe(allowed => (this.allowSelfSetPresent = allowed)); } diff --git a/client/src/app/site/users/services/user-filter-list.service.ts b/client/src/app/site/users/services/user-filter-list.service.ts index 520b7f9bd..8c4e7765a 100644 --- a/client/src/app/site/users/services/user-filter-list.service.ts +++ b/client/src/app/site/users/services/user-filter-list.service.ts @@ -85,6 +85,14 @@ export class UserFilterListService extends BaseFilterListService { { condition: true, label: this.translate.instant('Got an email') }, { condition: false, label: this.translate.instant("Didn't get an email") } ] + }, + { + property: 'isVoteWeightOne', + label: this.translate.instant('Vote Weight'), + options: [ + { condition: false, label: this.translate.instant('Has changed vote weight') }, + { condition: true, label: this.translate.instant('Has unchanged vote weight') } + ] } ]; return staticFilterOptions.concat(this.userGroupFilterOptions); diff --git a/client/src/app/site/users/services/user-import.service.ts b/client/src/app/site/users/services/user-import.service.ts index dcdfbeb85..6e419f87d 100644 --- a/client/src/app/site/users/services/user-import.service.ts +++ b/client/src/app/site/users/services/user-import.service.ts @@ -33,7 +33,8 @@ export class UserImportService extends BaseImportService { 'default_password', 'email', 'username', - 'gender' + 'gender', + 'vote_weight' ]; /** @@ -117,6 +118,13 @@ export class UserImportService extends BaseImportService { case 'number': newViewUser.number = line[idx]; break; + case 'vote_weight': + if (!line[idx]) { + newViewUser[this.expectedHeader[idx]] = 1; + } else { + newViewUser[this.expectedHeader[idx]] = line[idx]; + } + break; default: newViewUser[this.expectedHeader[idx]] = line[idx]; break; diff --git a/client/src/app/site/users/services/user-sort-list.service.ts b/client/src/app/site/users/services/user-sort-list.service.ts index 22023ab90..e1805b5f0 100644 --- a/client/src/app/site/users/services/user-sort-list.service.ts +++ b/client/src/app/site/users/services/user-sort-list.service.ts @@ -31,6 +31,7 @@ export class UserSortListService extends BaseSortListService { { property: 'is_committee', label: 'Is committee' }, { property: 'number', label: 'Participant number' }, { property: 'structure_level', label: 'Structure level' }, + { property: 'vote_weight', label: 'Vote weight' }, { property: 'comment' } // TODO email send? ]; diff --git a/openslides/assignments/views.py b/openslides/assignments/views.py index 6aaba3356..48c5c0727 100644 --- a/openslides/assignments/views.py +++ b/openslides/assignments/views.py @@ -3,6 +3,7 @@ from decimal import Decimal from django.contrib.auth import get_user_model from django.db import transaction +from openslides.core.config import config from openslides.poll.views import BaseOptionViewSet, BasePollViewSet, BaseVoteViewSet from openslides.utils.auth import has_perm from openslides.utils.autoupdate import inform_changed_data @@ -489,14 +490,20 @@ class AssignmentPollViewSet(BasePollViewSet): # skip creating votes with empty weights if amount == 0: continue + weight = Decimal(amount) + if config["users_activate_vote_weight"]: + weight *= user.vote_weight vote = AssignmentVote.objects.create( - option=option, user=user, weight=Decimal(amount), value="Y" + option=option, user=user, weight=weight, value="Y" ) inform_changed_data(vote, no_delete_on_restriction=True) else: # global_no or global_abstain option = options[0] + weight = ( + user.vote_weight if config["users_activate_vote_weight"] else Decimal(1) + ) vote = AssignmentVote.objects.create( - option=option, user=user, weight=Decimal(1), value=data + option=option, user=user, weight=weight, value=data ) inform_changed_data(vote, no_delete_on_restriction=True) inform_changed_data(option) @@ -512,13 +519,15 @@ class AssignmentPollViewSet(BasePollViewSet): vote_user is the one put into the vote """ options = poll.get_options() + weight = ( + check_user.vote_weight + if config["users_activate_vote_weight"] + else Decimal(1) + ) for option_id, result in data.items(): option = options.get(pk=option_id) vote = AssignmentVote.objects.create( - option=option, - user=vote_user, - value=result, - weight=check_user.vote_weight, + option=option, user=vote_user, value=result, weight=weight, ) inform_changed_data(vote, no_delete_on_restriction=True) inform_changed_data(option, no_delete_on_restriction=True) diff --git a/openslides/motions/views.py b/openslides/motions/views.py index 8f9189e18..a7a699242 100644 --- a/openslides/motions/views.py +++ b/openslides/motions/views.py @@ -1,3 +1,4 @@ +from decimal import Decimal from typing import List, Set import jsonschema @@ -1227,16 +1228,20 @@ class MotionPollViewSet(BasePollViewSet): VotedModel.objects.create(motionpoll=poll, user=user) def handle_named_vote(self, data, poll, user): - self.handle_named_and_pseudoanonymous_vote(data, user.vote_weight, user, poll) + self.handle_named_and_pseudoanonymous_vote(data, user, user, poll) def handle_pseudoanonymous_vote(self, data, poll, user): - self.handle_named_and_pseudoanonymous_vote(data, user.vote_weight, None, poll) + self.handle_named_and_pseudoanonymous_vote(data, user, None, poll) - def handle_named_and_pseudoanonymous_vote(self, data, weight, user, poll): + def handle_named_and_pseudoanonymous_vote(self, data, weight_user, vote_user, poll): option = poll.options.get() - vote = MotionVote.objects.create(user=user, option=option) + vote = MotionVote.objects.create(user=vote_user, option=option) vote.value = data - vote.weight = weight + vote.weight = ( + weight_user.vote_weight + if config["users_activate_vote_weight"] + else Decimal(1) + ) vote.save(no_delete_on_restriction=True) inform_changed_data(option) diff --git a/openslides/poll/models.py b/openslides/poll/models.py index 305535b41..555d0494b 100644 --- a/openslides/poll/models.py +++ b/openslides/poll/models.py @@ -5,6 +5,7 @@ from django.conf import settings from django.core.validators import MinValueValidator from django.db import models +from ..core.config import config from ..utils.autoupdate import inform_changed_data, inform_deleted_data from ..utils.models import SET_NULL_AND_AUTOUPDATE @@ -184,7 +185,7 @@ class BasePoll(models.Model): if self.type == self.TYPE_ANALOG: return self.db_votesvalid else: - return Decimal(self.amount_users_voted()) + return Decimal(self.amount_users_voted_with_individual_weight()) def set_votesvalid(self, value): if self.type != self.TYPE_ANALOG: @@ -210,7 +211,7 @@ class BasePoll(models.Model): if self.type == self.TYPE_ANALOG: return self.db_votescast else: - return Decimal(self.amount_users_voted()) + return Decimal(self.amount_users_voted_with_individual_weight()) def set_votescast(self, value): if self.type != self.TYPE_ANALOG: @@ -219,8 +220,11 @@ class BasePoll(models.Model): votescast = property(get_votescast, set_votescast) - def amount_users_voted(self): - return len(self.voted.all()) + def amount_users_voted_with_individual_weight(self): + if config["users_activate_vote_weight"]: + return sum(user.vote_weight for user in self.voted.all()) + else: + return len(self.voted.all()) def create_options(self): """ Should be called after creation of this model. """ diff --git a/openslides/users/config_variables.py b/openslides/users/config_variables.py index e05a009cc..52d2d04f8 100644 --- a/openslides/users/config_variables.py +++ b/openslides/users/config_variables.py @@ -44,6 +44,15 @@ def get_config_variables(): group="Participants", ) + yield ConfigVariable( + name="users_activate_vote_weight", + default_value=False, + input_type="boolean", + label="Activate vote weight", + weight=513, + group="Participants", + ) + # PDF yield ConfigVariable( diff --git a/tests/integration/assignments/test_polls.py b/tests/integration/assignments/test_polls.py index 6d0575a06..579e514fb 100644 --- a/tests/integration/assignments/test_polls.py +++ b/tests/integration/assignments/test_polls.py @@ -15,6 +15,7 @@ from openslides.assignments.models import ( AssignmentPoll, AssignmentVote, ) +from openslides.core.config import config from openslides.poll.models import BasePoll from openslides.utils.auth import get_group_model from openslides.utils.autoupdate import inform_changed_data @@ -982,6 +983,7 @@ class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass): self.assertEqual(poll.votesinvalid, Decimal("0")) self.assertEqual(poll.votescast, Decimal("1")) self.assertEqual(poll.state, AssignmentPoll.STATE_STARTED) + self.assertEqual(poll.amount_users_voted_with_individual_weight(), Decimal("1")) option1 = poll.options.get(pk=1) option2 = poll.options.get(pk=2) option3 = poll.options.get(pk=3) @@ -995,6 +997,43 @@ class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass): self.assertEqual(option3.no, Decimal("0")) self.assertEqual(option3.abstain, Decimal("1")) + def test_vote_with_voteweight(self): + config["users_activate_vote_weight"] = True + self.admin.vote_weight = weight = Decimal("4.2") + self.admin.save() + self.add_candidate() + self.add_candidate() + self.start_poll() + response = self.client.post( + reverse("assignmentpoll-vote", args=[self.poll.pk]), + {"1": "Y", "2": "N", "3": "A"}, + ) + self.assertHttpStatusVerbose(response, status.HTTP_200_OK) + self.assertEqual(AssignmentVote.objects.count(), 3) + poll = AssignmentPoll.objects.get() + self.assertEqual(poll.votesvalid, weight) + self.assertEqual(poll.votesinvalid, Decimal("0")) + self.assertEqual(poll.votescast, weight) + self.assertEqual(poll.state, AssignmentPoll.STATE_STARTED) + self.assertEqual(poll.amount_users_voted_with_individual_weight(), weight) + option1 = poll.options.get(pk=1) + option2 = poll.options.get(pk=2) + option3 = poll.options.get(pk=3) + self.assertEqual(option1.yes, weight) + self.assertEqual(option1.no, Decimal("0")) + self.assertEqual(option1.abstain, Decimal("0")) + self.assertEqual(option2.yes, Decimal("0")) + self.assertEqual(option2.no, weight) + self.assertEqual(option2.abstain, Decimal("0")) + self.assertEqual(option3.yes, Decimal("0")) + self.assertEqual(option3.no, Decimal("0")) + self.assertEqual(option3.abstain, weight) + + def test_vote_without_voteweight(self): + self.admin.vote_weight = Decimal("4.2") + self.admin.save() + self.test_vote() + def test_change_vote(self): self.start_poll() response = self.client.post( @@ -2233,7 +2272,7 @@ class PseudoanonymizeAssignmentPoll(TestCase): self.assertHttpStatusVerbose(response, status.HTTP_200_OK) poll = AssignmentPoll.objects.get() self.assertEqual(poll.get_votes().count(), 2) - self.assertEqual(poll.amount_users_voted(), 2) + self.assertEqual(poll.amount_users_voted_with_individual_weight(), 2) self.assertEqual(poll.votesvalid, Decimal("2")) self.assertEqual(poll.votesinvalid, Decimal("0")) self.assertEqual(poll.votescast, Decimal("2")) diff --git a/tests/integration/motions/test_polls.py b/tests/integration/motions/test_polls.py index 245b33db0..3c2b46a91 100644 --- a/tests/integration/motions/test_polls.py +++ b/tests/integration/motions/test_polls.py @@ -763,6 +763,36 @@ class VoteMotionPollNamed(TestCase): self.assertEqual(option.abstain, Decimal("0")) vote = option.votes.get() self.assertEqual(vote.user, self.admin) + self.assertEqual(vote.weight, Decimal("1")) + + def test_vote_with_voteweight(self): + config["users_activate_vote_weight"] = True + self.start_poll() + self.make_admin_delegate() + self.make_admin_present() + self.admin.vote_weight = weight = Decimal("3.5") + self.admin.save() + response = self.client.post( + reverse("motionpoll-vote", args=[self.poll.pk]), "A" + ) + self.assertHttpStatusVerbose(response, status.HTTP_200_OK) + poll = MotionPoll.objects.get() + self.assertEqual(poll.votesvalid, weight) + self.assertEqual(poll.votesinvalid, Decimal("0")) + self.assertEqual(poll.votescast, weight) + self.assertEqual(poll.get_votes().count(), 1) + self.assertEqual(poll.amount_users_voted_with_individual_weight(), weight) + option = poll.options.get() + self.assertEqual(option.yes, Decimal("0")) + self.assertEqual(option.no, Decimal("0")) + self.assertEqual(option.abstain, weight) + vote = option.votes.get() + self.assertEqual(vote.weight, weight) + + def test_vote_without_voteweight(self): + self.admin.vote_weight = Decimal("3.5") + self.admin.save() + self.test_vote() def test_change_vote(self): self.start_poll() @@ -1155,7 +1185,7 @@ class VoteMotionPollPseudoanonymous(TestCase): self.assertEqual(poll.votesinvalid, Decimal("0")) self.assertEqual(poll.votescast, Decimal("1")) self.assertEqual(poll.get_votes().count(), 1) - self.assertEqual(poll.amount_users_voted(), 1) + self.assertEqual(poll.amount_users_voted_with_individual_weight(), 1) option = poll.options.get() self.assertEqual(option.yes, Decimal("0")) self.assertEqual(option.no, Decimal("1")) @@ -1378,7 +1408,7 @@ class PseudoanonymizeMotionPoll(TestCase): self.assertHttpStatusVerbose(response, status.HTTP_200_OK) poll = MotionPoll.objects.get() self.assertEqual(poll.get_votes().count(), 2) - self.assertEqual(poll.amount_users_voted(), 2) + self.assertEqual(poll.amount_users_voted_with_individual_weight(), 2) self.assertEqual(poll.votesvalid, Decimal("2")) self.assertEqual(poll.votesinvalid, Decimal("0")) self.assertEqual(poll.votescast, Decimal("2")) @@ -1446,7 +1476,7 @@ class ResetMotionPoll(TestCase): self.assertHttpStatusVerbose(response, status.HTTP_200_OK) poll = MotionPoll.objects.get() self.assertEqual(poll.get_votes().count(), 0) - self.assertEqual(poll.amount_users_voted(), 0) + self.assertEqual(poll.amount_users_voted_with_individual_weight(), 0) self.assertEqual(poll.votesvalid, None) self.assertEqual(poll.votesinvalid, None) self.assertEqual(poll.votescast, None)