Commit f5c964b5 authored by Honcoop, T.'s avatar Honcoop, T.
Browse files

Merge branch '6-previous-buttons-quiz-assignment-video-back-end' into 'master'

Resolve "Previous buttons (quiz, assignment, video) back-end"

Closes #6

See merge request !4
parents 775c9d8c 1fd0fb95
Pipeline #2375 passed with stage
in 3 minutes and 14 seconds
......@@ -51,6 +51,7 @@ class ItemSerializer(serializers.ModelSerializer):
def get_lesson_name(self, obj):
return obj.lesson.name
class VideoSerializer(ItemSerializer):
"""
......@@ -74,7 +75,7 @@ class VideoSerializer(ItemSerializer):
"video_likes",
"video_dislikes",
]
watched_video = serializers.SerializerMethodField()
finished_video = serializers.SerializerMethodField()
video_comments = serializers.SerializerMethodField()
......@@ -219,11 +220,15 @@ class VideoAnalyticsSerializer(VideoSerializer):
fields = VideoSerializer.Meta.fields + [
"next_item",
"next_video",
"last_item",
"last_video",
"views_over_runtime",
]
next_item = serializers.SerializerMethodField()
next_video = serializers.SerializerMethodField()
last_item = serializers.SerializerMethodField()
last_video = serializers.SerializerMethodField()
views_over_runtime = serializers.SerializerMethodField()
@cached_property
......@@ -341,6 +346,50 @@ class VideoAnalyticsSerializer(VideoSerializer):
except IndexError:
return {"item_id": "", "type": 0, "category": ""}
def get_last_item(self, obj):
"""
Return the next item in the lesson, if any.
If the next item is a quiz, also include the passing rate of learners
who have watched the video.
"""
try:
return obj.next_item_id
except AttributeError:
try:
item = Item.objects.get(
branch=obj.branch, lesson=obj.lesson, order=obj.order - 1
)
return {
"item_id": item.item_id,
"type": item.type.id,
"category": item.type.category,
}
except Item.DoesNotExist:
return {"item_id": "", "type": 0, "category": ""}
def get_last_video(self, obj):
"""
Return the next video in the lesson, if any.
"""
try:
return obj.next_video_id
except AttributeError:
try:
item = Item.objects.filter(
branch=obj.branch,
lesson=obj.lesson,
order__lt=obj.order,
type__id=1,
).order_by("-order")[0]
return {
"item_id": item.item_id,
"type": item.type.id,
"category": item.type.category,
}
except IndexError:
return {"item_id": "", "type": 0, "category": ""}
def get_views_over_runtime(self, obj):
"""
Return the number of views for each 5-second interval in the video,
......@@ -357,6 +406,7 @@ class VideoAnalyticsSerializer(VideoSerializer):
)
)
class AssignmentSerializer(ItemSerializer):
"""
Serialize an Item of type Assignment with its basic properties
......@@ -372,7 +422,6 @@ class AssignmentSerializer(ItemSerializer):
The average grade of all students who completed the assignment.
"""
class Meta(ItemSerializer.Meta):
fields = ItemSerializer.Meta.fields + [
"submissions",
......@@ -396,17 +445,25 @@ class AssignmentAnalyticsSerializer(AssignmentSerializer):
The next item in the lesson.
next_assignment:
The next item of type Assignment in the lesson.
last_item:
The previous item in the lesson.
last_assignment:
The previous item of type Assignment in the lesson.
"""
class Meta(AssignmentSerializer.Meta):
fields = AssignmentSerializer.Meta.fields + [
"next_item",
"next_assignment",
"last_item",
"last_assignment",
"submission_ratio_paying_learners"
]
next_item = serializers.SerializerMethodField()
next_assignment = serializers.SerializerMethodField()
last_item = serializers.SerializerMethodField()
last_assignment = serializers.SerializerMethodField()
submission_ratio_paying_learners = serializers.SerializerMethodField()
@cached_property
......@@ -453,7 +510,7 @@ class AssignmentAnalyticsSerializer(AssignmentSerializer):
Return the next assignment in the lesson, if any.
"""
try:
return obj.next_video_id
return obj.next_assignment
except AttributeError:
try:
item = Item.peer_assignment_objects.filter(
......@@ -467,6 +524,44 @@ class AssignmentAnalyticsSerializer(AssignmentSerializer):
except IndexError:
return {"item_id": "", "type": 0, "category": ""}
def get_last_item(self, obj):
"""
Return the next item in the lesson, if any.
"""
try:
return obj.next_item_id
except AttributeError:
try:
item = Item.objects.get(
branch=obj.branch, lesson=obj.lesson, order=obj.order - 1
)
return {
"item_id": item.item_id,
"type": item.type.id,
"category": item.type.category,
}
except Item.DoesNotExist:
return {"item_id": "", "type": 0, "category": ""}
def get_last_assignment(self, obj):
"""
Return the next assignment in the lesson, if any.
"""
try:
return obj.last_assignment
except AttributeError:
try:
item = Item.peer_assignment_objects.filter(
branch=obj.branch, lesson=obj.lesson, order__lt=obj.order
).order_by("-order")[0]
return {
"item_id": item.item_id,
"type": item.type.id,
"category": item.type.category,
}
except IndexError:
return {"item_id": "", "type": 0, "category": ""}
def get_submission_ratio_paying_learners(self, obj):
"""
Return the submission ratio for paying learners.
......
......@@ -133,6 +133,10 @@ class QuizAnalyticsSerializer(QuizSerializer):
The next item in the lesson.
next_quiz:
The next item of type Quiz in the lesson.
last_item:
The previous item in the lesson.
last_quiz:
The previous item of type Quiz in the lesson.
unique_learners:
Number of unique learners taking the quiz.
"""
......@@ -149,6 +153,8 @@ class QuizAnalyticsSerializer(QuizSerializer):
"last_attempt_grade_distribution",
"next_item",
"next_quiz",
"last_item",
"last_quiz",
"unique_leaners",
"geo_data",
"geo_data_not_passed",
......@@ -165,6 +171,8 @@ class QuizAnalyticsSerializer(QuizSerializer):
last_attempt_grade_distribution = serializers.SerializerMethodField()
next_item = serializers.SerializerMethodField()
next_quiz = serializers.SerializerMethodField()
last_item = serializers.SerializerMethodField()
last_quiz = serializers.SerializerMethodField()
unique_leaners = serializers.SerializerMethodField()
geo_data = serializers.SerializerMethodField()
geo_data_not_passed = serializers.SerializerMethodField()
......@@ -374,6 +382,52 @@ class QuizAnalyticsSerializer(QuizSerializer):
except IndexError:
return {"assessment_id": "", "assessment_version": 0}
def get_last_item(self, obj):
"""
Return the next item in the lesson, if any.
"""
try:
return obj.next_item_id
except AttributeError:
try:
item = Item.objects.get(
branch=obj.items.all()[0].branch,
lesson=obj.items.all()[0].lesson,
order=obj.items.all()[0].order - 1,
)
return {
"item_id": item.item_id,
"type": item.type.id,
"category": item.type.category,
}
except Item.DoesNotExist:
return {"item_id": "", "type": 0, "category": ""}
def get_last_quiz(self, obj):
"""
Return the next quiz in the lesson, if any.
"""
try:
return obj.next_video_id
except AttributeError:
try:
item = Item.objects.filter(
branch=obj.items.all()[0].branch,
lesson=obj.items.all()[0].lesson,
order__lt=obj.items.all()[0].order,
type__category="quiz",
).order_by("-order")[0]
return {
"assessment_id": getattr(
item.quizzes.order_by("-version").first(), "base_id", None
),
"assessment_version": getattr(
item.quizzes.order_by("-version").first(), "version", None
),
}
except IndexError:
return {"assessment_id": "", "assessment_version": 0}
def get_unique_leaners(self, obj):
"""
Return the number of unique learners that took a quiz.
......
......@@ -261,6 +261,8 @@ def test_video_analytics_view(
- video_dislikes
- next_item
- next_video
- last_item
- last_video
- views_over_runtime
"""
response = teacher_api_client.get(
......@@ -286,6 +288,8 @@ def test_video_analytics_view(
"video_dislikes",
"next_item",
"next_video",
"last_item",
"last_video",
"views_over_runtime",
]
assert response.status_code == 200, str(response.content)
......@@ -406,6 +410,8 @@ def test_video_analytics_view_invalid_date_filter(
"video_dislikes",
"next_item",
"next_video",
"last_item",
"last_video",
"views_over_runtime",
]
assert response.status_code == 200, str(response.content)
......@@ -584,6 +590,8 @@ def test_quiz_analytics_view(
- last_attempt_grade_distribution
- next_item
- next_quiz
- last_item
- last_quiz
- unique_leaners
- geo_data
- geo_data_not_passed
......@@ -624,6 +632,8 @@ def test_quiz_analytics_view(
"last_attempt_grade_distribution",
"next_item",
"next_quiz",
"last_item",
"last_quiz",
"unique_leaners",
"geo_data",
"geo_data_not_passed",
......@@ -772,6 +782,8 @@ def test_quiz_analytics_view_invalid_date_filter(
"last_attempt_grade_distribution",
"next_item",
"next_quiz",
"last_item",
"last_quiz",
"unique_leaners",
"geo_data",
"geo_data_not_passed",
......@@ -781,6 +793,59 @@ def test_quiz_analytics_view_invalid_date_filter(
assert list(response.data.keys()) == keys
@pytest.mark.django_db
def test_quiz_analytics_last_quiz(teacher_api_client, coursera_alt_course_id):
"""
Test that the quiz detail view returns a correct response when there is a
next quiz.
"""
response = teacher_api_client.get(
reverse(
"coursera-api:quiz-detail",
kwargs={
"course_id": coursera_alt_course_id,
"base_id": "IBpCiXM5EeWKJA7piulVWw",
"version": 5,
},
)
)
keys = [
"id",
"base_id",
"version",
"name",
"type",
"update_timestamp",
"passing_fraction",
"graded",
"lesson",
"lesson_name",
"item_id",
"branch",
"average_grade",
"average_attempts",
"grade_distribution",
"number_of_attempts",
"correct_ratio_per_question",
"quiz_comments",
"quiz_likes",
"quiz_dislikes",
"last_attempt_average_grade",
"last_attempt_grade_distribution",
"next_item",
"next_quiz",
"last_item",
"last_quiz",
"unique_leaners",
"geo_data",
"geo_data_not_passed",
"score_per_attempt",
]
assert response.status_code == 200, str(response.content)
assert list(response.data.keys()) == keys
@pytest.mark.django_db
def test_quiz_analytics_next_quiz(teacher_api_client, coursera_alt_course_id):
"""
......@@ -822,6 +887,8 @@ def test_quiz_analytics_next_quiz(teacher_api_client, coursera_alt_course_id):
"last_attempt_grade_distribution",
"next_item",
"next_quiz",
"last_item",
"last_quiz",
"unique_leaners",
"geo_data",
"geo_data_not_passed",
......@@ -984,6 +1051,8 @@ def test_assignment_analytics_view(
- average_grade
- next_item
- next_assignment
- last_item
- last_assignment
- submission_ratio_paying_learners
"""
response = teacher_api_client.get(
......@@ -1007,6 +1076,8 @@ def test_assignment_analytics_view(
"average_grade",
"next_item",
"next_assignment",
"last_item",
"last_assignment",
"submission_ratio_paying_learners",
]
assert response.status_code == 200, str(response.content)
......@@ -1042,6 +1113,82 @@ def test_assignment_analytics_view_next_assignment(
"average_grade",
"next_item",
"next_assignment",
"last_item",
"last_assignment",
"submission_ratio_paying_learners",
]
assert response.status_code == 200, str(response.content)
assert list(response.data.keys()) == keys
@pytest.mark.django_db
def test_assignment_analytics_view_no_last_assignment(
teacher_api_client, coursera_course_id, coursera_assignment_id
):
"""
Test that the assignment detail view returns a valid response
when the lesson contains a next assignment.
"""
response = teacher_api_client.get(
reverse(
"coursera-api:assignment-detail",
kwargs={"course_id": "V4m7Xf5qEeS9ISIACxWDhA", "item_id": "8IF0h"},
)
)
keys = [
"id",
"branch",
"item_id",
"lesson",
"lesson_name",
"order",
"type",
"name",
"optional",
"submissions",
"submission_ratio",
"average_grade",
"next_item",
"next_assignment",
"last_item",
"last_assignment",
"submission_ratio_paying_learners",
]
assert response.status_code == 200, str(response.content)
assert list(response.data.keys()) == keys
@pytest.mark.django_db
def test_assignment_analytics_view_last_assignment(
teacher_api_client, coursera_course_id, coursera_assignment_id
):
"""
Test that the assignment detail view returns a valid response
when the lesson contains a previous assignment.
"""
response = teacher_api_client.get(
reverse(
"coursera-api:assignment-detail",
kwargs={"course_id": "V4m7Xf5qEeS9ISIACxWDhA", "item_id": "LpdBV"},
)
)
keys = [
"id",
"branch",
"item_id",
"lesson",
"lesson_name",
"order",
"type",
"name",
"optional",
"submissions",
"submission_ratio",
"average_grade",
"next_item",
"next_assignment",
"last_item",
"last_assignment",
"submission_ratio_paying_learners",
]
assert response.status_code == 200, str(response.content)
......@@ -1130,6 +1277,8 @@ def test_assignment_analytics_view_invalid_date_filter(
"average_grade",
"next_item",
"next_assignment",
"last_item",
"last_assignment",
"submission_ratio_paying_learners",
]
assert response.status_code == 200, str(response.content)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment