Merge branch 'feature/cale/cms-master' into feature/christina/tiny-mce
This commit is contained in:
@@ -41,11 +41,14 @@ def i_press_the_category_delete_icon(step, category):
|
||||
####### HELPER FUNCTIONS ##############
|
||||
def create_studio_user(
|
||||
uname='robot',
|
||||
em='robot+studio@edx.org',
|
||||
password='test'):
|
||||
email='robot+studio@edx.org',
|
||||
password='test',
|
||||
is_staff=False):
|
||||
studio_user = UserFactory.build(
|
||||
username=uname,
|
||||
email=em)
|
||||
email=email,
|
||||
password=password,
|
||||
is_staff=is_staff)
|
||||
studio_user.set_password(password)
|
||||
studio_user.save()
|
||||
|
||||
@@ -91,11 +94,12 @@ def fill_in_course_info(
|
||||
def log_into_studio(
|
||||
uname='robot',
|
||||
email='robot+studio@edx.org',
|
||||
password='test'):
|
||||
create_studio_user(uname, email)
|
||||
password='test',
|
||||
is_staff=False):
|
||||
create_studio_user(uname=uname, email=email, is_staff=is_staff)
|
||||
world.browser.cookies.delete()
|
||||
world.browser.visit(django_url('/'))
|
||||
world.browser.is_element_present_by_css('body.no-header', 10)
|
||||
world.browser.is_element_present_by_css('body.no-header', 10)
|
||||
|
||||
login_form = world.browser.find_by_css('form#login_form')
|
||||
login_form.find_by_name('email').fill(email)
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
Feature: Overview Toggle Section
|
||||
In order to quickly view the details of a course's section or to scan the inventory of sections
|
||||
As a course author
|
||||
I want to toggle the visibility of each section's subsection details in the overview listing
|
||||
|
||||
Scenario: The default layout for the overview page is to show sections in expanded view
|
||||
Given I have a course with multiple sections
|
||||
When I navigate to the course overview page
|
||||
Then I see the "Collapse All Sections" link
|
||||
And all sections are expanded
|
||||
|
||||
Scenario: Expand/collapse for a course with no sections
|
||||
Given I have a course with no sections
|
||||
When I navigate to the course overview page
|
||||
Then I do not see the "Collapse All Sections" link
|
||||
|
||||
Scenario: Collapse link appears after creating first section of a course
|
||||
Given I have a course with no sections
|
||||
When I navigate to the course overview page
|
||||
And I add a section
|
||||
Then I see the "Collapse All Sections" link
|
||||
And all sections are expanded
|
||||
|
||||
Scenario: Collapse link is removed after last section of a course is deleted
|
||||
Given I have a course with 1 section
|
||||
And I navigate to the course overview page
|
||||
When I press the "section" delete icon
|
||||
And I confirm the alert
|
||||
Then I do not see the "Collapse All Sections" link
|
||||
|
||||
Scenario: Collapsing all sections when all sections are expanded
|
||||
Given I navigate to the courseware page of a course with multiple sections
|
||||
And all sections are expanded
|
||||
When I click the "Collapse All Sections" link
|
||||
Then I see the "Expand All Sections" link
|
||||
And all sections are collapsed
|
||||
|
||||
Scenario: Collapsing all sections when 1 or more sections are already collapsed
|
||||
Given I navigate to the courseware page of a course with multiple sections
|
||||
And all sections are expanded
|
||||
When I collapse the first section
|
||||
And I click the "Collapse All Sections" link
|
||||
Then I see the "Expand All Sections" link
|
||||
And all sections are collapsed
|
||||
|
||||
Scenario: Expanding all sections when all sections are collapsed
|
||||
Given I navigate to the courseware page of a course with multiple sections
|
||||
And I click the "Collapse All Sections" link
|
||||
When I click the "Expand All Sections" link
|
||||
Then I see the "Collapse All Sections" link
|
||||
And all sections are expanded
|
||||
|
||||
Scenario: Expanding all sections when 1 or more sections are already expanded
|
||||
Given I navigate to the courseware page of a course with multiple sections
|
||||
And I click the "Collapse All Sections" link
|
||||
When I expand the first section
|
||||
And I click the "Expand All Sections" link
|
||||
Then I see the "Collapse All Sections" link
|
||||
And all sections are expanded
|
||||
@@ -0,0 +1,104 @@
|
||||
from lettuce import world, step
|
||||
from terrain.factories import *
|
||||
from common import *
|
||||
from nose.tools import assert_true, assert_false, assert_equal
|
||||
|
||||
from logging import getLogger
|
||||
logger = getLogger(__name__)
|
||||
|
||||
@step(u'I have a course with no sections$')
|
||||
def have_a_course(step):
|
||||
clear_courses()
|
||||
course = CourseFactory.create()
|
||||
|
||||
@step(u'I have a course with 1 section$')
|
||||
def have_a_course_with_1_section(step):
|
||||
clear_courses()
|
||||
course = CourseFactory.create()
|
||||
section = ItemFactory.create(parent_location=course.location)
|
||||
subsection1 = ItemFactory.create(
|
||||
parent_location=section.location,
|
||||
template = 'i4x://edx/templates/sequential/Empty',
|
||||
display_name = 'Subsection One',)
|
||||
|
||||
@step(u'I have a course with multiple sections$')
|
||||
def have_a_course_with_two_sections(step):
|
||||
clear_courses()
|
||||
course = CourseFactory.create()
|
||||
section = ItemFactory.create(parent_location=course.location)
|
||||
subsection1 = ItemFactory.create(
|
||||
parent_location=section.location,
|
||||
template = 'i4x://edx/templates/sequential/Empty',
|
||||
display_name = 'Subsection One',)
|
||||
section2 = ItemFactory.create(
|
||||
parent_location=course.location,
|
||||
display_name='Section Two',)
|
||||
subsection2 = ItemFactory.create(
|
||||
parent_location=section2.location,
|
||||
template = 'i4x://edx/templates/sequential/Empty',
|
||||
display_name = 'Subsection Alpha',)
|
||||
subsection3 = ItemFactory.create(
|
||||
parent_location=section2.location,
|
||||
template = 'i4x://edx/templates/sequential/Empty',
|
||||
display_name = 'Subsection Beta',)
|
||||
|
||||
@step(u'I navigate to the course overview page$')
|
||||
def navigate_to_the_course_overview_page(step):
|
||||
log_into_studio(is_staff=True)
|
||||
course_locator = '.class-name'
|
||||
css_click(course_locator)
|
||||
|
||||
@step(u'I navigate to the courseware page of a course with multiple sections')
|
||||
def nav_to_the_courseware_page_of_a_course_with_multiple_sections(step):
|
||||
step.given('I have a course with multiple sections')
|
||||
step.given('I navigate to the course overview page')
|
||||
|
||||
@step(u'I add a section')
|
||||
def i_add_a_section(step):
|
||||
add_section(name='My New Section That I Just Added')
|
||||
|
||||
@step(u'I click the "([^"]*)" link$')
|
||||
def i_click_the_text_span(step, text):
|
||||
span_locator = '.toggle-button-sections span'
|
||||
assert_true(world.browser.is_element_present_by_css(span_locator, 5))
|
||||
# first make sure that the expand/collapse text is the one you expected
|
||||
assert_equal(world.browser.find_by_css(span_locator).value, text)
|
||||
css_click(span_locator)
|
||||
|
||||
@step(u'I collapse the first section$')
|
||||
def i_collapse_a_section(step):
|
||||
collapse_locator = 'section.courseware-section a.collapse'
|
||||
css_click(collapse_locator)
|
||||
|
||||
@step(u'I expand the first section$')
|
||||
def i_expand_a_section(step):
|
||||
expand_locator = 'section.courseware-section a.expand'
|
||||
css_click(expand_locator)
|
||||
|
||||
@step(u'I see the "([^"]*)" link$')
|
||||
def i_see_the_span_with_text(step, text):
|
||||
span_locator = '.toggle-button-sections span'
|
||||
assert_true(world.browser.is_element_present_by_css(span_locator, 5))
|
||||
assert_equal(world.browser.find_by_css(span_locator).value, text)
|
||||
assert_true(world.browser.find_by_css(span_locator).visible)
|
||||
|
||||
@step(u'I do not see the "([^"]*)" link$')
|
||||
def i_do_not_see_the_span_with_text(step, text):
|
||||
# Note that the span will exist on the page but not be visible
|
||||
span_locator = '.toggle-button-sections span'
|
||||
assert_true(world.browser.is_element_present_by_css(span_locator))
|
||||
assert_false(world.browser.find_by_css(span_locator).visible)
|
||||
|
||||
@step(u'all sections are expanded$')
|
||||
def all_sections_are_expanded(step):
|
||||
subsection_locator = 'div.subsection-list'
|
||||
subsections = world.browser.find_by_css(subsection_locator)
|
||||
for s in subsections:
|
||||
assert_true(s.visible)
|
||||
|
||||
@step(u'all sections are collapsed$')
|
||||
def all_sections_are_expanded(step):
|
||||
subsection_locator = 'div.subsection-list'
|
||||
subsections = world.browser.find_by_css(subsection_locator)
|
||||
for s in subsections:
|
||||
assert_false(s.visible)
|
||||
@@ -0,0 +1,6 @@
|
||||
<section class="problem-editor editor">
|
||||
<div class="row">
|
||||
<textarea class="markdown-box">markdown</textarea>
|
||||
<textarea class="xml-box" rows="8" cols="40">xml</textarea>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,5 @@
|
||||
<section class="problem-editor editor">
|
||||
<div class="row">
|
||||
<textarea class="xml-box" rows="8" cols="40">xml only</textarea>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,4 +1,24 @@
|
||||
describe 'MarkdownEditingDescriptor', ->
|
||||
describe 'save stores the correct data', ->
|
||||
it 'saves markdown from markdown editor', ->
|
||||
loadFixtures 'problem-with-markdown.html'
|
||||
@descriptor = new MarkdownEditingDescriptor($('.problem-editor'))
|
||||
saveResult = @descriptor.save()
|
||||
expect(saveResult.metadata.markdown).toEqual('markdown')
|
||||
expect(saveResult.data).toEqual('<problem>\n<p>markdown</p>\n</problem>')
|
||||
it 'clears markdown when xml editor is selected', ->
|
||||
loadFixtures 'problem-with-markdown.html'
|
||||
@descriptor = new MarkdownEditingDescriptor($('.problem-editor'))
|
||||
@descriptor.createXMLEditor('replace with markdown')
|
||||
saveResult = @descriptor.save()
|
||||
expect(saveResult.metadata.markdown).toEqual(null)
|
||||
expect(saveResult.data).toEqual('replace with markdown')
|
||||
it 'saves xml from the xml editor', ->
|
||||
loadFixtures 'problem-without-markdown.html'
|
||||
@descriptor = new MarkdownEditingDescriptor($('.problem-editor'))
|
||||
saveResult = @descriptor.save()
|
||||
expect(saveResult.metadata.markdown).toEqual(null)
|
||||
expect(saveResult.data).toEqual('xml only')
|
||||
|
||||
describe 'insertMultipleChoice', ->
|
||||
it 'inserts the template if selection is empty', ->
|
||||
@@ -62,24 +82,20 @@ describe 'MarkdownEditingDescriptor', ->
|
||||
|
||||
Enter the numerical value of Pi:
|
||||
= 3.14159 +- .02
|
||||
|
||||
|
||||
Enter the approximate value of 502*9:
|
||||
= 4518 +- 15%
|
||||
|
||||
|
||||
Enter the number of fingers on a human hand:
|
||||
= 5
|
||||
|
||||
<solution>
|
||||
<div class='detailed-solution'>
|
||||
Explanation
|
||||
|
||||
[Explanation]
|
||||
Pi, or the the ratio between a circle's circumference to its diameter, is an irrational number known to extreme precision. It is value is approximately equal to 3.14.
|
||||
|
||||
Although you can get an exact value by typing 502*9 into a calculator, the result will be close to 500*10, or 5,000. The grader accepts any response within 15% of the true value, 4518, so that you can use any estimation technique that you like.
|
||||
|
||||
If you look at your hand, you can count that you have five fingers.
|
||||
</div>
|
||||
</solution>
|
||||
[Explanation]
|
||||
""")
|
||||
expect(data).toEqual("""<problem>
|
||||
<p>A numerical response problem accepts a line of text input from the student, and evaluates the input for correctness based on its numerical value.</p>
|
||||
@@ -104,7 +120,7 @@ describe 'MarkdownEditingDescriptor', ->
|
||||
</numericalresponse>
|
||||
|
||||
<solution>
|
||||
<div class='detailed-solution'>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
|
||||
<p>Pi, or the the ratio between a circle's circumference to its diameter, is an irrational number known to extreme precision. It is value is approximately equal to 3.14.</p>
|
||||
@@ -112,6 +128,7 @@ describe 'MarkdownEditingDescriptor', ->
|
||||
<p>Although you can get an exact value by typing 502*9 into a calculator, the result will be close to 500*10, or 5,000. The grader accepts any response within 15% of the true value, 4518, so that you can use any estimation technique that you like.</p>
|
||||
|
||||
<p>If you look at your hand, you can count that you have five fingers.</p>
|
||||
|
||||
</div>
|
||||
</solution>
|
||||
</problem>""")
|
||||
@@ -128,13 +145,9 @@ describe 'MarkdownEditingDescriptor', ->
|
||||
( ) Android
|
||||
( ) The Beatles
|
||||
|
||||
<solution>
|
||||
<div class='detailed-solution'>
|
||||
Explanation
|
||||
|
||||
[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.
|
||||
</div>
|
||||
</solution>
|
||||
[Explanation]
|
||||
""")
|
||||
expect(data).toEqual("""<problem>
|
||||
<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>
|
||||
@@ -154,10 +167,11 @@ describe 'MarkdownEditingDescriptor', ->
|
||||
</multiplechoiceresponse>
|
||||
|
||||
<solution>
|
||||
<div class='detailed-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>""")
|
||||
@@ -169,13 +183,9 @@ describe 'MarkdownEditingDescriptor', ->
|
||||
Translation between Option Response and __________ is extremely straightforward:
|
||||
[[(Multiple Choice), String Response, Numerical Response, External Response, Image Response]]
|
||||
|
||||
<solution>
|
||||
<div class='detailed-solution'>
|
||||
Explanation
|
||||
|
||||
[Explanation]
|
||||
Multiple Choice also allows students to select from a variety of pre-written responses, although the format makes it easier for students to read very long response options. Optionresponse also differs slightly because students are more likely to think of an answer and then search for it rather than relying purely on recognition to answer the question.
|
||||
</div>
|
||||
</solution>
|
||||
[Explanation]
|
||||
""")
|
||||
expect(data).toEqual("""<problem>
|
||||
<p>OptionResponse gives a limited set of options for students to respond with, and presents those options in a format that encourages them to search for a specific answer rather than being immediately presented with options from which to recognize the correct answer.</p>
|
||||
@@ -189,14 +199,15 @@ describe 'MarkdownEditingDescriptor', ->
|
||||
</optionresponse>
|
||||
|
||||
<solution>
|
||||
<div class='detailed-solution'>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
|
||||
<p>Multiple Choice also allows students to select from a variety of pre-written responses, although the format makes it easier for students to read very long response options. Optionresponse also differs slightly because students are more likely to think of an answer and then search for it rather than relying purely on recognition to answer the question.</p>
|
||||
|
||||
</div>
|
||||
</solution>
|
||||
</problem>""")
|
||||
it 'converts OptionResponse to xml', ->
|
||||
it 'converts StringResponse to xml', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""A string response problem accepts a line of text input from the student, and evaluates the input for correctness based on an expected answer within each input box.
|
||||
|
||||
The answer is correct if it matches every character of the expected answer. This can be a problem with international spelling, dates, or anything where the format of the answer is not clear.
|
||||
@@ -204,14 +215,9 @@ describe 'MarkdownEditingDescriptor', ->
|
||||
Which US state has Lansing as its capital?
|
||||
= Michigan
|
||||
|
||||
<solution>
|
||||
<div class='detailed-solution'>
|
||||
Explanation
|
||||
|
||||
[Explanation]
|
||||
Lansing is the capital of Michigan, although it is not Michgan's largest city, or even the seat of the county in which it resides.
|
||||
|
||||
</div>
|
||||
</solution>
|
||||
[Explanation]
|
||||
""")
|
||||
expect(data).toEqual("""<problem>
|
||||
<p>A string response problem accepts a line of text input from the student, and evaluates the input for correctness based on an expected answer within each input box.</p>
|
||||
@@ -224,11 +230,11 @@ describe 'MarkdownEditingDescriptor', ->
|
||||
</stringresponse>
|
||||
|
||||
<solution>
|
||||
<div class='detailed-solution'>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
|
||||
<p>Lansing is the capital of Michigan, although it is not Michgan's largest city, or even the seat of the county in which it resides.</p>
|
||||
|
||||
|
||||
</div>
|
||||
</solution>
|
||||
</problem>""")
|
||||
@@ -261,6 +267,11 @@ describe 'MarkdownEditingDescriptor', ->
|
||||
|
||||
What happens w/ empty correct options?
|
||||
[[()]]
|
||||
|
||||
[Explanation]see[/expLanation]
|
||||
|
||||
[explanation]
|
||||
orphaned start
|
||||
|
||||
No p tags in the below
|
||||
<script type='javascript'>
|
||||
@@ -321,6 +332,17 @@ describe 'MarkdownEditingDescriptor', ->
|
||||
<optioninput options="('')" correct=""></optioninput>
|
||||
</optionresponse>
|
||||
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
|
||||
<p>see</p>
|
||||
</div>
|
||||
</solution>
|
||||
|
||||
<p>[explanation]</p>
|
||||
<p>orphaned start</p>
|
||||
|
||||
<p>No p tags in the below</p>
|
||||
<script type='javascript'>
|
||||
var two = 2;
|
||||
|
||||
@@ -259,6 +259,12 @@ class @MarkdownEditingDescriptor extends XModule.Descriptor
|
||||
selectString += '</optionresponse>\n\n';
|
||||
return selectString;
|
||||
});
|
||||
|
||||
// replace explanations
|
||||
xml = xml.replace(/\[explanation\]\n?([^\]]*)\[\/?explanation\]/gmi, function(match, p1) {
|
||||
var selectString = '<solution>\n<div class="detailed-solution">\nExplanation\n\n' + p1 + '\n</div>\n</solution>';
|
||||
return selectString;
|
||||
});
|
||||
|
||||
// split scripts and wrap paragraphs
|
||||
var splits = xml.split(/(\<\/?script.*?\>)/g);
|
||||
|
||||
@@ -29,19 +29,10 @@ metadata:
|
||||
( ) The Beatles
|
||||
|
||||
|
||||
<solution>
|
||||
|
||||
<div class='detailed-solution'>
|
||||
|
||||
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.
|
||||
|
||||
</div>
|
||||
|
||||
</solution>
|
||||
[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]
|
||||
"
|
||||
data: |
|
||||
<problem>
|
||||
|
||||
@@ -28,26 +28,16 @@ metadata:
|
||||
= 5
|
||||
|
||||
|
||||
<solution>
|
||||
[Explanation]
|
||||
Pi, or the the ratio between a circle's circumference to its diameter, is an irrational number
|
||||
known to extreme precision. It is value is approximately equal to 3.14.
|
||||
|
||||
Although you can get an exact value by typing 502*9 into a calculator, the result will be close to
|
||||
500*10, or 5,000. The grader accepts any response within 15% of the true value, 4518, so that you
|
||||
can use any estimation technique that you like.
|
||||
|
||||
<div class='detailed-solution'>
|
||||
|
||||
Explanation
|
||||
|
||||
|
||||
Pi, or the the ratio between a circle's circumference to its diameter, is an irrational number
|
||||
known to extreme precision. It is value is approximately equal to 3.14.
|
||||
|
||||
|
||||
Although you can get an exact value by typing 502*9 into a calculator, the result will be close to
|
||||
500*10, or 5,000. The grader accepts any response within 15% of the true value, 4518, so that you
|
||||
can use any estimation technique that you like.
|
||||
|
||||
|
||||
If you look at your hand, you can count that you have five fingers.
|
||||
|
||||
</div>
|
||||
</solution>
|
||||
If you look at your hand, you can count that you have five fingers.
|
||||
[Explanation]
|
||||
"
|
||||
|
||||
data: |
|
||||
|
||||
@@ -17,21 +17,12 @@ metadata:
|
||||
[[(Multiple Choice), String Response, Numerical Response, External Response, Image Response]]
|
||||
|
||||
|
||||
<solution>
|
||||
|
||||
<div class='detailed-solution'>
|
||||
|
||||
Explanation
|
||||
|
||||
|
||||
Multiple Choice also allows students to select from a variety of pre-written responses, although the
|
||||
format makes it easier for students to read very long response options. Optionresponse also differs
|
||||
slightly because students are more likely to think of an answer and then search for it rather than
|
||||
relying purely on recognition to answer the question.
|
||||
|
||||
</div>
|
||||
|
||||
</solution>
|
||||
[Explanation]
|
||||
Multiple Choice also allows students to select from a variety of pre-written responses, although the
|
||||
format makes it easier for students to read very long response options. Optionresponse also differs
|
||||
slightly because students are more likely to think of an answer and then search for it rather than
|
||||
relying purely on recognition to answer the question.
|
||||
[Explanation]
|
||||
"
|
||||
data: |
|
||||
<problem>
|
||||
|
||||
@@ -19,20 +19,10 @@ metadata:
|
||||
= Michigan
|
||||
|
||||
|
||||
<solution>
|
||||
|
||||
<div class='detailed-solution'>
|
||||
|
||||
Explanation
|
||||
|
||||
|
||||
Lansing is the capital of Michigan, although it is not Michgan's largest city,
|
||||
or even the seat of the county in which it resides.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</solution>
|
||||
[Explanation]
|
||||
Lansing is the capital of Michigan, although it is not Michgan's largest city,
|
||||
or even the seat of the county in which it resides.
|
||||
[Explanation]
|
||||
"
|
||||
data: |
|
||||
<problem showanswer="always">
|
||||
|
||||
@@ -379,7 +379,7 @@ class XmlDescriptor(XModuleDescriptor):
|
||||
# Write the definition to a file
|
||||
url_path = name_to_pathname(self.url_name)
|
||||
filepath = self.__class__._format_filepath(self.category, url_path)
|
||||
resource_fs.makedir(os.path.dirname(filepath), allow_recreate=True)
|
||||
resource_fs.makedir(os.path.dirname(filepath), recursive=True, allow_recreate=True)
|
||||
with resource_fs.open(filepath, 'w') as file:
|
||||
file.write(etree.tostring(xml_object, pretty_print=True, encoding='utf-8'))
|
||||
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import factory
|
||||
from student.models import User, UserProfile, Registration
|
||||
from datetime import datetime
|
||||
import uuid
|
||||
from factory import Factory
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from time import gmtime
|
||||
from uuid import uuid4
|
||||
from xmodule.timeparse import stringify_time
|
||||
|
||||
class UserProfileFactory(factory.Factory):
|
||||
class UserProfileFactory(Factory):
|
||||
FACTORY_FOR = UserProfile
|
||||
|
||||
user = None
|
||||
@@ -13,13 +17,13 @@ class UserProfileFactory(factory.Factory):
|
||||
mailing_address = None
|
||||
goals = 'World domination'
|
||||
|
||||
class RegistrationFactory(factory.Factory):
|
||||
class RegistrationFactory(Factory):
|
||||
FACTORY_FOR = Registration
|
||||
|
||||
user = None
|
||||
activation_key = uuid.uuid4().hex
|
||||
activation_key = uuid4().hex
|
||||
|
||||
class UserFactory(factory.Factory):
|
||||
class UserFactory(Factory):
|
||||
FACTORY_FOR = User
|
||||
|
||||
username = 'robot'
|
||||
@@ -32,3 +36,113 @@ class UserFactory(factory.Factory):
|
||||
is_superuser = False
|
||||
last_login = datetime(2012, 1, 1)
|
||||
date_joined = datetime(2011, 1, 1)
|
||||
|
||||
def XMODULE_COURSE_CREATION(class_to_create, **kwargs):
|
||||
return XModuleCourseFactory._create(class_to_create, **kwargs)
|
||||
|
||||
def XMODULE_ITEM_CREATION(class_to_create, **kwargs):
|
||||
return XModuleItemFactory._create(class_to_create, **kwargs)
|
||||
|
||||
class XModuleCourseFactory(Factory):
|
||||
"""
|
||||
Factory for XModule courses.
|
||||
"""
|
||||
|
||||
ABSTRACT_FACTORY = True
|
||||
_creation_function = (XMODULE_COURSE_CREATION,)
|
||||
|
||||
@classmethod
|
||||
def _create(cls, target_class, *args, **kwargs):
|
||||
|
||||
template = Location('i4x', 'edx', 'templates', 'course', 'Empty')
|
||||
org = kwargs.get('org')
|
||||
number = kwargs.get('number')
|
||||
display_name = kwargs.get('display_name')
|
||||
location = Location('i4x', org, number,
|
||||
'course', Location.clean(display_name))
|
||||
|
||||
store = modulestore('direct')
|
||||
|
||||
# Write the data to the mongo datastore
|
||||
new_course = store.clone_item(template, location)
|
||||
|
||||
# This metadata code was copied from cms/djangoapps/contentstore/views.py
|
||||
if display_name is not None:
|
||||
new_course.metadata['display_name'] = display_name
|
||||
|
||||
new_course.metadata['data_dir'] = uuid4().hex
|
||||
new_course.metadata['start'] = stringify_time(gmtime())
|
||||
new_course.tabs = [{"type": "courseware"},
|
||||
{"type": "course_info", "name": "Course Info"},
|
||||
{"type": "discussion", "name": "Discussion"},
|
||||
{"type": "wiki", "name": "Wiki"},
|
||||
{"type": "progress", "name": "Progress"}]
|
||||
|
||||
# Update the data in the mongo datastore
|
||||
store.update_metadata(new_course.location.url(), new_course.own_metadata)
|
||||
|
||||
return new_course
|
||||
|
||||
class Course:
|
||||
pass
|
||||
|
||||
class CourseFactory(XModuleCourseFactory):
|
||||
FACTORY_FOR = Course
|
||||
|
||||
template = 'i4x://edx/templates/course/Empty'
|
||||
org = 'MITx'
|
||||
number = '999'
|
||||
display_name = 'Robot Super Course'
|
||||
|
||||
class XModuleItemFactory(Factory):
|
||||
"""
|
||||
Factory for XModule items.
|
||||
"""
|
||||
|
||||
ABSTRACT_FACTORY = True
|
||||
_creation_function = (XMODULE_ITEM_CREATION,)
|
||||
|
||||
@classmethod
|
||||
def _create(cls, target_class, *args, **kwargs):
|
||||
"""
|
||||
kwargs must include parent_location, template. Can contain display_name
|
||||
target_class is ignored
|
||||
"""
|
||||
|
||||
DETACHED_CATEGORIES = ['about', 'static_tab', 'course_info']
|
||||
|
||||
parent_location = Location(kwargs.get('parent_location'))
|
||||
template = Location(kwargs.get('template'))
|
||||
display_name = kwargs.get('display_name')
|
||||
|
||||
store = modulestore('direct')
|
||||
|
||||
# This code was based off that in cms/djangoapps/contentstore/views.py
|
||||
parent = store.get_item(parent_location)
|
||||
dest_location = parent_location._replace(category=template.category, name=uuid4().hex)
|
||||
|
||||
new_item = store.clone_item(template, dest_location)
|
||||
|
||||
# TODO: This needs to be deleted when we have proper storage for static content
|
||||
new_item.metadata['data_dir'] = parent.metadata['data_dir']
|
||||
|
||||
# replace the display name with an optional parameter passed in from the caller
|
||||
if display_name is not None:
|
||||
new_item.metadata['display_name'] = display_name
|
||||
|
||||
store.update_metadata(new_item.location.url(), new_item.own_metadata)
|
||||
|
||||
if new_item.location.category not in DETACHED_CATEGORIES:
|
||||
store.update_children(parent_location, parent.definition.get('children', []) + [new_item.location.url()])
|
||||
|
||||
return new_item
|
||||
|
||||
class Item:
|
||||
pass
|
||||
|
||||
class ItemFactory(XModuleItemFactory):
|
||||
FACTORY_FOR = Item
|
||||
|
||||
parent_location = 'i4x://MITx/999/course/Robot_Super_Course'
|
||||
template = 'i4x://edx/templates/chapter/Empty'
|
||||
display_name = 'Section One'
|
||||
Reference in New Issue
Block a user