def cache_solution_info(user, solutions): task_ids = [x.task_id for x in solutions] tasks = [x.task for x in solutions] check_prerequisites_for_tasks(tasks, user) if user.is_authenticated(): my_solutions = Solution.objects.filter(author=user, task_id__in=task_ids) my_solutions = {x.task_id: x for x in my_solutions} else: my_solutions = {} # Can view solutions? # First, ignore those with SOLUTIONS_VISIBLE setting, and remove duplicates. explicit_ids = set([x.task_id for x in solutions \ if x.task.solution_settings != Task.SOLUTIONS_VISIBLE \ and x.task.author_id != user.id]) if explicit_ids: # Second, if any left, ask for permissions. explicit_ids = get_object_ids_with_exclusive_permission( user, VIEW_SOLUTIONS, model=Task, filter_ids=explicit_ids) explicit_ids = set(explicit_ids) for y in solutions: y._cache_my_solution = my_solutions.get(y.task_id) y.task._cache_can_view_solutions = y.task_id in explicit_ids \ or y.task.solution_settings == Task.SOLUTIONS_VISIBLE \ or y.task.author_id == user.id return ''
def check_prerequisites_for_tasks(tasks, user): """ Checks if all of the prerequisite tasks have been solved for each of the given task. Saves the result in: task.cache_prerequisites_met Return value: None Does not check visibility! Prerequisites are met if: a) user is the author of the task b) there are no task prerequisites at all c) user solved all of the prerequisites d) user has the VIEW_SOLUTION permission """ to_check = [] # for solutions or VIEW_SOLUTIONS for x in tasks: x._cache_prerequisites = x._get_prerequisites() if not x._cache_prerequisites or x.author_id == user.id: x.cache_prerequisites_met = True else: to_check.append(x) if not user.is_authenticated(): for x in to_check: x.cache_prerequisites_met = False return if to_check: # All tasks for which solutions we are interested in. all_tasks = sum([x._cache_prerequisites for x in tasks], []) solved_tasks = set(Solution.objects.filter(author_id=user.id, task_id__in=all_tasks, detailed_status=CORRECT) \ .values_list('task_id', flat=True)) another_check = [] # VIEW_SOLUTIONS check for x in to_check: if set(x._cache_prerequisites).issubset(solved_tasks): x.cache_prerequisites_met = True else: another_check.append(x) if another_check: # Tasks with VIEW_SOLUTIONS permission ids = [x.id for x in another_check] # Author already checked. accepted = set(get_object_ids_with_exclusive_permission(user, VIEW_SOLUTIONS, model=Task, filter_ids=ids)) for x in another_check: x.cache_prerequisites_met = x.id in accepted
def find_unrated_solutions(user): """ Returns the list of all unrated solutions visible and important (not obfuscated) to the given user. """ profile = user.get_profile() # Get all unrated solutions. Remove nonvisible, remove my own solutions. solutions = Solution.objects \ .filter_visible_tasks_for_user(user) \ .filter(detailed_status=UNRATED) \ .exclude(author_id=user.id) \ .select_related('task') # Solution is interesting iff task is interesting. So, we are checking # tasks, not solutions. tasks = {} for x in solutions: if x.task_id not in tasks: tasks[x.task_id] = x.task # Task is accepted if # a) we have permission to view other solutions (check task & my solution) # b) we want to view solutions (check profile & my solution) # List of task ids get_solution_correct = [] # solutions to check for CORRECT get_solution_solved = [] # solutions to check for CORRECT or AS_SOLVED get_view_solutions = [] # VIEW_SOLUTIONS permissions to check with_permission = [] # tasks with permission to view solutions for id, x in tasks.iteritems(): if x.author_id != user.id: if x.solution_settings == Task.SOLUTIONS_NOT_VISIBLE: # Visible only with the VIEW_SOLUTIONS permission get_view_solutions.append(id) elif x.solution_settings == Task.SOLUTIONS_VISIBLE_IF_ACCEPTED: # Visible with VIEW_SOLUTIONS, or with a correct solution get_solution_correct.append(id) else: with_permission.append(id) else: with_permission.append(id) if profile.check_solution_obfuscation_preference( x.difficulty_rating_avg): # User wouldn't like to see the solutions of the problem he/she # didn't solve. Check for the solution then. get_solution_solved.append(id) get_any_solution = get_solution_correct + get_solution_solved if get_any_solution: # dict {task_id: detailed_status} my_solutions = dict(Solution.objects .filter(author_id=user.id, task_id__in=get_any_solution, detailed_status__in=[CORRECT, AS_SOLVED]) .values_list('task_id', 'detailed_status')) # Accept SOLUTIONS_VISIBLE_IF_ACCEPTED tasks if the solution is correct for id in get_solution_correct: if my_solutions.get(id) == CORRECT: with_permission.append(id) else: # no solution --> check for permissions get_view_solutions.append(id) else: my_solutions = {} if get_view_solutions: # WARNING: checking permissions manually! with_permission.extend(get_object_ids_with_exclusive_permission(user, VIEW_SOLUTIONS, model=Task, filter_ids=get_view_solutions)) # Now when I know which tasks I'm able to see, filter those I want to see. # Remember, I want to see if 1) I solved the problem OR 2) I'm fine with # seeing the solution anyway (depending on the difficulty rating). accepted_tasks = set(id for id in with_permission \ if id in my_solutions or not profile.check_solution_obfuscation_preference( tasks[id].difficulty_rating_avg)) # Finally, filter solutions with accepted tasks return [x for x in solutions if x.task_id in accepted_tasks]
def get_visible_folder_tree(folders, user, exclude_subtree=None): """ Get whole visible folder tree containing selected folders. To remove a subtree of a specific folder, use exclude_subtree. exclude_subtree = xyz would skip xyz's subtree, but it would leave xyz itself. Also returns some other useful data. Fills returned folder instances with ._depth. """ # Pick all ancestors, together with the original folders. Used for # permission check. ancestor_ids = sum([x.cache_ancestor_ids.split(',') for x in folders], []) ancestor_ids = [int(x) for x in ancestor_ids if x] ancestor_ids.extend([x.id for x in folders]) ancestor_counter = Counter(ancestor_ids) # Now remove duplicates ancestor_ids = set(ancestor_ids) # Get all visible subfolders related to any of the folders on the path to # the root. all_folders = Folder.objects.for_user(user, VIEW) \ .filter(parent_id__in=ancestor_ids).distinct() all_folders = {x.id: x for x in all_folders} # Include also the root. Not the best solution... Still, cached always. root = Folder.objects.get(parent__isnull=True) all_folders[root.id] = root visible_original_folder_ids = set() to_check = [] for folder in folders: # WARNING: manually checking Folder permissions here! if folder.id in all_folders or not folder.hidden or folder.author == user: # OK, accessible. Use old instances where possible, as there is # maybe some cached data. all_folders[folder.id] = folder visible_original_folder_ids.add(folder.id) else: # still not sure to_check.append(folder.id) if to_check: # NOTE: .hidden and .author permission is already checked. queryset = get_object_ids_with_exclusive_permission(user, VIEW, model=Folder, filter_ids=to_check) visible_original_folder_ids |= set(queryset) # Check permission for original folders for folder in folders: # Check if this (main) folder is accessible / visible chain_ids = [int(x) for x in folder.cache_ancestor_ids.split(',') if x] # To have access to the folder, user has to have permission to view the # folder itself and *all of its ancestors*. if folder.id not in visible_original_folder_ids \ or not all(x in all_folders for x in chain_ids): # Not visible, remove it from menu tree! I.e. do not just remove # the original folder, remove whole chain to the root. (that's why # we need the counter here) ancestor_counter.subtract(chain_ids) ancestor_counter.subtract([folder.id]) # Map folders to their parents folder_children = defaultdict(list) for x in all_folders.itervalues(): # Ignore inaccessible folders. None means it is not an ancestor, 0 # means no access. if ancestor_counter.get(x.id) is not 0: folder_children[x.parent_id].append(x) try: # Root folder is None's only subfolder. root = folder_children[None][0] except IndexError: # No access to root, i.e. no access to any of the original folders. return None # Bye # Generate folder tree. This is probably the best way to # 1) Ignore inaccessible folders # 2) Sort folders, i.e. show in right order stack = [root] sorted_folders = [] while len(stack): folder = stack.pop() if folder.parent_id: folder._depth = all_folders[folder.parent_id]._depth + 1 sorted_folders.append(folder) # root not in sorted folders! else: folder._depth = 0 # root if not exclude_subtree or exclude_subtree.id != folder.id: children = folder_children[folder.id] # Sort by parent_index in reverse order (note that we are using # LIFO)!. stack.extend(sorted(children, key=lambda x: -x.parent_index)) return { 'ancestor_ids': ancestor_ids, 'folder_children': folder_children, 'sorted_folders': sorted_folders, }
def many_get_user_stats(folders, user): """ Get user statistics and other information for multiple folders. If user not authenticated, returns empty dictionary. Otherwise, returns dictionary {folder.id: [visible task count, visible solvable task count, solution_stats]} (*) where solution_stats is a list of solution counters, one element for each detailed status. E.g. (50, [0, 3, 0, 1, 2 (...)]) (*) For implementation reasons, it returns lists instead of tuples. """ start_time = time.time() # Prepare folder-filters, i.e. FolderTasks for those folders Folder._prepare_folderfilters(folders) # Get all the tasks in these folders. Check for permission to get the # visible count immediately. folder_task_ids = {} # {folder_id: [visible task count, solution statistics]} result = {x.id: [0, 0, [0] * (SOLUTION_DETAILED_STATUS_MAX + 1)] for x in folders} query = 'SELECT FT.folder_id, T.id, T.author_id, T.hidden, T.solvable, S.detailed_status FROM folder_folder_tasks FT' \ ' INNER JOIN task_task T ON (FT.task_id = T.id)' \ ' LEFT OUTER JOIN solution_solution S ON (S.task_id = T.id AND S.author_id = {})' \ ' WHERE FT.folder_id IN ({});'.format( user.id, ','.join(str(x.id) for x in folders) ) cursor = connection.cursor() cursor.execute(query) # Task whose permissions have to be checked explicitly. to_check = [] db_result = list(cursor.fetchall()) for folder_id, task_id, author_id, hidden, solvable, detailed_status in db_result: if hidden and author_id != user.id: to_check.append(task_id) # Filter only visible Tasks visible_tasks = set(get_object_ids_with_exclusive_permission( user, VIEW, model=Task, filter_ids=to_check)) \ if to_check else set() # Fill statistics for folder_id, task_id, author_id, hidden, solvable, detailed_status in db_result: # Is Task visible? if not hidden or author_id == user.id or task_id in visible_tasks: ref = result[folder_id] ref[0] += 1 # Total Task count if solvable: ref[1] += 1 # Total solvable Task count if detailed_status is not None: ref[2][detailed_status] += 1 # Solution statistics print 'user solution stats for %d folders found in %lfs' % (len(folders), time.time() - start_time) return result