2323from reportlab .lib .units import inch
2424from reportlab .platypus import PageBreak , Paragraph , SimpleDocTemplate , Spacer , Table , TableStyle
2525
26+ from ....settings .__init__ import ATTENDANCE_CODE_BUFFER
2627from ....utils .date import get_date_range_this_year
2728from ...auth .decorators import attendance_taker_required , deny_restricted , eighth_admin_required
2829from ...dashboard .views import gen_sponsor_schedule
29- from ...schedule .models import Day
30+ from ...schedule .models import Block , Day , shift_time
3031from ...schedule .views import decode_date
3132from ..forms .admin .activities import ActivitySelectionForm
3233from ..forms .admin .blocks import BlockSelectionForm
3334from ..models import EighthActivity , EighthBlock , EighthScheduledActivity , EighthSignup , EighthSponsor , EighthWaitlist
3435from ..tasks import email_scheduled_activity_students_task
3536from ..utils import get_start_date
36- from .signup import shift_time
3737
3838logger = logging .getLogger (__name__ )
3939
@@ -290,6 +290,11 @@ def take_attendance_view(request, scheduled_activity_id):
290290 scheduled_activity .save ()
291291 invalidate_obj (scheduled_activity )
292292
293+ for signup in EighthSignup .objects .filter (scheduled_activity = scheduled_activity ):
294+ signup .attendance_marked = False
295+ signup .save ()
296+ invalidate_obj (signup )
297+
293298 messages .success (request , f"Attendance bit cleared for { scheduled_activity } " )
294299
295300 redirect_url = reverse (url_name , args = [scheduled_activity .id ])
@@ -420,6 +425,21 @@ def take_attendance_view(request, scheduled_activity_id):
420425
421426 members .sort (key = lambda m : m ["name" ])
422427
428+ auto_time_string = ""
429+ try :
430+ day_block = (
431+ Day .objects .select_related ("day_type" )
432+ .get (date = scheduled_activity .block .date )
433+ .day_type .blocks .get (name = "8" + scheduled_activity .block .block_letter )
434+ )
435+ start_time = shift_time (tm (hour = day_block .start .hour , minute = day_block .start .minute ), - ATTENDANCE_CODE_BUFFER )
436+ end_time = shift_time (tm (hour = day_block .end .hour , minute = day_block .end .minute ), ATTENDANCE_CODE_BUFFER )
437+ auto_time_string = f"({ start_time .strftime ('%-I:%M' )} - { end_time .strftime ('%-I:%M %p' )} )"
438+ except Day .DoesNotExist :
439+ auto_time_string = ""
440+ except Block .DoesNotExist :
441+ auto_time_string = ""
442+ print (auto_time_string )
423443 context = {
424444 "scheduled_activity" : scheduled_activity ,
425445 "passes" : passes ,
@@ -430,7 +450,8 @@ def take_attendance_view(request, scheduled_activity_id):
430450 "show_checkboxes" : (scheduled_activity .block .locked or request .user .is_eighth_admin ),
431451 "show_icons" : (scheduled_activity .block .locked and scheduled_activity .block .attendance_locked () and not request .user .is_eighth_admin ),
432452 "bbcu_script" : settings .BBCU_SCRIPT ,
433- "qr_url" : request .build_absolute_uri (reverse ("qr_attendance" , args = [scheduled_activity .id , scheduled_activity .attendance_code ])),
453+ "qr_url" : request .build_absolute_uri (reverse ("student_attendance" , args = [scheduled_activity .id , scheduled_activity .attendance_code ])),
454+ "auto_time_string" : auto_time_string ,
434455 }
435456
436457 if request .user .is_eighth_admin :
@@ -776,143 +797,123 @@ def email_students_view(request, scheduled_activity_id):
776797
777798@login_required
778799@deny_restricted
779- def student_attendance_view (request ):
780- blocks = EighthBlock .objects .get_blocks_today ()
781- mark_block = None
782- mark_result = None
783- if request .method == "POST" :
784- now = timezone .localtime ()
785- try :
786- day_blocks = Day .objects .select_related ("day_type" ).get (date = now ).day_type .blocks
787- except Day .DoesNotExist :
788- messages .error (request , "Error. Attendance is only available on school days." )
789- return redirect ("index" )
790- for block in blocks :
791- block_letter = block .block_letter
792- code = request .POST .get (block_letter )
793- if code is None :
794- continue
795- act = request .user .eighthscheduledactivity_set .get (block = block )
796- if act .get_code_mode_display () == "Auto" :
797- try :
798- day_block = day_blocks .get (name = "8" + block_letter )
799- except Exception :
800- mark_result = "invalid_time"
801- mark_block = block
802- break
803- start_time = shift_time (tm (hour = day_block .start .hour , minute = day_block .start .minute ), - 20 )
804- end_time = shift_time (tm (hour = day_block .end .hour , minute = day_block .end .minute ), 20 )
805- if not start_time <= now .time () <= end_time :
806- mark_result = "invalid_time"
807- mark_block = block
808- break
809- elif act .get_code_mode_display () == "Closed" :
810- mark_result = "code_closed"
811- mark_block = block
812- break
813- code = code .upper ()
814- if code == act .attendance_code :
815- try :
816- present = EighthSignup .objects .get (scheduled_activity = act , user__in = [request .user .id ])
817- present .was_absent = False
818- present .attendance_marked = True
819- present .save ()
820- invalidate_obj (present )
821- act .attendance_taken = True
822- act .save ()
823- invalidate_obj (act )
824- mark_result = "code_correct"
825- mark_block = block
826- except Exception :
827- mark_result = "code_fail"
828- mark_block = block
829- break
830- else :
831- mark_result = "code_fail"
832- mark_block = block
833- break
834- return student_frontend (request , mark_block , mark_result )
835-
800+ def student_attendance_view (request , sch_act_id , code = None ):
801+ """Handles the initial steps for code or QR code student-based attendance
802+ Args:
803+ request: the user's request
804+ sch_act_id: the id of the EighthScheduledActivity for which the user attempts to take attendance. It is from url, required.
805+ code: the code which the user is attempting to take attendance. From url, optional (if the user just wants to access the page)
806+ Returns:
807+ An HttpResponse rendering the student attendance page for the specified EighthScheduledActivity (if it exists).
808+ Otherwise, redirects to index / dashboard.
809+ """
810+ result = None # could end as None (no attendance attempt), no_signup, no_school, invalid_time, code_closed, code_fail, or marked
811+ try :
812+ sch_act = EighthScheduledActivity .objects .get (id = sch_act_id )
813+ except EighthScheduledActivity .DoesNotExist :
814+ messages .error (request , "Error marking attendance." )
815+ return redirect ("index" )
836816
837- @login_required
838- @deny_restricted
839- def student_frontend (request , mark_block : EighthBlock = None , mark_result : str = None ):
840- blocks = EighthBlock .objects .get_blocks_today ()
841- if blocks :
842- sch_acts = []
843- att_marked = []
844- for block in blocks :
845- try :
846- act = request .user .eighthscheduledactivity_set .get (block = block )
847- if act .activity .name != "z - Hybrid Sticky" :
848- sch_acts .append (
849- [block , act , ", " .join ([r .name for r in act .get_true_rooms ()]), ", " .join ([s .name for s in act .get_true_sponsors ()])]
850- )
851- signup = EighthSignup .objects .get (user = request .user , scheduled_activity = act )
852- if (not signup .was_absent ) and signup .attendance_marked and act .attendance_taken :
853- att_marked .append (block )
854- except EighthScheduledActivity .DoesNotExist :
855- sch_acts .append ([block , None ])
856- results = {
857- "code_correct" : "" ,
858- "code_closed" : '<p style="color: red;">Error. Ask your teacher to open the attendance code.</p>' ,
859- "code_fail" : '<p style="color: red;">Invalid Code.</p>' ,
860- "invalid_time" : f'<p style="color: red;">Invalid time. Please fill this out during { block .block_letter } block.</p>' ,
861- }
862- response = render (
863- request ,
864- "eighth/student_submit_attendance.html" ,
865- context = {"sch_acts" : sch_acts , "att_marked" : att_marked , "mark_block" : mark_block , "attendance_result" : results .get (mark_result )},
866- )
817+ try :
818+ user_signup = EighthSignup .objects .get (user = request .user , scheduled_activity = sch_act )
819+ if user_signup .attendance_marked and not user_signup .was_absent :
820+ return student_frontend (request , sch_act_id , result = "marked" )
821+ except EighthSignup .DoesNotExist :
822+ result = "no_signup"
823+ return student_frontend (request , sch_act_id , result )
824+
825+ is_open = check_attendance_open (sch_act ) # returns None if attendance is open, error string if closed
826+ if is_open is None :
827+ if code is not None :
828+ result = mark_attendance (request , sch_act , code )
867829 else :
868- messages . error ( request , "There are no eighth period blocks scheduled today." )
869- response = redirect ( "index" )
870- return response
830+ result = is_open
831+
832+ return student_frontend ( request , sch_act_id , result )
871833
872834
873835@login_required
874836@deny_restricted
875- def qr_attendance_view (request , act_id , code ):
876- act = get_object_or_404 (EighthScheduledActivity , id = act_id )
877- error = False
837+ def student_frontend (request , sch_act_id , result ):
838+ """Handles the process of rendering the frontend UI for student-based attendance.
839+ It is only called by the student_attendance_view function
840+ Args:
841+ request: the user's request
842+ sch_act_id: the EighthScheduledActivity for which the user attempts to take attendance
843+ result: The result of the student's attempt to take attendance (if there was an attempt)
844+ Returns:
845+ An HttpResponse which renders the UI with a corresponding message. Returns this to student_attendance_view.
846+ """
847+
848+ result_to_html = {
849+ None : "" ,
850+ "no_signup" : '<p style="color: red;">Error: You are not signed up for this activity.</p>' ,
851+ "no_school" : '<p style="color: red;">Error: Attendance is only available on school days.</p>' ,
852+ "invalid_time" : '<p style="color: red;">Error: Please fill out attendance during the timeframe of this activity.</p>' ,
853+ "code_closed" : '<p style="color: red;">Error: Your activity sponsor has closed the attendance code.</p>' ,
854+ "code_fail" : '<p style="color: red;">Error: Invalid Code.</p>' ,
855+ "marked" : '<p style="color: green;">Attendance marked.</p>' ,
856+ }
857+ html_text = result_to_html [result ]
858+ sch_act = EighthScheduledActivity .objects .get (id = sch_act_id )
859+ date_string = sch_act .block .date .strftime ("%b %d" )
860+ show_form = result in (None , "code_fail" )
861+ return render (
862+ request ,
863+ "eighth/student_submit_attendance.html" ,
864+ context = {"result" : result , "result_html" : html_text , "show_form" : show_form , "sch_act" : sch_act , "date_string" : date_string },
865+ )
866+
867+
868+ def mark_attendance (request , act , code ):
869+ """Handles the process of checking the code and marking attendance for the user.
870+ Args:
871+ request: the user's request
872+ act: the EighthScheduledActivity for which the user attempts to take attendance
873+ code: the code which the user is attempting to take attendance
874+ Returns:
875+ A string reporting the result of the attempt. Either "code_correct" or "code_fail"
876+ """
877+ if code .upper () == act .attendance_code :
878+ try :
879+ present = EighthSignup .objects .get (scheduled_activity = act , user__in = [request .user .id ])
880+ present .was_absent = False
881+ present .attendance_marked = True
882+ present .save ()
883+ invalidate_obj (present )
884+ act .attendance_taken = True
885+ act .save ()
886+ invalidate_obj (act )
887+ return "marked"
888+ except EighthSignup .DoesNotExist :
889+ return "code_fail"
890+ return "code_fail"
891+
892+
893+ def check_attendance_open (act : EighthScheduledActivity ):
894+ """Checks whether attendance is open for an EighthScheduledActivity.
895+ Args:
896+ act: the EighthScheduledActivity in question
897+ day_blocks: the set of Block objects in the current day's day_type (defined in student_attendance_view)
898+ Returns:
899+ mark_result: a string, either "no_school", "invalid_time" or "code_closed", or None if attendance is open.
900+ """
878901 block = act .block
879902 mark_result = None
880903 if act .get_code_mode_display () == "Auto" :
881904 now = timezone .localtime ()
882- day_blocks = Day .objects .select_related ("day_type" ).get (date = now ).day_type .blocks
905+ try :
906+ day_blocks = Day .objects .select_related ("day_type" ).get (date = now ).day_type .blocks
907+ except Day .DoesNotExist :
908+ return "no_school"
883909 try :
884910 day_block = day_blocks .get (name = "8" + block .block_letter )
885- start_time = shift_time (tm (hour = day_block .start .hour , minute = day_block .start .minute ), - 20 )
886- end_time = shift_time (tm (hour = day_block .end .hour , minute = day_block .end .minute ), 20 )
887- if not start_time <= now .time () <= end_time :
888- mark_result = "invalid_time"
889- error = True
890- except Exception :
911+ except Block .DoesNotExist :
912+ mark_result = "invalid_time"
913+ start_time = shift_time (tm (hour = day_block .start .hour , minute = day_block .start .minute ), - ATTENDANCE_CODE_BUFFER )
914+ end_time = shift_time (tm (hour = day_block .end .hour , minute = day_block .end .minute ), ATTENDANCE_CODE_BUFFER )
915+ if not start_time <= timezone .localtime ().time () <= end_time :
891916 mark_result = "invalid_time"
892- error = True
893917 elif act .get_code_mode_display () == "Closed" :
894918 mark_result = "code_closed"
895- error = True
896- if not error :
897- code = code .upper ()
898- if code == act .attendance_code :
899- try :
900- present = EighthSignup .objects .get (scheduled_activity = act , user__in = [request .user .id ])
901- present .was_absent = False
902- present .attendance_marked = True
903- present .save ()
904- invalidate_obj (present )
905- act .attendance_taken = True
906- act .save ()
907- invalidate_obj (act )
908- mark_result = "code_correct"
909- messages .success (request , "Attendance marked." )
910- except Exception :
911- mark_result = "code_fail"
912- messages .error (request , "Failed to mark attendance." )
913- else :
914- mark_result = "code_fail"
915- messages .error (request , "Failed to mark attendance." )
916- else :
917- messages .error (request , "Failed to mark attendance." )
918- return student_frontend (request , block , mark_result )
919+ return mark_result
0 commit comments