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
@@ -701,6 +710,39 @@ def get_course_assignments(course_key, user, include_access=False): # lint-amne
701
710
return assignments
702
711
703
712
713
+ def get_assignments_grades (user , course_id , cache_timeout ):
714
+ """
715
+ Calculate the progress of the assignment for the user in the course.
716
+
717
+ Arguments:
718
+ user (User): Django User object.
719
+ course_id (CourseLocator): The course key.
720
+ cache_timeout (int): Cache timeout in seconds
721
+ Returns:
722
+ list (ReadSubsectionGrade, ZeroSubsectionGrade): The list with assignments grades.
723
+ """
724
+ is_staff = bool (has_access (user , 'staff' , course_id ))
725
+
726
+ try :
727
+ course = get_course_with_access (user , 'load' , course_id )
728
+ cache_key = f'course_block_structure_{ str (course_id )} _{ str (course .course_version )} _{ user .id } '
729
+ collected_block_structure = cache .get (cache_key )
730
+ if not collected_block_structure :
731
+ collected_block_structure = get_block_structure_manager (course_id ).get_collected ()
732
+ cache .set (cache_key , collected_block_structure , cache_timeout )
733
+
734
+ course_grade = CourseGradeFactory ().read (user , collected_block_structure = collected_block_structure )
735
+
736
+ # recalculate course grade from visible grades (stored grade was calculated over all grades, visible or not)
737
+ course_grade .update (visible_grades_only = True , has_staff_access = is_staff )
738
+ subsection_grades = list (course_grade .subsection_grades .values ())
739
+ except Exception as err : # pylint: disable=broad-except
740
+ log .warning (f'Could not get grades for the course: { course_id } , error: { err } ' )
741
+ return []
742
+
743
+ return subsection_grades
744
+
745
+
704
746
def get_first_component_of_block (block_key , block_data ):
705
747
"""
706
748
This function returns the first leaf block of a section(block_key)
@@ -956,3 +998,64 @@ def get_course_chapter_ids(course_key):
956
998
log .exception ('Failed to retrieve course from modulestore.' )
957
999
return []
958
1000
return [str (chapter_key ) for chapter_key in chapter_keys if chapter_key .block_type == 'chapter' ]
1001
+
1002
+
1003
+ def get_past_and_future_course_assignments (request , user , course ):
1004
+ """
1005
+ Returns the future assignment data and past assignments data for given user and course.
1006
+
1007
+ Arguments:
1008
+ request (Request): The HTTP GET request.
1009
+ user (User): The user for whom the assignments are received.
1010
+ course (Course): Course object for whom the assignments are received.
1011
+ Returns:
1012
+ tuple (list, list): Tuple of `past_assignments` list and `next_assignments` list.
1013
+ `next_assignments` list contains only uncompleted assignments.
1014
+ """
1015
+ assignments = get_course_assignment_date_blocks (course , user , request , include_past_dates = True )
1016
+ past_assignments = []
1017
+ future_assignments = []
1018
+
1019
+ timezone = get_user_timezone_or_last_seen_timezone_or_utc (user )
1020
+ for assignment in sorted (assignments , key = lambda x : x .date ):
1021
+ if assignment .date < datetime .now (timezone ):
1022
+ past_assignments .append (assignment )
1023
+ else :
1024
+ if not assignment .complete :
1025
+ future_assignments .append (assignment )
1026
+
1027
+ if future_assignments :
1028
+ future_assignment_date = future_assignments [0 ].date .date ()
1029
+ next_assignments = [
1030
+ assignment for assignment in future_assignments if assignment .date .date () == future_assignment_date
1031
+ ]
1032
+ else :
1033
+ next_assignments = []
1034
+
1035
+ return next_assignments , past_assignments
1036
+
1037
+
1038
+ def get_assignments_completions (course_key , user ):
1039
+ """
1040
+ Calculate the progress of the user in the course by assignments.
1041
+
1042
+ Arguments:
1043
+ course_key (CourseLocator): The Course for which course progress is requested.
1044
+ user (User): The user for whom course progress is requested.
1045
+ Returns:
1046
+ dict (dict): Dictionary contains information about total assignments count
1047
+ in the given course and how many assignments the user has completed.
1048
+ """
1049
+ course_assignments = get_course_assignments (course_key , user , include_without_due = True )
1050
+
1051
+ total_assignments_count = 0
1052
+ assignments_completed = 0
1053
+
1054
+ if course_assignments :
1055
+ total_assignments_count = len (course_assignments )
1056
+ assignments_completed = len ([assignment for assignment in course_assignments if assignment .complete ])
1057
+
1058
+ return {
1059
+ 'total_assignments_count' : total_assignments_count ,
1060
+ 'assignments_completed' : assignments_completed ,
1061
+ }
0 commit comments