Move models from common to cms.
Add unit tests.
This commit is contained in:
0
cms/djangoapps/__init__.py
Normal file
0
cms/djangoapps/__init__.py
Normal file
194
cms/djangoapps/contentstore/tests/test_course_settings.py
Normal file
194
cms/djangoapps/contentstore/tests/test_course_settings.py
Normal file
@@ -0,0 +1,194 @@
|
||||
from django.test.testcases import TestCase
|
||||
import datetime
|
||||
import time
|
||||
from django.contrib.auth.models import User
|
||||
import xmodule
|
||||
from django.test.client import Client
|
||||
from django.core.urlresolvers import reverse
|
||||
from xmodule.modulestore import Location
|
||||
from cms.djangoapps.models.settings.course_details import CourseDetails,\
|
||||
CourseDetailsEncoder
|
||||
import json
|
||||
from common.djangoapps.util import converters
|
||||
|
||||
# YYYY-MM-DDThh:mm:ss.s+/-HH:MM
|
||||
class ConvertersTestCase(TestCase):
|
||||
def struct_to_datetime(self, struct_time):
|
||||
return datetime.datetime(struct_time.tm_year, struct_time.tm_mon, struct_time.tm_mday, struct_time.tm_hour, struct_time.tm_min, struct_time.tm_sec)
|
||||
|
||||
def compare_dates(self, date1, date2, expected_delta):
|
||||
dt1 = self.struct_to_datetime(date1)
|
||||
dt2 = self.struct_to_datetime(date2)
|
||||
self.assertEqual(dt1 - dt2, expected_delta, str(date1) + "-" + str(date2) + "!=" + str(expected_delta))
|
||||
|
||||
def test_iso_to_struct(self):
|
||||
self.compare_dates(converters.jsdate_to_time("2013-01-01"), converters.jsdate_to_time("2012-12-31"), datetime.timedelta(days=1))
|
||||
self.compare_dates(converters.jsdate_to_time("2013-01-01T00"), converters.jsdate_to_time("2012-12-31T23"), datetime.timedelta(hours=1))
|
||||
self.compare_dates(converters.jsdate_to_time("2013-01-01T00:00"), converters.jsdate_to_time("2012-12-31T23:59"), datetime.timedelta(minutes=1))
|
||||
self.compare_dates(converters.jsdate_to_time("2013-01-01T00:00:00"), converters.jsdate_to_time("2012-12-31T23:59:59"), datetime.timedelta(seconds=1))
|
||||
|
||||
class CourseDetailsTestCase(TestCase):
|
||||
def setUp(self):
|
||||
uname = 'testuser'
|
||||
email = 'test+courses@edx.org'
|
||||
password = 'foo'
|
||||
|
||||
# Create the use so we can log them in.
|
||||
self.user = User.objects.create_user(uname, email, password)
|
||||
|
||||
# Note that we do not actually need to do anything
|
||||
# for registration if we directly mark them active.
|
||||
self.user.is_active = True
|
||||
# Staff has access to view all courses
|
||||
self.user.is_staff = True
|
||||
self.user.save()
|
||||
|
||||
# Flush and initialize the module store
|
||||
# It needs the templates because it creates new records
|
||||
# by cloning from the template.
|
||||
# Note that if your test module gets in some weird state
|
||||
# (though it shouldn't), do this manually
|
||||
# from the bash shell to drop it:
|
||||
# $ mongo test_xmodule --eval "db.dropDatabase()"
|
||||
xmodule.modulestore.django._MODULESTORES = {}
|
||||
xmodule.modulestore.django.modulestore().collection.drop()
|
||||
xmodule.templates.update_templates()
|
||||
|
||||
self.client = Client()
|
||||
self.client.login(username=uname, password=password)
|
||||
|
||||
self.course_data = {
|
||||
'template': 'i4x://edx/templates/course/Empty',
|
||||
'org': 'MITx',
|
||||
'number': '999',
|
||||
'display_name': 'Robot Super Course',
|
||||
}
|
||||
self.course_location = Location('i4x', 'MITx', '999', 'course', 'Robot_Super_Course')
|
||||
self.create_course()
|
||||
|
||||
def tearDown(self):
|
||||
xmodule.modulestore.django._MODULESTORES = {}
|
||||
xmodule.modulestore.django.modulestore().collection.drop()
|
||||
|
||||
def create_course(self):
|
||||
"""Create new course"""
|
||||
self.client.post(reverse('create_new_course'), self.course_data)
|
||||
|
||||
def test_virgin_fetch(self):
|
||||
details = CourseDetails.fetch(self.course_location)
|
||||
self.assertEqual(details.course_location, self.course_location, "Location not copied into")
|
||||
self.assertIsNone(details.end_date, "end date somehow initialized " + str(details.end_date))
|
||||
self.assertIsNone(details.enrollment_start, "enrollment_start date somehow initialized " + str(details.enrollment_start))
|
||||
self.assertIsNone(details.enrollment_end, "enrollment_end date somehow initialized " + str(details.enrollment_end))
|
||||
self.assertIsNone(details.syllabus, "syllabus somehow initialized" + str(details.syllabus))
|
||||
self.assertEqual(details.overview, "", "overview somehow initialized" + details.overview)
|
||||
self.assertIsNone(details.intro_video, "intro_video somehow initialized" + str(details.intro_video))
|
||||
self.assertIsNone(details.effort, "effort somehow initialized" + str(details.effort))
|
||||
|
||||
def test_encoder(self):
|
||||
details = CourseDetails.fetch(self.course_location)
|
||||
jsondetails = json.dumps(details, cls=CourseDetailsEncoder)
|
||||
jsondetails = json.loads(jsondetails)
|
||||
self.assertTupleEqual(Location(jsondetails['course_location']), self.course_location, "Location !=")
|
||||
# Note, start_date is being initialized someplace. I'm not sure why b/c the default will make no sense.
|
||||
self.assertIsNone(jsondetails['end_date'], "end date somehow initialized ")
|
||||
self.assertIsNone(jsondetails['enrollment_start'], "enrollment_start date somehow initialized ")
|
||||
self.assertIsNone(jsondetails['enrollment_end'], "enrollment_end date somehow initialized ")
|
||||
self.assertIsNone(jsondetails['syllabus'], "syllabus somehow initialized")
|
||||
self.assertEqual(jsondetails['overview'], "", "overview somehow initialized")
|
||||
self.assertIsNone(jsondetails['intro_video'], "intro_video somehow initialized")
|
||||
self.assertIsNone(jsondetails['effort'], "effort somehow initialized")
|
||||
|
||||
def test_update_and_fetch(self):
|
||||
## NOTE: I couldn't figure out how to validly test time setting w/ all the conversions
|
||||
jsondetails = CourseDetails.fetch(self.course_location)
|
||||
jsondetails.syllabus = "<a href='foo'>bar</a>"
|
||||
self.assertEqual(CourseDetails.update_from_json(jsondetails.__dict__).syllabus,
|
||||
jsondetails.syllabus, "After set syllabus")
|
||||
jsondetails.overview = "Overview"
|
||||
self.assertEqual(CourseDetails.update_from_json(jsondetails.__dict__).overview,
|
||||
jsondetails.overview, "After set overview")
|
||||
jsondetails.intro_video = "intro_video"
|
||||
self.assertEqual(CourseDetails.update_from_json(jsondetails.__dict__).intro_video,
|
||||
jsondetails.intro_video, "After set intro_video")
|
||||
jsondetails.effort = "effort"
|
||||
self.assertEqual(CourseDetails.update_from_json(jsondetails.__dict__).effort,
|
||||
jsondetails.effort, "After set effort")
|
||||
|
||||
class CourseDetailsViewTest(TestCase):
|
||||
def setUp(self):
|
||||
uname = 'testuser'
|
||||
email = 'test+courses@edx.org'
|
||||
password = 'foo'
|
||||
|
||||
# Create the use so we can log them in.
|
||||
self.user = User.objects.create_user(uname, email, password)
|
||||
|
||||
# Note that we do not actually need to do anything
|
||||
# for registration if we directly mark them active.
|
||||
self.user.is_active = True
|
||||
# Staff has access to view all courses
|
||||
self.user.is_staff = True
|
||||
self.user.save()
|
||||
|
||||
# Flush and initialize the module store
|
||||
# It needs the templates because it creates new records
|
||||
# by cloning from the template.
|
||||
# Note that if your test module gets in some weird state
|
||||
# (though it shouldn't), do this manually
|
||||
# from the bash shell to drop it:
|
||||
# $ mongo test_xmodule --eval "db.dropDatabase()"
|
||||
xmodule.modulestore.django._MODULESTORES = {}
|
||||
xmodule.modulestore.django.modulestore().collection.drop()
|
||||
xmodule.templates.update_templates()
|
||||
|
||||
self.client = Client()
|
||||
self.client.login(username=uname, password=password)
|
||||
|
||||
self.course_data = {
|
||||
'template': 'i4x://edx/templates/course/Empty',
|
||||
'org': 'MITx',
|
||||
'number': '999',
|
||||
'display_name': 'Robot Super Course',
|
||||
}
|
||||
self.course_location = Location('i4x', 'MITx', '999', 'course', 'Robot_Super_Course')
|
||||
self.create_course()
|
||||
|
||||
def tearDown(self):
|
||||
xmodule.modulestore.django._MODULESTORES = {}
|
||||
xmodule.modulestore.django.modulestore().collection.drop()
|
||||
|
||||
def create_course(self):
|
||||
"""Create new course"""
|
||||
self.client.post(reverse('create_new_course'), self.course_data)
|
||||
|
||||
def alter_field(self, url, details, field, val):
|
||||
details[field] = val
|
||||
jsondetails = json.dumps(details, cls=CourseDetailsEncoder)
|
||||
resp = self.client.post(url, jsondetails)
|
||||
self.assertDictEqual(json.loads(resp), details, field + val)
|
||||
|
||||
def test_update_and_fetch(self):
|
||||
details = CourseDetails.fetch(self.course_location)
|
||||
details_loc = self.course_location.dict().copy()
|
||||
details_loc['section'] = 'details'
|
||||
|
||||
resp = self.client.get(reverse('contentstore.views.get_course_settings', kwargs=self.course_location.dict()))
|
||||
self.assertContains(resp, '<li><a href="#" class="is-shown" data-section="details">Course Details</a></li>', status_code=200, html=True)
|
||||
|
||||
# resp s/b json from here on
|
||||
url = reverse('contentstore.views.course_settings_updates', kwargs=details_loc)
|
||||
resp = self.client.get(url)
|
||||
jsondetails = json.dumps(details, cls=CourseDetailsEncoder)
|
||||
self.assertDictEqual(resp, jsondetails, "virgin get")
|
||||
|
||||
self.alter_field(url, details, 'start_date', time.time() * 1000)
|
||||
self.alter_field(url, details, 'start_date', time.time() * 1000 + 60 * 60 * 24)
|
||||
self.alter_field(url, details, 'end_date', time.time() * 1000 + 60 * 60 * 24 * 100)
|
||||
self.alter_field(url, details, 'enrollment_start', time.time() * 1000)
|
||||
|
||||
self.alter_field(url, details, 'enrollment_end', time.time() * 1000 + 60 * 60 * 24 * 8)
|
||||
self.alter_field(url, details, 'syllabus', "<a href='foo'>bar</a>")
|
||||
self.alter_field(url, details, 'overview', "Overview")
|
||||
self.alter_field(url, details, 'intro_video', "intro_video")
|
||||
self.alter_field(url, details, 'effort', "effort")
|
||||
@@ -955,8 +955,7 @@ def get_course_settings(request, org, course, name):
|
||||
return render_to_response('settings.html', {
|
||||
'active_tab': 'settings-tab',
|
||||
'context_course': course_module,
|
||||
'course_details' : json.dumps(course_details, cls=CourseDetailsEncoder),
|
||||
'video_editor_html' : preview_component(request, course_details.intro_video_loc)
|
||||
'course_details' : json.dumps(course_details, cls=CourseDetailsEncoder)
|
||||
})
|
||||
|
||||
@expect_json
|
||||
|
||||
0
cms/djangoapps/models/__init__.py
Normal file
0
cms/djangoapps/models/__init__.py
Normal file
@@ -17,7 +17,6 @@ class CourseDetails:
|
||||
self.syllabus = None # a pdf file asset
|
||||
self.overview = "" # html to render as the overview
|
||||
self.intro_video = None # a video pointer
|
||||
self.intro_video_loc = None # a video pointer
|
||||
self.effort = None # int hours/week
|
||||
|
||||
@classmethod
|
||||
@@ -58,7 +57,6 @@ class CourseDetails:
|
||||
temploc = temploc._replace(name='video')
|
||||
try:
|
||||
course.intro_video = get_modulestore(temploc).get_item(temploc).definition['data']
|
||||
course.intro_video_loc = temploc
|
||||
except ItemNotFoundError:
|
||||
pass
|
||||
|
||||
|
||||
22
cms/djangoapps/models/settings/course_faculty.py
Normal file
22
cms/djangoapps/models/settings/course_faculty.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from xmodule.modulestore import Location
|
||||
class CourseFaculty:
|
||||
def __init__(self, location):
|
||||
if not isinstance(location, Location):
|
||||
location = Location(location)
|
||||
# course_location is used so that updates know where to get the relevant data
|
||||
self.course_location = location
|
||||
self.first_name = ""
|
||||
self.last_name = ""
|
||||
self.photo = None
|
||||
self.bio = ""
|
||||
|
||||
|
||||
@classmethod
|
||||
def fetch(cls, course_location):
|
||||
"""
|
||||
Fetch a list of faculty for the course
|
||||
"""
|
||||
if not isinstance(course_location, Location):
|
||||
course_location = Location(course_location)
|
||||
|
||||
# Must always have at least one faculty member (possibly empty)
|
||||
@@ -10,12 +10,12 @@ def time_to_date(time_obj):
|
||||
|
||||
def jsdate_to_time(field):
|
||||
"""
|
||||
Convert a true universal time (msec since epoch) from a string to a time obj
|
||||
Convert a universal time (iso format) or msec since epoch to a time obj
|
||||
"""
|
||||
if field is None:
|
||||
return field
|
||||
elif isinstance(field, unicode): # iso format but ignores time zone assuming it's Z
|
||||
d=datetime.datetime(*map(int, re.split('[^\d]', field)[:-1]))
|
||||
elif isinstance(field, unicode) or isinstance(field, str): # iso format but ignores time zone assuming it's Z
|
||||
d=datetime.datetime(*map(int, re.split('[^\d]', field)[:6])) # stop after seconds. Debatable
|
||||
return d.utctimetuple()
|
||||
elif isinstance(field, int):
|
||||
elif isinstance(field, int) or isinstance(field, float):
|
||||
return time.gmtime(field / 1000)
|
||||
Reference in New Issue
Block a user