Move models from common to cms.

Add unit tests.
This commit is contained in:
Don Mitchell
2012-12-03 12:20:15 -05:00
parent 8379e3038d
commit 50d7e6160e
7 changed files with 221 additions and 8 deletions

View File

View 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")

View File

@@ -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

View File

View 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

View 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)

View File

@@ -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)