Commit 812b0be5 by Chris Dodge

get course updates API method

parent cd121cf4
......@@ -13,5 +13,6 @@ urlpatterns = patterns(
url(r'^(?P<course_id>[a-zA-Z0-9/_:]+)/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>[a-zA-Z0-9/_:]+)/overview$', 'course_overview'),
url(r'^(?P<course_id>[a-zA-Z0-9/_:]+)/updates$', 'course_updates'),
url(r'^(?P<course_id>[a-zA-Z0-9/_:]+)$', 'courses_detail'),
)
......@@ -5,6 +5,7 @@ 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.decorators import api_view, permission_classes
......@@ -15,7 +16,9 @@ from api_manager.models import CourseGroupRelationship
from xmodule.modulestore.django import modulestore
from xmodule.modulestore import Location, InvalidLocationError
from courseware.courses import get_course_about_section
from courseware.courses import get_course_about_section, get_course_info_section
log = logging.getLogger(__name__)
def _generate_base_uri(request):
"""
......@@ -310,7 +313,9 @@ def _inner_content(tag):
"""
inner_content = None
if tag is not None:
inner_content = u''.join(etree.tostring(e) for e in tag)
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
......@@ -386,15 +391,91 @@ def course_overview(request, course_id):
if not course_module:
return Response({}, status=status.HTTP_404_NOT_FOUND)
overview = get_course_about_section(course_module, 'overview')
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['sections'] = _parse_overview_html(overview)
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['overview_html'] = overview
response_data['content'] = content
except InvalidLocationError:
return Response({}, status=status.HTTP_404_NOT_FOUND)
......
"""
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_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>
""")
......@@ -11,8 +11,6 @@ import unittest
>>>>>>> initial implementation
import uuid
from textwrap import dedent
from django.core.cache import cache
from django.test import TestCase, Client
from django.test.utils import override_settings
......@@ -20,48 +18,10 @@ 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
from .content import TEST_COURSE_OVERVIEW_CONTENT, TEST_COURSE_UPDATES_CONTENT
TEST_API_KEY = str(uuid.uuid4())
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>
""")
class SecureClient(Client):
""" Django test client using a "secure" connection. """
......@@ -114,6 +74,13 @@ class CoursesApiTests(TestCase):
display_name="overview"
)
self.updates = ItemFactory.create(
category="course_info",
parent_location=self.course.location,
data=TEST_COURSE_UPDATES_CONTENT,
display_name="updates"
)
self.test_course_id = self.course.id
self.test_course_name = self.course.display_name
self.test_course_number = self.course.number
......@@ -398,3 +365,28 @@ class CoursesApiTests(TestCase):
self.assertGreater(len(prerequisites['body']), 0)
faq = self._find_item_by_class(sections, 'faq')
self.assertGreater(len(faq['body']), 0)
def test_get_course_updates(self):
# first try raw without any parsing
test_uri = self.base_courses_uri + '/' + self.test_course_id + '/updates'
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200)
self.assertGreater(len(response.data), 0)
self.assertEqual(response.data['content'], self.updates.data)
# then try parsed
test_uri = self.base_courses_uri + '/' + self.test_course_id + '/updates?parse=True'
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200)
self.assertGreater(len(response.data), 0)
postings = response.data['postings']
self.assertEqual(len(postings), 4)
self.assertEqual(postings[0]['date'], 'April 18, 2014')
self.assertEqual(postings[0]['content'], 'This does not have a paragraph tag around it')
self.assertEqual(postings[1]['date'], 'April 17, 2014')
self.assertEqual(postings[1]['content'], 'Some text before paragraph tag<p>This is inside paragraph tag</p>Some text after tag')
self.assertEqual(postings[2]['date'], 'April 16, 2014')
self.assertEqual(postings[2]['content'], 'Some text before paragraph tag<p>This is inside paragraph tag</p>Some text after tag<p>one more</p>')
self.assertEqual(postings[3]['date'], 'April 15, 2014')
self.assertEqual(postings[3]['content'], '<p>A perfectly</p><p>formatted piece</p><p>of HTML</p>')
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