Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
d67b000
Added elections model to alembic/env.py
DerpyWasHere Aug 9, 2024
905170b
Added election tables revision to alembic
DerpyWasHere Aug 9, 2024
7cb496c
Initial elections model implementation
DerpyWasHere Aug 9, 2024
8b06dda
Redid revision, added position column to NomineeAplication, renamed N…
DerpyWasHere Aug 10, 2024
b5103ca
Added ElectionOfficer class, created has_permission() to check whethe…
DerpyWasHere Aug 12, 2024
b579e08
Changed date to be nonnull, officer_id in alembic revision
DerpyWasHere Aug 13, 2024
c89e659
Removed unique constraint on officer_id
DerpyWasHere Aug 13, 2024
117611a
Removed unique constraint on officer_id, removed nullability from date
DerpyWasHere Aug 13, 2024
3aded13
properly handled session validation on create_elections
DerpyWasHere Aug 29, 2024
fc8def5
created create_election stub
DerpyWasHere Aug 29, 2024
d9b8e52
added elections router to main
DerpyWasHere Aug 29, 2024
e239b9b
Working commit containing fns to create, delete, and update elections
DerpyWasHere Sep 27, 2024
8bf5dcb
Merge branch 'main' into dev-issue-25
EarthenSky Oct 3, 2024
06fe816
switch from models to tables
EarthenSky Oct 3, 2024
82685ef
fix small import bug
EarthenSky Oct 3, 2024
0c299d2
fix past alembic migration
EarthenSky Oct 3, 2024
5a2b886
Removed enum in urls.py, satisfied linter for tables.py
DerpyWasHere Jan 13, 2025
5faad35
Changed old ElectionTypes enum into a string array
DerpyWasHere Jan 13, 2025
7b465b5
Changed query in get_election() to be more SQL-like.
DerpyWasHere Jan 13, 2025
44d60f3
Changed list comprehension in create_election() to just a 'if not in'…
DerpyWasHere Jan 13, 2025
40eedd7
Made change referenced in pr 63 wrt committing transactions in get_el…
DerpyWasHere Jan 13, 2025
e2bb4db
Removed commits from crud.py and added commits to endpoints in urls.py
DerpyWasHere Jan 13, 2025
50302b1
Changed occurrences of websurvey to survey_link to match that websurv…
DerpyWasHere Jan 13, 2025
82ccc24
Changed election parameters from a list to a dedicated dataclass, ref…
DerpyWasHere Jan 13, 2025
a36eef6
Changed parameter orders to be consistent with other crud functions i…
DerpyWasHere Jan 13, 2025
057e405
Merge branch 'main' into dev-issue-25
DerpyWasHere Jan 13, 2025
ff2951c
Appeased linter
DerpyWasHere Jan 13, 2025
8d0e267
Reintroduced elections router into main.py
DerpyWasHere Jan 13, 2025
40041ad
update down revision to be blog posts
EarthenSky Jan 13, 2025
853038d
Added lost default param in elections table migration
DerpyWasHere Jan 13, 2025
2dee83a
Changed discord id length in election_nominee table from 18 to 32.
DerpyWasHere Jan 13, 2025
945fb29
Changed date -> start_date in elections table
DerpyWasHere Jan 13, 2025
57f4b2a
Changed date -> start_datetime, start_date -> start_datetime, end_dat…
DerpyWasHere Jan 13, 2025
1c61134
Changed date -> start_datetime, end_date -> end_datetime
DerpyWasHere Jan 13, 2025
def5346
update formatting & fix some small access bugs
EarthenSky Mar 8, 2025
dadd620
update comment
EarthenSky Apr 5, 2025
3a66f37
Update urls.py
EarthenSky Apr 5, 2025
80a6105
update POST election
EarthenSky Apr 5, 2025
bfbd082
Update urls.py
EarthenSky Apr 5, 2025
df870d0
fix import style
EarthenSky Apr 5, 2025
8e64f4d
complete election crud and election crud urls
EarthenSky Apr 6, 2025
551e73f
add test data, fix bugs, refactor
EarthenSky Apr 7, 2025
d54ec21
test all endpoints & fix bugs
EarthenSky Apr 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/alembic/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import auth.tables
import blog.tables
import database
import elections.tables
import officers.tables
from alembic import context

Expand Down
61 changes: 61 additions & 0 deletions src/alembic/versions/243190df5588_create_election_tables.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""create election tables

Revision ID: 243190df5588
Revises: 43f71e4bd6fc
Create Date: 2024-08-10 08:32:54.037614

