Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
9f86f188
Commit
9f86f188
authored
Sep 01, 2015
by
Christina Roberts
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #9503 from edx/christina/composite-index
Team API Performance Improvements
parents
af609f10
9992ba66
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
342 additions
and
39 deletions
+342
-39
lms/djangoapps/teams/errors.py
+5
-0
lms/djangoapps/teams/migrations/0005_add_course_id_and_topic_id_composite_index.py
+82
-0
lms/djangoapps/teams/migrations/0006_add_team_size.py
+98
-0
lms/djangoapps/teams/migrations/0007_auto__del_field_courseteam_is_active.py
+86
-0
lms/djangoapps/teams/models.py
+38
-5
lms/djangoapps/teams/serializers.py
+0
-1
lms/djangoapps/teams/static/teams/js/models/team.js
+0
-1
lms/djangoapps/teams/static/teams/js/spec/views/edit_team_spec.js
+0
-1
lms/djangoapps/teams/static/teams/js/spec_helpers/team_spec_helpers.js
+0
-1
lms/djangoapps/teams/tests/test_models.py
+28
-7
lms/djangoapps/teams/tests/test_views.py
+0
-0
lms/djangoapps/teams/views.py
+5
-23
No files found.
lms/djangoapps/teams/errors.py
View file @
9f86f188
...
...
@@ -14,3 +14,8 @@ class NotEnrolledInCourseForTeam(TeamAPIRequestError):
class
AlreadyOnTeamInCourse
(
TeamAPIRequestError
):
"""User is already a member of another team in the same course."""
pass
class
ImmutableMembershipFieldException
(
Exception
):
"""An attempt was made to change an immutable field on a CourseTeamMembership model"""
pass
lms/djangoapps/teams/migrations/0005_add_course_id_and_topic_id_composite_index.py
0 → 100644
View file @
9f86f188
# -*- coding: utf-8 -*-
import
pytz
from
south.utils
import
datetime_utils
as
datetime
from
south.db
import
db
from
south.v2
import
SchemaMigration
from
django.db
import
models
class
Migration
(
SchemaMigration
):
def
forwards
(
self
,
orm
):
# Create a composite index of course_id and topic_id.
db
.
create_index
(
'teams_courseteam'
,
[
'course_id'
,
'topic_id'
])
def
backwards
(
self
,
orm
):
# Delete the composite index of course_id and topic_id.
db
.
delete_index
(
'teams_courseteam'
,
[
'course_id'
,
'topic_id'
])
models
=
{
'auth.group'
:
{
'Meta'
:
{
'object_name'
:
'Group'
},
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'80'
}),
'permissions'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['auth.Permission']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
})
},
'auth.permission'
:
{
'Meta'
:
{
'ordering'
:
"('content_type__app_label', 'content_type__model', 'codename')"
,
'unique_together'
:
"(('content_type', 'codename'),)"
,
'object_name'
:
'Permission'
},
'codename'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'content_type'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['contenttypes.ContentType']"
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'50'
})
},
'auth.user'
:
{
'Meta'
:
{
'object_name'
:
'User'
},
'date_joined'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'email'
:
(
'django.db.models.fields.EmailField'
,
[],
{
'max_length'
:
'75'
,
'blank'
:
'True'
}),
'first_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'30'
,
'blank'
:
'True'
}),
'groups'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['auth.Group']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'is_active'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'is_staff'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'is_superuser'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'last_login'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'last_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'30'
,
'blank'
:
'True'
}),
'password'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
}),
'user_permissions'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['auth.Permission']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
}),
'username'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'30'
})
},
'contenttypes.contenttype'
:
{
'Meta'
:
{
'ordering'
:
"('name',)"
,
'unique_together'
:
"(('app_label', 'model'),)"
,
'object_name'
:
'ContentType'
,
'db_table'
:
"'django_content_type'"
},
'app_label'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'model'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
})
},
'teams.courseteam'
:
{
'Meta'
:
{
'object_name'
:
'CourseTeam'
},
'country'
:
(
'django_countries.fields.CountryField'
,
[],
{
'max_length'
:
'2'
,
'blank'
:
'True'
}),
'course_id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'date_created'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'description'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'300'
}),
'discussion_topic_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'255'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'is_active'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'language'
:
(
'student.models.LanguageField'
,
[],
{
'max_length'
:
'16'
,
'blank'
:
'True'
}),
'last_activity_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'team_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'255'
}),
'topic_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'db_index'
:
'True'
,
'max_length'
:
'255'
,
'blank'
:
'True'
}),
'users'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'db_index'
:
'True'
,
'related_name'
:
"'teams'"
,
'symmetrical'
:
'False'
,
'through'
:
"orm['teams.CourseTeamMembership']"
,
'to'
:
"orm['auth.User']"
})
},
'teams.courseteammembership'
:
{
'Meta'
:
{
'unique_together'
:
"(('user', 'team'),)"
,
'object_name'
:
'CourseTeamMembership'
},
'date_joined'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'last_activity_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{}),
'team'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'membership'"
,
'to'
:
"orm['teams.CourseTeam']"
}),
'user'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
})
}
}
complete_apps
=
[
'teams'
]
lms/djangoapps/teams/migrations/0006_add_team_size.py
0 → 100644
View file @
9f86f188
# -*- coding: utf-8 -*-
from
south.utils
import
datetime_utils
as
datetime
from
south.db
import
db
from
south.v2
import
SchemaMigration
from
django.db
import
models
from
teams.models
import
CourseTeamMembership
class
Migration
(
SchemaMigration
):
def
forwards
(
self
,
orm
):
# Adding field 'CourseTeam.team_size'
db
.
add_column
(
'teams_courseteam'
,
'team_size'
,
self
.
gf
(
'django.db.models.fields.IntegerField'
)(
default
=
0
,
db_index
=
True
),
keep_default
=
False
)
# Adding index on 'CourseTeam', fields ['last_activity_at']
db
.
create_index
(
'teams_courseteam'
,
[
'last_activity_at'
])
if
not
db
.
dry_run
:
for
team
in
orm
.
CourseTeam
.
objects
.
all
():
team
.
team_size
=
CourseTeamMembership
.
objects
.
filter
(
team
=
team
)
.
count
()
team
.
save
()
def
backwards
(
self
,
orm
):
# Removing index on 'CourseTeam', fields ['last_activity_at']
db
.
delete_index
(
'teams_courseteam'
,
[
'last_activity_at'
])
# Deleting field 'CourseTeam.team_size'
db
.
delete_column
(
'teams_courseteam'
,
'team_size'
)
models
=
{
'auth.group'
:
{
'Meta'
:
{
'object_name'
:
'Group'
},
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'80'
}),
'permissions'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['auth.Permission']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
})
},
'auth.permission'
:
{
'Meta'
:
{
'ordering'
:
"('content_type__app_label', 'content_type__model', 'codename')"
,
'unique_together'
:
"(('content_type', 'codename'),)"
,
'object_name'
:
'Permission'
},
'codename'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'content_type'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['contenttypes.ContentType']"
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'50'
})
},
'auth.user'
:
{
'Meta'
:
{
'object_name'
:
'User'
},
'date_joined'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'email'
:
(
'django.db.models.fields.EmailField'
,
[],
{
'max_length'
:
'75'
,
'blank'
:
'True'
}),
'first_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'30'
,
'blank'
:
'True'
}),
'groups'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['auth.Group']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'is_active'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'is_staff'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'is_superuser'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'last_login'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'last_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'30'
,
'blank'
:
'True'
}),
'password'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
}),
'user_permissions'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['auth.Permission']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
}),
'username'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'30'
})
},
'contenttypes.contenttype'
:
{
'Meta'
:
{
'ordering'
:
"('name',)"
,
'unique_together'
:
"(('app_label', 'model'),)"
,
'object_name'
:
'ContentType'
,
'db_table'
:
"'django_content_type'"
},
'app_label'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'model'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
})
},
'teams.courseteam'
:
{
'Meta'
:
{
'object_name'
:
'CourseTeam'
},
'country'
:
(
'django_countries.fields.CountryField'
,
[],
{
'max_length'
:
'2'
,
'blank'
:
'True'
}),
'course_id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'date_created'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'description'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'300'
}),
'discussion_topic_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'255'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'is_active'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'language'
:
(
'student.models.LanguageField'
,
[],
{
'max_length'
:
'16'
,
'blank'
:
'True'
}),
'last_activity_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'db_index'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'team_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'255'
}),
'team_size'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'default'
:
'0'
,
'db_index'
:
'True'
}),
'topic_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'db_index'
:
'True'
,
'max_length'
:
'255'
,
'blank'
:
'True'
}),
'users'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'db_index'
:
'True'
,
'related_name'
:
"'teams'"
,
'symmetrical'
:
'False'
,
'through'
:
"orm['teams.CourseTeamMembership']"
,
'to'
:
"orm['auth.User']"
})
},
'teams.courseteammembership'
:
{
'Meta'
:
{
'unique_together'
:
"(('user', 'team'),)"
,
'object_name'
:
'CourseTeamMembership'
},
'date_joined'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'last_activity_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{}),
'team'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'membership'"
,
'to'
:
"orm['teams.CourseTeam']"
}),
'user'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
})
}
}
complete_apps
=
[
'teams'
]
lms/djangoapps/teams/migrations/0007_auto__del_field_courseteam_is_active.py
0 → 100644
View file @
9f86f188
# -*- coding: utf-8 -*-
from
south.utils
import
datetime_utils
as
datetime
from
south.db
import
db
from
south.v2
import
SchemaMigration
from
django.db
import
models
class
Migration
(
SchemaMigration
):
def
forwards
(
self
,
orm
):
# Deleting field 'CourseTeam.is_active'
db
.
delete_column
(
'teams_courseteam'
,
'is_active'
)
def
backwards
(
self
,
orm
):
# Adding field 'CourseTeam.is_active'
db
.
add_column
(
'teams_courseteam'
,
'is_active'
,
self
.
gf
(
'django.db.models.fields.BooleanField'
)(
default
=
True
),
keep_default
=
False
)
models
=
{
'auth.group'
:
{
'Meta'
:
{
'object_name'
:
'Group'
},
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'80'
}),
'permissions'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['auth.Permission']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
})
},
'auth.permission'
:
{
'Meta'
:
{
'ordering'
:
"('content_type__app_label', 'content_type__model', 'codename')"
,
'unique_together'
:
"(('content_type', 'codename'),)"
,
'object_name'
:
'Permission'
},
'codename'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'content_type'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['contenttypes.ContentType']"
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'50'
})
},
'auth.user'
:
{
'Meta'
:
{
'object_name'
:
'User'
},
'date_joined'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'email'
:
(
'django.db.models.fields.EmailField'
,
[],
{
'max_length'
:
'75'
,
'blank'
:
'True'
}),
'first_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'30'
,
'blank'
:
'True'
}),
'groups'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['auth.Group']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'is_active'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'is_staff'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'is_superuser'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'last_login'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'last_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'30'
,
'blank'
:
'True'
}),
'password'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
}),
'user_permissions'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['auth.Permission']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
}),
'username'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'30'
})
},
'contenttypes.contenttype'
:
{
'Meta'
:
{
'ordering'
:
"('name',)"
,
'unique_together'
:
"(('app_label', 'model'),)"
,
'object_name'
:
'ContentType'
,
'db_table'
:
"'django_content_type'"
},
'app_label'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'model'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
})
},
'teams.courseteam'
:
{
'Meta'
:
{
'object_name'
:
'CourseTeam'
},
'country'
:
(
'django_countries.fields.CountryField'
,
[],
{
'max_length'
:
'2'
,
'blank'
:
'True'
}),
'course_id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'date_created'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'description'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'300'
}),
'discussion_topic_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'255'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'language'
:
(
'student.models.LanguageField'
,
[],
{
'max_length'
:
'16'
,
'blank'
:
'True'
}),
'last_activity_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'db_index'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'team_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'255'
}),
'team_size'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'default'
:
'0'
,
'db_index'
:
'True'
}),
'topic_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'db_index'
:
'True'
,
'max_length'
:
'255'
,
'blank'
:
'True'
}),
'users'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'db_index'
:
'True'
,
'related_name'
:
"'teams'"
,
'symmetrical'
:
'False'
,
'through'
:
"orm['teams.CourseTeamMembership']"
,
'to'
:
"orm['auth.User']"
})
},
'teams.courseteammembership'
:
{
'Meta'
:
{
'unique_together'
:
"(('user', 'team'),)"
,
'object_name'
:
'CourseTeamMembership'
},
'date_joined'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'last_activity_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{}),
'team'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'membership'"
,
'to'
:
"orm['teams.CourseTeam']"
}),
'user'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
})
}
}
complete_apps
=
[
'teams'
]
\ No newline at end of file
lms/djangoapps/teams/models.py
View file @
9f86f188
...
...
@@ -26,7 +26,7 @@ from django_comment_common.signals import (
from
xmodule_django.models
import
CourseKeyField
from
util.model_utils
import
slugify
from
student.models
import
LanguageField
,
CourseEnrollment
from
.errors
import
AlreadyOnTeamInCourse
,
NotEnrolledInCourseForTeam
from
.errors
import
AlreadyOnTeamInCourse
,
NotEnrolledInCourseForTeam
,
ImmutableMembershipFieldException
from
teams
import
TEAM_DISCUSSION_CONTEXT
...
...
@@ -76,7 +76,6 @@ class CourseTeam(models.Model):
team_id
=
models
.
CharField
(
max_length
=
255
,
unique
=
True
)
discussion_topic_id
=
models
.
CharField
(
max_length
=
255
,
unique
=
True
)
name
=
models
.
CharField
(
max_length
=
255
,
db_index
=
True
)
is_active
=
models
.
BooleanField
(
default
=
True
)
course_id
=
CourseKeyField
(
max_length
=
255
,
db_index
=
True
)
topic_id
=
models
.
CharField
(
max_length
=
255
,
db_index
=
True
,
blank
=
True
)
date_created
=
models
.
DateTimeField
(
auto_now_add
=
True
)
...
...
@@ -86,8 +85,9 @@ class CourseTeam(models.Model):
blank
=
True
,
help_text
=
ugettext_lazy
(
"Optional language the team uses as ISO 639-1 code."
),
)
last_activity_at
=
models
.
DateTimeField
(
)
last_activity_at
=
models
.
DateTimeField
(
db_index
=
True
)
# indexed for ordering
users
=
models
.
ManyToManyField
(
User
,
db_index
=
True
,
related_name
=
'teams'
,
through
=
'CourseTeamMembership'
)
team_size
=
models
.
IntegerField
(
default
=
0
,
db_index
=
True
)
# indexed for ordering
@classmethod
def
create
(
cls
,
name
,
course_id
,
description
,
topic_id
=
None
,
country
=
None
,
language
=
None
):
...
...
@@ -135,6 +135,11 @@ class CourseTeam(models.Model):
team
=
self
)
def
reset_team_size
(
self
):
"""Reset team_size to reflect the current membership count."""
self
.
team_size
=
CourseTeamMembership
.
objects
.
filter
(
team
=
self
)
.
count
()
self
.
save
()
class
CourseTeamMembership
(
models
.
Model
):
"""This model represents the membership of a single user in a single team."""
...
...
@@ -148,12 +153,40 @@ class CourseTeamMembership(models.Model):
date_joined
=
models
.
DateTimeField
(
auto_now_add
=
True
)
last_activity_at
=
models
.
DateTimeField
()
immutable_fields
=
(
'user'
,
'team'
,
'date_joined'
)
def
__setattr__
(
self
,
name
,
value
):
"""Memberships are immutable, with the exception of last activity
date.
"""
if
name
in
self
.
immutable_fields
:
# Check the current value -- if it is None, then this
# model is being created from the database and it's fine
# to set the value. Otherwise, we're trying to overwrite
# an immutable field.
current_value
=
getattr
(
self
,
name
,
None
)
if
current_value
is
not
None
:
raise
ImmutableMembershipFieldException
super
(
CourseTeamMembership
,
self
)
.
__setattr__
(
name
,
value
)
def
save
(
self
,
*
args
,
**
kwargs
):
""" Customize save method to set the last_activity_at if it does not currently exist. """
"""Customize save method to set the last_activity_at if it does not
currently exist. Also resets the team's size if this model is
being created.
"""
should_reset_team_size
=
False
if
self
.
pk
is
None
:
should_reset_team_size
=
True
if
not
self
.
last_activity_at
:
self
.
last_activity_at
=
datetime
.
utcnow
()
.
replace
(
tzinfo
=
pytz
.
utc
)
super
(
CourseTeamMembership
,
self
)
.
save
(
*
args
,
**
kwargs
)
if
should_reset_team_size
:
self
.
team
.
reset_team_size
()
# pylint: disable=no-member
def
delete
(
self
,
*
args
,
**
kwargs
):
"""Recompute the related team's team_size after deleting a membership"""
super
(
CourseTeamMembership
,
self
)
.
delete
(
*
args
,
**
kwargs
)
self
.
team
.
reset_team_size
()
# pylint: disable=no-member
@classmethod
def
get_memberships
(
cls
,
username
=
None
,
course_ids
=
None
,
team_id
=
None
):
...
...
lms/djangoapps/teams/serializers.py
View file @
9f86f188
...
...
@@ -51,7 +51,6 @@ class CourseTeamSerializer(serializers.ModelSerializer):
"id"
,
"discussion_topic_id"
,
"name"
,
"is_active"
,
"course_id"
,
"topic_id"
,
"date_created"
,
...
...
lms/djangoapps/teams/static/teams/js/models/team.js
View file @
9f86f188
...
...
@@ -8,7 +8,6 @@
defaults
:
{
id
:
null
,
name
:
''
,
is_active
:
null
,
course_id
:
''
,
topic_id
:
''
,
date_created
:
''
,
...
...
lms/djangoapps/teams/static/teams/js/spec/views/edit_team_spec.js
View file @
9f86f188
...
...
@@ -14,7 +14,6 @@ define([
createTeamData
=
{
id
:
null
,
name
:
"TeamName"
,
is_active
:
null
,
course_id
:
"a/b/c"
,
topic_id
:
"awesomeness"
,
date_created
:
""
,
...
...
lms/djangoapps/teams/static/teams/js/spec_helpers/team_spec_helpers.js
View file @
9f86f188
...
...
@@ -32,7 +32,6 @@ define([
id
:
"id "
+
i
,
language
:
testLanguages
[
i
%
4
][
0
],
country
:
testCountries
[
i
%
4
][
0
],
is_active
:
true
,
membership
:
[],
last_activity_at
:
''
};
...
...
lms/djangoapps/teams/tests/test_models.py
View file @
9f86f188
# -*- coding: utf-8 -*-
# pylint: disable=no-member
"""Tests for the teams API at the HTTP request level."""
from
contextlib
import
contextmanager
from
datetime
import
datetime
...
...
@@ -20,7 +21,7 @@ from django_comment_common.signals import (
)
from
xmodule.modulestore.tests.django_utils
import
SharedModuleStoreTestCase
from
opaque_keys.edx.keys
import
CourseKey
from
student.tests.factories
import
UserFactory
from
student.tests.factories
import
CourseEnrollmentFactory
,
UserFactory
from
.factories
import
CourseTeamFactory
,
CourseTeamMembershipFactory
from
..models
import
CourseTeam
,
CourseTeamMembership
...
...
@@ -42,16 +43,18 @@ class TeamMembershipTest(SharedModuleStoreTestCase):
self
.
user1
=
UserFactory
.
create
(
username
=
'user1'
)
self
.
user2
=
UserFactory
.
create
(
username
=
'user2'
)
self
.
user3
=
UserFactory
.
create
(
username
=
'user3'
)
for
user
in
(
self
.
user1
,
self
.
user2
,
self
.
user3
):
CourseEnrollmentFactory
.
create
(
user
=
user
,
course_id
=
COURSE_KEY1
)
CourseEnrollmentFactory
.
create
(
user
=
self
.
user1
,
course_id
=
COURSE_KEY2
)
self
.
team1
=
CourseTeamFactory
(
course_id
=
COURSE_KEY1
,
team_id
=
'team1'
)
self
.
team2
=
CourseTeamFactory
(
course_id
=
COURSE_KEY2
,
team_id
=
'team2'
)
self
.
team_membership11
=
CourseTeamMembership
(
user
=
self
.
user1
,
team
=
self
.
team1
)
self
.
team_membership11
.
save
()
self
.
team_membership12
=
CourseTeamMembership
(
user
=
self
.
user2
,
team
=
self
.
team1
)
self
.
team_membership12
.
save
()
self
.
team_membership21
=
CourseTeamMembership
(
user
=
self
.
user1
,
team
=
self
.
team2
)
self
.
team_membership21
.
save
()
self
.
team_membership11
=
self
.
team1
.
add_user
(
self
.
user1
)
self
.
team_membership12
=
self
.
team1
.
add_user
(
self
.
user2
)
self
.
team_membership21
=
self
.
team2
.
add_user
(
self
.
user1
)
def
test_membership_last_activity_set
(
self
):
current_last_activity
=
self
.
team_membership11
.
last_activity_at
...
...
@@ -64,6 +67,24 @@ class TeamMembershipTest(SharedModuleStoreTestCase):
# already exist.
self
.
assertEqual
(
self
.
team_membership11
.
last_activity_at
,
current_last_activity
)
def
test_team_size_delete_membership
(
self
):
"""Test that the team size field is correctly updated when deleting a
team membership.
"""
self
.
assertEqual
(
self
.
team1
.
team_size
,
2
)
self
.
team_membership11
.
delete
()
team
=
CourseTeam
.
objects
.
get
(
id
=
self
.
team1
.
id
)
self
.
assertEqual
(
team
.
team_size
,
1
)
def
test_team_size_create_membership
(
self
):
"""Test that the team size field is correctly updated when creating a
team membership.
"""
self
.
assertEqual
(
self
.
team1
.
team_size
,
2
)
self
.
team1
.
add_user
(
self
.
user3
)
team
=
CourseTeam
.
objects
.
get
(
id
=
self
.
team1
.
id
)
self
.
assertEqual
(
team
.
team_size
,
3
)
@ddt.data
(
(
None
,
None
,
None
,
3
),
(
'user1'
,
None
,
None
,
2
),
...
...
lms/djangoapps/teams/tests/test_views.py
View file @
9f86f188
This diff is collapsed.
Click to expand it.
lms/djangoapps/teams/views.py
View file @
9f86f188
...
...
@@ -191,9 +191,6 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
* page: Page number to retrieve.
* include_inactive: If true, inactive teams will be returned. The
default is to not include inactive teams.
* expand: Comma separated list of types for which to return
expanded representations. Supports "user" and "team".
...
...
@@ -220,10 +217,6 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
* name: The name of the team.
* is_active: True if the team is currently active. If false, the
team is considered "soft deleted" and will not be included by
default in results.
* course_id: The identifier for the course this team belongs to.
* topic_id: Optionally specifies which topic the team is associated
...
...
@@ -266,8 +259,8 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
Any logged in user who has verified their email address can create
a team. The format mirrors that of a GET for an individual team,
but does not include the id,
is_active, date_created, or membership
fields.
id is automatically computed based on name.
but does not include the id,
date_created, or membership fields.
id is automatically computed based on name.
If the user is not logged in, a 401 error is returned.
...
...
@@ -292,9 +285,7 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
def
get
(
self
,
request
):
"""GET /api/team/v0/teams/"""
result_filter
=
{
'is_active'
:
True
}
result_filter
=
{}
if
'course_id'
in
request
.
QUERY_PARAMS
:
course_id_string
=
request
.
QUERY_PARAMS
[
'course_id'
]
...
...
@@ -335,8 +326,6 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
)
return
Response
(
error
,
status
=
status
.
HTTP_400_BAD_REQUEST
)
result_filter
.
update
({
'topic_id'
:
request
.
QUERY_PARAMS
[
'topic_id'
]})
if
'include_inactive'
in
request
.
QUERY_PARAMS
and
request
.
QUERY_PARAMS
[
'include_inactive'
]
.
lower
()
==
'true'
:
del
result_filter
[
'is_active'
]
if
'text_search'
in
request
.
QUERY_PARAMS
and
CourseTeamIndexer
.
search_is_enabled
():
search_engine
=
CourseTeamIndexer
.
engine
()
...
...
@@ -355,19 +344,16 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
self
.
get_paginate_by
(),
self
.
get_page
()
)
serializer
=
self
.
get_pagination_serializer
(
paginated_results
)
else
:
queryset
=
CourseTeam
.
objects
.
filter
(
**
result_filter
)
order_by_input
=
request
.
QUERY_PARAMS
.
get
(
'order_by'
,
'name'
)
if
order_by_input
==
'name'
:
queryset
=
queryset
.
extra
(
select
=
{
'lower_name'
:
"lower(name)"
})
queryset
=
queryset
.
order_by
(
'
lower_
name'
)
# MySQL does case-insensitive order_by.
queryset
=
queryset
.
order_by
(
'name'
)
elif
order_by_input
==
'open_slots'
:
queryset
=
queryset
.
annotate
(
team_size
=
Count
(
'users'
))
queryset
=
queryset
.
order_by
(
'team_size'
,
'-last_activity_at'
)
elif
order_by_input
==
'last_activity_at'
:
queryset
=
queryset
.
annotate
(
team_size
=
Count
(
'users'
))
queryset
=
queryset
.
order_by
(
'-last_activity_at'
,
'team_size'
)
else
:
return
Response
({
...
...
@@ -496,10 +482,6 @@ class TeamsDetailView(ExpandableFieldViewMixin, RetrievePatchAPIView):
* name: The name of the team.
* is_active: True if the team is currently active. If false, the team
is considered "soft deleted" and will not be included by default in
results.
* course_id: The identifier for the course this team belongs to.
* topic_id: Optionally specifies which topic the team is
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment