@@ -46,6 +46,7 @@ def recent_problem_sets(self, n=3):
4646 return self .problem_sets .reverse ().filter (visible = True )[:n ]
4747
4848 def user_attempts (self , user ):
49+ """This function ignores problem visibility, because it assumes it is only called from problems/models.py:marking_file() by a teacher user."""
4950 attempts = {}
5051 for attempt in user .attempts .filter (part__problem__problem_set__course = self ):
5152 attempts [attempt .part_id ] = attempt
@@ -108,7 +109,7 @@ def annotate_for_teacher(self):
108109 student_count = len (students )
109110
110111 part_sets = Part .objects .filter (
111- problem__problem_set__in = self .annotated_problem_sets
112+ problem__problem_set__in = self .annotated_problem_sets , problem__visible = True
112113 )
113114 parts_count = (
114115 part_sets .values ("problem__problem_set_id" )
@@ -123,6 +124,7 @@ def annotate_for_teacher(self):
123124 attempts_full = Attempt .objects .filter (
124125 user__in = students ,
125126 part__problem__problem_set__in = self .annotated_problem_sets ,
127+ part__problem__visible = True ,
126128 )
127129 attempts = attempts_full .values ("valid" , "part__problem__problem_set_id" )
128130 attempts_dict = {}
@@ -197,8 +199,12 @@ def observed_students(self):
197199 def student_success (self ):
198200 students = self .observed_students ()
199201 problem_sets = self .problem_sets .filter (visible = True )
200- part_count = Part .objects .filter (problem__problem_set__in = problem_sets ).count ()
201- attempts = Attempt .objects .filter (part__problem__problem_set__in = problem_sets )
202+ part_count = Part .objects .filter (
203+ problem__problem_set__in = problem_sets , problem__visible = True
204+ ).count ()
205+ attempts = Attempt .objects .filter (
206+ part__problem__problem_set__in = problem_sets , part__problem__visible = True
207+ )
202208 valid_attempts = (
203209 attempts .filter (valid = True ).values ("user" ).annotate (Count ("user" ))
204210 )
@@ -253,9 +259,8 @@ def student_success_by_problem_set(self):
253259 "problems" , "problems__parts"
254260 ):
255261 different_subtasks = 0
256- for problem in problem_set .problems .all ():
257- for part in problem .parts .all ():
258- different_subtasks += 1
262+ for problem in problem_set .problems .filter (visible = True ):
263+ different_subtasks += problem .parts .count ()
259264
260265 # In case there are no parts, we do not want to divide by 0
261266 if different_subtasks == 0 :
@@ -268,7 +273,9 @@ def student_success_by_problem_set(self):
268273 ] # Valid, invalid
269274
270275 attempts = Attempt .objects .filter (
271- part__problem__problem_set = problem_set , user__in = students
276+ part__problem__problem_set = problem_set ,
277+ user__in = students ,
278+ part__problem__visible = True ,
272279 )
273280 for attempt in attempts :
274281 if attempt .valid :
@@ -312,13 +319,13 @@ def student_success_by_problemset_grouped_by_groups(self):
312319 groups = self .groups .all ()
313320 student_success = self .student_success_by_problem_set ()
314321
315- student_sucess_by_groups = {}
322+ student_success_by_groups = {}
316323 for group in groups :
317- student_sucess_by_groups [group ] = {}
324+ student_success_by_groups [group ] = {}
318325 for student in group .students .all ():
319- student_sucess_by_groups [group ][student ] = student_success [student ]
326+ student_success_by_groups [group ][student ] = student_success [student ]
320327
321- return student_sucess_by_groups
328+ return student_success_by_groups
322329
323330
324331class StudentEnrollment (models .Model ):
@@ -395,7 +402,13 @@ def get_absolute_url(self):
395402 return reverse ("problem_set_detail" , args = [str (self .pk )])
396403
397404 def attempts_archive (self , user ):
398- files = [problem .attempt_file (user ) for problem in self .problems .all ()]
405+ if user .can_edit_problem_set (self ):
406+ files = [problem .attempt_file (user ) for problem in self .problems .all ()]
407+ else :
408+ files = [
409+ problem .attempt_file (user )
410+ for problem in self .problems .filter (visible = True )
411+ ]
399412 archive_name = slugify (self .title )
400413 return archive_name , files
401414
@@ -433,33 +446,33 @@ def results_archive(self, user):
433446 attempt_dict [user_id ] = user_attempts
434447 users = User .objects .filter (id__in = user_ids )
435448
436- archive_name = "{0}-results" . format ( slugify (self .title ))
449+ archive_name = f" { slugify (self .title )} -results"
437450 files = []
438451
439452 bare_files = {}
440453 for problem in self .problems .all ():
441454 folder = slugify (problem .title )
442455 for user in users .all ():
443456 filename , contents = problem .marking_file (user )
444- files .append (("{0 }/{1}" . format ( folder , filename ) , contents ))
457+ files .append ((f" { folder } /{ filename } " , contents ))
445458 filename , contents = problem .bare_file (user )
446459 bare_files [filename ] = bare_files .get (filename , "" ) + contents + "\n \n "
447460
448461 for filename , contents in bare_files .items ():
449- files .append (("bare/{0}" . format ( filename ) , contents ))
462+ files .append ((f "bare/{ filename } " , contents ))
450463
451464 for user , history in self .attempt_history ().items ():
452465 username = user .get_full_name () or user .username
453466 problem_slug = slugify (username ).replace ("-" , "_" )
454467 extension = "py"
455- filename = "{0 }.{1}" . format ( problem_slug , extension )
468+ filename = f" { problem_slug } .{ extension } "
456469 contents = render_to_string (
457- "history.{0}" . format ( extension ) ,
470+ f "history.{ extension } " ,
458471 {
459472 "history" : history ,
460473 },
461474 )
462- files .append (("history/{0}" . format ( filename ) , contents ))
475+ files .append ((f "history/{ filename } " , contents ))
463476
464477 users = []
465478 for user in User .objects .filter (id__in = user_ids ).order_by ("last_name" ):
@@ -524,6 +537,7 @@ def student_statistics(self):
524537 "title" : problem .title ,
525538 "pk" : problem .pk ,
526539 "parts" : parts ,
540+ "visible" : problem .visible ,
527541 }
528542 )
529543 return statistics
@@ -532,10 +546,13 @@ def valid_percentage(self, user):
532546 """
533547 Returns the percentage of parts (rounded to the nearest integer)
534548 of parts in this problem set for which the given user has a valid attempt.
549+ Doesn't count parts of problems that have visible set to False.
535550 """
536- number_of_all_parts = Part .objects .filter (problem__problem_set = self ).count ()
551+ number_of_all_parts = Part .objects .filter (
552+ problem__problem_set = self , problem__visible = True
553+ ).count ()
537554 number_of_valid_parts = user .attempts .filter (
538- valid = True , part__problem__problem_set = self
555+ valid = True , part__problem__problem_set = self , part__problem__visible = True
539556 ).count ()
540557 if number_of_all_parts == 0 :
541558 return None
0 commit comments