@@ -540,6 +540,7 @@ class CourseAboutSearchIndexer(object):
|
||||
AboutInfo("enrollment_end", AboutInfo.PROPERTY, AboutInfo.FROM_COURSE_PROPERTY),
|
||||
AboutInfo("org", AboutInfo.PROPERTY, AboutInfo.FROM_COURSE_PROPERTY),
|
||||
AboutInfo("modes", AboutInfo.PROPERTY, AboutInfo.FROM_COURSE_MODE),
|
||||
AboutInfo("language", AboutInfo.PROPERTY, AboutInfo.FROM_COURSE_PROPERTY),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -1022,6 +1022,14 @@ class ContentStoreTest(ContentStoreTestCase):
|
||||
"""Test new course creation - happy path"""
|
||||
self.assert_created_course()
|
||||
|
||||
@override_settings(DEFAULT_COURSE_LANGUAGE='hr')
|
||||
def test_create_course_default_language(self):
|
||||
"""Test new course creation and verify default language"""
|
||||
test_course_data = self.assert_created_course()
|
||||
course_id = _get_course_id(self.store, test_course_data)
|
||||
course_module = self.store.get_course(course_id)
|
||||
self.assertEquals(course_module.language, 'hr')
|
||||
|
||||
def test_create_course_with_dots(self):
|
||||
"""Test new course creation with dots in the name"""
|
||||
self.course_data['org'] = 'org.foo.bar'
|
||||
|
||||
@@ -50,6 +50,7 @@ class CourseDetailsTestCase(CourseTestCase):
|
||||
self.assertIsNone(details.syllabus, "syllabus somehow initialized" + str(details.syllabus))
|
||||
self.assertIsNone(details.intro_video, "intro_video somehow initialized" + str(details.intro_video))
|
||||
self.assertIsNone(details.effort, "effort somehow initialized" + str(details.effort))
|
||||
self.assertIsNone(details.language, "language somehow initialized" + str(details.language))
|
||||
|
||||
def test_encoder(self):
|
||||
details = CourseDetails.fetch(self.course.id)
|
||||
@@ -62,6 +63,7 @@ class CourseDetailsTestCase(CourseTestCase):
|
||||
self.assertIsNone(jsondetails['syllabus'], "syllabus somehow initialized")
|
||||
self.assertIsNone(jsondetails['intro_video'], "intro_video somehow initialized")
|
||||
self.assertIsNone(jsondetails['effort'], "effort somehow initialized")
|
||||
self.assertIsNone(jsondetails['language'], "language somehow initialized")
|
||||
|
||||
def test_ooc_encoder(self):
|
||||
"""
|
||||
@@ -116,6 +118,11 @@ class CourseDetailsTestCase(CourseTestCase):
|
||||
CourseDetails.update_from_json(self.course.id, jsondetails.__dict__, self.user).course_image_name,
|
||||
jsondetails.course_image_name
|
||||
)
|
||||
jsondetails.language = "hr"
|
||||
self.assertEqual(
|
||||
CourseDetails.update_from_json(self.course.id, jsondetails.__dict__, self.user).language,
|
||||
jsondetails.language
|
||||
)
|
||||
|
||||
@override_settings(MKTG_URLS={'ROOT': 'dummy-root'})
|
||||
def test_marketing_site_fetch(self):
|
||||
@@ -316,6 +323,7 @@ class CourseDetailsViewTest(CourseTestCase):
|
||||
self.alter_field(url, details, 'intro_video', "intro_video")
|
||||
self.alter_field(url, details, 'effort', "effort")
|
||||
self.alter_field(url, details, 'course_image_name', "course_image_name")
|
||||
self.alter_field(url, details, 'language', "en")
|
||||
|
||||
def compare_details_with_encoding(self, encoded, details, context):
|
||||
"""
|
||||
@@ -330,6 +338,7 @@ class CourseDetailsViewTest(CourseTestCase):
|
||||
self.assertEqual(details['intro_video'], encoded.get('intro_video', None), context + " intro_video not ==")
|
||||
self.assertEqual(details['effort'], encoded['effort'], context + " efforts not ==")
|
||||
self.assertEqual(details['course_image_name'], encoded['course_image_name'], context + " images not ==")
|
||||
self.assertEqual(details['language'], encoded['language'], context + " languages not ==")
|
||||
|
||||
def compare_date_fields(self, details, encoded, context, field):
|
||||
"""
|
||||
|
||||
@@ -693,6 +693,9 @@ def create_new_course_in_store(store, user, org, number, run, fields):
|
||||
definition_data = {'wiki_slug': wiki_slug}
|
||||
fields.update(definition_data)
|
||||
|
||||
# Set default language from settings
|
||||
fields.update({'language': getattr(settings, 'DEFAULT_COURSE_LANGUAGE', 'en')})
|
||||
|
||||
with modulestore().default_store(store):
|
||||
# Creating the course raises DuplicateCourseError if an existing course with this org/name is found
|
||||
new_course = modulestore().create_course(
|
||||
@@ -868,6 +871,7 @@ def settings_handler(request, course_key_string):
|
||||
'short_description_editable': short_description_editable,
|
||||
'upload_asset_url': upload_asset_url,
|
||||
'course_handler_url': reverse_course_url('course_handler', course_key),
|
||||
'language_options': settings.ALL_LANGUAGES,
|
||||
}
|
||||
if prerequisite_course_enabled:
|
||||
courses, in_process_course_actions = get_courses_accessible_to_user(request)
|
||||
|
||||
@@ -33,6 +33,7 @@ class CourseDetails(object):
|
||||
self.org = org
|
||||
self.course_id = course_id
|
||||
self.run = run
|
||||
self.language = None
|
||||
self.start_date = None # 'start'
|
||||
self.end_date = None # 'end'
|
||||
self.enrollment_start = None
|
||||
@@ -80,6 +81,7 @@ class CourseDetails(object):
|
||||
course_details.pre_requisite_courses = descriptor.pre_requisite_courses
|
||||
course_details.course_image_name = descriptor.course_image
|
||||
course_details.course_image_asset_path = course_image_url(descriptor)
|
||||
course_details.language = descriptor.language
|
||||
# Default course license is "All Rights Reserved"
|
||||
course_details.license = getattr(descriptor, "license", "all-rights-reserved")
|
||||
|
||||
@@ -180,6 +182,10 @@ class CourseDetails(object):
|
||||
descriptor.license = jsondict['license']
|
||||
dirty = True
|
||||
|
||||
if 'language' in jsondict and jsondict['language'] != descriptor.language:
|
||||
descriptor.language = jsondict['language']
|
||||
dirty = True
|
||||
|
||||
if dirty:
|
||||
module_store.update_item(descriptor, user.id)
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ class CourseMetadata(object):
|
||||
'entrance_exam_id',
|
||||
'is_entrance_exam',
|
||||
'in_entrance_exam',
|
||||
'language',
|
||||
]
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -874,6 +874,10 @@ MAX_ASSET_UPLOAD_FILE_SIZE_URL = ""
|
||||
### Default value for entrance exam minimum score
|
||||
ENTRANCE_EXAM_MIN_SCORE_PCT = 50
|
||||
|
||||
### Default language for a new course
|
||||
DEFAULT_COURSE_LANGUAGE = "en"
|
||||
|
||||
|
||||
################ ADVANCED_COMPONENT_TYPES ###############
|
||||
|
||||
ADVANCED_COMPONENT_TYPES = [
|
||||
|
||||
@@ -6,6 +6,7 @@ var CourseDetails = Backbone.Model.extend({
|
||||
org : '',
|
||||
course_id: '',
|
||||
run: '',
|
||||
language: '',
|
||||
start_date: null, // maps to 'start'
|
||||
end_date: null, // maps to 'end'
|
||||
enrollment_start: null,
|
||||
|
||||
@@ -30,7 +30,8 @@ define([
|
||||
pre_requisite_courses : [],
|
||||
entrance_exam_enabled : '',
|
||||
entrance_exam_minimum_score_pct: '50',
|
||||
license: null
|
||||
license: null,
|
||||
language: ''
|
||||
},
|
||||
mockSettingsPage = readFixtures('mock/mock-settings-page.underscore');
|
||||
|
||||
@@ -154,5 +155,28 @@ define([
|
||||
);
|
||||
AjaxHelpers.respondWithJson(requests, expectedJson);
|
||||
});
|
||||
|
||||
it('should save language as part of course details', function(){
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
var expectedJson = $.extend(true, {}, modelData, {
|
||||
language: 'en',
|
||||
});
|
||||
$('#course-language').val('en').trigger('change');
|
||||
expect(this.model.get('language')).toEqual('en');
|
||||
this.view.saveView();
|
||||
AjaxHelpers.expectJsonRequest(
|
||||
requests, 'POST', urlRoot, expectedJson
|
||||
);
|
||||
});
|
||||
|
||||
it('should not error if about_page_editable is False', function(){
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
// if about_page_editable is false, there is no section.course_details
|
||||
$('.course_details').remove();
|
||||
expect(this.model.get('language')).toEqual('');
|
||||
this.view.saveView();
|
||||
AjaxHelpers.expectJsonRequest(requests, 'POST', urlRoot, modelData);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,6 +24,7 @@ var DetailsView = ValidatingView.extend({
|
||||
initialize : function() {
|
||||
this.fileAnchorTemplate = _.template('<a href="<%= fullpath %>"> <i class="icon fa fa-file"></i><%= filename %></a>');
|
||||
// fill in fields
|
||||
this.$el.find("#course-language").val(this.model.get('language'));
|
||||
this.$el.find("#course-organization").val(this.model.get('org'));
|
||||
this.$el.find("#course-number").val(this.model.get('course_id'));
|
||||
this.$el.find("#course-name").val(this.model.get('run'));
|
||||
@@ -93,6 +94,7 @@ var DetailsView = ValidatingView.extend({
|
||||
return this;
|
||||
},
|
||||
fieldToSelectorMap : {
|
||||
'language' : 'course-language',
|
||||
'start_date' : "course-start",
|
||||
'end_date' : 'course-end',
|
||||
'enrollment_start' : 'enrollment-start',
|
||||
@@ -166,6 +168,9 @@ var DetailsView = ValidatingView.extend({
|
||||
|
||||
updateModel: function(event) {
|
||||
switch (event.currentTarget.id) {
|
||||
case 'course-language':
|
||||
this.setField(event);
|
||||
break;
|
||||
case 'course-image-url':
|
||||
this.setField(event);
|
||||
var url = $(event.currentTarget).val();
|
||||
|
||||
@@ -90,4 +90,21 @@
|
||||
</li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<section class="group-settings course_details">
|
||||
<header>
|
||||
<h2 class="title-2">Course Details</h2>
|
||||
<span class="tip">Provide useful information about your course</span>
|
||||
</header>
|
||||
<ol class="list-input">
|
||||
<li class="field" id="field-course-language">
|
||||
<label for="course-language">Course Language</label>
|
||||
<select id="course-language">
|
||||
<option value="" selected> - </option>
|
||||
<option value="en">English</option>
|
||||
</select>
|
||||
<span class="tip tip-stacked">Identify the course language here. This is used to assist users find courses that are taught in a specific language.</span>
|
||||
</li>
|
||||
</ol>
|
||||
</section>
|
||||
</form>
|
||||
|
||||
@@ -199,6 +199,28 @@ CMS.URL.UPLOAD_ASSET = '${upload_asset_url}';
|
||||
</div>
|
||||
% endif
|
||||
</section>
|
||||
|
||||
% if about_page_editable:
|
||||
<section class="group-settings course_details">
|
||||
<header>
|
||||
<h2 class="title-2">${_('Course Details')}</h2>
|
||||
<span class="tip">${_('Provide useful information about your course')}</span>
|
||||
</header>
|
||||
<ol class="list-input">
|
||||
<li class="field" id="field-course-language">
|
||||
<label for="course-language">${_("Course Language")}</label>
|
||||
<select id="course-language">
|
||||
<option value="" selected> - </option>
|
||||
% for lang, label in language_options:
|
||||
<option value="${lang}">${label}</option>
|
||||
% endfor
|
||||
</select>
|
||||
<span class="tip tip-stacked">${_("Identify the course language here. This is used to assist users find courses that are taught in a specific language.")}</span>
|
||||
</li>
|
||||
</ol>
|
||||
</section>
|
||||
% endif
|
||||
|
||||
<hr class="divide" />
|
||||
<section class="group-settings marketing">
|
||||
<header>
|
||||
|
||||
@@ -23,6 +23,7 @@ from xblock.fields import Scope, List, String, Dict, Boolean, Integer, Float
|
||||
from .fields import Date
|
||||
from django.utils.timezone import UTC
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# Make '_' a no-op so we can scrape strings
|
||||
@@ -851,6 +852,12 @@ class CourseFields(object):
|
||||
default=None,
|
||||
scope=Scope.settings,
|
||||
)
|
||||
language = String(
|
||||
display_name=_("Course Language"),
|
||||
help=_("Specify the language of your course."),
|
||||
default=None,
|
||||
scope=Scope.settings
|
||||
)
|
||||
|
||||
teams_configuration = Dict(
|
||||
display_name=_("Teams Configuration"),
|
||||
|
||||
Reference in New Issue
Block a user