diff --git a/lms/djangoapps/teams/errors.py b/lms/djangoapps/teams/errors.py index 36b8ca54a0..0f718d93a6 100644 --- a/lms/djangoapps/teams/errors.py +++ b/lms/djangoapps/teams/errors.py @@ -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 diff --git a/lms/djangoapps/teams/migrations/0006_add_team_size.py b/lms/djangoapps/teams/migrations/0006_add_team_size.py new file mode 100644 index 0000000000..a89c03895c --- /dev/null +++ b/lms/djangoapps/teams/migrations/0006_add_team_size.py @@ -0,0 +1,98 @@ +# -*- 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'] diff --git a/lms/djangoapps/teams/migrations/0007_auto__del_field_courseteam_is_active.py b/lms/djangoapps/teams/migrations/0007_auto__del_field_courseteam_is_active.py new file mode 100644 index 0000000000..1810468dbe --- /dev/null +++ b/lms/djangoapps/teams/migrations/0007_auto__del_field_courseteam_is_active.py @@ -0,0 +1,85 @@ +# -*- 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 diff --git a/lms/djangoapps/teams/models.py b/lms/djangoapps/teams/models.py index 8cba347235..e94e76da26 100644 --- a/lms/djangoapps/teams/models.py +++ b/lms/djangoapps/teams/models.py @@ -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): diff --git a/lms/djangoapps/teams/serializers.py b/lms/djangoapps/teams/serializers.py index 7f403c2957..79183e464c 100644 --- a/lms/djangoapps/teams/serializers.py +++ b/lms/djangoapps/teams/serializers.py @@ -51,7 +51,6 @@ class CourseTeamSerializer(serializers.ModelSerializer): "id", "discussion_topic_id", "name", - "is_active", "course_id", "topic_id", "date_created", diff --git a/lms/djangoapps/teams/static/teams/js/models/team.js b/lms/djangoapps/teams/static/teams/js/models/team.js index f8771f6e80..3ee13b53ae 100644 --- a/lms/djangoapps/teams/static/teams/js/models/team.js +++ b/lms/djangoapps/teams/static/teams/js/models/team.js @@ -8,7 +8,6 @@ defaults: { id: null, name: '', - is_active: null, course_id: '', topic_id: '', date_created: '', diff --git a/lms/djangoapps/teams/static/teams/js/spec/views/edit_team_spec.js b/lms/djangoapps/teams/static/teams/js/spec/views/edit_team_spec.js index 5a68d3163b..97c959000c 100644 --- a/lms/djangoapps/teams/static/teams/js/spec/views/edit_team_spec.js +++ b/lms/djangoapps/teams/static/teams/js/spec/views/edit_team_spec.js @@ -14,7 +14,6 @@ define([ createTeamData = { id: null, name: "TeamName", - is_active: null, course_id: "a/b/c", topic_id: "awesomeness", date_created: "", diff --git a/lms/djangoapps/teams/static/teams/js/spec_helpers/team_spec_helpers.js b/lms/djangoapps/teams/static/teams/js/spec_helpers/team_spec_helpers.js index a210bf7c4e..08d3ada6ba 100644 --- a/lms/djangoapps/teams/static/teams/js/spec_helpers/team_spec_helpers.js +++ b/lms/djangoapps/teams/static/teams/js/spec_helpers/team_spec_helpers.js @@ -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: '' }; diff --git a/lms/djangoapps/teams/tests/test_models.py b/lms/djangoapps/teams/tests/test_models.py index 39c645dd78..ba9fc6f53c 100644 --- a/lms/djangoapps/teams/tests/test_models.py +++ b/lms/djangoapps/teams/tests/test_models.py @@ -1,4 +1,5 @@ # -*- 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), diff --git a/lms/djangoapps/teams/tests/test_views.py b/lms/djangoapps/teams/tests/test_views.py index 1f24835c82..8b61630c93 100644 --- a/lms/djangoapps/teams/tests/test_views.py +++ b/lms/djangoapps/teams/tests/test_views.py @@ -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_3 = CourseTeamFactory.create(name='Nuclear Team', course_id=self.test_course_1.id) - self.test_team_4 = CourseTeamFactory.create( - name='Coal Team', - 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( + self.test_team_4 = CourseTeamFactory.create(name='Another Team', course_id=self.test_course_2.id) + self.test_team_5 = CourseTeamFactory.create( name='Public Profile Team', course_id=self.test_course_2.id, topic_id='topic_6' @@ -234,7 +229,6 @@ class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase): self.test_team_3, self.test_team_4, self.test_team_5, - self.test_team_6, self.test_team_7, )} @@ -245,8 +239,8 @@ class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase): 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_5.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_4.add_user(self.users['student_enrolled_both_courses_other_team']) + self.test_team_5.add_user(self.users['student_enrolled_public_profile']) def build_membership_data_raw(self, username, team): """Assembles a membership creation payload based on the raw values provided.""" @@ -400,7 +394,7 @@ class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase): def verify_expanded_team(self, team): """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) @@ -446,9 +440,6 @@ class TestListTeamsAPI(TeamAPITestCase): 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']) - 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( (None, 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): 'name': 'Fully specified team', 'language': 'fr', 'country': 'CA', - 'is_active': True, 'topic_id': 'great-topic', 'course_id': str(self.test_course_1.id), 'description': 'Another fantastic team' @@ -723,7 +713,7 @@ class TestDetailTeamAPI(TeamAPITestCase): def test_expand_public_user(self): result = self.get_team_detail( - self.test_team_6.team_id, + self.test_team_5.team_id, 200, {'expand': 'user'}, user='student_enrolled_public_profile' @@ -1025,7 +1015,7 @@ class TestListMembershipAPI(TeamAPITestCase): def test_expand_public_user(self): result = self.get_membership_list( 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' ) self.verify_expanded_public_user(result['results'][0]['user']) @@ -1113,7 +1103,7 @@ class TestCreateMembershipAPI(TeamAPITestCase): def test_over_max_team_size_in_course_2(self): response = self.post_create_membership( 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' ) self.assertIn('full', json.loads(response.content)['developer_message']) @@ -1167,7 +1157,7 @@ class TestDetailMembershipAPI(TeamAPITestCase): def test_expand_public_user(self): result = self.get_membership_detail( - self.test_team_6.team_id, + self.test_team_5.team_id, self.users['student_enrolled_public_profile'].username, 200, {'expand': 'user'}, diff --git a/lms/djangoapps/teams/views.py b/lms/djangoapps/teams/views.py index 87cf06a3b3..4dbb72dfa0 100644 --- a/lms/djangoapps/teams/views.py +++ b/lms/djangoapps/teams/views.py @@ -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,7 +344,6 @@ 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) @@ -364,10 +352,8 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView): # 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