Commit 176b1c89 by Davorin Sego

Course Discovery - Language Filtering

Filter by Language:
Designation for course language(s) in Studio
allows for filtering of courses based on their language in the sidebar
Reuses active filter view UI/IA from find courses page on edx.org
view for clearing individual or all filters, as with edx.org filtering
parent de3cccdb
......@@ -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"),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment