Skip to content

Commit

Permalink
Merge pull request #11 from edx/sofiya/milestones-test
Browse files Browse the repository at this point in the history
API endpoint for milestones experiment.
  • Loading branch information
ssemenova authored Mar 20, 2018
2 parents e781b71 + 935d9b1 commit 78e8aa4
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 2 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ Alex Dusenbery <[email protected]>
Simon Chen <[email protected]>
Gregory Martin <[email protected]>
Alessandro Roux <[email protected]>
Sofiya Semenova <[email protected]>
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ Unreleased
~~~~~~~~~~
* Add query method for all completions by course

[0.0.10] - 2018-03-20
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* Added "subsection-completion/{username}/{course_key}/{subsection_id}" API
endpoint, to be used with the completion milestones experiment.

[0.0.9] - 2018-02-27
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
2 changes: 1 addition & 1 deletion completion/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
from __future__ import unicode_literals


__version__ = '0.0.10'
__version__ = '0.0.11'

default_app_config = 'completion.apps.CompletionAppConfig' # pylint: disable=invalid-name
21 changes: 21 additions & 0 deletions completion/api/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""
from __future__ import unicode_literals

from django.http import Http404
from rest_framework.permissions import BasePermission


Expand All @@ -22,3 +23,23 @@ def has_permission(self, request, view):
or (user.username == getattr(request, 'data', {}).get('username')) \
or (user.username == getattr(request, 'data', {}).get('user')) \
or (user.username == getattr(view, 'kwargs', {}).get('username'))


class IsUserInUrl(BasePermission):
"""
Permission that checks to see if the request user matches the user in the URL.
"""

def has_permission(self, request, view):
"""
Returns true if the current request is by the user themselves.
Note: a 404 is returned for non-staff instead of a 403. This is to prevent
users from being able to detect the existence of accounts.
"""
url_username = request.parser_context.get('kwargs', {}).get('username', '')
if request.user.username.lower() != url_username.lower():
if request.user.is_staff:
return False # staff gets 403
raise Http404()
return True
7 changes: 7 additions & 0 deletions completion/api/v1/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,11 @@

urlpatterns = [
url(r'^completion-batch', views.CompletionBatchView.as_view(), name='completion-batch'),
url(r'^subsection-completion/{username}/{course_key}/{subsection_id}'.format(
username=r'(?P<username>[^/]*)',
course_key=r'(?P<course_key>[^/+]+(/|\+)[^/+]+(/|\+)[^/?]+)',
subsection_id=r'(?P<subsection_id>[^/]*)'
),
views.SubsectionCompletionView.as_view(),
name='subsection-completion')
]
68 changes: 67 additions & 1 deletion completion/api/v1/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@
try:
from openedx.core.djangoapps.content.course_structures.models import CourseStructure
from student.models import CourseEnrollment
from lms.djangoapps.course_api.blocks.api import get_blocks
except ImportError:
pass

from completion import waffle
from completion.api.permissions import IsStaffOrOwner
from completion.api.permissions import IsStaffOrOwner, IsUserInUrl
from completion.models import BlockCompletion


Expand Down Expand Up @@ -148,3 +149,68 @@ def post(self, request, *args, **kwargs): # pylint: disable=unused-argument
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

return Response({"detail": _("ok")}, status=status.HTTP_200_OK)


class SubsectionCompletionView(APIView):
"""
Handles API endpoints for the milestones experiments.
TODO: EDUCATOR-2358 Remove this class after the
milestones experiment is no longer running.
"""

permission_classes = (permissions.IsAuthenticated, IsUserInUrl)

def get(self, request, username, course_key, subsection_id):
"""
Returns completion for a (user, subsection, course).
"""
def get_completion(course_completions, all_blocks, block_id):
"""
Recursively get the aggregate completion for a subsection,
given the subsection block and a list of all blocks.
Parameters:
course_completions: a dictionary of completion values by block IDs
all_blocks: a dictionary of the block structure for a subsection
block_id: an ID of a block for which to get completion
"""
block = all_blocks.get(block_id)
child_ids = block.get('children', [])
if not child_ids:
return course_completions.get(block.serializer.instance, 0)

completion = 0
total_children = 0
for child_id in child_ids:
completion += get_completion(course_completions, all_blocks, child_id)
total_children += 1

return int(completion == total_children)

user_id = User.objects.get(username=username).id
block_types_filter = [
'course',
'chapter',
'sequential',
'vertical',
'html',
'problem',
'video',
'discussion',
'drag-and-drop-v2'
]

blocks = get_blocks(
request,
UsageKey.from_string(subsection_id),
nav_depth=2,
requested_fields=[
'children'
],
block_types_filter=block_types_filter
)

course_completions = BlockCompletion.get_course_completions(user_id, CourseKey.from_string(course_key))
aggregated_completion = get_completion(course_completions, blocks['blocks'], blocks['root'])

return Response({"completion": aggregated_completion}, status=status.HTTP_200_OK)

0 comments on commit 78e8aa4

Please sign in to comment.