Example #1
0
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 ''
Example #2
0
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
Example #3
0
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]
Example #4
0
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,
    }
Example #5
0
    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