"""
from collections.abc import Sequence
from typing import Union

import sqlalchemy as sa

from alembic import op

# revision identifiers, used by Alembic.
revision: str = "243190df5588"
down_revision: str | None = "2a6ea95342dc"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None


def upgrade() -> None:
op.create_table(
"election",
sa.Column("slug", sa.String(length=64), nullable=False),
sa.Column("name", sa.String(length=64), nullable=False),
sa.Column("type", sa.String(length=64), default="general_election"),
sa.Column("datetime_start_nominations", sa.DateTime(), nullable=False),
sa.Column("datetime_start_voting", sa.DateTime(), nullable=False),
sa.Column("datetime_end_voting", sa.DateTime(), nullable=False),
sa.Column("survey_link", sa.String(length=300), nullable=True),
sa.PrimaryKeyConstraint("slug")
)
op.create_table(
"election_nominee",
sa.Column("computing_id", sa.String(length=32), nullable=False),
sa.Column("full_name", sa.String(length=64), nullable=False),
sa.Column("facebook", sa.String(length=128), nullable=True),
sa.Column("instagram", sa.String(length=128), nullable=True),
sa.Column("email", sa.String(length=64), nullable=True),
sa.Column("discord", sa.String(length=32), nullable=True),
sa.Column("discord_id", sa.String(length=32), nullable=True),
sa.Column("discord_username", sa.String(length=32), nullable=True),
sa.PrimaryKeyConstraint("computing_id")
)
op.create_table(
"nominee_application",
sa.Column("computing_id", sa.String(length=32), nullable=False),
sa.Column("nominee_election", sa.String(length=32), nullable=False),
sa.Column("speech", sa.Text(), nullable=True),
sa.Column("position", sa.String(length=64), nullable=False),
sa.ForeignKeyConstraint(["computing_id"], ["election_nominee.computing_id"]),
sa.ForeignKeyConstraint(["nominee_election"], ["election.slug"]),
sa.PrimaryKeyConstraint("computing_id", "nominee_election")
)


def downgrade() -> None:
op.drop_table("nominee_application")
op.drop_table("election_nominee")
op.drop_table("election")
51 changes: 51 additions & 0 deletions src/elections/crud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import logging

import sqlalchemy
from sqlalchemy.ext.asyncio import AsyncSession

from elections.tables import Election

_logger = logging.getLogger(__name__)

async def get_election(db_session: AsyncSession, election_slug: str) -> Election | None:
return await db_session.scalar(
sqlalchemy
.select(Election)
.where(Election.slug == election_slug)
)

async def create_election(db_session: AsyncSession, election: Election) -> None:
"""
Creates a new election with given parameters.
Does not validate if an election _already_ exists
"""
db_session.add(election)

async def delete_election(db_session: AsyncSession, slug: str) -> None:
"""
Deletes a given election by its slug.
Does not validate if an election exists
"""
await db_session.execute(
sqlalchemy
.delete(Election)
.where(Election.slug == slug)
)

async def update_election(db_session: AsyncSession, new_election: Election) -> bool:
"""
You attempting to change the name or slug will fail. Instead, you must create a new election.
"""
target_slug = new_election.slug
target_election = await get_election(db_session, target_slug)

if target_election is None:
return False
else:
await db_session.execute(
sqlalchemy
.update(Election)
.where(Election.slug == target_slug)
.values(new_election.to_update_dict())
)
return True
96 changes: 96 additions & 0 deletions src/elections/tables.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
from sqlalchemy import (
Column,
DateTime,
ForeignKey,
PrimaryKeyConstraint,
String,
Text,
)

from constants import (
COMPUTING_ID_LEN,
DISCORD_ID_LEN,
DISCORD_NAME_LEN,
DISCORD_NICKNAME_LEN,
)
from database import Base

election_types = ["general_election", "by_election", "council_rep_election"]

MAX_ELECTION_NAME = 64
MAX_ELECTION_SLUG = 64

class Election(Base):
__tablename__ = "election"

# Slugs are unique identifiers
slug = Column(String(MAX_ELECTION_SLUG), primary_key=True)
name = Column(String(MAX_ELECTION_NAME), nullable=False)
type = Column(String(64), default="general_election")
datetime_start_nominations = Column(DateTime, nullable=False)
datetime_start_voting = Column(DateTime, nullable=False)
datetime_end_voting = Column(DateTime, nullable=False)
survey_link = Column(String(300))

def serializable_dict(self) -> dict:
return {
"slug": self.slug,
"name": self.name,
"type": self.type,

"datetime_start_nominations": self.datetime_start_nominations.isoformat(),
"datetime_start_voting": self.datetime_start_voting.isoformat(),
"datetime_end_voting": self.datetime_end_voting.isoformat(),

"survey_link": self.survey_link,
}

def public_details(self) -> dict:
return {
"slug": self.slug,
"name": self.name,
"type": self.type,

"datetime_start_nominations": self.datetime_start_nominations.isoformat(),
"datetime_start_voting": self.datetime_start_voting.isoformat(),
"datetime_end_voting": self.datetime_end_voting.isoformat(),
}

def to_update_dict(self) -> dict:
return {
"slug": self.slug,
"name": self.name,
"type": self.type,

"datetime_start_nominations": self.datetime_start_nominations,
"datetime_start_voting": self.datetime_start_voting,
"datetime_end_voting": self.datetime_end_voting,

"survey_link": self.survey_link,
}

# Each row represents a nominee of a given election
class Nominee(Base):
__tablename__ = "election_nominee"

# Previously named sfuid
computing_id = Column(String(COMPUTING_ID_LEN), primary_key=True)
full_name = Column(String(64), nullable=False)
facebook = Column(String(128))
instagram = Column(String(128))
email = Column(String(64))
discord = Column(String(DISCORD_NAME_LEN))
discord_id = Column(String(DISCORD_ID_LEN))
discord_username = Column(String(DISCORD_NICKNAME_LEN))

class NomineeApplication(Base):
__tablename__ = "nominee_application"

computing_id = Column(ForeignKey("election_nominee.computing_id"), primary_key=True)
nominee_election = Column(ForeignKey("election.slug"), primary_key=True)
speech = Column(Text)
position = Column(String(64), nullable=False)

__table_args__ = (
PrimaryKeyConstraint(computing_id, nominee_election),
)
Loading