Convert due_date_display_format tests to use factories
Previously, these tests modified due_date, which failed due to the new restrictions on writing to Scope.settings from the LMS.
This commit is contained in:
@@ -7,7 +7,6 @@ from pytz import UTC
|
||||
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.x_module import XModuleDescriptor
|
||||
from xmodule.course_module import CourseDescriptor
|
||||
|
||||
|
||||
class Dummy(object):
|
||||
@@ -124,16 +123,28 @@ class ItemFactory(XModuleFactory):
|
||||
:target_class: is ignored
|
||||
"""
|
||||
|
||||
# All class attributes (from this class and base classes) are
|
||||
# passed in via **kwargs. However, some of those aren't actual field values,
|
||||
# so pop those off for use separately
|
||||
|
||||
DETACHED_CATEGORIES = ['about', 'static_tab', 'course_info']
|
||||
# catch any old style users before they get into trouble
|
||||
assert not 'template' in kwargs
|
||||
data = kwargs.get('data')
|
||||
category = kwargs.get('category')
|
||||
display_name = kwargs.get('display_name')
|
||||
metadata = kwargs.get('metadata', {})
|
||||
location = kwargs.get('location')
|
||||
if kwargs.get('boilerplate') is not None:
|
||||
template_id = kwargs.get('boilerplate')
|
||||
assert 'template' not in kwargs
|
||||
parent_location = Location(kwargs.pop('parent_location', None))
|
||||
data = kwargs.pop('data', None)
|
||||
category = kwargs.pop('category', None)
|
||||
display_name = kwargs.pop('display_name', None)
|
||||
metadata = kwargs.pop('metadata', {})
|
||||
location = kwargs.pop('location')
|
||||
assert location != parent_location
|
||||
|
||||
store = kwargs.pop('modulestore')
|
||||
|
||||
# This code was based off that in cms/djangoapps/contentstore/views.py
|
||||
parent = kwargs.pop('parent', None) or store.get_item(parent_location)
|
||||
|
||||
if 'boilerplate' in kwargs:
|
||||
template_id = kwargs.pop('boilerplate')
|
||||
clz = XModuleDescriptor.load_class(category)
|
||||
template = clz.get_template(template_id)
|
||||
assert template is not None
|
||||
@@ -141,21 +152,20 @@ class ItemFactory(XModuleFactory):
|
||||
if not isinstance(data, basestring):
|
||||
data.update(template.get('data'))
|
||||
|
||||
store = kwargs.get('modulestore')
|
||||
|
||||
# replace the display name with an optional parameter passed in from the caller
|
||||
if display_name is not None:
|
||||
metadata['display_name'] = display_name
|
||||
store.create_and_save_xmodule(location, metadata=metadata, definition_data=data)
|
||||
module = store.create_and_save_xmodule(location, metadata=metadata, definition_data=data)
|
||||
|
||||
module = store.get_item(location)
|
||||
|
||||
for attr, val in kwargs.items():
|
||||
setattr(module, attr, val)
|
||||
module.save()
|
||||
|
||||
store.save_xmodule(module)
|
||||
|
||||
if location.category not in DETACHED_CATEGORIES:
|
||||
|
||||
parent_location = Location(kwargs.get('parent_location'))
|
||||
assert location != parent_location
|
||||
|
||||
# This code was based off that in cms/djangoapps/contentstore/views.py
|
||||
parent = kwargs.get('parent') or store.get_item(parent_location)
|
||||
|
||||
parent.children.append(location.url())
|
||||
store.update_children(parent_location, parent.children)
|
||||
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
<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="/static/images/pl-faculty.png" align="left" style="margin:0 20 px 0">
|
||||
</div>
|
||||
|
||||
<h3>Staff Member #1</h3>
|
||||
<p>Biography of instructor/staff member #1</p>
|
||||
</article>
|
||||
|
||||
<article class="teacher">
|
||||
<div class="teacher-image">
|
||||
<img src="/static/images/pl-faculty.png" align="left" style="margin:0 20 px 0">
|
||||
</div>
|
||||
|
||||
<h3>Staff Member #2</h3>
|
||||
<p>Biography of instructor/staff member #2</p>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="faq">
|
||||
<section class="responses">
|
||||
<h2>Frequently Asked Questions</h2>
|
||||
<article class="response">
|
||||
<h3>Do I need to buy a textbook?</h3>
|
||||
<p>No, a free online version of Chemistry: Principles, Patterns, and Applications, First Edition by Bruce Averill and Patricia Eldredge will be available, though you can purchase a printed version (published by FlatWorld Knowledge) if you’d like.</p>
|
||||
</article>
|
||||
|
||||
<article class="response">
|
||||
<h3>Question #2</h3>
|
||||
<p>Your answer would be displayed here.</p>
|
||||
</article>
|
||||
</section>
|
||||
</section>
|
||||
@@ -1,3 +0,0 @@
|
||||
<chapter display_name="Section">
|
||||
<sequential url_name="c804fa32227142a1bd9d5bc183d4a20d"/>
|
||||
</chapter>
|
||||
@@ -1 +0,0 @@
|
||||
<course url_name="2013_fall" org="edX" course="due_date"/>
|
||||
@@ -1,3 +0,0 @@
|
||||
<course display_name="due_date">
|
||||
<chapter url_name="c8ee0db7e5a84c85bac80b7013cf6512"/>
|
||||
</course>
|
||||
@@ -1 +0,0 @@
|
||||
{"GRADER": [{"short_label": "HW", "min_count": 12, "type": "Homework", "drop_count": 2, "weight": 0.15}, {"min_count": 12, "type": "Lab", "drop_count": 2, "weight": 0.15}, {"short_label": "Midterm", "min_count": 1, "type": "Midterm Exam", "drop_count": 0, "weight": 0.3}, {"short_label": "Final", "min_count": 1, "type": "Final Exam", "drop_count": 0, "weight": 0.4}], "GRADE_CUTOFFS": {"Pass": 0.5}}
|
||||
@@ -1 +0,0 @@
|
||||
{"course/2013_fall": {"tabs": [{"type": "courseware"}, {"type": "course_info", "name": "Course Info"}, {"type": "textbooks"}, {"type": "discussion", "name": "Discussion"}, {"type": "wiki", "name": "Wiki"}, {"type": "progress", "name": "Progress"}], "display_name": "due_date", "discussion_topics": {"General": {"id": "i4x-edX-due_date-course-2013_fall"}}, "show_timezone": "false"}}
|
||||
@@ -1,23 +0,0 @@
|
||||
<problem display_name="Multiple Choice" markdown="A multiple choice problem presents radio buttons for student input. Students can only select a single option presented. Multiple Choice questions have been the subject of many areas of research due to the early invention and adoption of bubble sheets. One of the main elements that goes into a good multiple choice question is the existence of good distractors. That is, each of the alternate responses presented to the student should be the result of a plausible mistake that a student might make. What Apple device competed with the portable CD player? ( ) The iPad ( ) Napster (x) The iPod ( ) The vegetable peeler [explanation] The release of the iPod allowed consumers to carry their entire music library with them in a format that did not rely on fragile and energy-intensive spinning disks. [explanation] ">
|
||||
<p>
|
||||
A multiple choice problem presents radio buttons for student
|
||||
input. Students can only select a single option presented. Multiple Choice questions have been the subject of many areas of research due to the early invention and adoption of bubble sheets.</p>
|
||||
<p> One of the main elements that goes into a good multiple choice question is the existence of good distractors. That is, each of the alternate responses presented to the student should be the result of a plausible mistake that a student might make.
|
||||
</p>
|
||||
|
||||
<p>What Apple device competed with the portable CD player?</p>
|
||||
<multiplechoiceresponse>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false" name="ipad">The iPad</choice>
|
||||
<choice correct="false" name="beatles">Napster</choice>
|
||||
<choice correct="true" name="ipod">The iPod</choice>
|
||||
<choice correct="false" name="peeler">The vegetable peeler</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>The release of the iPod allowed consumers to carry their entire music library with them in a format that did not rely on fragile and energy-intensive spinning disks. </p>
|
||||
</div>
|
||||
</solution>
|
||||
</problem>
|
||||
@@ -1,3 +0,0 @@
|
||||
<sequential display_name="Subsection" due="2013-09-18T11:30:00Z" start="1970-01-01T00:00:00Z">
|
||||
<vertical url_name="45640305a210424ebcc6f8e045fad0be"/>
|
||||
</sequential>
|
||||
@@ -1,3 +0,0 @@
|
||||
<vertical display_name="New Unit">
|
||||
<problem url_name="d392c80f5c044e45a4a5f2d62f94efc5"/>
|
||||
</vertical>
|
||||
@@ -1,9 +1,10 @@
|
||||
"""
|
||||
Tests courseware views.py
|
||||
"""
|
||||
from mock import MagicMock, patch
|
||||
import datetime
|
||||
import unittest
|
||||
from mock import MagicMock, patch
|
||||
from datetime import datetime
|
||||
from pytz import UTC
|
||||
|
||||
from django.test import TestCase
|
||||
from django.http import Http404
|
||||
@@ -18,12 +19,13 @@ from student.models import CourseEnrollment
|
||||
from student.tests.factories import AdminFactory
|
||||
from mitxmako.middleware import MakoMiddleware
|
||||
|
||||
from xmodule.modulestore.django import modulestore, clear_existing_modulestores
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from student.tests.factories import UserFactory
|
||||
|
||||
import courseware.views as views
|
||||
from xmodule.modulestore import Location
|
||||
from pytz import UTC
|
||||
from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
|
||||
from course_modes.models import CourseMode
|
||||
import shoppingcart
|
||||
@@ -73,7 +75,7 @@ class ViewsTestCase(TestCase):
|
||||
def setUp(self):
|
||||
self.user = User.objects.create(username='dummy', password='123456',
|
||||
email='test@mit.edu')
|
||||
self.date = datetime.datetime(2013, 1, 22, tzinfo=UTC)
|
||||
self.date = datetime(2013, 1, 22, tzinfo=UTC)
|
||||
self.course_id = 'edX/toy/2012_Fall'
|
||||
self.enrollment = CourseEnrollment.enroll(self.user, self.course_id)
|
||||
self.enrollment.created = self.date
|
||||
@@ -109,7 +111,6 @@ class ViewsTestCase(TestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn(in_cart_span, response.content)
|
||||
|
||||
|
||||
def test_user_groups(self):
|
||||
# depreciated function
|
||||
mock_user = MagicMock()
|
||||
@@ -254,98 +255,115 @@ class ViewsTestCase(TestCase):
|
||||
response = self.client.get(url)
|
||||
self.assertFalse('<script>' in response.content)
|
||||
|
||||
def test_accordion_due_date(self):
|
||||
|
||||
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
|
||||
class BaseDueDateTests(ModuleStoreTestCase):
|
||||
"""
|
||||
Base class that verifies that due dates are rendered correctly on a page
|
||||
"""
|
||||
__test__ = False
|
||||
|
||||
def get_text(self, course): # pylint: disable=unused-argument
|
||||
"""Return the rendered text for the page to be verified"""
|
||||
raise NotImplementedError
|
||||
|
||||
def set_up_course(self, **course_kwargs):
|
||||
"""
|
||||
Tests the formatting of due dates in the accordion view.
|
||||
Create a stock course with a specific due date.
|
||||
|
||||
:param course_kwargs: All kwargs are passed to through to the :class:`CourseFactory`
|
||||
"""
|
||||
def get_accordion():
|
||||
""" Returns the HTML for the accordion """
|
||||
return views.render_accordion(
|
||||
request, modulestore().get_course("edX/due_date/2013_fall"),
|
||||
"c804fa32227142a1bd9d5bc183d4a20d", None, None
|
||||
)
|
||||
course = CourseFactory(**course_kwargs)
|
||||
chapter = ItemFactory(category='chapter', parent_location=course.location) # pylint: disable=no-member
|
||||
section = ItemFactory(category='sequential', parent_location=chapter.location, due=datetime(2013, 9, 18, 11, 30, 00))
|
||||
vertical = ItemFactory(category='vertical', parent_location=section.location)
|
||||
ItemFactory(category='problem', parent_location=vertical.location)
|
||||
|
||||
request = self.request_factory.get("foo")
|
||||
self.verify_due_date(request, get_accordion)
|
||||
course = modulestore().get_instance(course.id, course.location) # pylint: disable=no-member
|
||||
self.assertIsNotNone(course.get_children()[0].get_children()[0].due)
|
||||
return course
|
||||
|
||||
def test_progress_due_date(self):
|
||||
"""
|
||||
Tests the formatting of due dates in the progress page.
|
||||
"""
|
||||
def get_progress():
|
||||
""" Returns the HTML for the progress page """
|
||||
return views.progress(request, "edX/due_date/2013_fall", self.user.id).content
|
||||
def setUp(self):
|
||||
self.request_factory = RequestFactory()
|
||||
self.user = UserFactory.create()
|
||||
self.request = self.request_factory.get("foo")
|
||||
self.request.user = self.user
|
||||
|
||||
request = self.request_factory.get("foo")
|
||||
self.verify_due_date(request, get_progress)
|
||||
|
||||
def verify_due_date(self, request, get_text):
|
||||
"""
|
||||
Verifies that due dates are formatted properly in text returned by get_text function.
|
||||
"""
|
||||
def set_show_timezone(show_timezone):
|
||||
"""
|
||||
Sets the show_timezone property and returns value from get_text function.
|
||||
|
||||
Note that show_timezone is deprecated and cannot be set by the user.
|
||||
"""
|
||||
course.show_timezone = show_timezone
|
||||
course.save()
|
||||
return get_text()
|
||||
|
||||
def set_due_date_format(due_date_format):
|
||||
"""
|
||||
Sets the due_date_display_format property and returns value from get_text function.
|
||||
"""
|
||||
course.due_date_display_format = due_date_format
|
||||
course.save()
|
||||
return get_text()
|
||||
|
||||
request.user = self.user
|
||||
# Clear out the modulestores, so we start with the test course in its default state.
|
||||
clear_existing_modulestores()
|
||||
course = modulestore().get_course("edX/due_date/2013_fall")
|
||||
|
||||
time_with_utc = "due Sep 18, 2013 at 11:30 UTC"
|
||||
time_without_utc = "due Sep 18, 2013 at 11:30"
|
||||
self.time_with_utc = "due Sep 18, 2013 at 11:30 UTC"
|
||||
self.time_without_utc = "due Sep 18, 2013 at 11:30"
|
||||
|
||||
def test_backwards_compatability(self):
|
||||
# The test course being used has show_timezone = False in the policy file
|
||||
# (and no due_date_display_format set). This is to test our backwards compatibility--
|
||||
# in course_module's init method, the date_display_format will be set accordingly to
|
||||
# remove the timezone.
|
||||
text = get_text()
|
||||
self.assertIn(time_without_utc, text)
|
||||
self.assertNotIn(time_with_utc, text)
|
||||
course = self.set_up_course(due_date_display_format=None, show_timezone=False)
|
||||
text = self.get_text(course)
|
||||
self.assertIn(self.time_without_utc, text)
|
||||
self.assertNotIn(self.time_with_utc, text)
|
||||
# Test that show_timezone has been cleared (which means you get the default value of True).
|
||||
self.assertTrue(course.show_timezone)
|
||||
|
||||
# Clear out the due date format and verify you get the default (with timezone).
|
||||
delattr(course, 'due_date_display_format')
|
||||
course.save()
|
||||
text = get_text()
|
||||
self.assertIn(time_with_utc, text)
|
||||
def test_defaults(self):
|
||||
course = self.set_up_course()
|
||||
text = self.get_text(course)
|
||||
self.assertIn(self.time_with_utc, text)
|
||||
|
||||
def test_format_none(self):
|
||||
# Same for setting the due date to None
|
||||
text = set_due_date_format(None)
|
||||
self.assertIn(time_with_utc, text)
|
||||
course = self.set_up_course(due_date_display_format=None)
|
||||
text = self.get_text(course)
|
||||
self.assertIn(self.time_with_utc, text)
|
||||
|
||||
def test_format_plain_text(self):
|
||||
# plain text due date
|
||||
text = set_due_date_format("foobar")
|
||||
self.assertNotIn(time_with_utc, text)
|
||||
course = self.set_up_course(due_date_display_format="foobar")
|
||||
text = self.get_text(course)
|
||||
self.assertNotIn(self.time_with_utc, text)
|
||||
self.assertIn("due foobar", text)
|
||||
|
||||
def test_format_date(self):
|
||||
|
||||
# due date with no time
|
||||
text = set_due_date_format(u"%b %d %y")
|
||||
self.assertNotIn(time_with_utc, text)
|
||||
course = self.set_up_course(due_date_display_format=u"%b %d %y")
|
||||
text = self.get_text(course)
|
||||
self.assertNotIn(self.time_with_utc, text)
|
||||
self.assertIn("due Sep 18 13", text)
|
||||
|
||||
def test_format_hidden(self):
|
||||
# hide due date completely
|
||||
text = set_due_date_format(u"")
|
||||
course = self.set_up_course(due_date_display_format=u"")
|
||||
text = self.get_text(course)
|
||||
self.assertNotIn("due ", text)
|
||||
|
||||
def test_format_invalid(self):
|
||||
# improperly formatted due_date_display_format falls through to default
|
||||
# (value of show_timezone does not matter-- setting to False to make that clear).
|
||||
set_show_timezone(False)
|
||||
text = set_due_date_format(u"%%%")
|
||||
course = self.set_up_course(due_date_display_format=u"%%%", show_timezone=False)
|
||||
text = self.get_text(course)
|
||||
self.assertNotIn("%%%", text)
|
||||
self.assertIn(time_with_utc, text)
|
||||
self.assertIn(self.time_with_utc, text)
|
||||
|
||||
|
||||
class TestProgressDueDate(BaseDueDateTests):
|
||||
"""
|
||||
Test that the progress page displays due dates correctly
|
||||
"""
|
||||
__test__ = True
|
||||
|
||||
def get_text(self, course):
|
||||
""" Returns the HTML for the progress page """
|
||||
return views.progress(self.request, course.id, self.user.id).content
|
||||
|
||||
|
||||
class TestAccordionDueDate(BaseDueDateTests):
|
||||
"""
|
||||
Test that the accordion page displays due dates correctly
|
||||
"""
|
||||
__test__ = True
|
||||
|
||||
def get_text(self, course):
|
||||
""" Returns the HTML for the accordion """
|
||||
return views.render_accordion(
|
||||
self.request, course, course.get_children()[0].id, None, None
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user