Skip to content

Commit 6794ec1

Browse files
committed
remove overcomplication, fix semantics, more DRY. For /all & /current
1 parent 27907f7 commit 6794ec1

File tree

5 files changed

+77
-67
lines changed

5 files changed

+77
-67
lines changed

src/officers/crud.py

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -96,46 +96,52 @@ async def current_executive_team(db_session: database.DBSession, include_private
9696
9797
Returns a mapping between officer position and officer terms
9898
"""
99-
100-
query = sqlalchemy.select(OfficerTerm)
99+
query = (
100+
sqlalchemy
101+
.select(OfficerTerm)
102+
.order_by(OfficerTerm.start_date.desc())
103+
)
101104
query = utils.is_active_officer(query)
102-
query = query.order_by(OfficerTerm.start_date.desc())
103105

104106
officer_terms = (await db_session.scalars(query)).all()
105-
num_officers = {}
107+
##num_officers = {}
106108
officer_data = {}
107-
108109
for term in officer_terms:
110+
"""
109111
if term.position not in OfficerPosition.position_list():
110112
_logger.warning(
111113
f"Unknown OfficerTerm.position={term.position} in database. Ignoring in request."
112114
)
113115
continue
116+
"""
114117

115-
officer_info_query = sqlalchemy.select(OfficerInfo)
116-
officer_info_query = officer_info_query.where(
117-
OfficerInfo.computing_id == term.computing_id
118+
officer_info_query = (
119+
sqlalchemy
120+
.select(OfficerInfo)
121+
.where(OfficerInfo.computing_id == term.computing_id)
118122
)
119123
officer_info = (await db_session.scalars(officer_info_query)).first()
120124
if officer_info is None:
121125
# TODO: make sure there are daily checks that this data actually exists
122126
continue
123127

124128
if term.position not in officer_data:
125-
num_officers[term.position] = 0
129+
##num_officers[term.position] = 0
126130
officer_data[term.position] = []
127131

132+
"""
128133
num_officers[term.position] += 1
129-
# TODO: move this to a ~~daily cronjob~~ SQL model checking
134+
# TODO: move this to a daily cronjob
130135
if num_officers[term.position] > OfficerPosition.num_active(term.position):
131136
# If there are more active positions than expected, log it to a file
132137
_logger.warning(
133138
f"There are more active {term.position} positions in the OfficerTerm than expected "
134139
f"({num_officers[term.position]} > {OfficerPosition.num_active(term.position)})"
135140
)
136-
141+
"""
137142
officer_data[term.position] += [OfficerData.from_data(term, officer_info, include_private, is_active=True)]
138143

144+
"""
139145
# validate & warn if there are any data issues
140146
# TODO: decide whether we should enforce empty instances or force the frontend to deal with it
141147
for position in OfficerPosition.expected_positions():
@@ -151,39 +157,39 @@ async def current_executive_team(db_session: database.DBSession, include_private
151157
f"Unexpected number of {position} entries "
152158
f"({len(officer_data[position])} entries) in current_executive_team response."
153159
)
154-
160+
"""
155161
return officer_data
156162

157163
async def all_officer_data(
158164
db_session: database.DBSession,
159-
include_private: bool,
160-
view_only_filled_in: bool,
165+
include_private_data: bool,
166+
view_not_started_officer_terms: bool,
161167
) -> list[OfficerData]:
162168
"""
163-
This could be a lot of data, so be careful.
164-
165-
TODO: optionally paginate data, so it's not so bad.
169+
This could be a lot of data, so be careful
166170
"""
167-
query = sqlalchemy.select(OfficerTerm)
168-
if view_only_filled_in:
169-
query = OfficerTerm.sql_is_filled_in(query)
170-
# Ordered recent first
171-
query = query.order_by(OfficerTerm.start_date.desc())
172-
officer_terms = (await db_session.scalars(query)).all()
171+
# NOTE: paginate data if needed
172+
query = (
173+
sqlalchemy
174+
.select(OfficerTerm)
175+
# Ordered recent first
176+
.order_by(OfficerTerm.start_date.desc())
177+
)
178+
if not view_not_started_officer_terms:
179+
query = utils.has_term_started(query)
173180

174181
officer_data_list = []
182+
officer_terms = (await db_session.scalars(query)).all()
175183
for term in officer_terms:
176-
officer_info_query = (
184+
officer_info = await db_session.scalar(
177185
sqlalchemy
178186
.select(OfficerInfo)
179187
.where(OfficerInfo.computing_id == term.computing_id)
180188
)
181-
officer_info = await db_session.scalar(officer_info_query)
182-
183189
officer_data_list += [OfficerData.from_data(
184190
term,
185191
officer_info,
186-
include_private,
192+
include_private_data,
187193
utils.is_active_term(term)
188194
)]
189195

src/officers/tables.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from __future__ import annotations
22

33
from sqlalchemy import (
4-
# Boolean,
54
Column,
65
DateTime,
76
ForeignKey,

src/officers/types.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
from fastapi import HTTPException
77

8-
from constants import COMPUTING_ID_MAX
98
from officers.constants import OfficerPosition
109
from officers.tables import OfficerInfo, OfficerTerm
1110

@@ -117,7 +116,7 @@ class OfficerData:
117116

118117
csss_email: str | None
119118
biography: str | None
120-
photo_url: str | None # some urls get big...
119+
photo_url: str | None
121120

122121
private_data: OfficerPrivateData | None
123122

@@ -133,7 +132,7 @@ def serializable_dict(self):
133132
def from_data(
134133
term: OfficerTerm,
135134
officer_info: OfficerInfo,
136-
include_private: bool,
135+
include_private_data: bool,
137136
is_active: bool,
138137
) -> OfficerData:
139138
return OfficerData(
@@ -162,5 +161,5 @@ def from_data(
162161
phone_number = officer_info.phone_number,
163162
github_username = officer_info.github_username,
164163
google_drive_email = officer_info.google_drive_email,
165-
) if include_private else None,
164+
) if include_private_data else None,
166165
)

src/officers/urls.py

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import logging
22
from dataclasses import dataclass
3-
from datetime import date, datetime
3+
from datetime import date
44

55
import sqlalchemy
66
from fastapi import APIRouter, Body, HTTPException, Request
@@ -25,7 +25,22 @@
2525
tags=["officers"],
2626
)
2727

28-
# TODO: combine the following two endpoints
28+
async def has_officer_private_info_access(
29+
request: Request,
30+
db_session: database.DBSession,
31+
) -> tuple[None | str, None | str, bool]:
32+
# determine if the user has access to this private data
33+
session_id = request.cookies.get("session_id", None)
34+
if session_id is None:
35+
return None, None, False
36+
37+
computing_id = await auth.crud.get_computing_id(db_session, session_id)
38+
if computing_id is None:
39+
return session_id, None, False
40+
41+
has_private_access = await OfficerPrivateInfo.has_permission(db_session, computing_id)
42+
return session_id, computing_id, has_private_access
43+
2944
@router.get(
3045
"/current",
3146
description="Get information about all the officers. More information is given if you're authenticated & have access to private executive data.",
@@ -35,13 +50,7 @@ async def current_officers(
3550
request: Request,
3651
db_session: database.DBSession,
3752
):
38-
# determine if the user has access to this private data
39-
session_id = request.cookies.get("session_id", None)
40-
if session_id is None:
41-
has_private_access = False
42-
else:
43-
computing_id = await auth.crud.get_computing_id(db_session, session_id)
44-
has_private_access = await OfficerPrivateInfo.has_permission(db_session, computing_id)
53+
_, _, has_private_access = await has_officer_private_info_access(request, db_session)
4554

4655
current_executives = await officers.crud.current_executive_team(db_session, has_private_access)
4756
json_current_executives = {
@@ -54,36 +63,26 @@ async def current_officers(
5463

5564
@router.get(
5665
"/all",
57-
description="Information from all exec terms. If year is not included, all years will be returned. If semester is not included, all semesters that year will be returned. If semester is given, but year is not, return all years and all semesters.",
66+
description="Information for all execs from all exec terms",
5867
)
5968
async def all_officers(
6069
request: Request,
6170
db_session: database.DBSession,
62-
view_only_filled_in: bool = True,
71+
# Officer terms for officers which have not yet started their term yet are considered private,
72+
# and may only be accessed by that officer and executives.
73+
view_not_started_officer_terms: bool = False,
6374
):
64-
async def has_access(db_session: database.DBSession, request: Request) -> bool:
65-
# determine if user has access to this private data
66-
session_id = request.cookies.get("session_id", None)
67-
if session_id is None:
68-
return False
69-
70-
computing_id = await auth.crud.get_computing_id(db_session, session_id)
71-
if computing_id is None:
72-
return False
73-
else:
74-
has_private_access = await OfficerPrivateInfo.has_permission(db_session, computing_id)
75-
is_website_admin = await WebsiteAdmin.has_permission(db_session, computing_id)
76-
77-
if not view_only_filled_in and (session_id is None or not is_website_admin):
78-
raise HTTPException(status_code=401, detail="must have private access to view not filled in terms")
79-
80-
return has_private_access
81-
82-
has_private_access = await has_access(db_session, request)
83-
84-
all_officer_data = await officers.crud.all_officer_data(db_session, has_private_access, view_only_filled_in)
85-
all_officer_data = [officer_data.serializable_dict() for officer_data in all_officer_data]
86-
return JSONResponse(all_officer_data)
75+
_, computing_id, has_private_access = await has_officer_private_info_access(request, db_session)
76+
if view_not_started_officer_terms:
77+
is_website_admin = (computing_id is not None) and (await WebsiteAdmin.has_permission(db_session, computing_id))
78+
if not is_website_admin:
79+
raise HTTPException(status_code=401, detail="only website admins can view all executive terms that have not started yet")
80+
81+
all_officer_data = await officers.crud.all_officer_data(db_session, has_private_access, not view_not_started_officer_terms)
82+
return JSONResponse([
83+
officer_data.serializable_dict()
84+
for officer_data in all_officer_data
85+
])
8786

8887
@router.get(
8988
"/terms/{computing_id}",

src/utils.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# we can't use and/or in sql expressions, so we must use these functions
77
from sqlalchemy.sql.expression import and_, or_
88

9-
from officers.tables import OfficerInfo, OfficerTerm
9+
from officers.tables import OfficerTerm
1010

1111

1212
def is_iso_format(date_str: str) -> bool:
@@ -17,6 +17,10 @@ def is_iso_format(date_str: str) -> bool:
1717
return False
1818

1919
def is_active_officer(query: Select) -> Select:
20+
"""
21+
An active officer is one who is currently part of the CSSS officer team.
22+
That is, they are not tentative, or past.
23+
"""
2024
query = query.where(
2125
and_(
2226
# cannot be an officer who has not started yet
@@ -43,6 +47,9 @@ def is_active_term(term: OfficerTerm) -> bool:
4347
)
4448
)
4549

50+
def has_term_started(term: OfficerTerm) -> bool:
51+
return term.start_date <= datetime.today()
52+
4653
def is_past_term(term: OfficerTerm) -> bool:
4754
"""Any term which has concluded"""
4855
return (

0 commit comments

Comments
 (0)