Commit 07d7754f by chrisndodge

Merge pull request #14 from edx-solutions/api

Api
parents e2a7b2c5 e1e473bb
"""
Courses API URI specification
The order of the URIs really matters here, due to the slash characters present in the identifiers
"""
from django.conf.urls import patterns, url
urlpatterns = patterns('api_manager.courses_views',
url(r'/*$^', 'courses_list'),
url(r'^(?P<course_id>[a-zA-Z0-9/_:]+)/modules/(?P<module_id>[a-zA-Z0-9/_:]+)/submodules/*$', 'modules_list'),
url(r'^(?P<course_id>[a-zA-Z0-9/_:]+)/modules/(?P<module_id>[a-zA-Z0-9/_:]+)$', 'modules_detail'),
url(r'^(?P<course_id>[a-zA-Z0-9/_:]+)/modules/*$', 'modules_list'),
url(r'^(?P<course_id>[a-zA-Z0-9/_:]+)$', 'courses_detail'),
)
""" API implementation for course-oriented interactions. """
from rest_framework import status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response
from api_manager.permissions import ApiKeyHeaderPermission
from xmodule.modulestore.django import modulestore
from xmodule.modulestore import Location, InvalidLocationError
def _get_module_submodules(module, submodule_type=None):
"""
Parses the provided module looking for child modules
Matches on submodule type (category) when specified
"""
submodules = []
if hasattr(module, 'children'):
child_modules = module.get_children()
for child_module in child_modules:
if submodule_type:
if getattr(child_module, 'category') == submodule_type:
submodules.append(child_module)
else:
submodules.append(child_module)
return submodules
def _serialize_module(request, course_id, module):
"""
Loads the specified module data into the response dict
This should probably evolve to use DRF serializers
"""
data = {}
if getattr(module, 'id') == course_id:
module_id = module.id
else:
module_id = module.location.url()
data['id'] = module_id
if hasattr(module, 'display_name'):
data['name'] = module.display_name
data['category'] = module.location.category
protocol = 'http'
if request.is_secure():
protocol = protocol + 's'
module_uri = '{}://{}/api/courses/{}'.format(
protocol,
request.get_host(),
course_id.encode('utf-8')
)
# Some things we do only if the module is a course
if (course_id == module_id):
data['number'] = module.location.course
data['org'] = module.location.org
# Other things we do only if the module is not a course
else:
module_uri = '{}/modules/{}'.format(module_uri, module_id)
data['uri'] = module_uri
return data
def _serialize_module_submodules(request, course_id, submodules):
"""
Loads the specified module submodule data into the response dict
This should probably evolve to use DRF serializers
"""
data = []
if submodules:
for submodule in submodules:
submodule_data = _serialize_module(
request,
course_id,
submodule
)
data.append(submodule_data)
return data
@api_view(['GET'])
@permission_classes((ApiKeyHeaderPermission,))
def modules_list(request, course_id, module_id=None):
"""
GET retrieves the list of submodules for a given module
We don't know where in the module hierarchy we are -- could even be the top
"""
if module_id is None:
module_id = course_id
response_data = []
submodule_type = request.QUERY_PARAMS.get('type', None)
store = modulestore()
if course_id != module_id:
try:
module = store.get_instance(course_id, Location(module_id))
except InvalidLocationError:
module = None
else:
module = store.get_course(course_id)
if module:
submodules = _get_module_submodules(module, submodule_type)
response_data = _serialize_module_submodules(
request,
course_id,
submodules
)
status_code = status.HTTP_200_OK
else:
status_code = status.HTTP_404_NOT_FOUND
return Response(response_data, status=status_code)
@api_view(['GET'])
@permission_classes((ApiKeyHeaderPermission,))
def modules_detail(request, course_id, module_id):
"""
GET retrieves an existing module from the system
"""
store = modulestore()
response_data = {}
submodule_type = request.QUERY_PARAMS.get('type', None)
if course_id != module_id:
try:
module = store.get_instance(course_id, Location(module_id))
except InvalidLocationError:
module = None
else:
module = store.get_course(course_id)
if module:
response_data = _serialize_module(
request,
course_id,
module
)
submodules = _get_module_submodules(module, submodule_type)
response_data['modules'] = _serialize_module_submodules(
request,
course_id,
submodules
)
status_code = status.HTTP_200_OK
else:
status_code = status.HTTP_404_NOT_FOUND
return Response(response_data, status=status_code)
@api_view(['GET'])
@permission_classes((ApiKeyHeaderPermission,))
def courses_list(request):
"""
GET returns the list of available courses
"""
response_data = []
store = modulestore()
course_descriptors = store.get_courses()
for course_descriptor in course_descriptors:
course_data = _serialize_module(
request,
course_descriptor.id,
course_descriptor
)
response_data.append(course_data)
return Response(response_data, status=status.HTTP_200_OK)
@api_view(['GET'])
@permission_classes((ApiKeyHeaderPermission,))
def courses_detail(request, course_id):
"""
GET retrieves an existing course from the system
"""
response_data = {}
store = modulestore()
try:
course_descriptor = store.get_course(course_id)
except ValueError:
course_descriptor = None
if course_descriptor:
response_data = _serialize_module(
request,
course_descriptor.id,
course_descriptor
)
submodules = _get_module_submodules(course_descriptor, None)
response_data['modules'] = _serialize_module_submodules(
request,
course_id,
submodules
)
status_code = status.HTTP_200_OK
else:
status_code = status.HTTP_404_NOT_FOUND
return Response(response_data, status=status_code)
# pylint: disable=E1103
"""
Run these tests @ Devstack:
rake fasttest_lms[common/djangoapps/api_manager/tests/test_group_views.py]
"""
import unittest
import uuid
from django.conf import settings
from django.core.cache import cache
from django.test import TestCase, Client
from django.test.utils import override_settings
from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
TEST_API_KEY = str(uuid.uuid4())
class SecureClient(Client):
""" Django test client using a "secure" connection. """
def __init__(self, *args, **kwargs):
kwargs = kwargs.copy()
kwargs.update({'SERVER_PORT': 443, 'wsgi.url_scheme': 'https'})
super(SecureClient, self).__init__(*args, **kwargs)
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
@override_settings(EDX_API_KEY=TEST_API_KEY)
class CoursesApiTests(TestCase):
""" Test suite for Courses API views """
def setUp(self):
self.test_server_prefix = 'https://testserver'
self.base_courses_uri = '/api/courses'
self.course = CourseFactory.create()
self.test_data = '<html>{}</html>'.format(str(uuid.uuid4()))
self.chapter = ItemFactory.create(
category="chapter",
parent_location=self.course.location,
data=self.test_data,
display_name="Overview"
)
self.module = ItemFactory.create(
category="videosequence",
parent_location=self.chapter.location,
data=self.test_data,
display_name="Video_Sequence"
)
self.submodule = ItemFactory.create(
category="video",
parent_location=self.module.location,
data=self.test_data,
display_name="Video_Resources"
)
self.test_course_id = self.course.id
self.test_course_name = self.course.display_name
self.test_course_number = self.course.number
self.test_course_org = self.course.org
self.test_chapter_id = self.chapter.id
self.test_module_id = self.module.id
self.test_submodule_id = self.submodule.id
self.base_modules_uri = '/api/courses/' + self.test_course_id + '/modules'
self.base_chapters_uri = self.base_modules_uri + '?type=chapter'
self.client = SecureClient()
cache.clear()
def do_get(self, uri):
"""Submit an HTTP GET request"""
headers = {
'Content-Type': 'application/json',
'X-Edx-Api-Key': str(TEST_API_KEY),
}
print "GET: " + uri
response = self.client.get(uri, headers=headers)
return response
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
def test_course_list_get(self):
test_uri = self.base_courses_uri
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200)
self.assertGreater(len(response.data), 0)
matched_course = False
for course in response.data:
if matched_course is False and course['id'] == self.test_course_id:
self.assertEqual(course['name'], self.test_course_name)
self.assertEqual(course['number'], self.test_course_number)
self.assertEqual(course['org'], self.test_course_org)
confirm_uri = self.test_server_prefix + test_uri + '/' + course['id']
self.assertEqual(course['uri'], confirm_uri)
matched_course = True
self.assertTrue(matched_course)
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
def test_course_detail_get(self):
test_uri = self.base_courses_uri + '/' + self.test_course_id
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200)
self.assertGreater(len(response.data), 0)
self.assertEqual(response.data['id'], self.test_course_id)
self.assertEqual(response.data['name'], self.test_course_name)
self.assertEqual(response.data['number'], self.test_course_number)
self.assertEqual(response.data['org'], self.test_course_org)
confirm_uri = self.test_server_prefix + test_uri
self.assertEqual(response.data['uri'], confirm_uri)
self.assertGreater(len(response.data['modules']), 0)
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
def test_course_detail_get_notfound(self):
test_uri = self.base_courses_uri + '/' + 'p29038cvp9hjwefion'
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 404)
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
def test_chapter_list_get(self):
test_uri = self.base_chapters_uri
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200)
self.assertGreater(len(response.data), 0)
matched_chapter = False
for chapter in response.data:
if matched_chapter is False and chapter['id'] == self.test_chapter_id:
self.assertIsNotNone(chapter['uri'])
self.assertGreater(len(chapter['uri']), 0)
confirm_uri = self.test_server_prefix + self.base_modules_uri + '/' + chapter['id']
self.assertEqual(chapter['uri'], confirm_uri)
matched_chapter = True
self.assertTrue(matched_chapter)
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
def test_chapter_detail_get(self):
test_uri = self.base_modules_uri + '/' + self.test_chapter_id
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200)
self.assertGreater(len(response.data['id']), 0)
self.assertEqual(response.data['id'], self.test_chapter_id)
confirm_uri = self.test_server_prefix + test_uri
self.assertEqual(response.data['uri'], confirm_uri)
self.assertGreater(len(response.data['modules']), 0)
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
def test_modules_list_get(self):
test_uri = self.base_modules_uri + '/' + self.test_module_id
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200)
self.assertGreater(len(response.data), 0)
matched_submodule = False
for submodule in response.data['modules']:
if matched_submodule is False and submodule['id'] == self.test_submodule_id:
self.assertIsNotNone(submodule['uri'])
self.assertGreater(len(submodule['uri']), 0)
confirm_uri = self.test_server_prefix + self.base_modules_uri + '/' + submodule['id']
self.assertEqual(submodule['uri'], confirm_uri)
matched_submodule = True
self.assertTrue(matched_submodule)
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
def test_modules_detail_get(self):
test_uri = self.base_modules_uri + '/' + self.test_module_id
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200)
self.assertGreater(len(response.data), 0)
self.assertEqual(response.data['id'], self.test_module_id)
confirm_uri = self.test_server_prefix + test_uri
self.assertEqual(response.data['uri'], confirm_uri)
self.assertGreater(len(response.data['modules']), 0)
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
def test_modules_detail_get_notfound(self):
test_uri = self.base_modules_uri + '/' + '2p38fp2hjfp9283'
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 404)
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
def test_modules_list_get_filtered_submodules_for_module(self):
test_uri = self.base_modules_uri + '/' + self.test_module_id + '/submodules?type=video'
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200)
self.assertGreater(len(response.data), 0)
matched_submodule = False
for submodule in response.data:
if matched_submodule is False and submodule['id'] == self.test_submodule_id:
confirm_uri = self.test_server_prefix + self.base_modules_uri + '/' + submodule['id']
self.assertEqual(submodule['uri'], confirm_uri)
matched_submodule = True
self.assertTrue(matched_submodule)
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
def test_modules_list_get_notfound(self):
test_uri = self.base_modules_uri + '/2p38fp2hjfp9283/submodules?type=video'
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 404)
"""
Courses API URI specification
The order of the URIs really matters here, due to the slash characters present in the identifiers
"""
from django.conf.urls import patterns, url
urlpatterns = patterns(
'api_manager.courses_views',
url(r'/*$^', 'courses_list'),
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/modules/(?P<module_id>[a-zA-Z0-9/_:]+)/submodules/*$', 'modules_list'),
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/modules/(?P<module_id>[a-zA-Z0-9/_:]+)$', 'modules_detail'),
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/modules/*$', 'modules_list'),
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/groups/(?P<group_id>[0-9]+)$', 'courses_groups_detail'),
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/groups/*$', 'courses_groups_list'),
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/overview$', 'course_overview'),
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/updates$', 'course_updates'),
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/static_tabs/(?P<tab_id>[a-zA-Z0-9/_:]+)$', 'static_tab_detail'),
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/static_tabs$', 'static_tabs_list'),
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/users$', 'course_users_list'),
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)$', 'courses_detail'),
)
......@@ -4,6 +4,8 @@ from django.conf.urls import patterns, url
urlpatterns = patterns('api_manager.groups_views',
url(r'/*$^', 'group_list'),
url(r'^(?P<group_id>[0-9]+)$', 'group_detail'),
url(r'^(?P<group_id>[0-9]+)/courses/*$', 'group_courses_list'),
url(r'^(?P<group_id>[0-9]+)/courses/(?P<course_id>[a-zA-Z0-9/_:]+)$', 'group_courses_detail'),
url(r'^(?P<group_id>[0-9]+)/users/*$', 'group_users_list'),
url(r'^(?P<group_id>[0-9]+)/users/(?P<user_id>[0-9]+)$', 'group_users_detail'),
url(r'^(?P<group_id>[0-9]+)/groups/*$', 'group_groups_list'),
......
""" API implementation for gourse-oriented interactions. """
""" API implementation for group-oriented interactions. """
import uuid
import json
from collections import OrderedDict
from django.contrib.auth.models import Group, User
from django.core.exceptions import ObjectDoesNotExist
......@@ -10,7 +12,9 @@ from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response
from api_manager.permissions import ApiKeyHeaderPermission
from api_manager.models import GroupRelationship
from api_manager.models import GroupRelationship, CourseGroupRelationship, GroupProfile
from xmodule.modulestore.django import modulestore
from xmodule.modulestore import Location, InvalidLocationError
RELATIONSHIP_TYPES = {'hierarchical': 'h', 'graph': 'g'}
......@@ -30,46 +34,73 @@ def _generate_base_uri(request):
return resource_uri
@api_view(['POST'])
@api_view(['GET', 'POST'])
@permission_classes((ApiKeyHeaderPermission,))
def group_list(request):
"""
GET retrieves a list of groups in the system filtered by type
POST creates a new group in the system
"""
response_data = {}
base_uri = _generate_base_uri(request)
# Group name must be unique, but we need to support dupes
group = Group.objects.create(name=str(uuid.uuid4()))
original_group_name = request.DATA['name']
group.name = '{:04d}: {}'.format(group.id, original_group_name)
group.record_active = True
group.record_date_created = timezone.now()
group.record_date_modified = timezone.now()
group.save()
# Relationship model also allows us to use duplicate names
GroupRelationship.objects.create(name=original_group_name, group_id=group.id, parent_group=None)
response_data = {'id': group.id, 'name': original_group_name}
base_uri = _generate_base_uri(request)
response_data['uri'] = '{}/{}'.format(base_uri, group.id)
response_status = status.HTTP_201_CREATED
return Response(response_data, status=response_status)
if request.method == 'GET':
if not 'type' in request.GET:
return Response({}, status=status.HTTP_400_BAD_REQUEST)
response_data = []
profiles = GroupProfile.objects.filter(group_type=request.GET['type'])
for profile in profiles:
item_data = OrderedDict()
item_data['group_id'] = profile.group_id
item_data['group_type'] = profile.group_type
item_data['data'] = json.loads(profile.data)
response_data.append(item_data)
return Response(response_data)
elif request.method == 'POST':
response_data = {}
base_uri = _generate_base_uri(request)
# Group name must be unique, but we need to support dupes
group = Group.objects.create(name=str(uuid.uuid4()))
original_group_name = request.DATA['name']
group.name = '{:04d}: {}'.format(group.id, original_group_name)
group.record_active = True
group.record_date_created = timezone.now()
group.record_date_modified = timezone.now()
group.save()
# Relationship model also allows us to use duplicate names
GroupRelationship.objects.create(name=original_group_name, group_id=group.id, parent_group=None)
@api_view(['GET'])
# allow for optional meta information about groups, this will end up in the GroupProfile table
group_type = request.DATA.get('group_type')
data = request.DATA.get('data')
if group_type or data:
profile, _ = GroupProfile.objects.get_or_create(group_id=group.id, group_type=group_type, data=data)
response_data = {'id': group.id, 'name': original_group_name}
base_uri = _generate_base_uri(request)
response_data['uri'] = '{}/{}'.format(base_uri, group.id)
response_status = status.HTTP_201_CREATED
return Response(response_data, status=response_status)
@api_view(['GET', 'POST'])
@permission_classes((ApiKeyHeaderPermission,))
def group_detail(request, group_id):
"""
GET retrieves an existing group from the system
"""
response_data = {}
base_uri = _generate_base_uri(request)
try:
existing_group = Group.objects.get(id=group_id)
existing_group_relationship = GroupRelationship.objects.get(group_id=group_id)
except ObjectDoesNotExist:
existing_group = None
existing_group_relationship = None
if existing_group and existing_group_relationship:
return Response({}, status.HTTP_404_NOT_FOUND)
if request.method == 'GET':
response_data['name'] = existing_group_relationship.name
response_data['id'] = existing_group.id
response_data['uri'] = base_uri
......@@ -78,10 +109,36 @@ def group_detail(request, group_id):
response_data['resources'].append({'uri': resource_uri})
resource_uri = '{}/groups'.format(base_uri)
response_data['resources'].append({'uri': resource_uri})
# see if there is an (optional) GroupProfile
try:
existing_group_profile = GroupProfile.objects.get(group_id=group_id)
if existing_group_profile.group_type:
response_data['group_type'] = existing_group_profile.group_type
data = existing_group_profile.data
if data:
response_data['data'] = json.loads(data)
except ObjectDoesNotExist:
pass
response_status = status.HTTP_200_OK
else:
response_status = status.HTTP_404_NOT_FOUND
return Response(response_data, status=response_status)
return Response(response_data, status=response_status)
elif request.method == 'POST':
# update GroupProfile data
group_type = request.DATA.get('group_type')
data = request.DATA.get('data')
if not group_type and not data:
return Response({}, status.HTTP_400_BAD_REQUEST)
profile, _ = GroupProfile.objects.get_or_create(group_id=group_id)
profile.group_type = group_type
profile.data = data
profile.save()
return Response({})
@api_view(['POST'])
......@@ -269,3 +326,77 @@ def group_groups_detail(request, group_id, related_group_id):
else:
response_status = status.HTTP_404_NOT_FOUND
return Response({}, status=response_status)
@api_view(['POST'])
@permission_classes((ApiKeyHeaderPermission,))
def group_courses_list(request, group_id):
"""
POST creates a new group-course relationship in the system
"""
response_data = {}
group_id = group_id
course_id = request.DATA['course_id']
base_uri = _generate_base_uri(request)
response_data['uri'] = '{}/{}'.format(base_uri, course_id)
store = modulestore()
try:
existing_group = Group.objects.get(id=group_id)
except ObjectDoesNotExist:
existing_group = None
try:
existing_course = store.get_course(course_id)
except ValueError:
existing_course = None
if existing_group and existing_course:
try:
existing_relationship = CourseGroupRelationship.objects.get(course_id=course_id, group=existing_group)
except ObjectDoesNotExist:
existing_relationship = None
if existing_relationship is None:
new_relationship = CourseGroupRelationship.objects.create(course_id=course_id, group=existing_group)
response_data['group_id'] = str(new_relationship.group_id)
response_data['course_id'] = str(new_relationship.course_id)
response_status = status.HTTP_201_CREATED
else:
response_data['message'] = "Relationship already exists."
response_status = status.HTTP_409_CONFLICT
else:
response_status = status.HTTP_404_NOT_FOUND
return Response(response_data, status=response_status)
@api_view(['GET', 'DELETE'])
@permission_classes((ApiKeyHeaderPermission,))
def group_courses_detail(request, group_id, course_id):
"""
GET retrieves an existing group-course relationship from the system
DELETE removes/inactivates/etc. an existing group-course relationship
"""
if request.method == 'GET':
response_data = {}
base_uri = _generate_base_uri(request)
response_data['uri'] = base_uri
try:
existing_group = Group.objects.get(id=group_id)
existing_relationship = CourseGroupRelationship.objects.get(course_id=course_id, group=existing_group)
except ObjectDoesNotExist:
existing_group = None
existing_relationship = None
if existing_group and existing_relationship:
response_data['group_id'] = existing_group.id
response_data['course_id'] = existing_relationship.course_id
response_status = status.HTTP_200_OK
else:
response_status = status.HTTP_404_NOT_FOUND
return Response(response_data, status=response_status)
elif request.method == 'DELETE':
try:
existing_group = Group.objects.get(id=group_id)
existing_group.coursegrouprelationship_set.get(course_id=course_id).delete()
existing_group.save()
except ObjectDoesNotExist:
pass
return Response({}, status=status.HTTP_204_NO_CONTENT)
# -*- 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 'CourseGroupRelationship'
db.create_table('api_manager_coursegrouprelationship', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('course_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['auth.Group'])),
))
db.send_create_signal('api_manager', ['CourseGroupRelationship'])
# Adding model 'GroupProfile'
db.create_table('auth_groupprofile', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('group', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.Group'])),
('group_type', self.gf('django.db.models.fields.CharField')(max_length=32, null=True, db_index=True)),
('data', self.gf('django.db.models.fields.TextField')(blank=True)),
))
db.send_create_signal('api_manager', ['GroupProfile'])
def backwards(self, orm):
# Deleting model 'CourseGroupRelationship'
db.delete_table('api_manager_coursegrouprelationship')
# Deleting model 'GroupProfile'
db.delete_table('auth_groupprofile')
models = {
'api_manager.coursegrouprelationship': {
'Meta': {'object_name': 'CourseGroupRelationship'},
'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'api_manager.groupprofile': {
'Meta': {'object_name': 'GroupProfile', 'db_table': "'auth_groupprofile'"},
'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'})
},
'api_manager.grouprelationship': {
'Meta': {'object_name': 'GroupRelationship'},
'group': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.Group']", 'unique': 'True', 'primary_key': 'True'}),
'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'}),
'record_date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2014, 4, 21, 0, 0)'}),
'record_date_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
},
'api_manager.linkedgrouprelationship': {
'Meta': {'object_name': 'LinkedGroupRelationship'},
'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'}),
'record_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'record_date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2014, 4, 21, 0, 0)'}),
'record_date_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': '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
......@@ -82,3 +82,29 @@ class LinkedGroupRelationship(models.Model):
record_active = models.BooleanField(default=True)
record_date_created = models.DateTimeField(default=timezone.now())
record_date_modified = models.DateTimeField(auto_now=True)
class CourseGroupRelationship(models.Model):
"""
The CourseGroupRelationship model contains information describing the
link between a course and a group. A typical use case for this table
is to manage the courses for an XSeries or other sort of program.
"""
course_id = models.CharField(max_length=255, db_index=True)
group = models.ForeignKey(Group, db_index=True)
class GroupProfile(models.Model):
"""
This table will provide additional tables regarding groups. This has a foreign key to
the auth_groups table
"""
class Meta:
db_table = "auth_groupprofile"
group = models.ForeignKey(Group, db_index=True)
group_type = models.CharField(null=True, max_length=32, db_index=True)
data = models.TextField(blank=True) # JSON dictionary for generic key/value pairs
......@@ -39,10 +39,10 @@ class ApiKeyHeaderPermission(permissions.BasePermission):
if header_key is None:
try:
header_key = request.META['headers'].get('X-Edx-Api-Key')
if header_key is None:
return False
except KeyError:
return False
if header_key is None:
return False
# The api key values need to be the same
if header_key != api_key:
......
"""
Some test content strings. Best to keep them out of the test files because they take up a lot of
text space
"""
from textwrap import dedent
TEST_COURSE_UPDATES_CONTENT = dedent("""
<ol>
<li>
<h2>April 18, 2014</h2>
This does not have a paragraph tag around it
</li>
<li>
<h2>April 17, 2014</h2>
Some text before paragraph tag<p>This is inside paragraph tag</p>Some text after tag
</li>
<li>
<h2>April 16, 2014</h2>
Some text before paragraph tag<p>This is inside paragraph tag</p>Some text after tag<p>one more</p>
</li>
<li>
<h2>April 15, 2014</h2>
<p>A perfectly</p><p>formatted piece</p><p>of HTML</p>
</li>
</ol>
"""
)
TEST_STATIC_TAB1_CONTENT = dedent("""
<div>This is static tab1</div>
"""
)
TEST_STATIC_TAB2_CONTENT = dedent("""
<div>This is static tab2</div>
"""
)
TEST_COURSE_OVERVIEW_CONTENT = dedent("""
<section class="about">
<h2>About This Course</h2>
<p>Include your long course description here. The long course description should contain 150-400 words.</p>
<p>This is paragraph 2 of the long course description. Add more paragraphs as needed. Make sure to enclose them in paragraph tags.</p>
</section>
<section class="prerequisites">
<h2>Prerequisites</h2>
<p>Add information about course prerequisites here.</p>
</section>
<section class="course-staff">
<h2>Course Staff</h2>
<article class="teacher">
<div class="teacher-image">
<img src="/images/pl-faculty.png" align="left" style="margin:0 20 px 0" alt="Course Staff Image #1">
</div>
<h3>Staff Member #1</h3>
<p>Biography of instructor/staff member #1</p>
</article>
<article class="teacher">
<div class="teacher-image">
<img src="/images/pl-faculty.png" align="left" style="margin:0 20 px 0" alt="Course Staff Image #2">
</div>
<h3>Staff Member #2</h3>
<p>Biography of instructor/staff member #2</p>
</article>
</section>
<section class="faq">
<p>Some text here</p>
</section>
""")
......@@ -51,9 +51,6 @@ class UsersApiTests(TestCase):
'X-Edx-Api-Key': str(TEST_API_KEY),
}
json_data = json.dumps(data)
print "POST: " + uri
print json_data
print ""
response = self.client.post(uri, headers=headers, content_type='application/json', data=json_data)
return response
......
......@@ -105,6 +105,9 @@ def user_list(request):
user.last_name = last_name
user.save()
# CDODGE: @TODO: We will have to extend this to look in the CourseEnrollmentAllowed table and
# auto-enroll students when they create a new account. Also be sure to remove from
# the CourseEnrollmentAllow table after the auto-registration has taken place
if user:
status_code = status.HTTP_201_CREATED
response_data = _serialize_user(response_data, user)
......
......@@ -773,7 +773,7 @@ def notification_image_for_tab(course_tab, user, course):
return None
def get_static_tab_contents(request, course, tab):
def get_static_tab_contents(request, course, tab, wrap_xmodule_display=True):
"""
Returns the contents for the given static tab
"""
......@@ -788,7 +788,7 @@ def get_static_tab_contents(request, course, tab):
course.id, request.user, modulestore().get_instance(course.id, loc), depth=0
)
tab_module = get_module(
request.user, request, loc, field_data_cache, course.id, static_asset_path=course.static_asset_path
request.user, request, loc, field_data_cache, course.id, static_asset_path=course.static_asset_path, wrap_xmodule_display=wrap_xmodule_display
)
logging.debug('course_module = {0}'.format(tab_module))
......
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