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):
...
@@ -14,3 +14,8 @@ class NotEnrolledInCourseForTeam(TeamAPIRequestError):
class
AlreadyOnTeamInCourse
(
TeamAPIRequestError
):
class
AlreadyOnTeamInCourse
(
TeamAPIRequestError
):
"""User is already a member of another team in the same course."""
"""User is already a member of another team in the same course."""
pass
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 (
...
@@ -26,7 +26,7 @@ from django_comment_common.signals import (
from
xmodule_django.models
import
CourseKeyField
from
xmodule_django.models
import
CourseKeyField
from
util.model_utils
import
slugify
from
util.model_utils
import
slugify
from
student.models
import
LanguageField
,
CourseEnrollment
from
student.models
import
LanguageField
,
CourseEnrollment
from
.errors
import
AlreadyOnTeamInCourse
,
NotEnrolledInCourseForTeam
from
.errors
import
AlreadyOnTeamInCourse
,
NotEnrolledInCourseForTeam
,
ImmutableMembershipFieldException
from
teams
import
TEAM_DISCUSSION_CONTEXT
from
teams
import
TEAM_DISCUSSION_CONTEXT
...
@@ -76,7 +76,6 @@ class CourseTeam(models.Model):
...
@@ -76,7 +76,6 @@ class CourseTeam(models.Model):
team_id
=
models
.
CharField
(
max_length
=
255
,
unique
=
True
)
team_id
=
models
.
CharField
(
max_length
=
255
,
unique
=
True
)
discussion_topic_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
)
name
=
models
.
CharField
(
max_length
=
255
,
db_index
=
True
)
is_active
=
models
.
BooleanField
(
default
=
True
)
course_id
=
CourseKeyField
(
max_length
=
255
,
db_index
=
True
)
course_id
=
CourseKeyField
(
max_length
=
255
,
db_index
=
True
)
topic_id
=
models
.
CharField
(
max_length
=
255
,
db_index
=
True
,
blank
=
True
)
topic_id
=
models
.
CharField
(
max_length
=
255
,
db_index
=
True
,
blank
=
True
)
date_created
=
models
.
DateTimeField
(
auto_now_add
=
True
)
date_created
=
models
.
DateTimeField
(
auto_now_add
=
True
)
...
@@ -86,8 +85,9 @@ class CourseTeam(models.Model):
...
@@ -86,8 +85,9 @@ class CourseTeam(models.Model):
blank
=
True
,
blank
=
True
,
help_text
=
ugettext_lazy
(
"Optional language the team uses as ISO 639-1 code."
),
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'
)
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
@classmethod
def
create
(
cls
,
name
,
course_id
,
description
,
topic_id
=
None
,
country
=
None
,
language
=
None
):
def
create
(
cls
,
name
,
course_id
,
description
,
topic_id
=
None
,
country
=
None
,
language
=
None
):
...
@@ -135,6 +135,11 @@ class CourseTeam(models.Model):
...
@@ -135,6 +135,11 @@ class CourseTeam(models.Model):
team
=
self
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
):
class
CourseTeamMembership
(
models
.
Model
):
"""This model represents the membership of a single user in a single team."""
"""This model represents the membership of a single user in a single team."""
...
@@ -148,12 +153,40 @@ class CourseTeamMembership(models.Model):
...
@@ -148,12 +153,40 @@ class CourseTeamMembership(models.Model):
date_joined
=
models
.
DateTimeField
(
auto_now_add
=
True
)
date_joined
=
models
.
DateTimeField
(
auto_now_add
=
True
)
last_activity_at
=
models
.
DateTimeField
()
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
):
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
:
if
not
self
.
last_activity_at
:
self
.
last_activity_at
=
datetime
.
utcnow
()
.
replace
(
tzinfo
=
pytz
.
utc
)
self
.
last_activity_at
=
datetime
.
utcnow
()
.
replace
(
tzinfo
=
pytz
.
utc
)
super
(
CourseTeamMembership
,
self
)
.
save
(
*
args
,
**
kwargs
)
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
@classmethod
def
get_memberships
(
cls
,
username
=
None
,
course_ids
=
None
,
team_id
=
None
):
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):
...
@@ -51,7 +51,6 @@ class CourseTeamSerializer(serializers.ModelSerializer):
"id"
,
"id"
,
"discussion_topic_id"
,
"discussion_topic_id"
,
"name"
,
"name"
,
"is_active"
,
"course_id"
,
"course_id"
,
"topic_id"
,
"topic_id"
,
"date_created"
,
"date_created"
,
...
...
lms/djangoapps/teams/static/teams/js/models/team.js
View file @
9f86f188
...
@@ -8,7 +8,6 @@
...
@@ -8,7 +8,6 @@
defaults
:
{
defaults
:
{
id
:
null
,
id
:
null
,
name
:
''
,
name
:
''
,
is_active
:
null
,
course_id
:
''
,
course_id
:
''
,
topic_id
:
''
,
topic_id
:
''
,
date_created
:
''
,
date_created
:
''
,
...
...
lms/djangoapps/teams/static/teams/js/spec/views/edit_team_spec.js
View file @
9f86f188
...
@@ -14,7 +14,6 @@ define([
...
@@ -14,7 +14,6 @@ define([
createTeamData
=
{
createTeamData
=
{
id
:
null
,
id
:
null
,
name
:
"TeamName"
,
name
:
"TeamName"
,
is_active
:
null
,
course_id
:
"a/b/c"
,
course_id
:
"a/b/c"
,
topic_id
:
"awesomeness"
,
topic_id
:
"awesomeness"
,
date_created
:
""
,
date_created
:
""
,
...
...
lms/djangoapps/teams/static/teams/js/spec_helpers/team_spec_helpers.js
View file @
9f86f188
...
@@ -32,7 +32,6 @@ define([
...
@@ -32,7 +32,6 @@ define([
id
:
"id "
+
i
,
id
:
"id "
+
i
,
language
:
testLanguages
[
i
%
4
][
0
],
language
:
testLanguages
[
i
%
4
][
0
],
country
:
testCountries
[
i
%
4
][
0
],
country
:
testCountries
[
i
%
4
][
0
],
is_active
:
true
,
membership
:
[],
membership
:
[],
last_activity_at
:
''
last_activity_at
:
''
};
};
...
...
lms/djangoapps/teams/tests/test_models.py
View file @
9f86f188
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
# pylint: disable=no-member
"""Tests for the teams API at the HTTP request level."""
"""Tests for the teams API at the HTTP request level."""
from
contextlib
import
contextmanager
from
contextlib
import
contextmanager
from
datetime
import
datetime
from
datetime
import
datetime
...
@@ -20,7 +21,7 @@ from django_comment_common.signals import (
...
@@ -20,7 +21,7 @@ from django_comment_common.signals import (
)
)
from
xmodule.modulestore.tests.django_utils
import
SharedModuleStoreTestCase
from
xmodule.modulestore.tests.django_utils
import
SharedModuleStoreTestCase
from
opaque_keys.edx.keys
import
CourseKey
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
.factories
import
CourseTeamFactory
,
CourseTeamMembershipFactory
from
..models
import
CourseTeam
,
CourseTeamMembership
from
..models
import
CourseTeam
,
CourseTeamMembership
...
@@ -42,16 +43,18 @@ class TeamMembershipTest(SharedModuleStoreTestCase):
...
@@ -42,16 +43,18 @@ class TeamMembershipTest(SharedModuleStoreTestCase):
self
.
user1
=
UserFactory
.
create
(
username
=
'user1'
)
self
.
user1
=
UserFactory
.
create
(
username
=
'user1'
)
self
.
user2
=
UserFactory
.
create
(
username
=
'user2'
)
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
.
team1
=
CourseTeamFactory
(
course_id
=
COURSE_KEY1
,
team_id
=
'team1'
)
self
.
team2
=
CourseTeamFactory
(
course_id
=
COURSE_KEY2
,
team_id
=
'team2'
)
self
.
team2
=
CourseTeamFactory
(
course_id
=
COURSE_KEY2
,
team_id
=
'team2'
)
self
.
team_membership11
=
CourseTeamMembership
(
user
=
self
.
user1
,
team
=
self
.
team1
)
self
.
team_membership11
=
self
.
team1
.
add_user
(
self
.
user1
)
self
.
team_membership11
.
save
()
self
.
team_membership12
=
self
.
team1
.
add_user
(
self
.
user2
)
self
.
team_membership12
=
CourseTeamMembership
(
user
=
self
.
user2
,
team
=
self
.
team1
)
self
.
team_membership21
=
self
.
team2
.
add_user
(
self
.
user1
)
self
.
team_membership12
.
save
()
self
.
team_membership21
=
CourseTeamMembership
(
user
=
self
.
user1
,
team
=
self
.
team2
)
self
.
team_membership21
.
save
()
def
test_membership_last_activity_set
(
self
):
def
test_membership_last_activity_set
(
self
):
current_last_activity
=
self
.
team_membership11
.
last_activity_at
current_last_activity
=
self
.
team_membership11
.
last_activity_at
...
@@ -64,6 +67,24 @@ class TeamMembershipTest(SharedModuleStoreTestCase):
...
@@ -64,6 +67,24 @@ class TeamMembershipTest(SharedModuleStoreTestCase):
# already exist.
# already exist.
self
.
assertEqual
(
self
.
team_membership11
.
last_activity_at
,
current_last_activity
)
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
(
@ddt.data
(
(
None
,
None
,
None
,
3
),
(
None
,
None
,
None
,
3
),
(
'user1'
,
None
,
None
,
2
),
(
'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):
...
@@ -191,9 +191,6 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
* page: Page number to retrieve.
* 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
* expand: Comma separated list of types for which to return
expanded representations. Supports "user" and "team".
expanded representations. Supports "user" and "team".
...
@@ -220,10 +217,6 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
...
@@ -220,10 +217,6 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
* name: The name of the team.
* 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.
* course_id: The identifier for the course this team belongs to.
* topic_id: Optionally specifies which topic the team is associated
* topic_id: Optionally specifies which topic the team is associated
...
@@ -266,8 +259,8 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
...
@@ -266,8 +259,8 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
Any logged in user who has verified their email address can create
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,
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
but does not include the id,
date_created, or membership fields.
fields.
id is automatically computed based on name.
id is automatically computed based on name.
If the user is not logged in, a 401 error is returned.
If the user is not logged in, a 401 error is returned.
...
@@ -292,9 +285,7 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
...
@@ -292,9 +285,7 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
def
get
(
self
,
request
):
def
get
(
self
,
request
):
"""GET /api/team/v0/teams/"""
"""GET /api/team/v0/teams/"""
result_filter
=
{
result_filter
=
{}
'is_active'
:
True
}
if
'course_id'
in
request
.
QUERY_PARAMS
:
if
'course_id'
in
request
.
QUERY_PARAMS
:
course_id_string
=
request
.
QUERY_PARAMS
[
'course_id'
]
course_id_string
=
request
.
QUERY_PARAMS
[
'course_id'
]
...
@@ -335,8 +326,6 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
...
@@ -335,8 +326,6 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
)
)
return
Response
(
error
,
status
=
status
.
HTTP_400_BAD_REQUEST
)
return
Response
(
error
,
status
=
status
.
HTTP_400_BAD_REQUEST
)
result_filter
.
update
({
'topic_id'
:
request
.
QUERY_PARAMS
[
'topic_id'
]})
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
():
if
'text_search'
in
request
.
QUERY_PARAMS
and
CourseTeamIndexer
.
search_is_enabled
():
search_engine
=
CourseTeamIndexer
.
engine
()
search_engine
=
CourseTeamIndexer
.
engine
()
...
@@ -355,19 +344,16 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
...
@@ -355,19 +344,16 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
self
.
get_paginate_by
(),
self
.
get_paginate_by
(),
self
.
get_page
()
self
.
get_page
()
)
)
serializer
=
self
.
get_pagination_serializer
(
paginated_results
)
serializer
=
self
.
get_pagination_serializer
(
paginated_results
)
else
:
else
:
queryset
=
CourseTeam
.
objects
.
filter
(
**
result_filter
)
queryset
=
CourseTeam
.
objects
.
filter
(
**
result_filter
)
order_by_input
=
request
.
QUERY_PARAMS
.
get
(
'order_by'
,
'name'
)
order_by_input
=
request
.
QUERY_PARAMS
.
get
(
'order_by'
,
'name'
)
if
order_by_input
==
'name'
:
if
order_by_input
==
'name'
:
queryset
=
queryset
.
extra
(
select
=
{
'lower_name'
:
"lower(name)"
})
# MySQL does case-insensitive order_by.
queryset
=
queryset
.
order_by
(
'
lower_
name'
)
queryset
=
queryset
.
order_by
(
'name'
)
elif
order_by_input
==
'open_slots'
:
elif
order_by_input
==
'open_slots'
:
queryset
=
queryset
.
annotate
(
team_size
=
Count
(
'users'
))
queryset
=
queryset
.
order_by
(
'team_size'
,
'-last_activity_at'
)
queryset
=
queryset
.
order_by
(
'team_size'
,
'-last_activity_at'
)
elif
order_by_input
==
'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'
)
queryset
=
queryset
.
order_by
(
'-last_activity_at'
,
'team_size'
)
else
:
else
:
return
Response
({
return
Response
({
...
@@ -496,10 +482,6 @@ class TeamsDetailView(ExpandableFieldViewMixin, RetrievePatchAPIView):
...
@@ -496,10 +482,6 @@ class TeamsDetailView(ExpandableFieldViewMixin, RetrievePatchAPIView):
* name: The name of the team.
* 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.
* course_id: The identifier for the course this team belongs to.
* topic_id: Optionally specifies which topic the team is
* 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