Skip to content

Commit

Permalink
Merge pull request #697 from edx-solutions/msaqib52/YONK-302
Browse files Browse the repository at this point in the history
YONK-302: Adds API endpoint to link Organization permission groups with users
  • Loading branch information
msaqib52 committed May 6, 2016
2 parents eb3f1a1 + 3df8b24 commit 7bcc2cc
Show file tree
Hide file tree
Showing 6 changed files with 416 additions and 1 deletion.
1 change: 1 addition & 0 deletions lms/djangoapps/api_manager/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
url(r'^groups/*', include('api_manager.groups.urls')),
url(r'^sessions/*', include('api_manager.sessions.urls')),
url(r'^courses/*', include('api_manager.courses.urls')),
url(r'^organizations/*', include('organizations.urls')),
)

server_api_router = SimpleRouter()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# -*- coding: utf-8 -*-
# pylint: disable=C0103, C0111
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models # pylint: disable=W0611


class Migration(SchemaMigration): # pylint: disable=C0111

def forwards(self, orm): # pylint: disable=C0111
# Adding model 'OrganizationGroupUser'
db.create_table('organizations_organizationgroupuser', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('created', self.gf('model_utils.fields.AutoCreatedField')(default=datetime.datetime.now)),
('modified', self.gf('model_utils.fields.AutoLastModifiedField')(default=datetime.datetime.now)),
('organization', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['organizations.Organization'])), # pylint: disable=C0301
('group', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.Group'])),
('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
))
db.send_create_signal('organizations', ['OrganizationGroupUser'])

# Adding unique constraint on 'OrganizationGroupUser', fields ['organization', 'group', 'user']
db.create_unique('organizations_organizationgroupuser', ['organization_id', 'group_id', 'user_id'])


def backwards(self, orm): # pylint: disable=C0111, W0613
# Removing unique constraint on 'OrganizationGroupUser', fields ['organization', 'group', 'user']
db.delete_unique('organizations_organizationgroupuser', ['organization_id', 'group_id', 'user_id'])

# Deleting model 'OrganizationGroupUser'
db.delete_table('organizations_organizationgroupuser')


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'}) # pylint: disable=C0301
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, # pylint: disable=C0301
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), # pylint: disable=C0301
'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'}), # pylint: disable=C0301
'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'}), # pylint: disable=C0301
'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'"}, # pylint: disable=C0301
'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'})
},
'organizations.organization': {
'Meta': {'object_name': 'Organization'},
'contact_email': ('django.db.models.fields.EmailField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), # pylint: disable=C0301
'contact_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), # pylint: disable=C0301
'contact_phone': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), # pylint: disable=C0301
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
'display_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), # pylint: disable=C0301
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'organizations'", 'symmetrical': 'False', 'to': "orm['auth.Group']"}), # pylint: disable=C0301
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'logo_url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), # pylint: disable=C0301
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'organizations'", 'symmetrical': 'False', 'to': "orm['auth.User']"}), # pylint: disable=C0301
'workgroups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'organizations'", 'symmetrical': 'False', 'to': "orm['projects.Workgroup']"}) # pylint: disable=C0301
},
'organizations.organizationgroupuser': {
'Meta': {'unique_together': "(('organization', 'group', 'user'),)", 'object_name': 'OrganizationGroupUser'},
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
'organization': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['organizations.Organization']"}), # pylint: disable=C0301
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
},
'projects.project': {
'Meta': {'unique_together': "(('course_id', 'content_id', 'organization'),)", 'object_name': 'Project'},
'content_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
'organization': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'projects'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['organizations.Organization']"}) # pylint: disable=C0301
},
'projects.workgroup': {
'Meta': {'object_name': 'Workgroup'},
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'workgroups'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.Group']"}), # pylint: disable=C0301
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'workgroups'", 'to': "orm['projects.Project']"}), # pylint: disable=C0301
'users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'workgroups'", 'to': "orm['auth.User']", 'through': "orm['projects.WorkgroupUser']", 'blank': 'True', 'symmetrical': 'False', 'null': 'True'}) # pylint: disable=C0301
},
'projects.workgroupuser': {
'Meta': {'object_name': 'WorkgroupUser', 'db_table': "'projects_workgroup_users'"},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
'workgroup': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Workgroup']"})
}
}

complete_apps = ['organizations']
16 changes: 16 additions & 0 deletions lms/djangoapps/organizations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,19 @@ class Organization(TimeStampedModel):
workgroups = models.ManyToManyField(Workgroup, related_name="organizations")
users = models.ManyToManyField(User, related_name="organizations")
groups = models.ManyToManyField(Group, related_name="organizations")


class OrganizationGroupUser(TimeStampedModel):
"""
The OrganizationGroupUser model contains information describing the
link between a particular user, group and an organization.
"""
organization = models.ForeignKey(Organization)
group = models.ForeignKey(Group)
user = models.ForeignKey(User)

class Meta(object):
"""
Meta class for setting model meta options
"""
unique_together = ("organization", "group", "user")
147 changes: 147 additions & 0 deletions lms/djangoapps/organizations/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

from api_manager.models import CourseGroupRelationship
from gradebook.models import StudentGradebook
from organizations.models import OrganizationGroupUser
from student.models import UserProfile
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, mixed_store_config
from student.tests.factories import CourseEnrollmentFactory, UserFactory, GroupFactory
Expand Down Expand Up @@ -611,3 +612,149 @@ def test_organizations_metrics_get_courses_filter(self):
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['users_grade_complete_count'], 0)
self.assertEqual(response.data['users_grade_average'], 0)

def test_organizations_groups_users_get(self):
organization = self.setup_test_organization()
organization_two = self.setup_test_organization()
group = GroupFactory.create()
users = UserFactory.create_batch(5)
group.organizations.add(organization['id'])
group.organizations.add(organization_two['id'])
for user in users:
OrganizationGroupUser.objects.create(organization_id=organization['id'], group=group, user=user)

# test when organization group have no users
test_uri = '{}{}/groups/{}/users'.format(self.base_organizations_uri, organization['id'], 1234)
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data), 0)

# test when organization group have users
test_uri = '{}{}/groups/{}/users'.format(self.base_organizations_uri, organization['id'], group.id)
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data), len(users))
for i, user in enumerate(response.data):
self.assertEqual(user['id'], users[i].id)

# test organization_two group users
test_uri = '{}{}/groups/{}/users'.format(self.base_organizations_uri, organization_two['id'], group.id)
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data), 0)

def test_organizations_groups_users_post(self):
organization = self.setup_test_organization()
organization_two = self.setup_test_organization()
groups = GroupFactory.create_batch(2)
users = UserFactory.create_batch(5)
groups[0].organizations.add(organization['id'])
groups[0].organizations.add(organization_two['id'])

# test for invalid user id
test_uri = '{}{}/groups/{}/users'.format(self.base_organizations_uri, organization['id'], groups[1].id)
data = {
'users': '1,qwerty'
}
response = self.do_delete(test_uri, data)
self.assertEqual(response.status_code, 400)

# group does not belong to organization
test_uri = '{}{}/groups/{}/users'.format(self.base_organizations_uri, organization['id'], groups[1].id)
data = {
'users': ','.join([str(user.id) for user in users])
}
response = self.do_post(test_uri, data)
self.assertEqual(response.status_code, 404)
expected_response = "Group {} does not belong to organization {}".format(groups[1].id, organization['id'])
self.assertEqual(response.data['detail'], expected_response)

# group belong to organization but users does not exit
test_uri = '{}{}/groups/{}/users'.format(self.base_organizations_uri, organization['id'], groups[0].id)
data = {
'users': '1234,9912,9800'
}
response = self.do_post(test_uri, data)
self.assertEqual(response.status_code, 204)
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data), 0)

# group belong to organization and users exist
data = {
'users': ','.join([str(user.id) for user in users])
}
response = self.do_post(test_uri, data)
self.assertEqual(response.status_code, 201)
user_ids = ', '.join([str(user.id) for user in users])
expected_response = "user id(s) {} added to organization {}'s group {}".format(user_ids,
organization['id'],
groups[0].id)
self.assertEqual(response.data['detail'], expected_response)
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data), len(users))
for i, user in enumerate(response.data):
self.assertEqual(user['id'], users[i].id)
# test users were not added to organization_two group relation
test_uri = '{}{}/groups/{}/users'.format(self.base_organizations_uri, organization_two['id'], groups[0].id)
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data), 0)

def test_organizations_groups_users_delete(self):
organization = self.setup_test_organization()
organization_two = self.setup_test_organization()
groups = GroupFactory.create_batch(2)
users = UserFactory.create_batch(5)
groups[0].organizations.add(organization['id'])
groups[0].organizations.add(organization_two['id'])
for user in users:
OrganizationGroupUser.objects.create(organization_id=organization['id'], group=groups[0], user=user)
OrganizationGroupUser.objects.create(organization_id=organization_two['id'], group=groups[0], user=user)

# test for invalid user id
test_uri = '{}{}/groups/{}/users'.format(self.base_organizations_uri, organization['id'], groups[1].id)
data = {
'users': '1,qwerty'
}
response = self.do_delete(test_uri, data)
self.assertEqual(response.status_code, 400)

# group does not belong to organization
test_uri = '{}{}/groups/{}/users'.format(self.base_organizations_uri, organization['id'], groups[1].id)
data = {
'users': ','.join([str(user.id) for user in users])
}
response = self.do_delete(test_uri, data)
self.assertEqual(response.status_code, 404)
expected_response = "Group {} does not belong to organization {}".format(groups[1].id, organization['id'])
self.assertEqual(response.data['detail'], expected_response)

# group belong to organization but users does not exit
test_uri = '{}{}/groups/{}/users'.format(self.base_organizations_uri, organization['id'], groups[0].id)
data = {
'users': '1234,9912,9800'
}
response = self.do_delete(test_uri, data)
self.assertEqual(response.status_code, 204)

# organization group user relationship exists for users
data = {
'users': ','.join([str(user.id) for user in users])
}
response = self.do_delete(test_uri, data)
self.assertEqual(response.status_code, 200)
user_ids = ', '.join([str(user.id) for user in users])
expected_response = "user id(s) {} removed from organization {}'s group {}".format(user_ids,
organization['id'],
groups[0].id)
self.assertEqual(response.data['detail'], expected_response)
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data), 0)
# test users were not removed from organization_two group relation
test_uri = '{}{}/groups/{}/users'.format(self.base_organizations_uri, organization_two['id'], groups[0].id)
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data), len(users))
11 changes: 11 additions & 0 deletions lms/djangoapps/organizations/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
""" Organizations API URI specification """
from django.conf.urls import patterns, url

from organizations import views as organizations_views


urlpatterns = patterns(
'',
url(r'^(?P<organization_id>[0-9]+)/groups/(?P<group_id>[0-9]+)/users$',
organizations_views.OrganizationsGroupsUsersList.as_view()),
)
Loading

0 comments on commit 7bcc2cc

Please sign in to comment.