Skip to content

Commit be40db2

Browse files
committed
Fixed honorary spelling, added constant value, and other changes per PR feedback
1 parent f13ba81 commit be40db2

10 files changed

Lines changed: 188 additions & 157 deletions

File tree

src/alembic/env.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@
77

88
import auth.tables
99
import blog.tables
10+
import candidates.tables
1011
import database
1112
import elections.tables
13+
import event.tables
14+
import honorary.tables
1215
import nominees.tables
1316
import officers.tables
14-
import candidates.tables
15-
import event.tables
16-
import honourary.tables
1717
from alembic import context
1818

1919
# this is the Alembic Config object, which provides

src/alembic/versions/3c00f28c26e2_create_honorary_member_table.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,36 @@
55
Create Date: 2026-06-29 15:43:07.361827
66
77
"""
8-
from typing import Sequence, Union
98

10-
from alembic import op
9+
from collections.abc import Sequence
10+
from typing import Union
11+
1112
import sqlalchemy as sa
1213

14+
from alembic import op
15+
from honorary.constants import HONORARY_MEMBER_MAX_LENGTH
1316

1417
# revision identifiers, used by Alembic.
15-
revision: str = '3c00f28c26e2'
16-
down_revision: Union[str, None] = '42f855bec532'
17-
branch_labels: Union[str, Sequence[str], None] = None
18-
depends_on: Union[str, Sequence[str], None] = None
18+
revision: str = "3c00f28c26e2"
19+
down_revision: str | None = "42f855bec532"
20+
branch_labels: str | Sequence[str] | None = None
21+
depends_on: str | Sequence[str] | None = None
1922

2023

2124
def upgrade() -> None:
2225
# ### commands auto generated by Alembic - please adjust! ###
23-
op.create_table('honorary_member',
24-
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
25-
sa.Column('name', sa.String(length=128), nullable=False),
26-
sa.Column('start_date', sa.Date(), nullable=False),
27-
sa.Column('end_date', sa.Date(), nullable=True),
28-
sa.PrimaryKeyConstraint('id', name=op.f('pk_honorary_member'))
26+
op.create_table(
27+
"honorary_member",
28+
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
29+
sa.Column("name", sa.String(length=HONORARY_MEMBER_MAX_LENGTH), nullable=False),
30+
sa.Column("start_date", sa.Date(), nullable=False),
31+
sa.Column("end_date", sa.Date(), nullable=True),
32+
sa.PrimaryKeyConstraint("id", name=op.f("pk_honorary_member")),
2933
)
3034
# ### end Alembic commands ###
3135

3236

3337
def downgrade() -> None:
3438
# ### commands auto generated by Alembic - please adjust! ###
35-
op.drop_table('honorary_member')
39+
op.drop_table("honorary_member")
3640
# ### end Alembic commands ###

src/honorary/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
HONORARY_MEMBER_MAX_LENGTH = 128
Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
from datetime import date
22

3-
from fastapi import HTTPException, status
43
from sqlalchemy import delete, select
54

65
import database
7-
from honourary.tables import HonoraryMemberDB
6+
from honorary.tables import HonoraryMemberDB
87

98

109
async def get_all_honorary_members(db_session: database.DBSession) -> list[HonoraryMemberDB]:
@@ -25,34 +24,22 @@ async def get_current_honorary_members(db_session: database.DBSession) -> list[H
2524
return list((await db_session.scalars(query)).all())
2625

2726

28-
async def get_honorary_member_by_id_or_raise(
27+
async def get_honorary_member_by_id(
2928
db_session: database.DBSession,
3029
term_id: int,
31-
) -> HonoraryMemberDB:
32-
honorary_member = await db_session.scalar(select(HonoraryMemberDB).where(HonoraryMemberDB.id == term_id))
33-
if honorary_member is None:
34-
raise HTTPException(
35-
status_code=status.HTTP_404_NOT_FOUND,
36-
detail=f"honorary member term with id={term_id} does not exist",
37-
)
38-
return honorary_member
30+
) -> HonoraryMemberDB | None:
31+
return await db_session.scalar(select(HonoraryMemberDB).where(HonoraryMemberDB.id == term_id))
3932

4033

41-
def ensure_term_has_not_ended(honorary_member: HonoraryMemberDB) -> None:
42-
if honorary_member.end_date is not None and honorary_member.end_date < date.today():
43-
raise HTTPException(
44-
status_code=status.HTTP_403_FORBIDDEN,
45-
detail="cannot update or delete a term that has already ended",
46-
)
34+
def has_term_ended(honorary_member: HonoraryMemberDB) -> bool:
35+
return honorary_member.end_date is not None and honorary_member.end_date < date.today()
4736

4837

4938
async def create_honorary_members(
5039
db_session: database.DBSession,
5140
honorary_members: list[HonoraryMemberDB],
5241
) -> list[HonoraryMemberDB]:
5342
db_session.add_all(honorary_members)
54-
await db_session.flush()
55-
5643
return honorary_members
5744

5845

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22

33
from pydantic import BaseModel, ConfigDict, Field, computed_field
44

5+
from honorary.constants import HONORARY_MEMBER_MAX_LENGTH
56
from utils import is_active_term
67

78

89
class HonoraryMemberCreate(BaseModel):
9-
name: str = Field(..., max_length=128)
10+
name: str = Field(..., max_length=HONORARY_MEMBER_MAX_LENGTH)
1011
start_date: date
1112
end_date: date | None = None
1213

@@ -23,6 +24,6 @@ def is_active(self) -> bool:
2324

2425

2526
class HonoraryMemberUpdate(BaseModel):
26-
name: str | None = Field(None, max_length=128)
27+
name: str | None = Field(None, max_length=HONORARY_MEMBER_MAX_LENGTH)
2728
start_date: date | None = None
2829
end_date: date | None = None
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44
from sqlalchemy.orm import Mapped, mapped_column
55

66
from database import Base
7+
from honorary.constants import HONORARY_MEMBER_MAX_LENGTH
78

89

910
class HonoraryMemberDB(Base):
1011
__tablename__ = "honorary_member"
1112

1213
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
13-
name: Mapped[str] = mapped_column(String(128), nullable=False)
14+
name: Mapped[str] = mapped_column(String(HONORARY_MEMBER_MAX_LENGTH), nullable=False)
1415
start_date: Mapped[date] = mapped_column(Date, nullable=False)
1516
end_date: Mapped[date] = mapped_column(Date, nullable=True)

src/honorary/urls.py

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
from fastapi import APIRouter, Depends, HTTPException, status
2+
from fastapi.responses import JSONResponse
3+
4+
import database
5+
import honorary.crud
6+
from dependencies import perm_admin
7+
from honorary.models import HonoraryMember, HonoraryMemberCreate, HonoraryMemberUpdate
8+
from honorary.tables import HonoraryMemberDB
9+
from utils.shared_models import DetailModel, SuccessResponse
10+
11+
router = APIRouter(
12+
prefix="/honorary",
13+
tags=["honorary"],
14+
)
15+
16+
17+
@router.get(
18+
"",
19+
description="Get all honorary member terms",
20+
response_model=list[HonoraryMember],
21+
responses={403: {"description": "must be a website admin", "model": DetailModel}},
22+
operation_id="get_all_honorary_members",
23+
dependencies=[Depends(perm_admin)],
24+
)
25+
async def get_all_honorary_members(db_session: database.DBSession):
26+
honorary_members = await honorary.crud.get_all_honorary_members(db_session)
27+
return JSONResponse(
28+
[
29+
HonoraryMember.model_validate(member).model_dump(mode="json", exclude_unset=True)
30+
for member in honorary_members
31+
]
32+
)
33+
34+
35+
@router.get(
36+
"/current",
37+
description="Get all active honorary member terms",
38+
response_model=list[HonoraryMember],
39+
responses={403: {"description": "must be a website admin", "model": DetailModel}},
40+
operation_id="get_current_honorary_members",
41+
dependencies=[Depends(perm_admin)],
42+
)
43+
async def get_current_honorary_members(db_session: database.DBSession):
44+
honorary_members = await honorary.crud.get_current_honorary_members(db_session)
45+
return JSONResponse(
46+
[
47+
HonoraryMember.model_validate(member).model_dump(mode="json", exclude_unset=True)
48+
for member in honorary_members
49+
]
50+
)
51+
52+
53+
@router.post(
54+
"",
55+
description="Create honorary member terms",
56+
response_model=list[HonoraryMember],
57+
responses={
58+
403: {"description": "must be a website admin", "model": DetailModel},
59+
500: {"description": "failed to create honorary member terms", "model": DetailModel},
60+
},
61+
operation_id="create_honorary_members",
62+
dependencies=[Depends(perm_admin)],
63+
)
64+
async def create_honorary_members(
65+
db_session: database.DBSession,
66+
body: list[HonoraryMemberCreate],
67+
):
68+
new_members = [HonoraryMemberDB(**member.model_dump()) for member in body]
69+
created_members = await honorary.crud.create_honorary_members(db_session, new_members)
70+
71+
await db_session.flush()
72+
73+
content = [
74+
HonoraryMember.model_validate(member).model_dump(mode="json", exclude_unset=True) for member in created_members
75+
]
76+
77+
await db_session.commit()
78+
79+
return JSONResponse(content)
80+
81+
82+
@router.patch(
83+
"/{term_id}",
84+
description="Update an honorary member term. Ended terms cannot be updated.",
85+
response_model=HonoraryMember,
86+
responses={
87+
403: {"description": "must be a website admin", "model": DetailModel},
88+
404: {"description": "honorary member term does not exist", "model": DetailModel},
89+
409: {"description": "honorary member term has already ended", "model": DetailModel},
90+
},
91+
operation_id="update_honorary_member",
92+
dependencies=[Depends(perm_admin)],
93+
)
94+
async def update_honorary_member(
95+
db_session: database.DBSession,
96+
term_id: int,
97+
body: HonoraryMemberUpdate,
98+
):
99+
honorary_member = await honorary.crud.get_honorary_member_by_id(db_session, term_id)
100+
if honorary_member is None:
101+
raise HTTPException(
102+
status_code=status.HTTP_404_NOT_FOUND,
103+
detail=f"honorary member term with id={term_id} does not exist",
104+
)
105+
106+
if honorary.crud.has_term_ended(honorary_member):
107+
raise HTTPException(
108+
status_code=status.HTTP_409_CONFLICT,
109+
detail="cannot update a term that has already ended",
110+
)
111+
112+
update_data = body.model_dump(exclude_unset=True)
113+
for key, value in update_data.items():
114+
setattr(honorary_member, key, value)
115+
116+
await db_session.commit()
117+
await db_session.refresh(honorary_member)
118+
119+
return JSONResponse(HonoraryMember.model_validate(honorary_member).model_dump(mode="json", exclude_unset=True))
120+
121+
122+
@router.delete(
123+
"/{term_id}",
124+
description="Delete an honorary member term. Ended terms cannot be deleted.",
125+
response_model=SuccessResponse,
126+
responses={
127+
403: {"description": "must be a website admin", "model": DetailModel},
128+
404: {"description": "honorary member term does not exist", "model": DetailModel},
129+
409: {"description": "honorary member term has already ended", "model": DetailModel},
130+
},
131+
operation_id="delete_honorary_member",
132+
dependencies=[Depends(perm_admin)],
133+
)
134+
async def delete_honorary_member(
135+
db_session: database.DBSession,
136+
term_id: int,
137+
):
138+
honorary_member = await honorary.crud.get_honorary_member_by_id(db_session, term_id)
139+
if honorary_member is None:
140+
raise HTTPException(
141+
status_code=status.HTTP_404_NOT_FOUND,
142+
detail=f"honorary member term with id={term_id} does not exist",
143+
)
144+
145+
if honorary.crud.has_term_ended(honorary_member):
146+
raise HTTPException(
147+
status_code=status.HTTP_409_CONFLICT,
148+
detail="cannot delete a term that has already ended",
149+
)
150+
151+
await honorary.crud.delete_honorary_member(db_session, honorary_member)
152+
await db_session.commit()
153+
154+
return SuccessResponse(success=True)

src/honourary/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)