1- import re
21from datetime import datetime
32
43from fastapi import APIRouter , HTTPException , Request , status
1110from elections .models import (
1211 ElectionParams ,
1312 ElectionResponse ,
14- ElectionStatusEnum ,
1513 ElectionTypeEnum ,
1614 ElectionUpdateParams ,
17- NomineeApplicationModel ,
18- NomineeApplicationParams ,
19- NomineeApplicationUpdateParams ,
2015 NomineeInfoModel ,
2116 NomineeInfoUpdateParams ,
2217)
23- from elections .tables import Election , NomineeApplication , NomineeInfo
18+ from elections .tables import Election , NomineeInfo
2419from officers .constants import COUNCIL_REP_ELECTION_POSITIONS , GENERAL_ELECTION_POSITIONS , OfficerPositionEnum
2520from permission .types import ElectionOfficer , WebsiteAdmin
2621from utils .shared_models import DetailModel , SuccessResponse
27- from utils .urls import admin_or_raise , get_current_user , logged_in_or_raise
22+ from utils .urls import admin_or_raise , get_current_user , slugify
2823
2924router = APIRouter (
3025 prefix = "/elections" ,
3126 tags = ["elections" ],
3227)
3328
34- def _slugify (text : str ) -> str :
35- """Creates a unique slug based on text passed in. Assumes non-unicode text."""
36- return re .sub (r"[\W_]+" , "-" , text .strip ().replace ("/" , "" ).replace ("&" , "" ))
37-
38- async def _get_user_permissions (
29+ async def get_user_permissions (
3930 request : Request ,
4031 db_session : database .DBSession ,
4132) -> tuple [bool , str | None , str | None ]:
@@ -108,7 +99,7 @@ async def list_elections(
10899 request : Request ,
109100 db_session : database .DBSession ,
110101):
111- is_admin , _ , _ = await _get_user_permissions (request , db_session )
102+ is_admin , _ , _ = await get_user_permissions (request , db_session )
112103 election_list = await elections .crud .get_all_elections (db_session )
113104 if election_list is None or len (election_list ) == 0 :
114105 raise HTTPException (
@@ -149,15 +140,15 @@ async def get_election(
149140 election_name : str
150141):
151142 current_time = datetime .now ()
152- slugified_name = _slugify (election_name )
143+ slugified_name = slugify (election_name )
153144 election = await elections .crud .get_election (db_session , slugified_name )
154145 if election is None :
155146 raise HTTPException (
156147 status_code = status .HTTP_404_NOT_FOUND ,
157148 detail = f"election with slug { slugified_name } does not exist"
158149 )
159150
160- is_valid_user , _ , _ = await _get_user_permissions (request , db_session )
151+ is_valid_user , _ , _ = await get_user_permissions (request , db_session )
161152 if current_time >= election .datetime_start_voting or is_valid_user :
162153
163154 election_json = election .private_details (current_time )
@@ -229,7 +220,7 @@ async def create_election(
229220 else :
230221 available_positions = body .available_positions
231222
232- slugified_name = _slugify (body .name )
223+ slugified_name = slugify (body .name )
233224 current_time = datetime .now ()
234225 start_nominations = datetime .fromisoformat (body .datetime_start_nominations )
235226 start_voting = datetime .fromisoformat (body .datetime_start_voting )
@@ -245,7 +236,7 @@ async def create_election(
245236 available_positions
246237 )
247238
248- is_valid_user , _ , _ = await _get_user_permissions (request , db_session )
239+ is_valid_user , _ , _ = await get_user_permissions (request , db_session )
249240 if not is_valid_user :
250241 raise HTTPException (
251242 status_code = status .HTTP_401_UNAUTHORIZED ,
@@ -305,14 +296,14 @@ async def update_election(
305296 db_session : database .DBSession ,
306297 election_name : str ,
307298):
308- is_valid_user , _ , _ = await _get_user_permissions (request , db_session )
299+ is_valid_user , _ , _ = await get_user_permissions (request , db_session )
309300 if not is_valid_user :
310301 raise HTTPException (
311302 status_code = status .HTTP_401_UNAUTHORIZED ,
312303 detail = "must have election officer or admin permission"
313304 )
314305
315- slugified_name = _slugify (election_name )
306+ slugified_name = slugify (election_name )
316307 election = await elections .crud .get_election (db_session , slugified_name )
317308 if not election :
318309 raise HTTPException (
@@ -360,8 +351,8 @@ async def delete_election(
360351 db_session : database .DBSession ,
361352 election_name : str
362353):
363- slugified_name = _slugify (election_name )
364- is_valid_user , _ , _ = await _get_user_permissions (request , db_session )
354+ slugified_name = slugify (election_name )
355+ is_valid_user , _ , _ = await get_user_permissions (request , db_session )
365356 if not is_valid_user :
366357 raise HTTPException (
367358 status_code = status .HTTP_401_UNAUTHORIZED ,
@@ -374,237 +365,6 @@ async def delete_election(
374365 old_election = await elections .crud .get_election (db_session , slugified_name )
375366 return JSONResponse ({"success" : old_election is None })
376367
377- # registration ------------------------------------------------------------- #
378-
379- @router .get (
380- "/registration/{election_name:str}" ,
381- description = "get all the registrations of a single election" ,
382- response_model = list [NomineeApplicationModel ],
383- responses = {
384- 401 : { "description" : "Not logged in" , "model" : DetailModel },
385- 404 : { "description" : "Election with slug does not exist" , "model" : DetailModel }
386- },
387- operation_id = "get_election_registrations"
388- )
389- async def get_election_registrations (
390- request : Request ,
391- db_session : database .DBSession ,
392- election_name : str
393- ):
394- _ , computing_id = await logged_in_or_raise (request , db_session )
395-
396- slugified_name = _slugify (election_name )
397- if await elections .crud .get_election (db_session , slugified_name ) is None :
398- raise HTTPException (
399- status_code = status .HTTP_404_NOT_FOUND ,
400- detail = f"election with slug { slugified_name } does not exist"
401- )
402-
403- registration_list = await elections .crud .get_all_registrations_of_user (db_session , computing_id , slugified_name )
404- if registration_list is None :
405- return JSONResponse ([])
406- return JSONResponse ([
407- item .serialize () for item in registration_list
408- ])
409-
410- @router .post (
411- "/registration/{election_name:str}" ,
412- description = "Register for a specific position in this election, but doesn't set a speech. Returns the created entry." ,
413- response_model = NomineeApplicationModel ,
414- responses = {
415- 400 : { "description" : "Bad request" , "model" : DetailModel },
416- 401 : { "description" : "Not logged in" , "model" : DetailModel },
417- 403 : { "description" : "Not an admin" , "model" : DetailModel },
418- 404 : { "description" : "No election found" , "model" : DetailModel },
419- },
420- operation_id = "register"
421- )
422- async def register_in_election (
423- request : Request ,
424- db_session : database .DBSession ,
425- body : NomineeApplicationParams ,
426- election_name : str
427- ):
428- await admin_or_raise (request , db_session )
429-
430- if body .position not in OfficerPositionEnum :
431- raise HTTPException (
432- status_code = status .HTTP_400_BAD_REQUEST ,
433- detail = f"invalid position { body .position } "
434- )
435-
436- if await elections .crud .get_nominee_info (db_session , body .computing_id ) is None :
437- # ensure that the user has a nominee info entry before allowing registration to occur.
438- raise HTTPException (
439- status_code = status .HTTP_400_BAD_REQUEST ,
440- detail = "must have submitted nominee info before registering"
441- )
442-
443- slugified_name = _slugify (election_name )
444- election = await elections .crud .get_election (db_session , slugified_name )
445- if election is None :
446- raise HTTPException (
447- status_code = status .HTTP_404_NOT_FOUND ,
448- detail = f"election with slug { slugified_name } does not exist"
449- )
450-
451- if body .position not in election .available_positions :
452- # NOTE: We only restrict creating a registration for a position that doesn't exist,
453- # not updating or deleting one
454- raise HTTPException (
455- status_code = status .HTTP_400_BAD_REQUEST ,
456- detail = f"{ body .position } is not available to register for in this election"
457- )
458-
459- if election .status (datetime .now ()) != ElectionStatusEnum .NOMINATIONS :
460- raise HTTPException (
461- status_code = status .HTTP_400_BAD_REQUEST ,
462- detail = "registrations can only be made during the nomination period"
463- )
464-
465- if await elections .crud .get_all_registrations_of_user (db_session , body .computing_id , slugified_name ):
466- raise HTTPException (
467- status_code = status .HTTP_400_BAD_REQUEST ,
468- detail = "person is already registered in this election"
469- )
470-
471- # TODO: associate specific elections officers with specific elections, then don't
472- # allow any elections officer running an election to register for it
473- await elections .crud .add_registration (db_session , NomineeApplication (
474- computing_id = body .computing_id ,
475- nominee_election = slugified_name ,
476- position = body .position ,
477- speech = None
478- ))
479- await db_session .commit ()
480-
481- registrant = await elections .crud .get_one_registration_in_election (
482- db_session , body .computing_id , slugified_name , body .position
483- )
484- if not registrant :
485- raise HTTPException (
486- status_code = status .HTTP_500_INTERNAL_SERVER_ERROR ,
487- detail = "failed to find new registrant"
488- )
489- return registrant
490-
491- @router .patch (
492- "/registration/{election_name:str}/{position:str}/{computing_id:str}" ,
493- description = "update the application of a specific registrant and return the changed entry" ,
494- response_model = NomineeApplicationModel ,
495- responses = {
496- 400 : { "description" : "Bad request" , "model" : DetailModel },
497- 401 : { "description" : "Not logged in" , "model" : DetailModel },
498- 403 : { "description" : "Not an admin" , "model" : DetailModel },
499- 404 : { "description" : "No election found" , "model" : DetailModel },
500- },
501- operation_id = "update_registration"
502- )
503- async def update_registration (
504- request : Request ,
505- db_session : database .DBSession ,
506- body : NomineeApplicationUpdateParams ,
507- election_name : str ,
508- computing_id : str ,
509- position : OfficerPositionEnum
510- ):
511- await admin_or_raise (request , db_session )
512-
513- if body .position not in OfficerPositionEnum :
514- raise HTTPException (
515- status_code = status .HTTP_400_BAD_REQUEST ,
516- detail = f"invalid position { body .position } "
517- )
518-
519- slugified_name = _slugify (election_name )
520- election = await elections .crud .get_election (db_session , slugified_name )
521- if election is None :
522- raise HTTPException (
523- status_code = status .HTTP_404_NOT_FOUND ,
524- detail = f"election with slug { slugified_name } does not exist"
525- )
526-
527- # self updates can only be done during nomination period. Officer updates can be done whenever
528- if election .status (datetime .now ()) != ElectionStatusEnum .NOMINATIONS :
529- raise HTTPException (
530- status_code = status .HTTP_400_BAD_REQUEST ,
531- detail = "speeches can only be updated during the nomination period"
532- )
533-
534- registration = await elections .crud .get_one_registration_in_election (db_session , computing_id , slugified_name , position )
535- if not registration :
536- raise HTTPException (
537- status_code = status .HTTP_404_NOT_FOUND ,
538- detail = "no registration record found"
539- )
540-
541- registration .update_from_params (body )
542-
543- await elections .crud .update_registration (db_session , registration )
544- await db_session .commit ()
545-
546- registrant = await elections .crud .get_one_registration_in_election (
547- db_session , registration .computing_id , slugified_name , registration .position
548- )
549- if not registrant :
550- raise HTTPException (
551- status_code = status .HTTP_500_INTERNAL_SERVER_ERROR ,
552- detail = "failed to find changed registrant"
553- )
554- return registrant
555-
556- @router .delete (
557- "/registration/{election_name:str}/{position:str}/{computing_id:str}" ,
558- description = "delete the registration of a person" ,
559- response_model = SuccessResponse ,
560- responses = {
561- 400 : { "description" : "Bad request" , "model" : DetailModel },
562- 401 : { "description" : "Not logged in" , "model" : DetailModel },
563- 403 : { "description" : "Not an admin" , "model" : DetailModel },
564- 404 : { "description" : "No election or registrant found" , "model" : DetailModel },
565- },
566- operation_id = "delete_registration"
567- )
568- async def delete_registration (
569- request : Request ,
570- db_session : database .DBSession ,
571- election_name : str ,
572- position : OfficerPositionEnum ,
573- computing_id : str
574- ):
575- await admin_or_raise (request , db_session )
576-
577- if position not in OfficerPositionEnum :
578- raise HTTPException (
579- status_code = status .HTTP_400_BAD_REQUEST ,
580- detail = f"invalid position { position } "
581- )
582-
583- slugified_name = _slugify (election_name )
584- election = await elections .crud .get_election (db_session , slugified_name )
585- if election is None :
586- raise HTTPException (
587- status_code = status .HTTP_404_NOT_FOUND ,
588- detail = f"election with slug { slugified_name } does not exist"
589- )
590-
591- if election .status (datetime .now ()) != ElectionStatusEnum .NOMINATIONS :
592- raise HTTPException (
593- status_code = status .HTTP_400_BAD_REQUEST ,
594- detail = "registration can only be revoked during the nomination period"
595- )
596-
597- if not await elections .crud .get_all_registrations_of_user (db_session , computing_id , slugified_name ):
598- raise HTTPException (
599- status_code = status .HTTP_404_NOT_FOUND ,
600- detail = f"{ computing_id } was not registered in election { slugified_name } for { position } "
601- )
602-
603- await elections .crud .delete_registration (db_session , computing_id , slugified_name , position )
604- await db_session .commit ()
605- old_election = await elections .crud .get_one_registration_in_election (db_session , computing_id , slugified_name , position )
606- return JSONResponse ({"success" : old_election is None })
607-
608368# nominee info ------------------------------------------------------------- #
609369
610370@router .get (
0 commit comments