diff --git a/poll/data/data_source/data_source.py b/poll/data/data_source/data_source.py index 991c790..6bf8f70 100644 --- a/poll/data/data_source/data_source.py +++ b/poll/data/data_source/data_source.py @@ -6,7 +6,7 @@ from poll.domain.model.poll.option import PollOptionInfo, PollOptionNumber from poll.domain.model.poll.poll import Poll, PollNumber from poll.domain.model.poll.publication import PollPublication -from poll.domain.model.poll.vote import OptionPollVote +from poll.domain.model.poll.vote import PollVote, OptionPollVote, OpenPollVote from poll.domain.model.user.state import State from poll.domain.model.user.user import User @@ -57,9 +57,15 @@ def exists_publication(self, publication: PollPublication) -> bool: def vote_option(self, vote: OptionPollVote): raise NotImplementedError() + def vote_open(self, vote: OpenPollVote): + raise NotImplementedError() + def unvote_option(self, vote: OptionPollVote): raise NotImplementedError() + def unvote_poll(self, vote: PollVote): + raise NotImplementedError() + def get_votes(self, poll: Poll, user: User) -> PollVotes: raise NotImplementedError() diff --git a/poll/data/data_source/sqlite/component/poll/vote/option.py b/poll/data/data_source/sqlite/component/poll/vote/option.py index 5fcca8b..8b2f7d0 100644 --- a/poll/data/data_source/sqlite/component/poll/vote/option.py +++ b/poll/data/data_source/sqlite/component/poll/vote/option.py @@ -94,6 +94,27 @@ )\ .build() +REMOVE_VOTES = Delete()\ + .table(POLL_VOTE_OPTION)\ + .where( + MultipleCondition( + AND, + Condition(USER, IN, + Select() + .fields(POLL_USER_ID) + .table(POLL_USER) + .where(Condition(POLL_USER_USER_ID, EQUAL, ":user_id")) + ), + Condition(PUBLICATION, IN, + Select() + .fields(PUBLICATION_ID) + .table(POLL_PUBLICATION) + .where(Condition(POLL_ID, EQUAL, ":poll_id")) + ) + ) + )\ + .build() + GET_USER_VOTES = Select()\ .fields(USER, PUBLICATION, OPTION)\ .table(POLL_VOTE_OPTION)\ @@ -142,6 +163,9 @@ def _get_next_number_for(self, vote: OptionPollVoteData) -> int: def unvote_option(self, user: User, poll: Poll, option: PollOption): self.statement(REMOVE_VOTE).execute(user_id=user.id, poll_id=poll.id, option=option.id) + def unvote_poll(self, user: User, poll: Poll): + self.statement(REMOVE_VOTES).execute(user_id=user.id, poll_id=poll.id) + def get_user_votes(self, poll: Poll, user: User) -> Iterable[OptionPollVoteData]: votes = self.statement(GET_USER_VOTES)\ .execute(poll_id=poll.id, user_id=user.id)\ diff --git a/poll/data/data_source/sqlite/model/poll/vote.py b/poll/data/data_source/sqlite/model/poll/vote.py index 0f4f0fe..bd590ed 100644 --- a/poll/data/data_source/sqlite/model/poll/vote.py +++ b/poll/data/data_source/sqlite/model/poll/vote.py @@ -13,3 +13,9 @@ class OptionPollVoteData(PollVoteData): def __init__(self, user: PollUserData, publication: PollPublicationData, option: PollOption): super().__init__(user, publication) self.option = option + + +class OpenPollVoteData(PollVoteData): + def __init__(self, user: PollUserData, publication: PollPublicationData, text: str): + super().__init__(user, publication) + self.text = text diff --git a/poll/data/data_source/sqlite/sqlite.py b/poll/data/data_source/sqlite/sqlite.py index 699afa6..dfe0a55 100644 --- a/poll/data/data_source/sqlite/sqlite.py +++ b/poll/data/data_source/sqlite/sqlite.py @@ -17,7 +17,7 @@ from poll.domain.model.poll.option import PollOptionInfo, PollOptionNumber from poll.domain.model.poll.poll import Poll, PollNumber from poll.domain.model.poll.publication import PollPublication -from poll.domain.model.poll.vote import OptionPollVote +from poll.domain.model.poll.vote import PollVote, OptionPollVote, OpenPollVote from poll.domain.model.user.state import State from poll.domain.model.user.user import User @@ -102,11 +102,18 @@ def vote_option(self, vote: OptionPollVote): vote = self.mappers.option_vote.map_option_vote(vote, poll_user_id) self.poll_vote_option.vote_option(vote) + def vote_open(self, vote: OpenPollVote): + raise NotImplementedError("unavailable yet") + def unvote_option(self, vote: OptionPollVote): poll = self.poll_publication.get_poll(vote.publication) option = self.poll_option.get_id(poll, vote.option) self.poll_vote_option.unvote_option(vote.user, poll, option) + def unvote_poll(self, vote: PollVote): + poll = self.poll_publication.get_poll(vote.publication) + self.poll_vote_option.unvote_poll(vote.user, poll) + def get_votes(self, poll: Poll, user: User) -> PollVotes: votes = self.poll_vote_option.get_user_votes(poll, user) return self.mappers.option_vote.unmap_option_votes(votes) diff --git a/poll/data/repository.py b/poll/data/repository.py index 14d7754..afcc0e8 100644 --- a/poll/data/repository.py +++ b/poll/data/repository.py @@ -10,7 +10,7 @@ from poll.domain.model.poll.option import PollOptionInfo, PollOptionNumber from poll.domain.model.poll.poll import PollNumber, Poll from poll.domain.model.poll.publication import PollPublication -from poll.domain.model.poll.vote import OptionPollVote +from poll.domain.model.poll.vote import PollVote, OptionPollVote, OpenPollVote from poll.domain.model.user.state import State from poll.domain.model.user.user import User from poll.domain.repository.poll.get import GetPollRepository @@ -104,12 +104,24 @@ def vote_option(self, vote: OptionPollVote): "vote_option" ) + def vote_open(self, vote: OpenPollVote): + self._with_result( + lambda: self.data_source.vote_open(vote), + "vote_open" + ) + def unvote_option(self, vote: OptionPollVote): self._with_result( lambda: self.data_source.unvote_option(vote), "unvote_option" ) + def unvote_poll(self, vote: PollVote): + self._with_result( + lambda: self.data_source.unvote_poll(vote), + "unvote_poll" + ) + def get_votes(self, poll: Poll, user: User) -> PollVotes: return self._with_result( lambda: self.data_source.get_votes(poll, user), diff --git a/poll/domain/interactors/poll/vote.py b/poll/domain/interactors/poll/vote.py index ff4a0ca..cd74a04 100644 --- a/poll/domain/interactors/poll/vote.py +++ b/poll/domain/interactors/poll/vote.py @@ -1,7 +1,7 @@ from poll.domain.check.unique_vote import UniqueVotePollCheck from poll.domain.model.poll.group.votes import OptionPollVotes from poll.domain.model.poll.poll import Poll -from poll.domain.model.poll.vote import PollVote, OptionPollVote +from poll.domain.model.poll.vote import PollVote, OptionPollVote, OpenPollVote from poll.domain.model.vote_result import VoteResult, VOTED, UNVOTED, CHANGED_VOTE from poll.domain.repository.poll.get import GetPollRepository from poll.domain.repository.poll.vote import VotePollRepository @@ -17,6 +17,8 @@ def vote(self, vote: PollVote) -> VoteResult: poll = self.get.get_from_publication(vote.publication) if isinstance(vote, OptionPollVote): return self._vote_option(poll, vote) + elif isinstance(vote, OpenPollVote): + return self._vote_open(poll, vote) else: raise Exception("unexpected vote type") @@ -36,3 +38,14 @@ def _vote_option(self, poll: Poll, vote: OptionPollVote) -> VoteResult: result = CHANGED_VOTE self.check.no_more_than_one_vote(previous_votes) return result + + def _vote_open(self, poll: Poll, vote: OpenPollVote) -> VoteResult: + previous_votes = self.repository_vote.get_votes(poll, vote.user) + result = VOTED + if not previous_votes.is_empty(): + previous_vote = previous_votes.first() + self.repository_vote.unvote_poll(previous_vote) + result = CHANGED_VOTE + self.repository_vote.vote_open(vote) + self.check.no_more_than_one_vote(previous_votes) + return result diff --git a/poll/domain/model/poll/full/poll.py b/poll/domain/model/poll/full/poll.py index 6b1cce6..f32f52f 100644 --- a/poll/domain/model/poll/full/poll.py +++ b/poll/domain/model/poll/full/poll.py @@ -1,5 +1,6 @@ from poll.domain.model.poll.full.options import FullPollOptions from poll.domain.model.poll.group.publications import PollPublications +from poll.domain.model.poll.group.votes import OpenPollVotes from poll.domain.model.poll.info import PollInfo @@ -13,3 +14,9 @@ class FullOptionPoll(FullPoll): def __init__(self, info: PollInfo, publications: PollPublications, options: FullPollOptions): super().__init__(info, publications) self.options = options + + +class FullOpenPoll(FullPoll): + def __init__(self, info: PollInfo, publications: PollPublications, votes: OpenPollVotes): + super().__init__(info, publications) + self.votes = votes diff --git a/poll/domain/model/poll/group/votes.py b/poll/domain/model/poll/group/votes.py index ea2e091..b035d70 100644 --- a/poll/domain/model/poll/group/votes.py +++ b/poll/domain/model/poll/group/votes.py @@ -1,6 +1,6 @@ from typing import Sequence -from poll.domain.model.poll.vote import PollVote, OptionPollVote +from poll.domain.model.poll.vote import PollVote, OptionPollVote, OpenPollVote class PollVotes: @@ -27,3 +27,9 @@ class OptionPollVotes(PollVotes): def __init__(self, votes: Sequence[OptionPollVote]): super().__init__(votes) self.votes = votes # fix type hinting + + +class OpenPollVotes(PollVotes): + def __init__(self, votes: Sequence[OpenPollVote]): + super().__init__(votes) + self.votes = votes # fix type hinting diff --git a/poll/domain/model/poll/settings/anonymity.py b/poll/domain/model/poll/settings/anonymity.py index 40c1182..ce4f5c4 100644 --- a/poll/domain/model/poll/settings/anonymity.py +++ b/poll/domain/model/poll/settings/anonymity.py @@ -8,3 +8,5 @@ def __init__(self, anonymity: int): PERSONAL = PollAnonymity(0) +ANONYMOUS = PollAnonymity(1) +ANONYMOUS_AND_ONLY_TO_CREATOR = PollAnonymity(2) diff --git a/poll/domain/model/poll/settings/type.py b/poll/domain/model/poll/settings/type.py index 670663c..695db61 100644 --- a/poll/domain/model/poll/settings/type.py +++ b/poll/domain/model/poll/settings/type.py @@ -8,3 +8,7 @@ def __init__(self, poll_type: int): SINGLE_VOTE = PollType(0) +MULTI_VOTE = PollType(1) +MULTI_VOTE_LIMITED = PollType(2) +OPEN = PollType(3) +RANGE = PollType(4) diff --git a/poll/domain/model/poll/vote.py b/poll/domain/model/poll/vote.py index 97140d4..b642d3e 100644 --- a/poll/domain/model/poll/vote.py +++ b/poll/domain/model/poll/vote.py @@ -13,3 +13,9 @@ class OptionPollVote(PollVote): def __init__(self, user: PollUser, publication: PollPublication, option: PollOptionNumber): super().__init__(user, publication) self.option = option + + +class OpenPollVote(PollVote): + def __init__(self, user: PollUser, publication: PollPublication, text: str): + super().__init__(user, publication) + self.text = text diff --git a/poll/domain/model/user/user.py b/poll/domain/model/user/user.py index 8fd6e3c..0bf8cd5 100644 --- a/poll/domain/model/user/user.py +++ b/poll/domain/model/user/user.py @@ -5,3 +5,8 @@ class User(Comparable): def __init__(self, user_id: int): super().__init__(user_id, User) self.id = user_id + + +class AnonymousUser(User): + def __init__(self, anonymized_user_id: int): + super().__init__(anonymized_user_id) diff --git a/poll/domain/repository/poll/vote.py b/poll/domain/repository/poll/vote.py index e5296b4..c41aab1 100644 --- a/poll/domain/repository/poll/vote.py +++ b/poll/domain/repository/poll/vote.py @@ -1,6 +1,6 @@ from poll.domain.model.poll.group.votes import PollVotes from poll.domain.model.poll.poll import Poll -from poll.domain.model.poll.vote import OptionPollVote +from poll.domain.model.poll.vote import OpenPollVote, OptionPollVote, PollVote from poll.domain.model.user.user import User @@ -8,8 +8,14 @@ class VotePollRepository: def vote_option(self, vote: OptionPollVote): raise NotImplementedError() + def vote_open(self, vote: OpenPollVote): + raise NotImplementedError() + def unvote_option(self, vote: OptionPollVote): raise NotImplementedError() + def unvote_poll(self, vote: PollVote): + raise NotImplementedError() + def get_votes(self, poll: Poll, user: User) -> PollVotes: raise NotImplementedError() diff --git a/poll/presentation/model/mapper/settings.py b/poll/presentation/model/mapper/settings.py index b51faf4..715efd5 100644 --- a/poll/presentation/model/mapper/settings.py +++ b/poll/presentation/model/mapper/settings.py @@ -1,15 +1,21 @@ -from poll.domain.model.poll.settings.anonymity import PERSONAL, PollAnonymity +from poll.domain.model.poll.settings.anonymity import PERSONAL, ANONYMOUS, ANONYMOUS_AND_ONLY_TO_CREATOR, PollAnonymity from poll.domain.model.poll.settings.settings import PollSettings -from poll.domain.model.poll.settings.type import PollType, SINGLE_VOTE +from poll.domain.model.poll.settings.type import PollType, SINGLE_VOTE, MULTI_VOTE, MULTI_VOTE_LIMITED, OPEN, RANGE from poll.presentation.model.poll.settings import PollTypeViewModel, PollAnonymityViewModel, PollSettingsViewModel POLL_TYPES = { - SINGLE_VOTE: "single vote" + SINGLE_VOTE: "single vote", + MULTI_VOTE: "doodle", + MULTI_VOTE_LIMITED: "limited doodle", + OPEN: "board", + RANGE: "choose in a range" } POLL_ANONYMITIES = { - PERSONAL: "personal" + PERSONAL: "personal", + ANONYMOUS: "anonymous", + ANONYMOUS_AND_ONLY_TO_CREATOR: "anonymous and only creator can see results" }