Commit 12c9d6b5 by Peter Fogg

Merge pull request #9714 from edx/peter-fogg/fix-slow-memberships

Prevent over-expansion of team memberships.
parents 5241b04b 48f72612
...@@ -112,7 +112,8 @@ class MembershipSerializer(serializers.ModelSerializer): ...@@ -112,7 +112,8 @@ class MembershipSerializer(serializers.ModelSerializer):
view_name='teams_detail', view_name='teams_detail',
read_only=True, read_only=True,
), ),
expanded_serializer=CourseTeamSerializer(read_only=True) expanded_serializer=CourseTeamSerializer(read_only=True),
exclude_expand_fields={'user'},
) )
class Meta(object): class Meta(object):
......
...@@ -68,8 +68,11 @@ define([ ...@@ -68,8 +68,11 @@ define([
return _.map(_.range(startIndex, stopIndex + 1), function (i) { return _.map(_.range(startIndex, stopIndex + 1), function (i) {
return { return {
user: { user: {
'username': testUser, username: testUser,
'url': 'https://openedx.example.com/api/user/v1/accounts/' + testUser url: 'https://openedx.example.com/api/user/v1/accounts/' + testUser,
profile_image: {
image_url_small: 'test_profile_image'
}
}, },
team: teams[i-1] team: teams[i-1]
}; };
......
...@@ -32,11 +32,13 @@ ...@@ -32,11 +32,13 @@
initialize: function (options) { initialize: function (options) {
this.maxTeamSize = options.maxTeamSize; this.maxTeamSize = options.maxTeamSize;
this.memberships = options.memberships;
}, },
render: function () { render: function () {
var allMemberships = _(this.model.get('membership')) var allMemberships = _(this.memberships).sortBy(function (member) {
.sortBy(function (member) {return new Date(member.last_activity_at);}).reverse(), return new Date(member.last_activity_at);
}).reverse(),
displayableMemberships = allMemberships.slice(0, 5), displayableMemberships = allMemberships.slice(0, 5),
maxMemberCount = this.maxTeamSize; maxMemberCount = this.maxTeamSize;
this.$el.html(this.template({ this.$el.html(this.template({
...@@ -97,7 +99,7 @@ ...@@ -97,7 +99,7 @@
CardView.prototype.initialize.apply(this, arguments); CardView.prototype.initialize.apply(this, arguments);
// TODO: show last activity detail view // TODO: show last activity detail view
this.detailViews = [ this.detailViews = [
new TeamMembershipView({model: this.teamModel(), maxTeamSize: this.maxTeamSize}), new TeamMembershipView({memberships: this.getMemberships(), maxTeamSize: this.maxTeamSize}),
new TeamCountryLanguageView({ new TeamCountryLanguageView({
model: this.teamModel(), model: this.teamModel(),
countries: this.countries, countries: this.countries,
...@@ -105,6 +107,9 @@ ...@@ -105,6 +107,9 @@
}), }),
new TeamActivityView({date: this.teamModel().get('last_activity_at')}) new TeamActivityView({date: this.teamModel().get('last_activity_at')})
]; ];
this.model.on('change:membership', function () {
this.detailViews[0].memberships = this.getMemberships();
}, this);
}, },
teamModel: function () { teamModel: function () {
...@@ -112,6 +117,15 @@ ...@@ -112,6 +117,15 @@
return this.model; return this.model;
}, },
getMemberships: function () {
if (this.model.has('team')) {
return [this.model.attributes];
}
else {
return this.model.get('membership');
}
},
configuration: 'list_card', configuration: 'list_card',
cardClass: 'team-card', cardClass: 'team-card',
title: function () { return this.teamModel().get('name'); }, title: function () { return this.teamModel().get('name'); },
......
...@@ -3,18 +3,24 @@ ...@@ -3,18 +3,24 @@
Tests for custom Teams Serializers. Tests for custom Teams Serializers.
""" """
from django.core.paginator import Paginator from django.core.paginator import Paginator
from django.test.client import RequestFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from student.tests.factories import CourseEnrollmentFactory, UserFactory
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory from xmodule.modulestore.tests.factories import CourseFactory
from lms.djangoapps.teams.tests.factories import CourseTeamFactory from lms.djangoapps.teams.tests.factories import CourseTeamFactory, CourseTeamMembershipFactory
from lms.djangoapps.teams.serializers import ( from lms.djangoapps.teams.serializers import (
BaseTopicSerializer, PaginatedTopicSerializer, BulkTeamCountPaginatedTopicSerializer, BaseTopicSerializer,
TopicSerializer, add_team_count PaginatedTopicSerializer,
BulkTeamCountPaginatedTopicSerializer,
TopicSerializer,
MembershipSerializer,
add_team_count
) )
class TopicTestCase(ModuleStoreTestCase): class SerializerTestCase(SharedModuleStoreTestCase):
""" """
Base test class to set up a course with topics Base test class to set up a course with topics
""" """
...@@ -22,7 +28,7 @@ class TopicTestCase(ModuleStoreTestCase): ...@@ -22,7 +28,7 @@ class TopicTestCase(ModuleStoreTestCase):
""" """
Set up a course with a teams configuration. Set up a course with a teams configuration.
""" """
super(TopicTestCase, self).setUp() super(SerializerTestCase, self).setUp()
self.course = CourseFactory.create( self.course = CourseFactory.create(
teams_configuration={ teams_configuration={
"max_team_size": 10, "max_team_size": 10,
...@@ -31,7 +37,46 @@ class TopicTestCase(ModuleStoreTestCase): ...@@ -31,7 +37,46 @@ class TopicTestCase(ModuleStoreTestCase):
) )
class BaseTopicSerializerTestCase(TopicTestCase): class MembershipSerializerTestCase(SerializerTestCase):
"""
Tests for the membership serializer.
"""
def setUp(self):
super(MembershipSerializerTestCase, self).setUp()
self.team = CourseTeamFactory.create(
course_id=self.course.id,
topic_id=self.course.teams_topics[0]['id']
)
self.user = UserFactory.create()
CourseEnrollmentFactory.create(user=self.user, course_id=self.course.id)
self.team_membership = CourseTeamMembershipFactory.create(team=self.team, user=self.user)
def test_membership_serializer_expand_user_and_team(self):
"""Verify that the serializer only expands the user and team one level."""
data = MembershipSerializer(self.team_membership, context={
'expand': [u'team', u'user'],
'request': RequestFactory().get('/api/team/v0/team_membership')
}).data
username = self.user.username
self.assertEqual(data['user'], {
'url': 'http://testserver/api/user/v1/accounts/' + username,
'username': username,
'profile_image': {
'image_url_full': 'http://testserver/static/default_500.png',
'image_url_large': 'http://testserver/static/default_120.png',
'image_url_medium': 'http://testserver/static/default_50.png',
'image_url_small': 'http://testserver/static/default_30.png',
'has_image': False
}
})
self.assertEqual(data['team']['membership'][0]['user'], {
'url': 'http://testserver/api/user/v1/accounts/' + username,
'username': username
})
class BaseTopicSerializerTestCase(SerializerTestCase):
""" """
Tests for the `BaseTopicSerializer`, which should not serialize team count Tests for the `BaseTopicSerializer`, which should not serialize team count
data. data.
...@@ -46,7 +91,7 @@ class BaseTopicSerializerTestCase(TopicTestCase): ...@@ -46,7 +91,7 @@ class BaseTopicSerializerTestCase(TopicTestCase):
) )
class TopicSerializerTestCase(TopicTestCase): class TopicSerializerTestCase(SerializerTestCase):
""" """
Tests for the `TopicSerializer`, which should serialize team count data for Tests for the `TopicSerializer`, which should serialize team count data for
a single topic. a single topic.
...@@ -95,7 +140,7 @@ class TopicSerializerTestCase(TopicTestCase): ...@@ -95,7 +140,7 @@ class TopicSerializerTestCase(TopicTestCase):
) )
class BasePaginatedTopicSerializerTestCase(TopicTestCase): class BasePaginatedTopicSerializerTestCase(SerializerTestCase):
""" """
Base class for testing the two paginated topic serializers. Base class for testing the two paginated topic serializers.
""" """
......
...@@ -5,18 +5,26 @@ from rest_framework.serializers import CharField, Field ...@@ -5,18 +5,26 @@ from rest_framework.serializers import CharField, Field
class ExpandableField(Field): class ExpandableField(Field):
"""Field that can dynamically use a more detailed serializer based on a user-provided "expand" parameter.""" """Field that can dynamically use a more detailed serializer based on a user-provided "expand" parameter.
Kwargs:
collapsed_serializer (Serializer): the serializer to use for a non-expanded representation.
expanded_serializer (Serializer): the serializer to use for an expanded representation.
exclude_expand_fields (set(str)): a set of fields which will not be expanded by sub-serializers.
"""
def __init__(self, **kwargs): def __init__(self, **kwargs):
"""Sets up the ExpandableField with the collapsed and expanded versions of the serializer.""" """Sets up the ExpandableField with the collapsed and expanded versions of the serializer."""
assert 'collapsed_serializer' in kwargs and 'expanded_serializer' in kwargs assert 'collapsed_serializer' in kwargs and 'expanded_serializer' in kwargs
self.collapsed = kwargs.pop('collapsed_serializer') self.collapsed = kwargs.pop('collapsed_serializer')
self.expanded = kwargs.pop('expanded_serializer') self.expanded = kwargs.pop('expanded_serializer')
self.exclude_expand_fields = kwargs.pop('exclude_expand_fields', set())
super(ExpandableField, self).__init__(**kwargs) super(ExpandableField, self).__init__(**kwargs)
def field_to_native(self, obj, field_name): def field_to_native(self, obj, field_name):
"""Converts obj to a native representation, using the expanded serializer if the context requires it.""" """Converts obj to a native representation, using the expanded serializer if the context requires it."""
if 'expand' in self.context and field_name in self.context['expand']: if 'expand' in self.context and field_name in self.context['expand']:
self.expanded.initialize(self, field_name) self.expanded.initialize(self, field_name)
self.expanded.context['expand'] = list(set(self.expanded.context['expand']) - self.exclude_expand_fields)
return self.expanded.field_to_native(obj, field_name) return self.expanded.field_to_native(obj, field_name)
else: else:
self.collapsed.initialize(self, field_name) self.collapsed.initialize(self, field_name)
......
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