Commit f59612ce by chrisndodge

Merge pull request #11 from edx-solutions/cdodge/sprint-H-api-work

Cdodge/sprint h api work
parents 7c581d06 49d491d0
...@@ -7,10 +7,14 @@ from django.conf.urls import patterns, url ...@@ -7,10 +7,14 @@ from django.conf.urls import patterns, url
urlpatterns = patterns( urlpatterns = patterns(
'api_manager.courses_views', 'api_manager.courses_views',
url(r'/*$^', 'courses_list'), 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>[^/]+/[^/]+/[^/]+)/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>[^/]+/[^/]+/[^/]+)/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>[^/]+/[^/]+/[^/]+)/modules/*$', 'modules_list'),
url(r'^(?P<course_id>[a-zA-Z0-9/_:]+)/groups/(?P<group_id>[0-9]+)$', 'courses_groups_detail'), url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/groups/(?P<group_id>[0-9]+)$', 'courses_groups_detail'),
url(r'^(?P<course_id>[a-zA-Z0-9/_:]+)/groups/*$', 'courses_groups_list'), url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/groups/*$', 'courses_groups_list'),
url(r'^(?P<course_id>[a-zA-Z0-9/_:]+)$', 'courses_detail'), 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>[^/]+/[^/]+/[^/]+)$', 'courses_detail'),
) )
...@@ -2,6 +2,10 @@ ...@@ -2,6 +2,10 @@
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from lxml import etree
from StringIO import StringIO
from collections import OrderedDict
import logging
from rest_framework import status from rest_framework import status
from rest_framework.decorators import api_view, permission_classes from rest_framework.decorators import api_view, permission_classes
...@@ -12,6 +16,10 @@ from api_manager.models import CourseGroupRelationship ...@@ -12,6 +16,10 @@ from api_manager.models import CourseGroupRelationship
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore import Location, InvalidLocationError from xmodule.modulestore import Location, InvalidLocationError
from courseware.courses import get_course_about_section, get_course_info_section
from courseware.views import get_static_tab_contents
log = logging.getLogger(__name__)
def _generate_base_uri(request): def _generate_base_uri(request):
""" """
...@@ -295,3 +303,249 @@ def courses_groups_detail(request, course_id, group_id): ...@@ -295,3 +303,249 @@ def courses_groups_detail(request, course_id, group_id):
except ObjectDoesNotExist: except ObjectDoesNotExist:
pass pass
return Response({}, status=status.HTTP_204_NO_CONTENT) return Response({}, status=status.HTTP_204_NO_CONTENT)
def _inner_content(tag):
"""
Helper method
"""
inner_content = None
if tag is not None:
inner_content = tag.text if tag.text else u''
inner_content += u''.join(etree.tostring(e) for e in tag)
inner_content += tag.tail if tag.tail else u''
return inner_content
def _parse_overview_html(html):
"""
Helper method to break up the course about HTML into components
"""
result = {}
parser = etree.HTMLParser()
tree = etree.parse(StringIO(html), parser)
sections = tree.findall('/body/section')
result = []
for section in sections:
section_class = section.get('class')
if section_class:
section_data = OrderedDict()
section_data['class'] = section_class
articles = section.findall('article')
if articles:
section_data['articles'] = []
for article in articles:
article_class = article.get('class')
if article_class:
article_data = OrderedDict()
article_data['class'] = article_class
if article_class == "teacher":
name_element = article.find('h3')
if name_element is not None:
article_data['name'] = name_element.text
image_element = article.find("./div[@class='teacher-image']/img")
if image_element is not None:
article_data['image_src'] = image_element.get('src')
bios = article.findall('p')
bio_html = ''
for bio in bios:
bio_html += etree.tostring(bio)
if bio_html:
article_data['bio'] = bio_html
else:
article_data['body'] = _inner_content(article)
section_data['articles'].append(article_data)
else:
section_data['body'] = _inner_content(section)
result.append(section_data)
return result
@api_view(['GET'])
@permission_classes((ApiKeyHeaderPermission,))
def course_overview(request, course_id):
"""
GET retrieves the course overview module, which - in MongoDB - is stored with the following
naming convention {"_id.org":"i4x", "_id.course":<course_num>, "_id.category":"about", "_id.name":"overview"}
"""
store = modulestore()
response_data = OrderedDict()
try:
course_module = store.get_course(course_id)
if not course_module:
return Response({}, status=status.HTTP_404_NOT_FOUND)
content = get_course_about_section(course_module, 'overview')
if request.GET.get('parse') and request.GET.get('parse') in ['True', 'true']:
try:
response_data['sections'] = _parse_overview_html(content)
except:
log.exception(
u"Error prasing course overview. Content = {0}".format(
content
))
return Response({'err': 'could_not_parse'}, status=status.HTTP_409_CONFLICT)
else:
response_data['overview_html'] = content
except InvalidLocationError:
return Response({}, status=status.HTTP_404_NOT_FOUND)
return Response(response_data)
def _parse_updates_html(html):
"""
Helper method to break up the course updates HTML into components
"""
result = {}
parser = etree.HTMLParser()
tree = etree.parse(StringIO(html), parser)
# get all of the individual postings
postings = tree.findall('/body/ol/li')
result = []
for posting in postings:
posting_data = {}
posting_date_element = posting.find('h2')
if posting_date_element is not None:
posting_data['date'] = posting_date_element.text
content = u''
for el in posting:
# note, we can't delete or skip over the date element in
# the HTML tree because there might be some tailing content
if el != posting_date_element:
content += etree.tostring(el)
else:
content += el.tail if el.tail else u''
posting_data['content'] = content.strip()
result.append(posting_data)
return result
@api_view(['GET'])
@permission_classes((ApiKeyHeaderPermission,))
def course_updates(request, course_id):
"""
GET retrieves the course overview module, which - in MongoDB - is stored with the following
naming convention {"_id.org":"i4x", "_id.course":<course_num>, "_id.category":"course_info", "_id.name":"updates"}
"""
store = modulestore()
response_data = OrderedDict()
try:
course_module = store.get_course(course_id)
if not course_module:
return Response({}, status=status.HTTP_404_NOT_FOUND)
content = get_course_info_section(request, course_module, 'updates')
if not content:
return Response({}, status=status.HTTP_404_NOT_FOUND)
if request.GET.get('parse') and request.GET.get('parse') in ['True', 'true']:
try:
response_data['postings'] = _parse_updates_html(content)
except:
log.exception(
u"Error prasing course updates. Content = {0}".format(
content
))
return Response({'err': 'could_not_parse'}, status=status.HTTP_409_CONFLICT)
else:
response_data['content'] = content
except InvalidLocationError:
return Response({}, status=status.HTTP_404_NOT_FOUND)
return Response(response_data)
@api_view(['GET'])
@permission_classes((ApiKeyHeaderPermission,))
def static_tabs_list(request, course_id):
"""
GET returns an array of Static Tabs inside of a course
"""
store = modulestore()
response_data = OrderedDict()
try:
course_module = store.get_course(course_id)
if not course_module:
return Response({}, status=status.HTTP_404_NOT_FOUND)
tabs = []
for tab in course_module.tabs:
if tab.type == 'static_tab':
tab_data = OrderedDict()
tab_data['id'] = tab.url_slug
tab_data['name'] = tab.name
if request.GET.get('detail') and request.GET.get('detail') in ['True', 'true']:
tab_data['content'] = get_static_tab_contents(request,
course_module,
tab,
wrap_xmodule_display=False
)
tabs.append(tab_data)
response_data['tabs'] = tabs
except InvalidLocationError:
return Response({}, status=status.HTTP_404_NOT_FOUND)
return Response(response_data)
@api_view(['GET'])
@permission_classes((ApiKeyHeaderPermission,))
def static_tab_detail(request, course_id, tab_id):
"""
GET returns an array of Static Tabs inside of a course
"""
store = modulestore()
response_data = OrderedDict()
try:
course_module = store.get_course(course_id)
if not course_module:
return Response({}, status=status.HTTP_404_NOT_FOUND)
for tab in course_module.tabs:
if tab.type == 'static_tab' and tab.url_slug == tab_id:
response_data['id'] = tab.url_slug
response_data['name'] = tab.name
response_data['content'] = get_static_tab_contents(request,
course_module,
tab,
wrap_xmodule_display=False
)
except InvalidLocationError:
return Response({}, status=status.HTTP_404_NOT_FOUND)
if not response_data:
return Response({}, status=status.HTTP_404_NOT_FOUND)
return Response(response_data)
...@@ -285,7 +285,6 @@ def group_courses_list(request, group_id): ...@@ -285,7 +285,6 @@ def group_courses_list(request, group_id):
base_uri = _generate_base_uri(request) base_uri = _generate_base_uri(request)
response_data['uri'] = '{}/{}'.format(base_uri, course_id) response_data['uri'] = '{}/{}'.format(base_uri, course_id)
store = modulestore() store = modulestore()
print "GROUP COURSES LIST"
try: try:
existing_group = Group.objects.get(id=group_id) existing_group = Group.objects.get(id=group_id)
except ObjectDoesNotExist: except ObjectDoesNotExist:
...@@ -294,17 +293,15 @@ def group_courses_list(request, group_id): ...@@ -294,17 +293,15 @@ def group_courses_list(request, group_id):
existing_course = store.get_course(course_id) existing_course = store.get_course(course_id)
except ValueError: except ValueError:
existing_course = None existing_course = None
print existing_group
print existing_course
if existing_group and existing_course: if existing_group and existing_course:
try: try:
existing_relationship = CourseGroupRelationship.objects.get(course_id=course_id, group=existing_group) existing_relationship = CourseGroupRelationship.objects.get(course_id=course_id, group=existing_group)
except ObjectDoesNotExist: except ObjectDoesNotExist:
existing_relationship = None existing_relationship = None
print existing_relationship
if existing_relationship is None: if existing_relationship is None:
new_relationship = CourseGroupRelationship.objects.create(course_id=course_id, group=existing_group) new_relationship = CourseGroupRelationship.objects.create(course_id=course_id, group=existing_group)
print new_relationship.__dict__
response_data['group_id'] = str(new_relationship.group_id) response_data['group_id'] = str(new_relationship.group_id)
response_data['course_id'] = str(new_relationship.course_id) response_data['course_id'] = str(new_relationship.course_id)
response_status = status.HTTP_201_CREATED response_status = status.HTTP_201_CREATED
...@@ -312,7 +309,6 @@ def group_courses_list(request, group_id): ...@@ -312,7 +309,6 @@ def group_courses_list(request, group_id):
response_data['message'] = "Relationship already exists." response_data['message'] = "Relationship already exists."
response_status = status.HTTP_409_CONFLICT response_status = status.HTTP_409_CONFLICT
else: else:
print request.DATA
response_status = status.HTTP_404_NOT_FOUND response_status = status.HTTP_404_NOT_FOUND
return Response(response_data, status=response_status) return Response(response_data, status=response_status)
......
"""
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>
""")
...@@ -564,7 +564,6 @@ class GroupsApiTests(TestCase): ...@@ -564,7 +564,6 @@ class GroupsApiTests(TestCase):
response = self.do_post(test_uri, data) response = self.do_post(test_uri, data)
self.assertEqual(response.status_code, 201) self.assertEqual(response.status_code, 201)
test_uri = '{}/{}/courses/{}'.format(self.base_groups_uri, group_id, self.test_course_id) test_uri = '{}/{}/courses/{}'.format(self.base_groups_uri, group_id, self.test_course_id)
print test_uri
response = self.do_get(test_uri) response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
confirm_uri = '{}{}/{}/courses/{}'.format( confirm_uri = '{}{}/{}/courses/{}'.format(
......
...@@ -51,9 +51,6 @@ class UsersApiTests(TestCase): ...@@ -51,9 +51,6 @@ class UsersApiTests(TestCase):
'X-Edx-Api-Key': str(TEST_API_KEY), 'X-Edx-Api-Key': str(TEST_API_KEY),
} }
json_data = json.dumps(data) 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) response = self.client.post(uri, headers=headers, content_type='application/json', data=json_data)
return response return response
......
...@@ -773,7 +773,7 @@ def notification_image_for_tab(course_tab, user, course): ...@@ -773,7 +773,7 @@ def notification_image_for_tab(course_tab, user, course):
return None 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 Returns the contents for the given static tab
""" """
...@@ -788,7 +788,7 @@ def get_static_tab_contents(request, course, 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 course.id, request.user, modulestore().get_instance(course.id, loc), depth=0
) )
tab_module = get_module( 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)) 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