12
12
from crum import get_current_request
13
13
from dateutil .parser import parse as parse_date
14
14
from django .conf import settings
15
+ from django .core .cache import cache
15
16
from django .http import Http404 , QueryDict
16
17
from django .urls import reverse
17
18
from django .utils .translation import gettext as _
35
36
)
36
37
from lms .djangoapps .courseware .access_utils import check_authentication , check_data_sharing_consent , check_enrollment , \
37
38
check_correct_active_enterprise_customer , is_priority_access_error
39
+ from lms .djangoapps .courseware .context_processor import get_user_timezone_or_last_seen_timezone_or_utc
38
40
from lms .djangoapps .courseware .courseware_access_exception import CoursewareAccessException
39
41
from lms .djangoapps .courseware .date_summary import (
40
42
CertificateAvailableDate ,
50
52
from lms .djangoapps .courseware .masquerade import check_content_start_date_for_masquerade_user
51
53
from lms .djangoapps .courseware .model_data import FieldDataCache
52
54
from lms .djangoapps .courseware .block_render import get_block
55
+ from lms .djangoapps .grades .api import CourseGradeFactory
53
56
from lms .djangoapps .survey .utils import SurveyRequiredAccessError , check_survey_required_and_unanswered
57
+ from openedx .core .djangoapps .content .block_structure .api import get_block_structure_manager
54
58
from openedx .core .djangoapps .content .course_overviews .models import CourseOverview
55
59
from openedx .core .djangoapps .enrollments .api import get_course_enrollment_details
56
60
from openedx .core .djangoapps .site_configuration import helpers as configuration_helpers
@@ -587,7 +591,7 @@ def get_course_blocks_completion_summary(course_key, user):
587
591
588
592
589
593
@request_cached ()
590
- def get_course_assignments (course_key , user , include_access = False ): # lint-amnesty, pylint: disable=too-many-statements
594
+ def get_course_assignments (course_key , user , include_access = False , include_without_due = False , ): # lint-amnesty, pylint: disable=too-many-statements
591
595
"""
592
596
Returns a list of assignment (at the subsection/sequential level) due dates for the given course.
593
597
@@ -607,7 +611,8 @@ def get_course_assignments(course_key, user, include_access=False): # lint-amne
607
611
for subsection_key in block_data .get_children (section_key ):
608
612
due = block_data .get_xblock_field (subsection_key , 'due' )
609
613
graded = block_data .get_xblock_field (subsection_key , 'graded' , False )
610
- if due and graded :
614
+
615
+ if (due or include_without_due ) and graded :
611
616
first_component_block_id = get_first_component_of_block (subsection_key , block_data )
612
617
contains_gated_content = include_access and block_data .get_xblock_field (
613
618
subsection_key , 'contains_gated_content' , False )
@@ -624,7 +629,11 @@ def get_course_assignments(course_key, user, include_access=False): # lint-amne
624
629
else :
625
630
complete = False
626
631
627
- past_due = not complete and due < now
632
+ if due :
633
+ past_due = not complete and due < now
634
+ else :
635
+ past_due = False
636
+ due = None
628
637
assignments .append (_Assignment (
629
638
subsection_key , title , url , due , contains_gated_content ,
630
639
complete , past_due , assignment_type , None , first_component_block_id
@@ -764,6 +773,39 @@ def _ora_assessment_to_assignment(
764
773
)
765
774
766
775
776
+ def get_assignments_grades (user , course_id , cache_timeout ):
777
+ """
778
+ Calculate the progress of the assignment for the user in the course.
779
+
780
+ Arguments:
781
+ user (User): Django User object.
782
+ course_id (CourseLocator): The course key.
783
+ cache_timeout (int): Cache timeout in seconds
784
+ Returns:
785
+ list (ReadSubsectionGrade, ZeroSubsectionGrade): The list with assignments grades.
786
+ """
787
+ is_staff = bool (has_access (user , 'staff' , course_id ))
788
+
789
+ try :
790
+ course = get_course_with_access (user , 'load' , course_id )
791
+ cache_key = f'course_block_structure_{ str (course_id )} _{ str (course .course_version )} _{ user .id } '
792
+ collected_block_structure = cache .get (cache_key )
793
+ if not collected_block_structure :
794
+ collected_block_structure = get_block_structure_manager (course_id ).get_collected ()
795
+ cache .set (cache_key , collected_block_structure , cache_timeout )
796
+
797
+ course_grade = CourseGradeFactory ().read (user , collected_block_structure = collected_block_structure )
798
+
799
+ # recalculate course grade from visible grades (stored grade was calculated over all grades, visible or not)
800
+ course_grade .update (visible_grades_only = True , has_staff_access = is_staff )
801
+ subsection_grades = list (course_grade .subsection_grades .values ())
802
+ except Exception as err : # pylint: disable=broad-except
803
+ log .warning (f'Could not get grades for the course: { course_id } , error: { err } ' )
804
+ return []
805
+
806
+ return subsection_grades
807
+
808
+
767
809
def get_first_component_of_block (block_key , block_data ):
768
810
"""
769
811
This function returns the first leaf block of a section(block_key)
@@ -1019,3 +1061,64 @@ def get_course_chapter_ids(course_key):
1019
1061
log .exception ('Failed to retrieve course from modulestore.' )
1020
1062
return []
1021
1063
return [str (chapter_key ) for chapter_key in chapter_keys if chapter_key .block_type == 'chapter' ]
1064
+
1065
+
1066
+ def get_past_and_future_course_assignments (request , user , course ):
1067
+ """
1068
+ Returns the future assignment data and past assignments data for given user and course.
1069
+
1070
+ Arguments:
1071
+ request (Request): The HTTP GET request.
1072
+ user (User): The user for whom the assignments are received.
1073
+ course (Course): Course object for whom the assignments are received.
1074
+ Returns:
1075
+ tuple (list, list): Tuple of `past_assignments` list and `next_assignments` list.
1076
+ `next_assignments` list contains only uncompleted assignments.
1077
+ """
1078
+ assignments = get_course_assignment_date_blocks (course , user , request , include_past_dates = True )
1079
+ past_assignments = []
1080
+ future_assignments = []
1081
+
1082
+ timezone = get_user_timezone_or_last_seen_timezone_or_utc (user )
1083
+ for assignment in sorted (assignments , key = lambda x : x .date ):
1084
+ if assignment .date < datetime .now (timezone ):
1085
+ past_assignments .append (assignment )
1086
+ else :
1087
+ if not assignment .complete :
1088
+ future_assignments .append (assignment )
1089
+
1090
+ if future_assignments :
1091
+ future_assignment_date = future_assignments [0 ].date .date ()
1092
+ next_assignments = [
1093
+ assignment for assignment in future_assignments if assignment .date .date () == future_assignment_date
1094
+ ]
1095
+ else :
1096
+ next_assignments = []
1097
+
1098
+ return next_assignments , past_assignments
1099
+
1100
+
1101
+ def get_assignments_completions (course_key , user ):
1102
+ """
1103
+ Calculate the progress of the user in the course by assignments.
1104
+
1105
+ Arguments:
1106
+ course_key (CourseLocator): The Course for which course progress is requested.
1107
+ user (User): The user for whom course progress is requested.
1108
+ Returns:
1109
+ dict (dict): Dictionary contains information about total assignments count
1110
+ in the given course and how many assignments the user has completed.
1111
+ """
1112
+ course_assignments = get_course_assignments (course_key , user , include_without_due = True )
1113
+
1114
+ total_assignments_count = 0
1115
+ assignments_completed = 0
1116
+
1117
+ if course_assignments :
1118
+ total_assignments_count = len (course_assignments )
1119
+ assignments_completed = len ([assignment for assignment in course_assignments if assignment .complete ])
1120
+
1121
+ return {
1122
+ 'total_assignments_count' : total_assignments_count ,
1123
+ 'assignments_completed' : assignments_completed ,
1124
+ }
0 commit comments