From f4f3bfe10322aa808cd14f0a12ed5ae89b7d5b43 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Sat, 18 May 2024 15:39:13 -0400 Subject: [PATCH 01/11] Edit database to add a type field to Votes. --- database_setup/database.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/database_setup/database.sql b/database_setup/database.sql index bab1494..b57e842 100644 --- a/database_setup/database.sql +++ b/database_setup/database.sql @@ -25,8 +25,9 @@ CREATE TABLE Votes( ElectionCycle varchar(30) not null, VoterId bigint unsigned not null, VotedForId bigint unsigned, + Type varchar(14) check("Type" in ('Council','DiscordManager','ClanLeader')), FOREIGN KEY (ElectionCycle) REFERENCES ElectionInformation(ElectionCycle), - PRIMARY KEY (ElectionCycle, VoterId) + PRIMARY KEY (ElectionCycle, VoterId, Type) ); CREATE TABLE UserBirthday ( From 475170025a66704f062cfc07f66c551dd04f698e Mon Sep 17 00:00:00 2001 From: imcompiling Date: Sat, 18 May 2024 15:39:13 -0400 Subject: [PATCH 02/11] Edit database to add a type field to Votes. --- database_setup/database.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/database_setup/database.sql b/database_setup/database.sql index bab1494..3c0809e 100644 --- a/database_setup/database.sql +++ b/database_setup/database.sql @@ -25,8 +25,9 @@ CREATE TABLE Votes( ElectionCycle varchar(30) not null, VoterId bigint unsigned not null, VotedForId bigint unsigned, + Type int unsigned not null, FOREIGN KEY (ElectionCycle) REFERENCES ElectionInformation(ElectionCycle), - PRIMARY KEY (ElectionCycle, VoterId) + PRIMARY KEY (ElectionCycle, VoterId, Type) ); CREATE TABLE UserBirthday ( From 2efd380a7e0c388fba3f6383c92f3faad890df77 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Sat, 18 May 2024 16:26:11 -0400 Subject: [PATCH 03/11] Add VoteType Enum. --- utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/utils.py b/utils.py index 75539cc..f852437 100644 --- a/utils.py +++ b/utils.py @@ -4,6 +4,7 @@ File contains util functions. import os from datetime import datetime +from enum import Enum from typing import Iterable import nextcord @@ -24,6 +25,8 @@ REPO_DIR = "./dakiny-messages" MESSAGES_DIR = f"{REPO_DIR}/messages" TEMPLATES_DIR = f"{REPO_DIR}/message_templates" +VoteType = Enum("VoteType", ["COUNCIL", "DISCORDMANAGER", "CLANLEADER"]) + def get_datetime(day: str) -> datetime: """ From 1d14f8171d50563b52c22b6314a33ad311c42298 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Sat, 18 May 2024 16:34:33 -0400 Subject: [PATCH 04/11] Add vote type to one statement. --- classes/election_cycle.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/classes/election_cycle.py b/classes/election_cycle.py index fe845d5..2f3aac0 100644 --- a/classes/election_cycle.py +++ b/classes/election_cycle.py @@ -22,6 +22,7 @@ from database_setup.sql_data_classes import ( ) from utils import ( TEMPLATES_DIR, + VoteType, build_highcouncil_votes_str, build_votes_str, edit_clan_council_info, @@ -294,7 +295,11 @@ class ElectionCycle: session.commit() # add the highcouncil role to everyone with 3 or more votes. set elected to true in database - stmt = select(Votes.VotedForId).where(Votes.ElectionCycle == election_name) + stmt = ( + select(Votes.VotedForId) + .where(Votes.ElectionCycle == election_name) + .where(Votes.Type == VoteType.COUNCIL.value) + ) vote_count = collections.Counter(session.scalars(stmt)) for user_id, votes in vote_count.items(): From 0dcdd038b8b7766866db6a8b3a8f14ee1656bf00 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Sat, 18 May 2024 23:08:01 -0400 Subject: [PATCH 05/11] Make the VoteType StrEnum and edit column in database to enforce only 3 values. --- database_setup/database.sql | 2 +- utils.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/database_setup/database.sql b/database_setup/database.sql index 3c0809e..03ad9cb 100644 --- a/database_setup/database.sql +++ b/database_setup/database.sql @@ -25,7 +25,7 @@ CREATE TABLE Votes( ElectionCycle varchar(30) not null, VoterId bigint unsigned not null, VotedForId bigint unsigned, - Type int unsigned not null, + Type varchar(14) not null DEFAULT 'council' check("Type" in ('council', 'discordmanager', 'clanleader')), FOREIGN KEY (ElectionCycle) REFERENCES ElectionInformation(ElectionCycle), PRIMARY KEY (ElectionCycle, VoterId, Type) ); diff --git a/utils.py b/utils.py index f852437..51f87f2 100644 --- a/utils.py +++ b/utils.py @@ -4,7 +4,7 @@ File contains util functions. import os from datetime import datetime -from enum import Enum +from enum import StrEnum from typing import Iterable import nextcord @@ -25,7 +25,7 @@ REPO_DIR = "./dakiny-messages" MESSAGES_DIR = f"{REPO_DIR}/messages" TEMPLATES_DIR = f"{REPO_DIR}/message_templates" -VoteType = Enum("VoteType", ["COUNCIL", "DISCORDMANAGER", "CLANLEADER"]) +VoteType = StrEnum("VoteType", ["COUNCIL", "DISCORDMANAGER", "CLANLEADER"]) def get_datetime(day: str) -> datetime: From 44c3961c468292087cae9c6bd780e7790b3937e1 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Sun, 19 May 2024 10:31:16 -0400 Subject: [PATCH 06/11] Use VoteType in election_cycle --- classes/election_cycle.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/classes/election_cycle.py b/classes/election_cycle.py index 2f3aac0..8eac21b 100644 --- a/classes/election_cycle.py +++ b/classes/election_cycle.py @@ -26,9 +26,7 @@ from utils import ( build_highcouncil_votes_str, build_votes_str, edit_clan_council_info, - get_cl_election_name, get_datetime, - get_dm_election_name, get_election_name, ) @@ -298,7 +296,7 @@ class ElectionCycle: stmt = ( select(Votes.VotedForId) .where(Votes.ElectionCycle == election_name) - .where(Votes.Type == VoteType.COUNCIL.value) + .where(Votes.Type == VoteType.COUNCIL) ) vote_count = collections.Counter(session.scalars(stmt)) @@ -363,11 +361,13 @@ class ElectionCycle: self.config.params.clan_council_role_id ) + election_cycle = get_election_name(self.config) + with Session(engine) as session: # role management stmt = ( select(CouncilMembers.MemberId) - .where(CouncilMembers.ElectionCycle == get_election_name(self.config)) + .where(CouncilMembers.ElectionCycle == election_cycle) .where(CouncilMembers.Elected == true()) ) @@ -384,14 +384,19 @@ class ElectionCycle: # announce high council members # ties are undefined behavior. - dm_election = get_dm_election_name(self.config) - cl_election = get_cl_election_name(self.config) - stmt = select(Votes.VotedForId).where(Votes.ElectionCycle == dm_election) + stmt = ( + select(Votes.VotedForId) + .where(Votes.ElectionCycle == election_cycle) + .where(Votes.Type == VoteType.DISCORDMANAGER) + ) dm_votes = collections.Counter(session.scalars(stmt)) - stmt = select(Votes.VotedForId).where(Votes.ElectionCycle == cl_election) + stmt = ( + select(Votes.VotedForId) + .where(Votes.ElectionCycle == election_cycle) + .where(Votes.Type == VoteType.CLANLEADER) + ) cl_votes = collections.Counter(session.scalars(stmt)) - election_cycle = get_election_name(self.config) try: dm_winner = max(dm_votes, key=dm_votes.get) stmt = ( From e93f37dcf0fec051fbb8592feddc5854182d70cb Mon Sep 17 00:00:00 2001 From: imcompiling Date: Sun, 19 May 2024 10:38:10 -0400 Subject: [PATCH 07/11] Use VoteType in voting highcouncil views. --- classes/voting_highcouncil_views.py | 34 ++++++++++++++--------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/classes/voting_highcouncil_views.py b/classes/voting_highcouncil_views.py index 3899b57..2a36b70 100644 --- a/classes/voting_highcouncil_views.py +++ b/classes/voting_highcouncil_views.py @@ -17,12 +17,7 @@ from database_setup.sql_data_classes import ( Votes, engine, ) -from utils import ( - build_highcouncil_votes_str, - get_cl_election_name, - get_dm_election_name, - get_election_name, -) +from utils import VoteType, build_highcouncil_votes_str, get_election_name class VoteSelect(nextcord.ui.StringSelect): @@ -42,17 +37,17 @@ class VoteSelect(nextcord.ui.StringSelect): # decide on election type match self.election_type: case "cl": - election_name: str = get_cl_election_name(self.config) + election_type: str = VoteType.CLANLEADER case "dm": - election_name: str = get_dm_election_name(self.config) + election_type: str = VoteType.DISCORDMANAGER case _: # should never happen raise ValueError('election type was not "cl" or "dm"') - cycle: str = get_election_name(self.config) + election_name: str = get_election_name(self.config) with Session(engine) as session: stmt = select(ElectionInformation.HighCouncilVoteMsgId).where( - ElectionInformation.ElectionCycle == cycle + ElectionInformation.ElectionCycle == election_name ) vote_msg_id = session.scalar(stmt) @@ -65,6 +60,7 @@ class VoteSelect(nextcord.ui.StringSelect): ElectionCycle=election_name, VoterId=interaction.user.id, VotedForId=voted_for, + Type=election_type, ) try: @@ -72,11 +68,13 @@ class VoteSelect(nextcord.ui.StringSelect): session.commit() except exc.IntegrityError: + # user already has a vote in the election and election type session.rollback() stmt = ( update(Votes) .where(Votes.ElectionCycle == election_name) .where(Votes.VoterId == interaction.user.id) + .where(Votes.Type == election_type) .values(VotedForId=voted_for) ) session.execute(stmt) @@ -135,20 +133,20 @@ class VoteHighcouncilView(nextcord.ui.View): ) return - cycle: str = get_election_name(self.config) - election: str = get_cl_election_name(self.config) + election_name: str = get_election_name(self.config) with Session(engine) as session: stmt = ( select(Votes.VotedForId) - .where(Votes.ElectionCycle == election) + .where(Votes.ElectionCycle == election_name) .where(Votes.VoterId == interaction.user.id) + .where(Votes.Type == VoteType.CLANLEADER) ) voted_for = session.scalar(stmt) stmt = ( select(CouncilMembers.MemberId) - .where(CouncilMembers.ElectionCycle == cycle) + .where(CouncilMembers.ElectionCycle == election_name) .filter(CouncilMembers.ClanLeaderSpeechId.is_not(None)) ) @@ -213,20 +211,20 @@ class VoteHighcouncilView(nextcord.ui.View): ) return - cycle: str = get_election_name(self.config) - election: str = get_dm_election_name(self.config) + election_name: str = get_election_name(self.config) with Session(engine) as session: stmt = ( select(Votes.VotedForId) - .where(Votes.ElectionCycle == election) + .where(Votes.ElectionCycle == election_name) .where(Votes.VoterId == interaction.user.id) + .where(Votes.Type == VoteType.DISCORDMANAGER) ) voted_for = session.scalar(stmt) stmt = ( select(CouncilMembers.MemberId) - .where(CouncilMembers.ElectionCycle == cycle) + .where(CouncilMembers.ElectionCycle == election_name) .filter(CouncilMembers.DiscordManagerSpeechId.is_not(None)) ) From 0caa41cc9036b771ffad342203de7233b2cd660f Mon Sep 17 00:00:00 2001 From: imcompiling Date: Sun, 19 May 2024 10:40:41 -0400 Subject: [PATCH 08/11] use VoteType in voting views. --- classes/voting_views.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/classes/voting_views.py b/classes/voting_views.py index 6d1694d..cdfc8ca 100644 --- a/classes/voting_views.py +++ b/classes/voting_views.py @@ -17,7 +17,7 @@ from database_setup.sql_data_classes import ( Votes, engine, ) -from utils import build_votes_str, get_election_name +from utils import VoteType, build_votes_str, get_election_name class VoteSelect(nextcord.ui.StringSelect): @@ -47,6 +47,7 @@ class VoteSelect(nextcord.ui.StringSelect): ElectionCycle=election_name, VoterId=interaction.user.id, VotedForId=voted_for, + Type=VoteType.COUNCIL, ) try: @@ -59,6 +60,7 @@ class VoteSelect(nextcord.ui.StringSelect): update(Votes) .where(Votes.ElectionCycle == election_name) .where(Votes.VoterId == interaction.user.id) + .where(Votes.Type == VoteType.COUNCIL) .values(VotedForId=voted_for) ) session.execute(stmt) @@ -121,6 +123,7 @@ class VoteView(nextcord.ui.View): select(Votes.VotedForId) .where(Votes.ElectionCycle == election) .where(Votes.VoterId == interaction.user.id) + .where(Votes.Type == VoteType.COUNCIL) ) voted_for = session.scalar(stmt) From ed07b2e23102f958269ba1ae36fd16f96edce408 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Sun, 19 May 2024 10:46:05 -0400 Subject: [PATCH 09/11] Use VoteType in view command. --- cogs/commands/view.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/cogs/commands/view.py b/cogs/commands/view.py index dcf9c6d..b186812 100644 --- a/cogs/commands/view.py +++ b/cogs/commands/view.py @@ -14,6 +14,7 @@ from database_setup.sql_data_classes import ( Votes, engine, ) +from utils import VoteType class ViewCog(commands.Cog, name="View Commands"): @@ -148,6 +149,11 @@ class ViewCog(commands.Cog, name="View Commands"): election_name: str = nextcord.SlashOption( required=True, description="The election you want to view votes for." ), + election_type: str = nextcord.SlashOption( + required=True, + description="The election type. Council, Discord Manager, or Clan Leader.", + choices=VoteType, + ), ): """ Takes in an election name and sends a response message to the user with a list of people who voted in that @@ -159,7 +165,11 @@ class ViewCog(commands.Cog, name="View Commands"): election_name (str): Is SlashOption. The name of the election to view votes for. """ - stmt = select(Votes).where(Votes.ElectionCycle == election_name) + stmt = ( + select(Votes) + .where(Votes.ElectionCycle == election_name) + .where(Votes.Type == election_type) + ) with Session(engine) as session: # Check if election exists. From 426f009ca7c0e4b65f7328c7168e2778ab503166 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Sun, 19 May 2024 10:50:23 -0400 Subject: [PATCH 10/11] Use VoteType in utils. Remove old functions of getting clan leader and discord manager election names. --- utils.py | 56 ++++++++++++++++++++++---------------------------------- 1 file changed, 22 insertions(+), 34 deletions(-) diff --git a/utils.py b/utils.py index 51f87f2..3caba58 100644 --- a/utils.py +++ b/utils.py @@ -85,34 +85,6 @@ def get_quarter_name(config: DakinyConfig) -> str: return f"{get_datetime(current_cycle.term_start).year}-{current_cycle.name}" -def get_dm_election_name(config: DakinyConfig) -> str: - """ - creates the name of the discord manager election - - Args: - config (DakinyConfig): Config sent to get_election_cycle_name - - Returns: - str: the name of the election - """ - - return f"{get_election_name(config)}-dm" - - -def get_cl_election_name(config: DakinyConfig) -> str: - """ - creates the name of the clan leader election - - Args: - config (DakinyConfig): Config sent to get_election_cycle_name - - Returns: - str: the name of the election - """ - - return f"{get_election_name(config)}-cl" - - def get_current_election_cycle(config: DakinyConfig) -> int: """ Returns index for active election. An election is considered active between the election start and the term start. @@ -173,9 +145,11 @@ def build_votes_str(config: DakinyConfig, guild: nextcord.Guild) -> str: votes: dict[int, int] = {} + election_name = get_election_name(config) + with Session(engine) as session: stmt = select(CouncilMembers).where( - CouncilMembers.ElectionCycle == get_election_name(config) + CouncilMembers.ElectionCycle == election_name ) for nominee in sorted( @@ -184,7 +158,11 @@ def build_votes_str(config: DakinyConfig, guild: nextcord.Guild) -> str: ): votes[nominee.MemberId] = 0 - stmt = select(Votes).where(Votes.ElectionCycle == get_election_name(config)) + stmt = ( + select(Votes) + .where(Votes.ElectionCycle == election_name) + .where(Votes.Type == VoteType.COUNCIL) + ) for vote in session.scalars(stmt): if vote.VotedForId is not None: @@ -211,10 +189,12 @@ def build_highcouncil_votes_str(config: DakinyConfig, guild: nextcord.Guild) -> cl_votes: dict[int, int] = {} dm_votes: dict[int, int] = {} + election_name = get_election_name(config) + with Session(engine) as session: stmt = ( select(CouncilMembers) - .where(CouncilMembers.ElectionCycle == get_election_name(config)) + .where(CouncilMembers.ElectionCycle == election_name) .filter(CouncilMembers.ClanLeaderSpeechId.is_not(None)) ) @@ -224,7 +204,11 @@ def build_highcouncil_votes_str(config: DakinyConfig, guild: nextcord.Guild) -> ): cl_votes[nominee.MemberId] = 0 - stmt = select(Votes).where(Votes.ElectionCycle == get_cl_election_name(config)) + stmt = ( + select(Votes) + .where(Votes.ElectionCycle == election_name) + .where(Votes.Type == VoteType.CLANLEADER) + ) for vote in session.scalars(stmt): if vote.VotedForId is not None: @@ -232,7 +216,7 @@ def build_highcouncil_votes_str(config: DakinyConfig, guild: nextcord.Guild) -> stmt = ( select(CouncilMembers) - .where(CouncilMembers.ElectionCycle == get_election_name(config)) + .where(CouncilMembers.ElectionCycle == election_name) .filter(CouncilMembers.DiscordManagerSpeechId.is_not(None)) ) @@ -242,7 +226,11 @@ def build_highcouncil_votes_str(config: DakinyConfig, guild: nextcord.Guild) -> ): dm_votes[nominee.MemberId] = 0 - stmt = select(Votes).where(Votes.ElectionCycle == get_dm_election_name(config)) + stmt = ( + select(Votes) + .where(Votes.ElectionCycle == election_name) + .where(Votes.Type == VoteType.DISCORDMANAGER) + ) for vote in session.scalars(stmt): if vote.VotedForId is not None: From dbd2863aab83d51174ed0bc508ebbe844ef619b6 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Sun, 19 May 2024 11:29:04 -0400 Subject: [PATCH 11/11] Pass VoteType str instead of manual one in VoteSelect. --- classes/election_cycle.py | 2 +- classes/voting_highcouncil_views.py | 24 ++++++++++-------------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/classes/election_cycle.py b/classes/election_cycle.py index 8eac21b..6cd6946 100644 --- a/classes/election_cycle.py +++ b/classes/election_cycle.py @@ -435,7 +435,7 @@ class ElectionCycle: await vote_msg.edit(view=None) highcouncil_msg: str = ( - f"Congratulations for our high council members!\n Clan Leader: <@{cl_winner}>\n" + f"Congratulations for our high council members!\nClan Leader: <@{cl_winner}>\n" ) highcouncil_msg += f"Discord Manager: <@{dm_winner}>" await election_thread.send(highcouncil_msg) diff --git a/classes/voting_highcouncil_views.py b/classes/voting_highcouncil_views.py index 2a36b70..f52ef5b 100644 --- a/classes/voting_highcouncil_views.py +++ b/classes/voting_highcouncil_views.py @@ -34,16 +34,6 @@ class VoteSelect(nextcord.ui.StringSelect): self.election_type = election_type async def callback(self, interaction: Interaction): - # decide on election type - match self.election_type: - case "cl": - election_type: str = VoteType.CLANLEADER - case "dm": - election_type: str = VoteType.DISCORDMANAGER - case _: - # should never happen - raise ValueError('election type was not "cl" or "dm"') - election_name: str = get_election_name(self.config) with Session(engine) as session: stmt = select(ElectionInformation.HighCouncilVoteMsgId).where( @@ -60,7 +50,7 @@ class VoteSelect(nextcord.ui.StringSelect): ElectionCycle=election_name, VoterId=interaction.user.id, VotedForId=voted_for, - Type=election_type, + Type=self.election_type, ) try: @@ -74,7 +64,7 @@ class VoteSelect(nextcord.ui.StringSelect): update(Votes) .where(Votes.ElectionCycle == election_name) .where(Votes.VoterId == interaction.user.id) - .where(Votes.Type == election_type) + .where(Votes.Type == self.election_type) .values(VotedForId=voted_for) ) session.execute(stmt) @@ -173,7 +163,9 @@ class VoteHighcouncilView(nextcord.ui.View): view = nextcord.ui.View() view.add_item( - VoteSelect(options=options, config=self.config, election_type="cl") + VoteSelect( + options=options, config=self.config, election_type=VoteType.CLANLEADER + ) ) await interaction.response.send_message( @@ -251,7 +243,11 @@ class VoteHighcouncilView(nextcord.ui.View): view = nextcord.ui.View() view.add_item( - VoteSelect(options=options, config=self.config, election_type="dm") + VoteSelect( + options=options, + config=self.config, + election_type=VoteType.DISCORDMANAGER, + ) ) await interaction.response.send_message(