Commit 17253ead by Peter Fogg Committed by cahrens

Cache team_size on CourseTeam model and remove is_active field.

TNL-3112 TNL-3154
parent 583b2659
...@@ -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
# -*- 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']
# -*- 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
...@@ -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):
......
...@@ -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",
......
...@@ -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: '',
......
...@@ -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: "",
......
...@@ -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: ''
}; };
......
# -*- 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),
......
...@@ -208,13 +208,8 @@ class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase): ...@@ -208,13 +208,8 @@ class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase):
) )
self.test_team_2 = CourseTeamFactory.create(name='Wind Team', course_id=self.test_course_1.id) self.test_team_2 = CourseTeamFactory.create(name='Wind Team', course_id=self.test_course_1.id)
self.test_team_3 = CourseTeamFactory.create(name='Nuclear Team', course_id=self.test_course_1.id) self.test_team_3 = CourseTeamFactory.create(name='Nuclear Team', course_id=self.test_course_1.id)
self.test_team_4 = CourseTeamFactory.create( self.test_team_4 = CourseTeamFactory.create(name='Another Team', course_id=self.test_course_2.id)
name='Coal Team', self.test_team_5 = CourseTeamFactory.create(
course_id=self.test_course_1.id,
is_active=False
)
self.test_team_5 = CourseTeamFactory.create(name='Another Team', course_id=self.test_course_2.id)
self.test_team_6 = CourseTeamFactory.create(
name='Public Profile Team', name='Public Profile Team',
course_id=self.test_course_2.id, course_id=self.test_course_2.id,
topic_id='topic_6' topic_id='topic_6'
...@@ -234,7 +229,6 @@ class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase): ...@@ -234,7 +229,6 @@ class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase):
self.test_team_3, self.test_team_3,
self.test_team_4, self.test_team_4,
self.test_team_5, self.test_team_5,
self.test_team_6,
self.test_team_7, self.test_team_7,
)} )}
...@@ -245,8 +239,8 @@ class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase): ...@@ -245,8 +239,8 @@ class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase):
self.test_team_1.add_user(self.users['student_enrolled']) self.test_team_1.add_user(self.users['student_enrolled'])
self.test_team_3.add_user(self.users['student_enrolled_both_courses_other_team']) self.test_team_3.add_user(self.users['student_enrolled_both_courses_other_team'])
self.test_team_5.add_user(self.users['student_enrolled_both_courses_other_team']) self.test_team_4.add_user(self.users['student_enrolled_both_courses_other_team'])
self.test_team_6.add_user(self.users['student_enrolled_public_profile']) self.test_team_5.add_user(self.users['student_enrolled_public_profile'])
def build_membership_data_raw(self, username, team): def build_membership_data_raw(self, username, team):
"""Assembles a membership creation payload based on the raw values provided.""" """Assembles a membership creation payload based on the raw values provided."""
...@@ -400,7 +394,7 @@ class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase): ...@@ -400,7 +394,7 @@ class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase):
def verify_expanded_team(self, team): def verify_expanded_team(self, team):
"""Verifies that fields exist on the returned team json indicating that it is expanded.""" """Verifies that fields exist on the returned team json indicating that it is expanded."""
for field in ['id', 'name', 'is_active', 'course_id', 'topic_id', 'date_created', 'description']: for field in ['id', 'name', 'course_id', 'topic_id', 'date_created', 'description']:
self.assertIn(field, team) self.assertIn(field, team)
...@@ -446,9 +440,6 @@ class TestListTeamsAPI(TeamAPITestCase): ...@@ -446,9 +440,6 @@ class TestListTeamsAPI(TeamAPITestCase):
def test_filter_topic_id(self): def test_filter_topic_id(self):
self.verify_names({'course_id': self.test_course_1.id, 'topic_id': 'topic_0'}, 200, [u'Sólar team']) self.verify_names({'course_id': self.test_course_1.id, 'topic_id': 'topic_0'}, 200, [u'Sólar team'])
def test_filter_include_inactive(self):
self.verify_names({'include_inactive': True}, 200, ['Coal Team', 'Nuclear Team', u'Sólar team', 'Wind Team'])
@ddt.data( @ddt.data(
(None, 200, ['Nuclear Team', u'Sólar team', 'Wind Team']), (None, 200, ['Nuclear Team', u'Sólar team', 'Wind Team']),
('name', 200, ['Nuclear Team', u'Sólar team', 'Wind Team']), ('name', 200, ['Nuclear Team', u'Sólar team', 'Wind Team']),
...@@ -673,7 +664,6 @@ class TestCreateTeamAPI(TeamAPITestCase): ...@@ -673,7 +664,6 @@ class TestCreateTeamAPI(TeamAPITestCase):
'name': 'Fully specified team', 'name': 'Fully specified team',
'language': 'fr', 'language': 'fr',
'country': 'CA', 'country': 'CA',
'is_active': True,
'topic_id': 'great-topic', 'topic_id': 'great-topic',
'course_id': str(self.test_course_1.id), 'course_id': str(self.test_course_1.id),
'description': 'Another fantastic team' 'description': 'Another fantastic team'
...@@ -723,7 +713,7 @@ class TestDetailTeamAPI(TeamAPITestCase): ...@@ -723,7 +713,7 @@ class TestDetailTeamAPI(TeamAPITestCase):
def test_expand_public_user(self): def test_expand_public_user(self):
result = self.get_team_detail( result = self.get_team_detail(
self.test_team_6.team_id, self.test_team_5.team_id,
200, 200,
{'expand': 'user'}, {'expand': 'user'},
user='student_enrolled_public_profile' user='student_enrolled_public_profile'
...@@ -1025,7 +1015,7 @@ class TestListMembershipAPI(TeamAPITestCase): ...@@ -1025,7 +1015,7 @@ class TestListMembershipAPI(TeamAPITestCase):
def test_expand_public_user(self): def test_expand_public_user(self):
result = self.get_membership_list( result = self.get_membership_list(
200, 200,
{'team_id': self.test_team_6.team_id, 'expand': 'user'}, {'team_id': self.test_team_5.team_id, 'expand': 'user'},
user='student_enrolled_public_profile' user='student_enrolled_public_profile'
) )
self.verify_expanded_public_user(result['results'][0]['user']) self.verify_expanded_public_user(result['results'][0]['user'])
...@@ -1113,7 +1103,7 @@ class TestCreateMembershipAPI(TeamAPITestCase): ...@@ -1113,7 +1103,7 @@ class TestCreateMembershipAPI(TeamAPITestCase):
def test_over_max_team_size_in_course_2(self): def test_over_max_team_size_in_course_2(self):
response = self.post_create_membership( response = self.post_create_membership(
400, 400,
self.build_membership_data('student_enrolled_other_course_not_on_team', self.test_team_5), self.build_membership_data('student_enrolled_other_course_not_on_team', self.test_team_4),
user='student_enrolled_other_course_not_on_team' user='student_enrolled_other_course_not_on_team'
) )
self.assertIn('full', json.loads(response.content)['developer_message']) self.assertIn('full', json.loads(response.content)['developer_message'])
...@@ -1167,7 +1157,7 @@ class TestDetailMembershipAPI(TeamAPITestCase): ...@@ -1167,7 +1157,7 @@ class TestDetailMembershipAPI(TeamAPITestCase):
def test_expand_public_user(self): def test_expand_public_user(self):
result = self.get_membership_detail( result = self.get_membership_detail(
self.test_team_6.team_id, self.test_team_5.team_id,
self.users['student_enrolled_public_profile'].username, self.users['student_enrolled_public_profile'].username,
200, 200,
{'expand': 'user'}, {'expand': 'user'},
......
...@@ -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,7 +344,6 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView): ...@@ -355,7 +344,6 @@ 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)
...@@ -364,10 +352,8 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView): ...@@ -364,10 +352,8 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
# MySQL does case-insensitive order_by. # MySQL does case-insensitive order_by.
queryset = queryset.order_by('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
......
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