1010from django .contrib import messages
1111from django .contrib .auth import get_user_model
1212from django .contrib .auth .decorators import login_required
13- from django .db .models import Q
13+ from django .db .models import Q , QuerySet
1414from django .http import Http404
1515from django .shortcuts import get_object_or_404 , redirect , render
1616from django .urls import reverse
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
@@ -420,6 +420,21 @@ def take_attendance_view(request, scheduled_activity_id):
420420
421421 members .sort (key = lambda m : m ["name" ])
422422
423+ auto_time_string = ""
424+ try :
425+ day_block = (
426+ Day .objects .select_related ("day_type" )
427+ .get (date = scheduled_activity .block .date )
428+ .day_type .blocks .get (name = "8" + scheduled_activity .block .block_letter )
429+ )
430+ start_time = shift_time (tm (hour = day_block .start .hour , minute = day_block .start .minute ), - ATTENDANCE_CODE_BUFFER )
431+ end_time = shift_time (tm (hour = day_block .end .hour , minute = day_block .end .minute ), ATTENDANCE_CODE_BUFFER )
432+ auto_time_string = "(" + start_time .strftime ("%H:%M" ) + " - " + end_time .strftime ("%H:%M" ) + ")"
433+ except Day .DoesNotExist :
434+ auto_time_string = ""
435+ except Block .DoesNotExist :
436+ auto_time_string = ""
437+ print (auto_time_string )
423438 context = {
424439 "scheduled_activity" : scheduled_activity ,
425440 "passes" : passes ,
@@ -430,7 +445,8 @@ def take_attendance_view(request, scheduled_activity_id):
430445 "show_checkboxes" : (scheduled_activity .block .locked or request .user .is_eighth_admin ),
431446 "show_icons" : (scheduled_activity .block .locked and scheduled_activity .block .attendance_locked () and not request .user .is_eighth_admin ),
432447 "bbcu_script" : settings .BBCU_SCRIPT ,
433- "qr_url" : request .build_absolute_uri (reverse ("qr_attendance" , args = [scheduled_activity .id , scheduled_activity .attendance_code ])),
448+ "qr_url" : request .build_absolute_uri (reverse ("student_attendance" , args = [scheduled_activity .id , scheduled_activity .attendance_code ])),
449+ "auto_time_string" : auto_time_string ,
434450 }
435451
436452 if request .user .is_eighth_admin :
@@ -776,67 +792,120 @@ def email_students_view(request, scheduled_activity_id):
776792
777793@login_required
778794@deny_restricted
779- def student_attendance_view (request ):
795+ def student_attendance_view (request , act_id = None , code = None ):
796+ """Marks attendance for code and QR code student-based attendance processes. Renders the student attendance page.
797+ Also handles GET requests from students accessing the student attendance page.
798+ Args:
799+ request: the user's request
800+ act_id: (For QR code attendance) the id of the act for which the student is attempting to take attendance
801+ code: (For QR code attendance) the code which the student is attempting to take attendance with
802+ Returns:
803+ An HttpResponse rendering the student attendance view (built from the student_frontend function), possibly with messages
804+ regarding the result of the attendance-marking attempt
805+ Can also possibly redirect to index or the generic student_attendance page if encountered an error
806+ """
780807 blocks = EighthBlock .objects .get_blocks_today ()
781808 mark_block = None
782809 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" )
810+ qr_mark = False
811+ now = timezone .localtime ()
812+ try :
813+ day_blocks = Day .objects .select_related ("day_type" ).get (date = now ).day_type .blocks
814+ except Day .DoesNotExist :
815+ messages .error (request , "Error. Attendance is only available on school days." )
816+ return redirect ("index" )
817+ if act_id is None and code is None :
790818 for block in blocks :
791819 block_letter = block .block_letter
792820 code = request .POST .get (block_letter )
793821 if code is None :
794822 continue
795823 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"
824+ result = check_attendance_open (act , day_blocks )
825+ if result is not None :
811826 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"
827+ mark_result = result
828+ elif act_id is not None and code is not None :
829+ try :
830+ act = request .user .eighthscheduledactivity_set .get (id = act_id )
831+ block = act .block
832+ result = check_attendance_open (act , day_blocks )
833+ qr_mark = True
834+ if result is not None :
832835 mark_block = block
833- break
836+ mark_result = result
837+ except EighthScheduledActivity .DoesNotExist :
838+ messages .error (request , "Error marking attendance." )
839+ return redirect ("student_attendance" )
840+ if code is not None and mark_result is None :
841+ mark_result = mark_attendance (request , act , code )
842+ mark_block = block
843+ if qr_mark :
844+ if mark_result != "code_correct" :
845+ messages .error (request , "Error marking attendance." )
846+ return redirect ("student_attendance" )
834847 return student_frontend (request , mark_block , mark_result )
835848
836849
850+ def mark_attendance (request , act , code ):
851+ """Handles the process of checking the code and marking attedance for the user.
852+ Args:
853+ request: the user's request
854+ act: the EighthScheduledActivity for which the user attempts to take attendance
855+ code: the code which the user is attempting to take attendance
856+ Returns:
857+ A string reporting the result of the attempt. Either "code_correct" or "code_fail"
858+ """
859+ if code .upper () == act .attendance_code :
860+ try :
861+ present = EighthSignup .objects .get (scheduled_activity = act , user__in = [request .user .id ])
862+ present .was_absent = False
863+ present .attendance_marked = True
864+ present .save ()
865+ invalidate_obj (present )
866+ act .attendance_taken = True
867+ act .save ()
868+ invalidate_obj (act )
869+ return "code_correct"
870+ except EighthSignup .DoesNotExist :
871+ return "code_fail"
872+ return "code_fail"
873+
874+
875+ def check_attendance_open (act : EighthScheduledActivity , day_blocks : QuerySet [Block ]):
876+ """Checks whether attendance is open for an EighthScheduledActivity.
877+ Args:
878+ act: the EighthScheduledActivity in question
879+ day_blocks: the set of Block objects in the current day's day_type (defined in student_attendance_view)
880+ Returns:
881+ mark_result: a string, either "invalid_time" or "code_closed", or None if attendance is open.
882+ """
883+ block = act .block
884+ mark_result = None
885+ if act .get_code_mode_display () == "Auto" :
886+ try :
887+ day_block = day_blocks .get (name = "8" + block .block_letter )
888+ except Block .DoesNotExist :
889+ mark_result = "invalid_time"
890+ start_time = shift_time (tm (hour = day_block .start .hour , minute = day_block .start .minute ), - ATTENDANCE_CODE_BUFFER )
891+ end_time = shift_time (tm (hour = day_block .end .hour , minute = day_block .end .minute ), ATTENDANCE_CODE_BUFFER )
892+ if not start_time <= timezone .localtime ().time () <= end_time :
893+ mark_result = "invalid_time"
894+ elif act .get_code_mode_display () == "Closed" :
895+ mark_result = "code_closed"
896+ return mark_result
897+
898+
837899@login_required
838900@deny_restricted
839901def student_frontend (request , mark_block : EighthBlock = None , mark_result : str = None ):
902+ """Generates the student frontend view for their eighth period blocks.
903+ Args:
904+ request: the user's HttpRequest, mark_block: the block for which the system attempted to mark the student's attendance,
905+ mark_result: the result of that attendance marking attempt ("invalid time", "code_closed", "code_fail", or "code_correct")
906+ Returns:
907+ An HttpResponse rendering the student frontend using the student_submit_attendance.html template, or redirecting to index.
908+ """
840909 blocks = EighthBlock .objects .get_blocks_today ()
841910 if blocks :
842911 sch_acts = []
@@ -868,51 +937,3 @@ def student_frontend(request, mark_block: EighthBlock = None, mark_result: str =
868937 messages .error (request , "There are no eighth period blocks scheduled today." )
869938 response = redirect ("index" )
870939 return response
871-
872-
873- @login_required
874- @deny_restricted
875- def qr_attendance_view (request , act_id , code ):
876- act = get_object_or_404 (EighthScheduledActivity , id = act_id )
877- error = False
878- block = act .block
879- mark_result = None
880- if act .get_code_mode_display () == "Auto" :
881- now = timezone .localtime ()
882- day_blocks = Day .objects .select_related ("day_type" ).get (date = now ).day_type .blocks
883- try :
884- 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 :
891- mark_result = "invalid_time"
892- error = True
893- elif act .get_code_mode_display () == "Closed" :
894- 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 )
0 commit comments