Commit 03517b26 by Matt Drayer Committed by Jonathan Piacenti

mattdrayer/api-modulesgroups: API - Added grouping capability for courseware content

parent b4f9b84d
......@@ -12,9 +12,11 @@ urlpatterns = patterns(
'',
url(r'/*$^', courses_views.CoursesList.as_view()),
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)$', courses_views.CoursesDetail.as_view()),
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/modules/(?P<module_id>[a-zA-Z0-9/_:]+)/submodules/*$', courses_views.ModulesList.as_view()),
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/modules/(?P<module_id>[a-zA-Z0-9/_:]+)$', courses_views.ModulesDetail.as_view()),
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/modules/*$', courses_views.ModulesList.as_view()),
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/content/(?P<content_id>[a-zA-Z0-9/_:]+)/groups/(?P<group_id>[0-9]+)$', courses_views.CourseContentGroupsDetail.as_view()),
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/content/(?P<content_id>[a-zA-Z0-9/_:]+)/groups/*$', courses_views.CourseContentGroupsList.as_view()),
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/content/(?P<content_id>[a-zA-Z0-9/_:]+)/children/*$', courses_views.CourseContentList.as_view()),
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/content/(?P<content_id>[a-zA-Z0-9/_:]+)$', courses_views.CourseContentDetail.as_view()),
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/content/*$', courses_views.CourseContentList.as_view()),
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/groups/(?P<group_id>[0-9]+)$', courses_views.CoursesGroupsDetail.as_view()),
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/groups/*$', courses_views.CoursesGroupsList.as_view()),
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/overview$', courses_views.CoursesOverview.as_view()),
......
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'CourseContentGroupRelationship'
db.create_table('api_manager_coursecontentgrouprelationship', (
('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)),
('course_id', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)),
('content_id', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)),
('group', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['api_manager.GroupProfile'])),
('record_active', self.gf('django.db.models.fields.BooleanField')(default=True)),
))
db.send_create_signal('api_manager', ['CourseContentGroupRelationship'])
db.create_index('api_manager_coursecontentgrouprelationship', ['course_id', 'content_id'], unique=False, db_tablespace='')
def backwards(self, orm):
# Deleting model 'CourseContentGroupRelationship'
db.delete_table('api_manager_coursecontentgrouprelationship')
models = {
'api_manager.coursegrouprelationship': {
'Meta': {'object_name': 'CourseGroupRelationship'},
'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'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'}),
'record_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'api_manager.groupprofile': {
'Meta': {'object_name': 'GroupProfile', 'db_table': "'auth_groupprofile'"},
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
'data': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']"}),
'group_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'db_index': 'True'}),
'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'}),
'record_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'api_manager.grouprelationship': {
'Meta': {'object_name': 'GroupRelationship'},
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
'group': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.Group']", 'unique': 'True', 'primary_key': 'True'}),
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'parent_group': ('django.db.models.fields.related.ForeignKey', [], {'default': '0', 'related_name': "'child_groups'", 'null': 'True', 'blank': 'True', 'to': "orm['api_manager.GroupRelationship']"}),
'record_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'api_manager.linkedgrouprelationship': {
'Meta': {'object_name': 'LinkedGroupRelationship'},
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
'from_group_relationship': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'from_group_relationships'", 'to': "orm['api_manager.GroupRelationship']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
'record_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'to_group_relationship': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'to_group_relationships'", 'to': "orm['api_manager.GroupRelationship']"})
},
'api_manager.coursecontentgrouprelationship': {
'Meta': {'object_name': 'CourseContentGroupRelationship'},
'content_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['api_manager.GroupProfile']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
'record_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'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'})
},
'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'})
}
}
complete_apps = ['api_manager']
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding unique constraint on 'CourseContentGroupRelationship', fields ['course_id', 'content_id', 'group']
db.create_unique('api_manager_coursecontentgrouprelationship', ['course_id', 'content_id', 'group_id'])
def backwards(self, orm):
# Removing unique constraint on 'CourseContentGroupRelationship', fields ['course_id', 'content_id', 'group']
db.delete_unique('api_manager_coursecontentgrouprelationship', ['course_id', 'content_id', 'group_id'])
models = {
'api_manager.coursecontentgrouprelationship': {
'Meta': {'unique_together': "(('course_id', 'content_id', 'group'),)", 'object_name': 'CourseContentGroupRelationship'},
'content_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['api_manager.GroupProfile']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
'record_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'api_manager.coursegrouprelationship': {
'Meta': {'object_name': 'CourseGroupRelationship'},
'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'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'}),
'record_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'api_manager.groupprofile': {
'Meta': {'object_name': 'GroupProfile', 'db_table': "'auth_groupprofile'"},
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
'data': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']"}),
'group_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'db_index': 'True'}),
'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'}),
'record_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'api_manager.grouprelationship': {
'Meta': {'object_name': 'GroupRelationship'},
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
'group': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.Group']", 'unique': 'True', 'primary_key': 'True'}),
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'parent_group': ('django.db.models.fields.related.ForeignKey', [], {'default': '0', 'related_name': "'child_groups'", 'null': 'True', 'blank': 'True', 'to': "orm['api_manager.GroupRelationship']"}),
'record_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'api_manager.linkedgrouprelationship': {
'Meta': {'object_name': 'LinkedGroupRelationship'},
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
'from_group_relationship': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'from_group_relationships'", 'to': "orm['api_manager.GroupRelationship']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
'record_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'to_group_relationship': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'to_group_relationships'", 'to': "orm['api_manager.GroupRelationship']"})
},
'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'})
},
'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'})
}
}
complete_apps = ['api_manager']
\ No newline at end of file
......@@ -106,3 +106,20 @@ class GroupProfile(TimeStampedModel):
name = models.CharField(max_length=255, null=True, blank=True)
data = models.TextField(blank=True) # JSON dictionary for generic key/value pairs
record_active = models.BooleanField(default=True)
class CourseContentGroupRelationship(TimeStampedModel):
"""
The CourseContentGroupRelationship model contains information describing the
link between a particular courseware element (chapter, unit, video, etc.)
and a group. A typical use case for this table is to support the concept
of a student workgroup for a given course, where the project is actually
a Chapter courseware element.
"""
course_id = models.CharField(max_length=255, db_index=True)
content_id = models.CharField(max_length=255, db_index=True)
group = models.ForeignKey(GroupProfile, db_index=True)
record_active = models.BooleanField(default=True)
class Meta:
unique_together = ("course_id", "content_id", "group")
......@@ -239,7 +239,7 @@ class UsersApiTests(TestCase):
def test_user_groups_list_post(self):
test_uri = '/api/groups'
data = {'name': 'Alpha Group'}
data = {'name': 'Alpha Group', 'type': 'test'}
response = self.do_post(test_uri, data)
group_id = response.data['id']
test_uri = '/api/users'
......@@ -262,7 +262,7 @@ class UsersApiTests(TestCase):
def test_user_groups_list_post_duplicate(self):
test_uri = '/api/groups'
data = {'name': 'Alpha Group'}
data = {'name': 'Alpha Group', 'type': 'test'}
response = self.do_post(test_uri, data)
group_id = response.data['id']
test_uri = '/api/users'
......@@ -281,7 +281,7 @@ class UsersApiTests(TestCase):
def test_user_groups_list_post_invalid_user(self):
test_uri = '/api/groups'
data = {'name': 'Alpha Group'}
data = {'name': 'Alpha Group', 'type': 'test'}
response = self.do_post(test_uri, data)
group_id = response.data['id']
test_uri = '/api/users/897698769/groups'
......@@ -292,7 +292,7 @@ class UsersApiTests(TestCase):
def test_user_groups_list_get(self):
test_uri = '/api/groups'
group_name = 'Alpha Group'
data = {'name': group_name}
data = {'name': group_name, 'type': 'test'}
response = self.do_post(test_uri, data)
group_id = response.data['id']
test_uri = '/api/users'
......@@ -327,7 +327,7 @@ class UsersApiTests(TestCase):
group_url = '/api/groups'
group_name = 'Alpha Group'
data = {'name': group_name, 'group_type': 'Engineer'}
data = {'name': group_name, 'type': 'Engineer'}
response = self.do_post(group_url, data)
group_id = response.data['id']
user_groups_uri = '{}/groups'.format(test_uri)
......@@ -336,7 +336,7 @@ class UsersApiTests(TestCase):
self.assertEqual(response.status_code, 201)
group_name = 'Beta Group'
data = {'name': group_name, 'group_type': 'Architect'}
data = {'name': group_name, 'type': 'Architect'}
response = self.do_post(group_url, data)
group_id = response.data['id']
data = {'group_id': group_id}
......@@ -367,7 +367,7 @@ class UsersApiTests(TestCase):
def test_user_groups_detail_get(self):
test_uri = '/api/groups'
data = {'name': 'Alpha Group'}
data = {'name': 'Alpha Group', 'type': 'test'}
response = self.do_post(test_uri, data)
group_id = response.data['id']
test_uri = '/api/users'
......@@ -390,7 +390,7 @@ class UsersApiTests(TestCase):
def test_user_groups_detail_delete(self):
test_uri = '/api/groups'
data = {'name': 'Alpha Group'}
data = {'name': 'Alpha Group', 'type': 'test'}
response = self.do_post(test_uri, data)
group_id = response.data['id']
test_uri = '/api/users'
......@@ -419,7 +419,7 @@ class UsersApiTests(TestCase):
def test_user_groups_detail_get_undefined(self):
test_uri = '/api/groups'
data = {'name': 'Alpha Group'}
data = {'name': 'Alpha Group', 'type': 'test'}
response = self.do_post(test_uri, data)
group_id = response.data['id']
test_uri = '/api/users'
......@@ -529,8 +529,8 @@ class UsersApiTests(TestCase):
self.assertEqual(response.status_code, 201)
position_data = {
'position': {
'parent_module_id': str(course.id),
'child_module_id': str(chapter3.location)
'parent_content_id': str(course.id),
'child_content_id': str(chapter3.location)
}
}
......@@ -551,15 +551,15 @@ class UsersApiTests(TestCase):
test_uri = '/api/users/{}/courses/{}'.format(str(user_id), course_id)
position_data = {
'position': {
'parent_module_id': course_id,
'child_module_id': str(chapter1.location)
'parent_content_id': course_id,
'child_content_id': str(chapter1.location)
}
}
response = self.do_post(test_uri, data=position_data)
self.assertEqual(response.status_code, 404)
def test_user_courses_detail_post_position_course_as_module(self):
def test_user_courses_detail_post_position_course_as_content(self):
course = CourseFactory.create()
test_data = '<html>{}</html>'.format(str(uuid.uuid4()))
chapter1 = ItemFactory.create(
......@@ -581,8 +581,8 @@ class UsersApiTests(TestCase):
self.assertEqual(response.status_code, 201)
position_data = {
'position': {
'parent_module_id': str(course.location),
'child_module_id': str(chapter1.location)
'parent_content_id': str(course.location),
'child_content_id': str(chapter1.location)
}
}
......@@ -617,8 +617,8 @@ class UsersApiTests(TestCase):
self.assertEqual(response.data['user_id'], user_id)
position_data = {
'position': {
'parent_module_id': str(course.location),
'child_module_id': str(chapter1.location)
'parent_content_id': str(course.location),
'child_content_id': str(chapter1.location)
}
}
......
......@@ -31,6 +31,7 @@ from util.bad_request_rate_limiter import BadRequestRateLimiter
log = logging.getLogger(__name__)
AUDIT_LOG = logging.getLogger("audit")
def _generate_base_uri(request):
"""
Constructs the protocol:host:path component of the resource uri
......@@ -57,6 +58,7 @@ def _serialize_user_profile(response_data, user_profile):
return response_data
def _serialize_user(response_data, user):
"""
Loads the object data into the response dict
......@@ -70,14 +72,15 @@ def _serialize_user(response_data, user):
response_data['is_active'] = user.is_active
return response_data
def _save_module_position(request, user, course_id, course_descriptor, position):
def _save_content_position(request, user, course_id, course_descriptor, position):
"""
Records the indicated position for the specified course
Really no reason to generalize this out of user_courses_detail aside from pylint complaining
"""
field_data_cache = FieldDataCache([course_descriptor], course_id, user)
if course_id == position['parent_module_id']:
parent_module = get_module_for_descriptor(
if course_id == position['parent_content_id']:
parent_content = get_module_for_descriptor(
user,
request,
course_descriptor,
......@@ -85,23 +88,23 @@ def _save_module_position(request, user, course_id, course_descriptor, position)
course_id
)
else:
parent_module = module_render.get_module(
parent_content = module_render.get_module(
user,
request,
position['parent_module_id'],
position['parent_content_id'],
field_data_cache,
course_id
)
child_module = module_render.get_module(
child_content = module_render.get_module(
user,
request,
position['child_module_id'],
position['child_content_id'],
field_data_cache,
course_id
)
save_child_position(parent_module, child_module.location.name)
saved_module = get_current_child(parent_module)
return saved_module.id
save_child_position(parent_content, child_content.location.name)
saved_content = get_current_child(parent_content)
return saved_content.id
class UsersList(APIView):
......@@ -539,7 +542,7 @@ class UsersCoursesDetail(APIView):
response_data['course_id'] = course_id
response_status = status.HTTP_201_CREATED
if request.DATA['position']:
response_data['position'] = _save_module_position(
response_data['position'] = _save_content_position(
request,
user,
course_id,
......@@ -568,13 +571,13 @@ class UsersCoursesDetail(APIView):
response_data['course_id'] = course_id
response_data['uri'] = base_uri
field_data_cache = FieldDataCache([course_descriptor], course_id, user)
course_module = module_render.get_module(
course_content = module_render.get_module(
user,
request,
course_descriptor.location,
field_data_cache,
course_id)
response_data['position'] = course_module.position
response_data['position'] = course_content.position
response_status = status.HTTP_200_OK
else:
response_status = status.HTTP_404_NOT_FOUND
......
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