Use Backbone for student account and profile JS.
Validate student account and profile form fields. Use RequireJS for Jasmine tests of account and profile JS.
This commit is contained in:
@@ -69,13 +69,15 @@ def preferred_language(preferred_language_code):
|
||||
if preferred_language_code in settings.LANGUAGE_DICT:
|
||||
# If the user has indicated a preference for a valid
|
||||
# language, record their preferred language
|
||||
preferred_language = settings.LANGUAGE_DICT[preferred_language_code]
|
||||
pass
|
||||
elif active_language_code in settings.LANGUAGE_DICT:
|
||||
# Otherwise, set the language used in the current thread
|
||||
# as the preferred language
|
||||
preferred_language = settings.LANGUAGE_DICT[active_language_code]
|
||||
preferred_language_code = active_language_code
|
||||
else:
|
||||
# Otherwise, use the default language
|
||||
preferred_language = settings.LANGUAGE_DICT[settings.LANGUAGE_CODE]
|
||||
preferred_language_code = settings.LANGUAGE_CODE
|
||||
|
||||
return preferred_language
|
||||
preferred_language = settings.LANGUAGE_DICT[preferred_language_code]
|
||||
|
||||
return Language(preferred_language_code, preferred_language)
|
||||
|
||||
@@ -18,13 +18,13 @@ class LanguageApiTest(TestCase):
|
||||
|
||||
def test_preferred_language(self):
|
||||
preferred_language = language_api.preferred_language('fr')
|
||||
self.assertEqual(preferred_language, u'Français')
|
||||
self.assertEqual(preferred_language, language_api.Language('fr', u'Français'))
|
||||
|
||||
@ddt.data(*INVALID_LANGUAGE_CODES)
|
||||
def test_invalid_preferred_language(self, language_code):
|
||||
preferred_language = language_api.preferred_language(language_code)
|
||||
self.assertEqual(preferred_language, u'English')
|
||||
self.assertEqual(preferred_language, language_api.Language('en', u'English'))
|
||||
|
||||
def test_no_preferred_language(self):
|
||||
preferred_language = language_api.preferred_language(None)
|
||||
self.assertEqual(preferred_language, u'English')
|
||||
self.assertEqual(preferred_language, language_api.Language('en', u'English'))
|
||||
|
||||
@@ -6,6 +6,7 @@ email address.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
from user_api.models import User, UserProfile, UserPreference
|
||||
from user_api.helpers import intercept_errors
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@ class AccountApiTest(TestCase):
|
||||
"@",
|
||||
"@domain.com",
|
||||
"test@no_extension",
|
||||
u"fŕáńḱ@example.com",
|
||||
u"frank@éxáḿṕĺé.ćőḿ",
|
||||
|
||||
# Long email -- subtract the length of the @domain
|
||||
# except for one character (so we exceed the max length limit)
|
||||
|
||||
Binary file not shown.
@@ -37,8 +37,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.1a\n"
|
||||
"Report-Msgid-Bugs-To: openedx-translation@googlegroups.com\n"
|
||||
"POT-Creation-Date: 2014-09-24 14:16-0400\n"
|
||||
"PO-Revision-Date: 2014-09-24 18:16:48.297294\n"
|
||||
"POT-Creation-Date: 2014-10-01 13:57+0000\n"
|
||||
"PO-Revision-Date: 2014-10-01 13:57:56.152525\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: openedx-translation <openedx-translation@googlegroups.com>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -111,7 +111,7 @@ msgstr "Réqüéstéd pägé müst ßé gréätér thän zérö Ⱡ'σяєм ι
|
||||
msgid "Honor Code Certificate"
|
||||
msgstr "Hönör Çödé Çértïfïçäté Ⱡ'σяє#"
|
||||
|
||||
#: common/djangoapps/course_modes/views.py common/djangoapps/student/views.py
|
||||
#: common/djangoapps/course_modes/views.py
|
||||
msgid "Enrollment is closed"
|
||||
msgstr "Énröllmént ïs çlöséd Ⱡ'σя#"
|
||||
|
||||
@@ -216,14 +216,9 @@ msgstr "Ìnvälïd çöürsé ïd Ⱡ'σ#"
|
||||
msgid "Course id is invalid"
|
||||
msgstr "Çöürsé ïd ïs ïnvälïd Ⱡ'σя#"
|
||||
|
||||
#: common/djangoapps/student/views.py
|
||||
#: lms/templates/courseware/course_about.html
|
||||
msgid "Course is full"
|
||||
msgstr "Çöürsé ïs füll Ⱡ'#"
|
||||
|
||||
#: common/djangoapps/student/views.py
|
||||
msgid "Student is already enrolled"
|
||||
msgstr "Stüdént ïs älréädý énrölléd Ⱡ'σяєм#"
|
||||
#: common/djangoapps/student/views.py common/djangoapps/student/views.py
|
||||
msgid "Could not enroll"
|
||||
msgstr "Çöüld nöt énröll Ⱡ'σ#"
|
||||
|
||||
#: common/djangoapps/student/views.py
|
||||
msgid "You are not enrolled in this course"
|
||||
@@ -2794,6 +2789,58 @@ msgstr ""
|
||||
"séttïng hïdés thé Läünçh ßüttön änd äný ÌFrämés för thïs çömpönént. Ⱡ'σяєм "
|
||||
"ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α∂ιριѕι¢ιηg єłιт, ѕє∂ ∂σ єιυ#"
|
||||
|
||||
#: common/lib/xmodule/xmodule/lti_module.py
|
||||
msgid "Request user's username"
|
||||
msgstr "Réqüést üsér's üsérnämé Ⱡ'σяє#"
|
||||
|
||||
#: common/lib/xmodule/xmodule/lti_module.py
|
||||
msgid ""
|
||||
"Select True to request the user's username. You must also set Open in New "
|
||||
"Page to True to get the user's information."
|
||||
msgstr ""
|
||||
"Séléçt Trüé tö réqüést thé üsér's üsérnämé. Ýöü müst älsö sét Öpén ïn Néw "
|
||||
"Pägé tö Trüé tö gét thé üsér's ïnförmätïön. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, "
|
||||
"¢σηѕє¢т#"
|
||||
|
||||
#: common/lib/xmodule/xmodule/lti_module.py
|
||||
msgid "Request user's email"
|
||||
msgstr "Réqüést üsér's émäïl Ⱡ'σя#"
|
||||
|
||||
#: common/lib/xmodule/xmodule/lti_module.py
|
||||
msgid ""
|
||||
"Select True to request the user's email address. You must also set Open in "
|
||||
"New Page to True to get the user's information."
|
||||
msgstr ""
|
||||
"Séléçt Trüé tö réqüést thé üsér's émäïl äddréss. Ýöü müst älsö sét Öpén ïn "
|
||||
"Néw Pägé tö Trüé tö gét thé üsér's ïnförmätïön. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт,"
|
||||
" ¢σηѕє¢тєт#"
|
||||
|
||||
#: common/lib/xmodule/xmodule/lti_module.py
|
||||
msgid "LTI Application Information"
|
||||
msgstr "LTÌ Àpplïçätïön Ìnförmätïön Ⱡ'σяєм#"
|
||||
|
||||
#: common/lib/xmodule/xmodule/lti_module.py
|
||||
msgid ""
|
||||
"Enter a description of the third party application. If requesting username "
|
||||
"and/or email, use this text box to inform users why their username and/or "
|
||||
"email will be forwarded to a third party application."
|
||||
msgstr ""
|
||||
"Éntér ä désçrïptïön öf thé thïrd pärtý äpplïçätïön. Ìf réqüéstïng üsérnämé "
|
||||
"änd/ör émäïl, üsé thïs téxt ßöx tö ïnförm üsérs whý théïr üsérnämé änd/ör "
|
||||
"émäïl wïll ßé förwärdéd tö ä thïrd pärtý äpplïçätïön. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт"
|
||||
" αмєт, ¢σηѕє¢тєтυя α∂ιριѕι¢ιηg єłιт, ѕє∂ ∂#"
|
||||
|
||||
#: common/lib/xmodule/xmodule/lti_module.py
|
||||
msgid "Button Text"
|
||||
msgstr "Büttön Téxt Ⱡ#"
|
||||
|
||||
#: common/lib/xmodule/xmodule/lti_module.py
|
||||
msgid ""
|
||||
"Enter the text on the button used to launch the third party application."
|
||||
msgstr ""
|
||||
"Éntér thé téxt ön thé ßüttön üséd tö läünçh thé thïrd pärtý äpplïçätïön. "
|
||||
"Ⱡ'σяєм ιρѕυм ∂σłσя ѕι#"
|
||||
|
||||
#: common/lib/xmodule/xmodule/lti_module.py
|
||||
msgid ""
|
||||
"Could not parse custom parameter: {custom_parameter}. Should be \"x=y\" "
|
||||
@@ -4318,6 +4365,10 @@ msgstr "Tïtlé çän't ßé émptý Ⱡ'σя#"
|
||||
msgid "Body can't be empty"
|
||||
msgstr "Bödý çän't ßé émptý Ⱡ'σя#"
|
||||
|
||||
#: lms/djangoapps/django_comment_client/base/views.py
|
||||
msgid "Topic doesn't exist"
|
||||
msgstr "Töpïç döésn't éxïst Ⱡ'σя#"
|
||||
|
||||
#: lms/djangoapps/django_comment_client/base/views.py
|
||||
#: lms/djangoapps/django_comment_client/base/views.py
|
||||
msgid "Comment level too deep"
|
||||
@@ -7689,14 +7740,17 @@ msgstr "édït Ⱡ'σяєм#"
|
||||
#. (for example, Google and LinkedIn) the user can link with or unlink from
|
||||
#. their edX account.
|
||||
#: lms/templates/dashboard.html
|
||||
#: lms/templates/student_profile/third_party_auth.html
|
||||
msgid "Connected Accounts"
|
||||
msgstr "Çönnéçtéd Àççöünts Ⱡ'σ#"
|
||||
|
||||
#: lms/templates/dashboard.html
|
||||
#: lms/templates/student_profile/third_party_auth.html
|
||||
msgid "Linked"
|
||||
msgstr "Lïnkéd Ⱡ'σяєм ιρѕ#"
|
||||
|
||||
#: lms/templates/dashboard.html
|
||||
#: lms/templates/student_profile/third_party_auth.html
|
||||
msgid "Not Linked"
|
||||
msgstr "Nöt Lïnkéd Ⱡ#"
|
||||
|
||||
@@ -7704,6 +7758,7 @@ msgstr "Nöt Lïnkéd Ⱡ#"
|
||||
#. and their account with an external authentication provider (like Google or
|
||||
#. LinkedIn).
|
||||
#: lms/templates/dashboard.html
|
||||
#: lms/templates/student_profile/third_party_auth.html
|
||||
msgid "Unlink"
|
||||
msgstr "Ûnlïnk Ⱡ'σяєм ιρѕ#"
|
||||
|
||||
@@ -7711,6 +7766,7 @@ msgstr "Ûnlïnk Ⱡ'σяєм ιρѕ#"
|
||||
#. and their account with an external authentication provider (like Google or
|
||||
#. LinkedIn).
|
||||
#: lms/templates/dashboard.html
|
||||
#: lms/templates/student_profile/third_party_auth.html
|
||||
msgid "Link"
|
||||
msgstr "Lïnk Ⱡ'σяєм#"
|
||||
|
||||
@@ -8531,10 +8587,6 @@ msgstr "Çönfïrm #"
|
||||
msgid "Reject"
|
||||
msgstr "Réjéçt Ⱡ'σяєм ιρѕ#"
|
||||
|
||||
#: lms/templates/navigation.html lms/templates/original_navigation.html
|
||||
msgid "Global Navigation"
|
||||
msgstr "Glößäl Nävïgätïön Ⱡ'σ#"
|
||||
|
||||
#: lms/templates/navigation.html lms/templates/navigation.html
|
||||
#: lms/templates/original_navigation.html
|
||||
msgid "Find Courses"
|
||||
@@ -8588,6 +8640,10 @@ msgstr ""
|
||||
msgid "You do not have any notes."
|
||||
msgstr "Ýöü dö nöt hävé äný nötés. Ⱡ'σяєм#"
|
||||
|
||||
#: lms/templates/original_navigation.html
|
||||
msgid "Global Navigation"
|
||||
msgstr "Glößäl Nävïgätïön Ⱡ'σ#"
|
||||
|
||||
#: lms/templates/original_navigation.html
|
||||
#: lms/templates/sysadmin_dashboard.html
|
||||
#: lms/templates/sysadmin_dashboard_gitlogs.html
|
||||
@@ -9602,6 +9658,10 @@ msgstr ""
|
||||
"Àdd {course.display_number_with_default} tö Çärt ({currency_symbol}{cost}) "
|
||||
"Ⱡ'σяє#"
|
||||
|
||||
#: lms/templates/courseware/course_about.html
|
||||
msgid "Course is full"
|
||||
msgstr "Çöürsé ïs füll Ⱡ'#"
|
||||
|
||||
#: lms/templates/courseware/course_about.html
|
||||
msgid "Enrollment in this course is by invitation only"
|
||||
msgstr "Énröllmént ïn thïs çöürsé ïs ßý ïnvïtätïön önlý Ⱡ'σяєм ιρѕυм #"
|
||||
@@ -9827,15 +9887,6 @@ msgstr ""
|
||||
"répört äný prößléms ör döwntïmé. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя "
|
||||
"α∂ιριѕι¢ιη#"
|
||||
|
||||
#: lms/templates/courseware/grade_summary.html
|
||||
#: lms/templates/courseware/instructor_dashboard.html
|
||||
msgid "Grade summary"
|
||||
msgstr "Grädé sümmärý Ⱡ'#"
|
||||
|
||||
#: lms/templates/courseware/grade_summary.html
|
||||
msgid "Not implemented yet"
|
||||
msgstr "Nöt ïmpléméntéd ýét Ⱡ'σя#"
|
||||
|
||||
#: lms/templates/courseware/gradebook.html
|
||||
#: lms/templates/courseware/instructor_dashboard.html
|
||||
msgid "Gradebook"
|
||||
@@ -11140,20 +11191,6 @@ msgstr ""
|
||||
"Qüéstïöns räïsé ïssüés thät nééd änswérs. Dïsçüssïöns shäré ïdéäs änd stärt "
|
||||
"çönvérsätïöns. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт#"
|
||||
|
||||
#: lms/templates/discussion/_underscore_templates.html
|
||||
msgid "Topic Area:"
|
||||
msgstr "Töpïç Àréä: Ⱡ#"
|
||||
|
||||
#: lms/templates/discussion/_underscore_templates.html
|
||||
#: lms/templates/discussion/_underscore_templates.html
|
||||
msgid "Filter topics"
|
||||
msgstr "Fïltér töpïçs Ⱡ'#"
|
||||
|
||||
#: lms/templates/discussion/_underscore_templates.html
|
||||
msgid "Add your post to a relevant topic to help others find it."
|
||||
msgstr ""
|
||||
"Àdd ýöür pöst tö ä rélévänt töpïç tö hélp öthérs fïnd ït. Ⱡ'σяєм ιρѕυм ∂σł#"
|
||||
|
||||
#. Translators: This labels the selector for which group of students can view
|
||||
#. a
|
||||
#. post
|
||||
@@ -11203,6 +11240,20 @@ msgstr "pöst änönýmöüslý tö çlässmätés Ⱡ'σяєм #"
|
||||
msgid "Add Post"
|
||||
msgstr "Àdd Pöst #"
|
||||
|
||||
#: lms/templates/discussion/_underscore_templates.html
|
||||
msgid "Topic Area:"
|
||||
msgstr "Töpïç Àréä: Ⱡ#"
|
||||
|
||||
#: lms/templates/discussion/_underscore_templates.html
|
||||
#: lms/templates/discussion/_underscore_templates.html
|
||||
msgid "Filter topics"
|
||||
msgstr "Fïltér töpïçs Ⱡ'#"
|
||||
|
||||
#: lms/templates/discussion/_underscore_templates.html
|
||||
msgid "Add your post to a relevant topic to help others find it."
|
||||
msgstr ""
|
||||
"Àdd ýöür pöst tö ä rélévänt töpïç tö hélp öthérs fïnd ït. Ⱡ'σяєм ιρѕυм ∂σł#"
|
||||
|
||||
#: lms/templates/discussion/_underscore_templates.html
|
||||
msgid "Endorse"
|
||||
msgstr "Éndörsé #"
|
||||
@@ -13089,10 +13140,12 @@ msgid "None Available"
|
||||
msgstr "Nöné Àväïläßlé Ⱡ'#"
|
||||
|
||||
#: lms/templates/modal/_modal-settings-language.html
|
||||
#: lms/templates/student_profile/language.html
|
||||
msgid "Change Preferred Language"
|
||||
msgstr "Çhängé Préférréd Längüägé Ⱡ'σяєм#"
|
||||
|
||||
#: lms/templates/modal/_modal-settings-language.html
|
||||
#: lms/templates/student_profile/language.html
|
||||
msgid "Please choose your preferred language"
|
||||
msgstr "Pléäsé çhöösé ýöür préférréd längüägé Ⱡ'σяєм ιρѕ#"
|
||||
|
||||
@@ -13101,6 +13154,7 @@ msgid "Save Language Settings"
|
||||
msgstr "Sävé Längüägé Séttïngs Ⱡ'σяє#"
|
||||
|
||||
#: lms/templates/modal/_modal-settings-language.html
|
||||
#: lms/templates/student_profile/language.html
|
||||
msgid ""
|
||||
"Don't see your preferred language? {link_start}Volunteer to become a "
|
||||
"translator!{link_end}"
|
||||
@@ -13871,6 +13925,50 @@ msgstr ""
|
||||
msgid "Currently the {platform_name} servers are overloaded"
|
||||
msgstr "Çürréntlý thé {platform_name} sérvérs äré övérlöädéd Ⱡ'σяєм ιρѕυ#"
|
||||
|
||||
#: lms/templates/student_account/email_change_failed.html
|
||||
msgid "Email change failed."
|
||||
msgstr "Émäïl çhängé fäïléd. Ⱡ'σя#"
|
||||
|
||||
#: lms/templates/student_account/email_change_failed.html
|
||||
msgid "Something went wrong. Please contact {support} for help."
|
||||
msgstr ""
|
||||
"Söméthïng wént wröng. Pléäsé çöntäçt {support} för hélp. Ⱡ'σяєм ιρѕυм ∂#"
|
||||
|
||||
#: lms/templates/student_account/email_change_failed.html
|
||||
msgid ""
|
||||
"The email address you wanted to use is already used by another "
|
||||
"{platform_name} account."
|
||||
msgstr ""
|
||||
"Thé émäïl äddréss ýöü wäntéd tö üsé ïs älréädý üséd ßý änöthér "
|
||||
"{platform_name} äççöünt. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт#"
|
||||
|
||||
#: lms/templates/student_account/email_change_failed.html
|
||||
msgid ""
|
||||
"You can try again from the {link_start}account settings{link_end} page."
|
||||
msgstr ""
|
||||
"Ýöü çän trý ägäïn fröm thé {link_start}äççöünt séttïngs{link_end} pägé. "
|
||||
"Ⱡ'σяєм ιρѕυм ∂σł#"
|
||||
|
||||
#: lms/templates/student_account/email_change_successful.html
|
||||
msgid "Email change successful!"
|
||||
msgstr "Émäïl çhängé süççéssfül! Ⱡ'σяє#"
|
||||
|
||||
#: lms/templates/student_account/email_change_successful.html
|
||||
msgid ""
|
||||
"You should see your new email address listed on the {link_start}account "
|
||||
"settings{link_end} page."
|
||||
msgstr ""
|
||||
"Ýöü shöüld séé ýöür néw émäïl äddréss lïstéd ön thé {link_start}äççöünt "
|
||||
"séttïngs{link_end} pägé. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт α#"
|
||||
|
||||
#: lms/templates/student_account/index.html
|
||||
msgid "Student Account"
|
||||
msgstr "Stüdént Àççöünt Ⱡ'#"
|
||||
|
||||
#: lms/templates/student_profile/index.html
|
||||
msgid "Student Profile"
|
||||
msgstr "Stüdént Pröfïlé Ⱡ'#"
|
||||
|
||||
#: lms/templates/verify_student/_modal_editname.html
|
||||
msgid "Edit Your Name"
|
||||
msgstr "Édït Ýöür Nämé Ⱡ'#"
|
||||
@@ -13951,11 +14049,13 @@ msgstr "Ýöü äré üpgrädïng ýöür régïsträtïön för Ⱡ'σяєм ι
|
||||
msgid "You are re-verifying for"
|
||||
msgstr "Ýöü äré ré-vérïfýïng för Ⱡ'σяє#"
|
||||
|
||||
#: lms/templates/verify_student/_verification_header.html
|
||||
#: lms/templates/verify_student/_verification_header.html
|
||||
#: lms/templates/verify_student/_verification_header.html
|
||||
msgid "You are registering for"
|
||||
msgstr "Ýöü äré régïstérïng för Ⱡ'σяє#"
|
||||
|
||||
#: lms/templates/verify_student/_verification_header.html
|
||||
#: lms/templates/verify_student/_verification_header.html
|
||||
msgid "Professional Education"
|
||||
msgstr "Pröféssïönäl Édüçätïön Ⱡ'σяє#"
|
||||
@@ -15235,23 +15335,67 @@ msgstr "Vïéw Lïvé Vérsïön Ⱡ'σ#"
|
||||
msgid "Preview Changes"
|
||||
msgstr "Prévïéw Çhängés Ⱡ'#"
|
||||
|
||||
#: cms/templates/container.html cms/templates/group_configurations.html
|
||||
#: cms/templates/settings_graders.html
|
||||
msgid "What can I do on this page?"
|
||||
msgstr "Whät çän Ì dö ön thïs pägé? Ⱡ'σяєм#"
|
||||
#: cms/templates/container.html
|
||||
msgid "Adding components"
|
||||
msgstr "Àddïng çömpönénts Ⱡ'σ#"
|
||||
|
||||
#: cms/templates/container.html
|
||||
msgid ""
|
||||
"You can view and edit course components that contain other components on "
|
||||
"this page. In the case of experiment blocks, this allows you to confirm that"
|
||||
" you have properly configured your experiment groups and make changes to "
|
||||
"existing content."
|
||||
"Select a component type under {em_start}Add New Component{em_end}. Then "
|
||||
"select a template."
|
||||
msgstr ""
|
||||
"Ýöü çän vïéw änd édït çöürsé çömpönénts thät çöntäïn öthér çömpönénts ön "
|
||||
"thïs pägé. Ìn thé çäsé öf éxpérïmént ßlöçks, thïs ällöws ýöü tö çönfïrm thät"
|
||||
" ýöü hävé pröpérlý çönfïgüréd ýöür éxpérïmént gröüps änd mäké çhängés tö "
|
||||
"éxïstïng çöntént. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α∂ιριѕι¢ιηg єłιт,"
|
||||
" ѕє∂ ∂σ єιυѕмσ∂ тє#"
|
||||
"Séléçt ä çömpönént týpé ündér {em_start}Àdd Néw Çömpönént{em_end}. Thén "
|
||||
"séléçt ä témpläté. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт #"
|
||||
|
||||
#: cms/templates/container.html
|
||||
msgid ""
|
||||
"The new component is added at the bottom of the page or group. You can then "
|
||||
"edit and move the component."
|
||||
msgstr ""
|
||||
"Thé néw çömpönént ïs äddéd ät thé ßöttöm öf thé pägé ör gröüp. Ýöü çän thén "
|
||||
"édït änd mövé thé çömpönént. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢ση#"
|
||||
|
||||
#: cms/templates/container.html
|
||||
msgid "Editing components"
|
||||
msgstr "Édïtïng çömpönénts Ⱡ'σ#"
|
||||
|
||||
#: cms/templates/container.html
|
||||
msgid ""
|
||||
"Click the {em_start}Edit{em_end} icon in a component to edit its content."
|
||||
msgstr ""
|
||||
"Çlïçk thé {em_start}Édït{em_end} ïçön ïn ä çömpönént tö édït ïts çöntént. "
|
||||
"Ⱡ'σяєм ιρѕυм ∂σłσя#"
|
||||
|
||||
#: cms/templates/container.html
|
||||
msgid "Reorganizing components"
|
||||
msgstr "Réörgänïzïng çömpönénts Ⱡ'σяє#"
|
||||
|
||||
#: cms/templates/container.html
|
||||
msgid "Drag components to new locations within this component."
|
||||
msgstr ""
|
||||
"Dräg çömpönénts tö néw löçätïöns wïthïn thïs çömpönént. Ⱡ'σяєм ιρѕυм ∂σł#"
|
||||
|
||||
#: cms/templates/container.html
|
||||
msgid "For content experiments, you can drag components to other groups."
|
||||
msgstr ""
|
||||
"För çöntént éxpérïménts, ýöü çän dräg çömpönénts tö öthér gröüps. Ⱡ'σяєм "
|
||||
"ιρѕυм ∂σłσя #"
|
||||
|
||||
#: cms/templates/container.html
|
||||
msgid "Working with content experiments"
|
||||
msgstr "Wörkïng wïth çöntént éxpérïménts Ⱡ'σяєм ι#"
|
||||
|
||||
#: cms/templates/container.html
|
||||
msgid ""
|
||||
"Confirm that you have properly configured content in each of your experiment"
|
||||
" groups."
|
||||
msgstr ""
|
||||
"Çönfïrm thät ýöü hävé pröpérlý çönfïgüréd çöntént ïn éäçh öf ýöür éxpérïmént"
|
||||
" gröüps. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αм#"
|
||||
|
||||
#: cms/templates/container.html
|
||||
msgid "Learn more about component containers"
|
||||
msgstr "Léärn möré äßöüt çömpönént çöntäïnérs Ⱡ'σяєм ιρѕ#"
|
||||
|
||||
#: cms/templates/container.html
|
||||
msgid "Unit Location"
|
||||
@@ -15920,6 +16064,10 @@ msgstr "Néw Gröüp Çönfïgürätïön Ⱡ'σяє#"
|
||||
msgid "This module is disabled at the moment."
|
||||
msgstr "Thïs mödülé ïs dïsäßléd ät thé mömént. Ⱡ'σяєм ιρѕ#"
|
||||
|
||||
#: cms/templates/group_configurations.html cms/templates/settings_graders.html
|
||||
msgid "What can I do on this page?"
|
||||
msgstr "Whät çän Ì dö ön thïs pägé? Ⱡ'σяєм#"
|
||||
|
||||
#: cms/templates/group_configurations.html
|
||||
msgid "You can create, edit, and delete group configurations."
|
||||
msgstr ""
|
||||
|
||||
Binary file not shown.
@@ -26,8 +26,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.1a\n"
|
||||
"Report-Msgid-Bugs-To: openedx-translation@googlegroups.com\n"
|
||||
"POT-Creation-Date: 2014-09-24 14:15-0400\n"
|
||||
"PO-Revision-Date: 2014-09-24 18:16:48.345620\n"
|
||||
"POT-Creation-Date: 2014-10-01 13:57+0000\n"
|
||||
"PO-Revision-Date: 2014-10-01 13:57:56.490708\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: openedx-translation <openedx-translation@googlegroups.com>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -1538,6 +1538,36 @@ msgstr ""
|
||||
"Ýöür ßröwsér döésn't süppört dïréçt äççéss tö thé çlïpßöärd. Pléäsé üsé thé "
|
||||
"Çtrl+X/Ç/V kéýßöärd shörtçüts ïnstéäd. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢#"
|
||||
|
||||
#: common/lib/xmodule/xmodule/js/src/lti/lti.js
|
||||
msgid ""
|
||||
"Click OK to have your username and e-mail address sent to a 3rd party application.\n"
|
||||
"\n"
|
||||
"Click Cancel to return to this page without sending your information."
|
||||
msgstr ""
|
||||
"Çlïçk ÖK tö hävé ýöür üsérnämé änd é-mäïl äddréss sént tö ä 3rd pärtý äpplïçätïön.\n"
|
||||
"\n"
|
||||
"Çlïçk Çänçél tö rétürn tö thïs pägé wïthöüt séndïng ýöür ïnförmätïön. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α∂ιριѕι#"
|
||||
|
||||
#: common/lib/xmodule/xmodule/js/src/lti/lti.js
|
||||
msgid ""
|
||||
"Click OK to have your username sent to a 3rd party application.\n"
|
||||
"\n"
|
||||
"Click Cancel to return to this page without sending your information."
|
||||
msgstr ""
|
||||
"Çlïçk ÖK tö hävé ýöür üsérnämé sént tö ä 3rd pärtý äpplïçätïön.\n"
|
||||
"\n"
|
||||
"Çlïçk Çänçél tö rétürn tö thïs pägé wïthöüt séndïng ýöür ïnförmätïön. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α#"
|
||||
|
||||
#: common/lib/xmodule/xmodule/js/src/lti/lti.js
|
||||
msgid ""
|
||||
"Click OK to have your e-mail address sent to a 3rd party application.\n"
|
||||
"\n"
|
||||
"Click Cancel to return to this page without sending your information."
|
||||
msgstr ""
|
||||
"Çlïçk ÖK tö hävé ýöür é-mäïl äddréss sént tö ä 3rd pärtý äpplïçätïön.\n"
|
||||
"\n"
|
||||
"Çlïçk Çänçél tö rétürn tö thïs pägé wïthöüt séndïng ýöür ïnförmätïön. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α∂ι#"
|
||||
|
||||
#: common/lib/xmodule/xmodule/js/src/sequence/display.js
|
||||
msgid ""
|
||||
"Sequence error! Cannot navigate to tab %(tab_name)s in the current "
|
||||
@@ -1747,9 +1777,7 @@ msgstr ""
|
||||
#: common/static/coffee/src/discussion/utils.js
|
||||
#: common/static/coffee/src/discussion/views/discussion_thread_list_view.js
|
||||
#: common/static/coffee/src/discussion/views/discussion_thread_list_view.js
|
||||
#: common/static/coffee/src/discussion/views/new_post_view.js
|
||||
#: common/static/coffee/src/discussion/views/new_post_view.js
|
||||
#: common/static/coffee/src/discussion/views/new_post_view.js
|
||||
#: common/static/coffee/src/discussion/views/discussion_topic_menu_view.js
|
||||
msgid "…"
|
||||
msgstr "… #"
|
||||
|
||||
@@ -2373,33 +2401,6 @@ msgstr "Çlösé Çälçülätör Ⱡ'σ#"
|
||||
msgid "Post body"
|
||||
msgstr "Pöst ßödý #"
|
||||
|
||||
#. Translators: "Distribution" refers to a grade distribution. This error
|
||||
#. message appears when there is an error getting the data on grade
|
||||
#. distribution.;
|
||||
#: lms/static/coffee/src/instructor_dashboard/analytics.js
|
||||
msgid "Error fetching distribution."
|
||||
msgstr "Érrör fétçhïng dïstrïßütïön. Ⱡ'σяєм #"
|
||||
|
||||
#: lms/static/coffee/src/instructor_dashboard/analytics.js
|
||||
#: lms/static/coffee/src/instructor_dashboard/instructor_analytics.js
|
||||
msgid "Unavailable metric display."
|
||||
msgstr "Ûnäväïläßlé métrïç dïspläý. Ⱡ'σяєм#"
|
||||
|
||||
#: lms/static/coffee/src/instructor_dashboard/analytics.js
|
||||
#: lms/static/coffee/src/instructor_dashboard/instructor_analytics.js
|
||||
msgid "Error fetching grade distributions."
|
||||
msgstr "Érrör fétçhïng grädé dïstrïßütïöns. Ⱡ'σяєм ιρ#"
|
||||
|
||||
#: lms/static/coffee/src/instructor_dashboard/analytics.js
|
||||
#: lms/static/coffee/src/instructor_dashboard/instructor_analytics.js
|
||||
msgid "Last Updated: <%= timestamp %>"
|
||||
msgstr "Läst Ûpdätéd: <%= timestamp %> Ⱡ'σ#"
|
||||
|
||||
#: lms/static/coffee/src/instructor_dashboard/analytics.js
|
||||
#: lms/static/coffee/src/instructor_dashboard/instructor_analytics.js
|
||||
msgid "<%= num_students %> students scored."
|
||||
msgstr "<%= num_students %> stüdénts sçöréd. Ⱡ'σя#"
|
||||
|
||||
#: lms/static/coffee/src/instructor_dashboard/data_download.js
|
||||
#: cms/templates/js/mock/mock-group-configuration-page.underscore
|
||||
msgid "Loading..."
|
||||
@@ -2429,6 +2430,22 @@ msgstr ""
|
||||
"Lïnks äré générätéd ön démänd änd éxpïré wïthïn 5 mïnütés düé tö thé "
|
||||
"sénsïtïvé nätüré öf stüdént ïnförmätïön. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕ#"
|
||||
|
||||
#: lms/static/coffee/src/instructor_dashboard/instructor_analytics.js
|
||||
msgid "Unavailable metric display."
|
||||
msgstr "Ûnäväïläßlé métrïç dïspläý. Ⱡ'σяєм#"
|
||||
|
||||
#: lms/static/coffee/src/instructor_dashboard/instructor_analytics.js
|
||||
msgid "Error fetching grade distributions."
|
||||
msgstr "Érrör fétçhïng grädé dïstrïßütïöns. Ⱡ'σяєм ιρ#"
|
||||
|
||||
#: lms/static/coffee/src/instructor_dashboard/instructor_analytics.js
|
||||
msgid "Last Updated: <%= timestamp %>"
|
||||
msgstr "Läst Ûpdätéd: <%= timestamp %> Ⱡ'σ#"
|
||||
|
||||
#: lms/static/coffee/src/instructor_dashboard/instructor_analytics.js
|
||||
msgid "<%= num_students %> students scored."
|
||||
msgstr "<%= num_students %> stüdénts sçöréd. Ⱡ'σя#"
|
||||
|
||||
#: lms/static/coffee/src/instructor_dashboard/membership.js
|
||||
msgid "Username"
|
||||
msgstr "Ûsérnämé #"
|
||||
@@ -3122,6 +3139,31 @@ msgstr "Süççéssfüllý résçöréd prößlém för üsér {user} Ⱡ'σяє
|
||||
msgid "Failed to rescore problem."
|
||||
msgstr "Fäïléd tö résçöré prößlém. Ⱡ'σяєм#"
|
||||
|
||||
#: lms/static/js/student_account/account.js
|
||||
#: lms/static/js/student_profile/profile.js
|
||||
msgid "The data could not be saved."
|
||||
msgstr "Thé dätä çöüld nöt ßé sävéd. Ⱡ'σяєм #"
|
||||
|
||||
#: lms/static/js/student_account/account.js
|
||||
msgid "Please enter a valid email address"
|
||||
msgstr "Pléäsé éntér ä välïd émäïl äddréss Ⱡ'σяєм ιρ#"
|
||||
|
||||
#: lms/static/js/student_account/account.js
|
||||
msgid "Please enter a valid password"
|
||||
msgstr "Pléäsé éntér ä välïd pässwörd Ⱡ'σяєм #"
|
||||
|
||||
#: lms/static/js/student_account/account.js
|
||||
msgid "Please check your email to confirm the change"
|
||||
msgstr "Pléäsé çhéçk ýöür émäïl tö çönfïrm thé çhängé Ⱡ'σяєм ιρѕυм#"
|
||||
|
||||
#: lms/static/js/student_profile/profile.js
|
||||
msgid "Full name cannot be blank"
|
||||
msgstr "Füll nämé çännöt ßé ßlänk Ⱡ'σяєм#"
|
||||
|
||||
#: lms/static/js/student_profile/profile.js
|
||||
msgid "Saved"
|
||||
msgstr "Sävéd Ⱡ'σяєм ι#"
|
||||
|
||||
#: lms/templates/class_dashboard/all_section_metrics.js
|
||||
#: lms/templates/class_dashboard/all_section_metrics.js
|
||||
msgid "Unable to retrieve data, please try again later."
|
||||
|
||||
@@ -62,7 +62,7 @@ class StudentAccountViewTest(UrlResetMixin, TestCase):
|
||||
|
||||
def test_change_email(self):
|
||||
response = self._change_email(self.NEW_EMAIL, self.PASSWORD)
|
||||
self.assertEquals(response.status_code, 204)
|
||||
self.assertEquals(response.status_code, 200)
|
||||
|
||||
# Verify that the email associated with the account remains unchanged
|
||||
profile_info = profile_api.profile_info(self.USERNAME)
|
||||
@@ -79,7 +79,7 @@ class StudentAccountViewTest(UrlResetMixin, TestCase):
|
||||
|
||||
# Retrieve the activation key from the email
|
||||
email_body = mail.outbox[0].body
|
||||
result = re.search('/email_change_confirm/([^ \n]+)', email_body)
|
||||
result = re.search('/email/confirmation/([^ \n]+)', email_body)
|
||||
self.assertIsNot(result, None)
|
||||
activation_key = result.group(1)
|
||||
|
||||
@@ -127,7 +127,7 @@ class StudentAccountViewTest(UrlResetMixin, TestCase):
|
||||
|
||||
# Request to change the original user's email to the email used by the inactive user
|
||||
response = self._change_email(self.NEW_EMAIL, self.PASSWORD)
|
||||
self.assertEquals(response.status_code, 204)
|
||||
self.assertEquals(response.status_code, 200)
|
||||
|
||||
@ddt.data(*INVALID_EMAILS)
|
||||
def test_email_change_request_email_invalid(self, invalid_email):
|
||||
@@ -192,14 +192,15 @@ class StudentAccountViewTest(UrlResetMixin, TestCase):
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
@ddt.data(
|
||||
('get', 'account_index'),
|
||||
('put', 'email_change_request')
|
||||
('get', 'account_index', []),
|
||||
('post', 'email_change_request', []),
|
||||
('get', 'email_change_confirm', [123])
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_require_login(self, method, url_name):
|
||||
def test_require_login(self, method, url_name, args):
|
||||
# Access the page while logged out
|
||||
self.client.logout()
|
||||
url = reverse(url_name)
|
||||
url = reverse(url_name, args=args)
|
||||
response = getattr(self.client, method)(url, follow=True)
|
||||
|
||||
# Should have been redirected to the login page
|
||||
@@ -207,13 +208,14 @@ class StudentAccountViewTest(UrlResetMixin, TestCase):
|
||||
self.assertIn('accounts/login?next=', response.redirect_chain[0][0])
|
||||
|
||||
@ddt.data(
|
||||
('get', 'account_index'),
|
||||
('put', 'email_change_request')
|
||||
('get', 'account_index', []),
|
||||
('post', 'email_change_request', []),
|
||||
('get', 'email_change_confirm', [123])
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_require_http_method(self, correct_method, url_name):
|
||||
def test_require_http_method(self, correct_method, url_name, args):
|
||||
wrong_methods = {'get', 'put', 'post', 'head', 'options', 'delete'} - {correct_method}
|
||||
url = reverse(url_name)
|
||||
url = reverse(url_name, args=args)
|
||||
|
||||
for method in wrong_methods:
|
||||
response = getattr(self.client, method)(url)
|
||||
@@ -230,15 +232,9 @@ class StudentAccountViewTest(UrlResetMixin, TestCase):
|
||||
data = {}
|
||||
|
||||
if new_email is not None:
|
||||
data['new_email'] = new_email
|
||||
data['email'] = new_email
|
||||
if password is not None:
|
||||
# We can't pass a Unicode object to urlencode, so we encode the Unicode object
|
||||
data['password'] = password.encode('utf-8')
|
||||
|
||||
response = self.client.put(
|
||||
path=reverse('email_change_request'),
|
||||
data=urlencode(data),
|
||||
content_type='application/x-www-form-urlencoded'
|
||||
)
|
||||
|
||||
return response
|
||||
return self.client.post(path=reverse('email_change_request'), data=data)
|
||||
|
||||
@@ -3,6 +3,6 @@ from django.conf.urls import patterns, url
|
||||
urlpatterns = patterns(
|
||||
'student_account.views',
|
||||
url(r'^$', 'index', name='account_index'),
|
||||
url(r'^email_change_request$', 'email_change_request_handler', name='email_change_request'),
|
||||
url(r'^email_change_confirm/(?P<key>[^/]*)$', 'email_change_confirmation_handler', name='email_change_confirm'),
|
||||
url(r'^email$', 'email_change_request_handler', name='email_change_request'),
|
||||
url(r'^email/confirmation/(?P<key>[^/]*)$', 'email_change_confirmation_handler', name='email_change_confirm'),
|
||||
)
|
||||
|
||||
@@ -42,7 +42,7 @@ def index(request):
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(['PUT'])
|
||||
@require_http_methods(['POST'])
|
||||
@ensure_csrf_cookie
|
||||
def email_change_request_handler(request):
|
||||
"""Handle a request to change the user's email address.
|
||||
@@ -51,7 +51,7 @@ def email_change_request_handler(request):
|
||||
request (HttpRequest)
|
||||
|
||||
Returns:
|
||||
HttpResponse: 204 if the confirmation email was sent successfully
|
||||
HttpResponse: 200 if the confirmation email was sent successfully
|
||||
HttpResponse: 302 if not logged in (redirect to login page)
|
||||
HttpResponse: 400 if the format of the new email is incorrect
|
||||
HttpResponse: 401 if the provided password (in the form) is incorrect
|
||||
@@ -62,22 +62,20 @@ def email_change_request_handler(request):
|
||||
|
||||
Example usage:
|
||||
|
||||
PUT /account/email_change_request
|
||||
POST /account/email
|
||||
|
||||
"""
|
||||
put = QueryDict(request.body)
|
||||
user = request.user
|
||||
password = put.get('password')
|
||||
|
||||
username = user.username
|
||||
old_email = profile_api.profile_info(username)['email']
|
||||
new_email = put.get('new_email')
|
||||
username = request.user.username
|
||||
password = request.POST.get('password')
|
||||
new_email = request.POST.get('email')
|
||||
|
||||
if new_email is None:
|
||||
return HttpResponseBadRequest("Missing param 'new_email'")
|
||||
return HttpResponseBadRequest("Missing param 'email'")
|
||||
if password is None:
|
||||
return HttpResponseBadRequest("Missing param 'password'")
|
||||
|
||||
old_email = profile_api.profile_info(username)['email']
|
||||
|
||||
try:
|
||||
key = account_api.request_email_change(username, new_email, password)
|
||||
except account_api.AccountUserNotFound:
|
||||
@@ -104,12 +102,11 @@ def email_change_request_handler(request):
|
||||
settings.DEFAULT_FROM_EMAIL
|
||||
)
|
||||
|
||||
# Email new address
|
||||
# Send a confirmation email to the new address containing the activation key
|
||||
send_mail(subject, message, from_address, [new_email])
|
||||
|
||||
# A 204 is intended to allow input for actions to take place
|
||||
# without causing a change to the user agent's active document view.
|
||||
return HttpResponse(status=204)
|
||||
# Send a 200 response code to the client to indicate that the email was sent successfully.
|
||||
return HttpResponse(status=200)
|
||||
|
||||
|
||||
@login_required
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
""" Tests for student profile views. """
|
||||
|
||||
from urllib import urlencode
|
||||
from collections import namedtuple
|
||||
import json
|
||||
|
||||
from mock import patch
|
||||
import ddt
|
||||
@@ -13,7 +13,7 @@ from django.core.urlresolvers import reverse
|
||||
from util.testing import UrlResetMixin
|
||||
from user_api.api import account as account_api
|
||||
from user_api.api import profile as profile_api
|
||||
from lang_pref import LANGUAGE_KEY
|
||||
from lang_pref import LANGUAGE_KEY, api as language_api
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@@ -25,8 +25,7 @@ class StudentProfileViewTest(UrlResetMixin, TestCase):
|
||||
EMAIL = u'walt@savewalterwhite.com'
|
||||
FULL_NAME = u'𝖂𝖆𝖑𝖙𝖊𝖗 𝖂𝖍𝖎𝖙𝖊'
|
||||
|
||||
Language = namedtuple('Language', 'code name')
|
||||
NEW_LANGUAGE = Language('fr', u'Français')
|
||||
TEST_LANGUAGE = language_api.Language('eo', u'Dummy language')
|
||||
|
||||
INVALID_LANGUAGE_CODES = [
|
||||
'',
|
||||
@@ -49,8 +48,6 @@ class StudentProfileViewTest(UrlResetMixin, TestCase):
|
||||
def test_index(self):
|
||||
response = self.client.get(reverse('profile_index'))
|
||||
self.assertContains(response, "Student Profile")
|
||||
self.assertContains(response, "Change My Name")
|
||||
self.assertContains(response, "Change Preferred Language")
|
||||
self.assertContains(response, "Connected Accounts")
|
||||
|
||||
def test_name_change(self):
|
||||
@@ -81,45 +78,61 @@ class StudentProfileViewTest(UrlResetMixin, TestCase):
|
||||
response = self._change_name(self.FULL_NAME)
|
||||
self.assertEqual(response.status_code, 500)
|
||||
|
||||
@patch('student_profile.views.language_api.preferred_language')
|
||||
@patch('student_profile.views.language_api.released_languages')
|
||||
def test_get_released_languages(self, mock_released_languages, mock_preferred_language):
|
||||
mock_released_languages.return_value = [self.TEST_LANGUAGE]
|
||||
mock_preferred_language.return_value = self.TEST_LANGUAGE
|
||||
|
||||
response = self.client.get(reverse('language_info'))
|
||||
self.assertEqual(
|
||||
json.loads(response.content),
|
||||
{
|
||||
'preferredLanguage': {'code': self.TEST_LANGUAGE.code, 'name': self.TEST_LANGUAGE.name},
|
||||
'languages': [{'code': self.TEST_LANGUAGE.code, 'name': self.TEST_LANGUAGE.name}]
|
||||
}
|
||||
)
|
||||
|
||||
@patch('student_profile.views.language_api.released_languages')
|
||||
def test_language_change(self, mock_released_languages):
|
||||
mock_released_languages.return_value = [self.NEW_LANGUAGE]
|
||||
mock_released_languages.return_value = [self.TEST_LANGUAGE]
|
||||
|
||||
# Set French as the user's preferred language
|
||||
response = self._change_language(self.NEW_LANGUAGE.code)
|
||||
# Set the dummy language as the user's preferred language
|
||||
response = self._change_preferences(language=self.TEST_LANGUAGE.code)
|
||||
self.assertEqual(response.status_code, 204)
|
||||
|
||||
# Verify that French is now the user's preferred language
|
||||
# Verify that the dummy language is now the user's preferred language
|
||||
preferences = profile_api.preference_info(self.USERNAME)
|
||||
self.assertEqual(preferences[LANGUAGE_KEY], self.NEW_LANGUAGE.code)
|
||||
self.assertEqual(preferences[LANGUAGE_KEY], self.TEST_LANGUAGE.code)
|
||||
|
||||
# Verify that the page reloads in French
|
||||
# Verify that the page reloads in the dummy language
|
||||
response = self.client.get(reverse('profile_index'))
|
||||
self.assertContains(response, "Merci de choisir la langue")
|
||||
self.assertContains(response, u"Stüdént Pröfïlé")
|
||||
|
||||
@ddt.data(*INVALID_LANGUAGE_CODES)
|
||||
def test_change_to_invalid_or_unreleased_language(self, language_code):
|
||||
response = self._change_language(language_code)
|
||||
response = self._change_preferences(language=language_code)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
def test_change_to_missing_language(self):
|
||||
response = self._change_language(None)
|
||||
response = self._change_preferences(language=None)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
@patch('student_profile.views.profile_api.update_preferences')
|
||||
@patch('student_profile.views.language_api.released_languages')
|
||||
def test_language_change_missing_profile(self, mock_released_languages, mock_update_preferences):
|
||||
# This can't happen if the user is logged in, but test it anyway
|
||||
mock_released_languages.return_value = [self.NEW_LANGUAGE]
|
||||
mock_released_languages.return_value = [self.TEST_LANGUAGE]
|
||||
mock_update_preferences.side_effect = profile_api.ProfileUserNotFound
|
||||
|
||||
response = self._change_language(self.NEW_LANGUAGE.code)
|
||||
response = self._change_preferences(language=self.TEST_LANGUAGE.code)
|
||||
self.assertEqual(response.status_code, 500)
|
||||
|
||||
@ddt.data(
|
||||
('get', 'profile_index'),
|
||||
('put', 'name_change'),
|
||||
('put', 'language_change')
|
||||
('put', 'profile_index'),
|
||||
('put', 'preference_handler'),
|
||||
('get', 'language_info'),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_require_login(self, method, url_name):
|
||||
@@ -133,13 +146,13 @@ class StudentProfileViewTest(UrlResetMixin, TestCase):
|
||||
self.assertIn('accounts/login?next=', response.redirect_chain[0][0])
|
||||
|
||||
@ddt.data(
|
||||
('get', 'profile_index'),
|
||||
('put', 'name_change'),
|
||||
('put', 'language_change')
|
||||
(['get', 'put'], 'profile_index'),
|
||||
(['put'], 'preference_handler'),
|
||||
(['get'], 'language_info'),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_require_http_method(self, correct_method, url_name):
|
||||
wrong_methods = {'get', 'put', 'post', 'head', 'options', 'delete'} - {correct_method}
|
||||
def test_require_http_method(self, correct_methods, url_name):
|
||||
wrong_methods = {'get', 'put', 'post', 'head', 'options', 'delete'} - set(correct_methods)
|
||||
url = reverse(url_name)
|
||||
|
||||
for method in wrong_methods:
|
||||
@@ -156,27 +169,28 @@ class StudentProfileViewTest(UrlResetMixin, TestCase):
|
||||
data = {}
|
||||
if new_name is not None:
|
||||
# We can't pass a Unicode object to urlencode, so we encode the Unicode object
|
||||
data['new_name'] = new_name.encode('utf-8')
|
||||
data['fullName'] = new_name.encode('utf-8')
|
||||
|
||||
return self.client.put(
|
||||
path=reverse('name_change'),
|
||||
path=reverse('profile_index'),
|
||||
data=urlencode(data),
|
||||
content_type='application/x-www-form-urlencoded'
|
||||
)
|
||||
|
||||
def _change_language(self, new_language):
|
||||
"""Request a language change.
|
||||
def _change_preferences(self, **preferences):
|
||||
"""Request a change to the user's preferences.
|
||||
|
||||
Returns:
|
||||
HttpResponse
|
||||
|
||||
"""
|
||||
data = {}
|
||||
if new_language is not None:
|
||||
data['new_language'] = new_language
|
||||
for key, value in preferences.iteritems():
|
||||
if value is not None:
|
||||
data[key] = value
|
||||
|
||||
return self.client.put(
|
||||
path=reverse('language_change'),
|
||||
path=reverse('preference_handler'),
|
||||
data=urlencode(data),
|
||||
content_type='application/x-www-form-urlencoded'
|
||||
)
|
||||
|
||||
@@ -3,6 +3,6 @@ from django.conf.urls import patterns, url
|
||||
urlpatterns = patterns(
|
||||
'student_profile.views',
|
||||
url(r'^$', 'index', name='profile_index'),
|
||||
url(r'^name_change$', 'name_change_handler', name='name_change'),
|
||||
url(r'^language_change$', 'language_change_handler', name='language_change'),
|
||||
url(r'^preferences$', 'preference_handler', name='preference_handler'),
|
||||
url(r'^preferences/languages$', 'language_info', name='language_info'),
|
||||
)
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
""" Views for a student's profile information. """
|
||||
|
||||
from django.conf import settings
|
||||
import json
|
||||
|
||||
from django.http import (
|
||||
QueryDict, HttpResponse,
|
||||
HttpResponseBadRequest, HttpResponseServerError
|
||||
)
|
||||
from django.conf import settings
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from django_future.csrf import ensure_csrf_cookie
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.views.decorators.http import require_http_methods
|
||||
|
||||
from edxmako.shortcuts import render_to_response
|
||||
from user_api.api import profile as profile_api
|
||||
from lang_pref import LANGUAGE_KEY, api as language_api
|
||||
@@ -16,34 +17,47 @@ from third_party_auth import pipeline
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(['GET'])
|
||||
def index(request):
|
||||
"""Render the profile info page.
|
||||
"""View or modify the student's profile.
|
||||
|
||||
GET: Retrieve the user's profile information.
|
||||
PUT: Update the user's profile information. Currently the only accept param is "fullName".
|
||||
|
||||
Args:
|
||||
request (HttpRequest)
|
||||
|
||||
Returns:
|
||||
HttpResponse: 200 if successful
|
||||
HttpResponse: 200 if successful on GET
|
||||
HttpResponse: 204 if successful on PUT
|
||||
HttpResponse: 302 if not logged in (redirect to login page)
|
||||
HttpResponse: 400 if the updated information is invalid
|
||||
HttpResponse: 405 if using an unsupported HTTP method
|
||||
HttpResponse: 500 if an unexpected error occurs.
|
||||
|
||||
Example:
|
||||
"""
|
||||
if request.method == "GET":
|
||||
return _get_profile(request)
|
||||
elif request.method == "PUT":
|
||||
return _update_profile(request)
|
||||
else:
|
||||
return HttpResponse(status=405)
|
||||
|
||||
GET /profile
|
||||
|
||||
def _get_profile(request):
|
||||
"""Retrieve the user's profile information, including an HTML form
|
||||
that students can use to update the information.
|
||||
|
||||
Args:
|
||||
request (HttpRequest)
|
||||
|
||||
Returns:
|
||||
HttpResponse
|
||||
|
||||
"""
|
||||
user = request.user
|
||||
|
||||
released_languages = language_api.released_languages()
|
||||
|
||||
preferred_language_code = profile_api.preference_info(user.username).get(LANGUAGE_KEY)
|
||||
preferred_language = language_api.preferred_language(preferred_language_code)
|
||||
|
||||
context = {
|
||||
'disable_courseware_js': True,
|
||||
'released_languages': released_languages,
|
||||
'preferred_language': preferred_language,
|
||||
'disable_courseware_js': True
|
||||
}
|
||||
|
||||
if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH'):
|
||||
@@ -52,34 +66,24 @@ def index(request):
|
||||
return render_to_response('student_profile/index.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(['PUT'])
|
||||
@ensure_csrf_cookie
|
||||
def name_change_handler(request):
|
||||
"""Change the user's name.
|
||||
def _update_profile(request):
|
||||
"""Update a user's profile information.
|
||||
|
||||
Args:
|
||||
request (HttpRequest)
|
||||
|
||||
Returns:
|
||||
HttpResponse: 204 if successful
|
||||
HttpResponse: 302 if not logged in (redirect to login page)
|
||||
HttpResponse: 400 if the provided name is invalid
|
||||
HttpResponse: 405 if using an unsupported HTTP method
|
||||
HttpResponse: 500 if an unexpected error occurs.
|
||||
|
||||
Example:
|
||||
|
||||
PUT /profile/name_change
|
||||
HttpResponse
|
||||
|
||||
"""
|
||||
put = QueryDict(request.body)
|
||||
|
||||
username = request.user.username
|
||||
new_name = put.get('new_name')
|
||||
new_name = put.get('fullName')
|
||||
|
||||
if new_name is None:
|
||||
return HttpResponseBadRequest("Missing param 'new_name'")
|
||||
return HttpResponseBadRequest("Missing param 'fullName'")
|
||||
|
||||
try:
|
||||
profile_api.update_profile(username, full_name=new_name)
|
||||
@@ -93,11 +97,48 @@ def name_change_handler(request):
|
||||
return HttpResponse(status=204)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(['GET'])
|
||||
def language_info(request):
|
||||
"""Retrieve information about languages.
|
||||
|
||||
Gets the user's preferred language and the list of released
|
||||
languages, encoding the information as JSON.
|
||||
|
||||
Args:
|
||||
request (HttpRequest)
|
||||
|
||||
Returns:
|
||||
HttpResponse: 200 if successful on GET
|
||||
HttpResponse: 302 if not logged in (redirect to login page)
|
||||
HttpResponse: 405 if using an unsupported HTTP method
|
||||
HttpResponse: 500 if an unexpected error occurs
|
||||
|
||||
Example:
|
||||
|
||||
GET /profile/preferences/languages
|
||||
|
||||
"""
|
||||
user = request.user
|
||||
|
||||
preferred_language_code = profile_api.preference_info(user.username).get(LANGUAGE_KEY)
|
||||
preferred_language = language_api.preferred_language(preferred_language_code)
|
||||
response_data = {'preferredLanguage': {'code': preferred_language.code, 'name': preferred_language.name}}
|
||||
|
||||
languages = language_api.released_languages()
|
||||
response_data['languages'] = [{'code': language.code, 'name': language.name} for language in languages]
|
||||
|
||||
return HttpResponse(json.dumps(response_data), content_type='application/json')
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(['PUT'])
|
||||
@ensure_csrf_cookie
|
||||
def language_change_handler(request):
|
||||
"""Change the user's language preference.
|
||||
def preference_handler(request):
|
||||
"""Change the user's preferences.
|
||||
|
||||
At the moment, the only supported preference is the user's
|
||||
language choice.
|
||||
|
||||
Args:
|
||||
request (HttpRequest)
|
||||
@@ -112,16 +153,16 @@ def language_change_handler(request):
|
||||
|
||||
Example:
|
||||
|
||||
PUT /profile/language_change
|
||||
PUT /profile/preferences
|
||||
|
||||
"""
|
||||
put = QueryDict(request.body)
|
||||
|
||||
username = request.user.username
|
||||
new_language = put.get('new_language')
|
||||
new_language = put.get('language')
|
||||
|
||||
if new_language is None:
|
||||
return HttpResponseBadRequest("Missing param 'new_language'")
|
||||
return HttpResponseBadRequest("Missing param 'language'")
|
||||
|
||||
# Check that the provided language code corresponds to a released language
|
||||
released_languages = language_api.released_languages()
|
||||
|
||||
@@ -212,6 +212,14 @@
|
||||
},
|
||||
|
||||
// LMS class loaded explicitly until they are converted to use RequireJS
|
||||
'js/student_account/account': {
|
||||
exports: 'js/student_account/account',
|
||||
deps: ['jquery', 'underscore', 'backbone', 'gettext', 'jquery.cookie']
|
||||
},
|
||||
'js/student_profile/profile': {
|
||||
exports: 'js/student_profile/profile',
|
||||
deps: ['jquery', 'underscore', 'backbone', 'gettext', 'jquery.cookie']
|
||||
},
|
||||
'js/verify_student/photocapture': {
|
||||
exports: 'js/verify_student/photocapture'
|
||||
},
|
||||
@@ -261,6 +269,8 @@
|
||||
'lms/include/js/spec/staff_debug_actions_spec.js',
|
||||
'lms/include/js/spec/views/notification_spec.js',
|
||||
'lms/include/js/spec/dashboard/donation.js',
|
||||
'lms/include/js/spec/student_account/account.js',
|
||||
'lms/include/js/spec/student_profile/profile.js'
|
||||
]);
|
||||
|
||||
}).call(this, requirejs, define);
|
||||
|
||||
196
lms/static/js/spec/student_account/account.js
Normal file
196
lms/static/js/spec/student_account/account.js
Normal file
@@ -0,0 +1,196 @@
|
||||
define(['js/student_account/account'],
|
||||
function() {
|
||||
describe("edx.student.account.AccountModel", function() {
|
||||
'use strict';
|
||||
|
||||
var account = null;
|
||||
|
||||
var assertValid = function(fields, isValid, expectedErrors) {
|
||||
account.set(fields);
|
||||
var errors = account.validate(account.attributes);
|
||||
|
||||
if (isValid) {
|
||||
expect(errors).toBe(undefined);
|
||||
} else {
|
||||
expect(errors).toEqual(expectedErrors);
|
||||
}
|
||||
};
|
||||
|
||||
var EXPECTED_ERRORS = {
|
||||
email: {
|
||||
email: "Please enter a valid email address"
|
||||
},
|
||||
password: {
|
||||
password: "Please enter a valid password"
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
account = new edx.student.account.AccountModel();
|
||||
account.set({
|
||||
email: "bob@example.com",
|
||||
password: "password"
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts valid email addresses", function() {
|
||||
assertValid({email: "bob@example.com"}, true);
|
||||
assertValid({email: "bob+smith@example.com"}, true);
|
||||
assertValid({email: "bob+smith@example.com"}, true);
|
||||
assertValid({email: "bob+smith@example.com"}, true);
|
||||
assertValid({email: "bob@test.example.com"}, true);
|
||||
assertValid({email: "bob@test-example.com"}, true);
|
||||
});
|
||||
|
||||
it("rejects blank email addresses", function() {
|
||||
assertValid({email: ""}, false, EXPECTED_ERRORS.email);
|
||||
assertValid({email: " "}, false, EXPECTED_ERRORS.email);
|
||||
});
|
||||
|
||||
it("rejects invalid email addresses", function() {
|
||||
assertValid({email: "bob"}, false, EXPECTED_ERRORS.email);
|
||||
assertValid({email: "bob@example"}, false, EXPECTED_ERRORS.email);
|
||||
assertValid({email: "@"}, false, EXPECTED_ERRORS.email);
|
||||
assertValid({email: "@example.com"}, false, EXPECTED_ERRORS.email);
|
||||
|
||||
// The server will reject emails with non-ASCII unicode
|
||||
// Technically these are valid email addresses, but the email validator
|
||||
// in Django 1.4 will reject them anyway, so we should too.
|
||||
assertValid({email: "fŕáńḱ@example.com"}, false, EXPECTED_ERRORS.email);
|
||||
assertValid({email: "frank@éxáḿṕĺé.com"}, false, EXPECTED_ERRORS.email);
|
||||
});
|
||||
|
||||
it("rejects a long email address", function() {
|
||||
// Construct an email exactly one character longer than the maximum length
|
||||
var longEmail = new Array(account.EMAIL_MAX_LENGTH - 10).join("e") + "@example.com";
|
||||
assertValid({email: longEmail}, false, EXPECTED_ERRORS.email);
|
||||
});
|
||||
|
||||
it("accepts a valid password", function() {
|
||||
assertValid({password: "password-test123"}, true, EXPECTED_ERRORS.password);
|
||||
});
|
||||
|
||||
it("rejects a short password", function() {
|
||||
assertValid({password: ""}, false, EXPECTED_ERRORS.password);
|
||||
assertValid({password: "a"}, false, EXPECTED_ERRORS.password);
|
||||
assertValid({password: "aa"}, true, EXPECTED_ERRORS.password);
|
||||
});
|
||||
|
||||
it("rejects a long password", function() {
|
||||
// Construct a password exactly one character longer than the maximum length
|
||||
var longPassword = new Array(account.PASSWORD_MAX_LENGTH + 2).join("a");
|
||||
assertValid({password: longPassword}, false, EXPECTED_ERRORS.password);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe("edx.student.account.AccountView", function() {
|
||||
var view = null,
|
||||
ajaxSuccess = true;
|
||||
|
||||
var requestEmailChange = function(email, password) {
|
||||
var fakeEvent = {preventDefault: function() {}};
|
||||
view.model.set({
|
||||
email: email,
|
||||
password: password
|
||||
});
|
||||
view.submit(fakeEvent);
|
||||
};
|
||||
|
||||
var assertAjax = function(url, method, data) {
|
||||
expect($.ajax).toHaveBeenCalled();
|
||||
var ajaxArgs = $.ajax.mostRecentCall.args[0];
|
||||
expect(ajaxArgs.url).toEqual(url);
|
||||
expect(ajaxArgs.type).toEqual(method);
|
||||
expect(ajaxArgs.data).toEqual(data);
|
||||
expect(ajaxArgs.headers.hasOwnProperty("X-CSRFToken")).toBe(true);
|
||||
};
|
||||
|
||||
var assertEmailStatus = function(success, expectedStatus) {
|
||||
if (!success) {
|
||||
expect(view.$emailStatus).toHaveClass("validation-error");
|
||||
} else {
|
||||
expect(view.$emailStatus).not.toHaveClass("validation-error");
|
||||
}
|
||||
expect(view.$emailStatus.text()).toEqual(expectedStatus);
|
||||
};
|
||||
|
||||
var assertPasswordStatus = function(success, expectedStatus) {
|
||||
if (!success) {
|
||||
expect(view.$passwordStatus).toHaveClass("validation-error");
|
||||
} else {
|
||||
expect(view.$passwordStatus).not.toHaveClass("validation-error");
|
||||
}
|
||||
expect(view.$passwordStatus.text()).toEqual(expectedStatus);
|
||||
};
|
||||
|
||||
var assertRequestStatus = function(success, expectedStatus) {
|
||||
if (!success) {
|
||||
expect(view.$requestStatus).toHaveClass("error");
|
||||
} else {
|
||||
expect(view.$requestStatus).not.toHaveClass("error");
|
||||
}
|
||||
expect(view.$requestStatus.text()).toEqual(expectedStatus);
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
var fixture = readFixtures("templates/student_account/account.underscore");
|
||||
setFixtures("<div id=\"account-tpl\">" + fixture + "</div>");
|
||||
|
||||
view = new edx.student.account.AccountView().render();
|
||||
|
||||
// Stub Ajax cals to return success/failure
|
||||
spyOn($, "ajax").andCallFake(function() {
|
||||
return $.Deferred(function(defer) {
|
||||
if (ajaxSuccess) {
|
||||
defer.resolve();
|
||||
} else {
|
||||
defer.reject();
|
||||
}
|
||||
}).promise();
|
||||
});
|
||||
});
|
||||
|
||||
it("requests an email address change", function() {
|
||||
requestEmailChange("bob@example.com", "password");
|
||||
assertAjax("email", "POST", {
|
||||
email: "bob@example.com",
|
||||
password: "password"
|
||||
});
|
||||
assertRequestStatus(true, "Please check your email to confirm the change");
|
||||
});
|
||||
|
||||
it("displays email validation errors", function() {
|
||||
// Invalid email should display an error
|
||||
requestEmailChange("invalid", "password");
|
||||
assertEmailStatus(false, "Please enter a valid email address");
|
||||
|
||||
// Once the error is fixed, the status should return to normal
|
||||
requestEmailChange("bob@example.com", "password");
|
||||
assertEmailStatus(true, "");
|
||||
});
|
||||
|
||||
it("displays an invalid password error", function() {
|
||||
// Password cannot be empty
|
||||
requestEmailChange("bob@example.com", "");
|
||||
assertPasswordStatus(false, "Please enter a valid password");
|
||||
|
||||
// Once the error is fixed, the status should return to normal
|
||||
requestEmailChange("bob@example.com", "password");
|
||||
assertPasswordStatus(true, "");
|
||||
});
|
||||
|
||||
it("displays server errors", function() {
|
||||
// Simulate an error from the server
|
||||
ajaxSuccess = false;
|
||||
requestEmailChange("bob@example.com", "password");
|
||||
assertRequestStatus(false, "The data could not be saved.");
|
||||
|
||||
// On retry, it should succeed
|
||||
ajaxSuccess = true;
|
||||
requestEmailChange("bob@example.com", "password");
|
||||
assertRequestStatus(true, "Please check your email to confirm the change");
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
178
lms/static/js/spec/student_profile/profile.js
Normal file
178
lms/static/js/spec/student_profile/profile.js
Normal file
@@ -0,0 +1,178 @@
|
||||
define(['js/student_profile/profile'],
|
||||
function() {
|
||||
describe("edx.student.profile.ProfileModel", function() {
|
||||
'use strict';
|
||||
|
||||
var profile = null;
|
||||
|
||||
beforeEach(function() {
|
||||
profile = new edx.student.profile.ProfileModel();
|
||||
});
|
||||
|
||||
it("validates the full name field", function() {
|
||||
// Full name cannot be blank
|
||||
profile.set("fullName", "");
|
||||
var errors = profile.validate(profile.attributes);
|
||||
expect(errors).toEqual({
|
||||
fullName: "Full name cannot be blank"
|
||||
});
|
||||
|
||||
// Fill in the name and expect that the model is valid
|
||||
profile.set("fullName", "Bob");
|
||||
errors = profile.validate(profile.attributes);
|
||||
expect(errors).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe("edx.student.profile.PreferencesModel", function() {
|
||||
var preferences = null;
|
||||
|
||||
beforeEach(function() {
|
||||
preferences = new edx.student.profile.PreferencesModel();
|
||||
});
|
||||
|
||||
it("validates the language field", function() {
|
||||
// Language cannot be blank
|
||||
preferences.set("language", "");
|
||||
var errors = preferences.validate(preferences.attributes);
|
||||
expect(errors).toEqual({
|
||||
language: "Language cannot be blank"
|
||||
});
|
||||
|
||||
// Fill in the language and expect that the model is valid
|
||||
preferences.set("language", "eo");
|
||||
errors = preferences.validate(preferences.attributes);
|
||||
expect(errors).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe("edx.student.profile.ProfileView", function() {
|
||||
var view = null,
|
||||
ajaxSuccess = true;
|
||||
|
||||
var updateProfile = function(fields) {
|
||||
view.profileModel.set(fields);
|
||||
view.clearStatus();
|
||||
view.profileModel.save();
|
||||
};
|
||||
|
||||
var updatePreferences = function(fields) {
|
||||
view.preferencesModel.set(fields);
|
||||
view.clearStatus();
|
||||
view.preferencesModel.save();
|
||||
};
|
||||
|
||||
var assertAjax = function(url, method, data) {
|
||||
expect($.ajax).toHaveBeenCalled();
|
||||
var ajaxArgs = $.ajax.mostRecentCall.args[0];
|
||||
expect(ajaxArgs.url).toEqual(url);
|
||||
expect(ajaxArgs.type).toEqual(method);
|
||||
expect(ajaxArgs.data).toEqual(data)
|
||||
expect(ajaxArgs.headers.hasOwnProperty("X-CSRFToken")).toBe(true);
|
||||
};
|
||||
|
||||
var assertSubmitStatus = function(success, expectedStatus) {
|
||||
if (!success) {
|
||||
expect(view.$submitStatus).toHaveClass("error");
|
||||
} else {
|
||||
expect(view.$submitStatus).not.toHaveClass("error");
|
||||
}
|
||||
expect(view.$submitStatus.text()).toEqual(expectedStatus);
|
||||
};
|
||||
|
||||
var assertValidationError = function(expectedError, selection) {
|
||||
if (expectedError === null) {
|
||||
expect(selection).not.toHaveClass("validation-error");
|
||||
expect(selection.text()).toEqual("");
|
||||
} else {
|
||||
expect(selection).toHaveClass("validation-error");
|
||||
expect(selection.text()).toEqual(expectedError);
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
var profileFixture = readFixtures("templates/student_profile/profile.underscore"),
|
||||
languageFixture = readFixtures("templates/student_profile/languages.underscore");
|
||||
|
||||
setFixtures("<div id=\"profile-tpl\">" + profileFixture + "</div>");
|
||||
appendSetFixtures("<div id=\"languages-tpl\">" + languageFixture + "</div>");
|
||||
|
||||
// Stub AJAX calls to return success / failure
|
||||
spyOn($, "ajax").andCallFake(function() {
|
||||
return $.Deferred(function(defer) {
|
||||
if (ajaxSuccess) {
|
||||
defer.resolve();
|
||||
} else {
|
||||
defer.reject();
|
||||
}
|
||||
}).promise();
|
||||
});
|
||||
|
||||
var json = {
|
||||
preferredLanguage: {code: 'eo', name: 'Dummy language'},
|
||||
languages: [{code: 'eo', name: 'Dummy language'}]
|
||||
};
|
||||
spyOn($, "getJSON").andCallFake(function() {
|
||||
return $.Deferred(function(defer) {
|
||||
if (ajaxSuccess) {
|
||||
defer.resolveWith(this, [json]);
|
||||
} else {
|
||||
defer.reject();
|
||||
}
|
||||
}).promise();
|
||||
});
|
||||
|
||||
// Stub location.reload() to prevent test suite from reloading repeatedly
|
||||
spyOn(edx.student.profile, "reloadPage").andCallFake(function() {
|
||||
return true;
|
||||
});
|
||||
|
||||
view = new edx.student.profile.ProfileView().render();
|
||||
});
|
||||
|
||||
it("updates the student profile", function() {
|
||||
updateProfile({fullName: "John Smith"});
|
||||
assertAjax("", "PUT", {fullName: "John Smith"});
|
||||
assertSubmitStatus(true, "Saved");
|
||||
});
|
||||
|
||||
it("updates the student preferences", function() {
|
||||
updatePreferences({language: "eo"});
|
||||
assertAjax("preferences", "PUT", {language: "eo"});
|
||||
assertSubmitStatus(true, "Saved");
|
||||
});
|
||||
|
||||
it("displays full name validation errors", function() {
|
||||
// Blank name should display a validation error
|
||||
updateProfile({fullName: ""});
|
||||
assertValidationError("Full name cannot be blank", view.$nameStatus);
|
||||
|
||||
// If we fix the problem and resubmit, the error should go away
|
||||
updateProfile({fullName: "John Smith"});
|
||||
assertValidationError(null, view.$nameStatus);
|
||||
});
|
||||
|
||||
it("displays language validation errors", function() {
|
||||
// Blank language should display a validation error
|
||||
updatePreferences({language: ""});
|
||||
assertValidationError("Language cannot be blank", view.$languageStatus);
|
||||
|
||||
// If we fix the problem and resubmit, the error should go away
|
||||
updatePreferences({language: "eo"});
|
||||
assertValidationError(null, view.$languageStatus);
|
||||
});
|
||||
|
||||
it("displays an error if the sync fails", function() {
|
||||
// If we get an error status on the AJAX request, display an error
|
||||
ajaxSuccess = false;
|
||||
updateProfile({fullName: "John Smith"});
|
||||
assertSubmitStatus(false, "The data could not be saved.");
|
||||
|
||||
// If we try again and succeed, the error should go away
|
||||
ajaxSuccess = true;
|
||||
updateProfile({fullName: "John Smith"});
|
||||
assertSubmitStatus(true, "Saved");
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -1,141 +1,155 @@
|
||||
var edx = edx || {};
|
||||
|
||||
(function($) {
|
||||
(function($, _, Backbone, gettext) {
|
||||
'use strict';
|
||||
|
||||
edx.student = edx.student || {};
|
||||
edx.student.account = {};
|
||||
|
||||
edx.student.account = (function() {
|
||||
var _fn = {
|
||||
init: function() {
|
||||
_fn.ajax.init();
|
||||
_fn.eventHandlers.init();
|
||||
},
|
||||
edx.student.account.AccountModel = Backbone.Model.extend({
|
||||
// These should be the same length limits enforced by the server
|
||||
EMAIL_MIN_LENGTH: 3,
|
||||
EMAIL_MAX_LENGTH: 254,
|
||||
PASSWORD_MIN_LENGTH: 2,
|
||||
PASSWORD_MAX_LENGTH: 75,
|
||||
|
||||
eventHandlers: {
|
||||
init: function() {
|
||||
_fn.eventHandlers.submit();
|
||||
},
|
||||
// This is the same regex used to validate email addresses in Django 1.4
|
||||
EMAIL_REGEX: new RegExp(
|
||||
"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" +
|
||||
'|^"([\\001-\\010\\013\\014\\016-\\037!#-\\[\\]-\\177]|\\\\[\\001-\\011\\013\\014\\016-\\177])*"' +
|
||||
')@((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\\.)+[A-Z]{2,6}\\.?$)' +
|
||||
'|\\[(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\]$',
|
||||
'i'
|
||||
),
|
||||
|
||||
submit: function() {
|
||||
$('#email-change-form').submit( _fn.form.submit );
|
||||
}
|
||||
},
|
||||
defaults: {
|
||||
email: '',
|
||||
password: ''
|
||||
},
|
||||
|
||||
ajax: {
|
||||
init: function() {
|
||||
var csrftoken = _fn.cookie.get( 'csrftoken' );
|
||||
urlRoot: 'email',
|
||||
|
||||
$.ajaxSetup({
|
||||
beforeSend: function(xhr, settings) {
|
||||
if ( settings.type === 'PUT' ) {
|
||||
xhr.setRequestHeader( 'X-CSRFToken', csrftoken );
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
sync: function(method, model) {
|
||||
var headers = {
|
||||
'X-CSRFToken': $.cookie('csrftoken')
|
||||
};
|
||||
|
||||
put: function( url, data ) {
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'PUT',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
},
|
||||
$.ajax({
|
||||
url: model.urlRoot,
|
||||
type: 'POST',
|
||||
data: model.attributes,
|
||||
headers: headers
|
||||
})
|
||||
.done(function() {
|
||||
model.trigger('sync');
|
||||
})
|
||||
.fail(function() {
|
||||
var error = gettext("The data could not be saved.");
|
||||
model.trigger('error', error);
|
||||
});
|
||||
},
|
||||
|
||||
cookie: {
|
||||
get: function( name ) {
|
||||
return $.cookie(name);
|
||||
}
|
||||
},
|
||||
validate: function(attrs) {
|
||||
var errors = {};
|
||||
|
||||
form: {
|
||||
isValid: true,
|
||||
if (attrs.email.length < this.EMAIL_MIN_LENGTH ||
|
||||
attrs.email.length > this.EMAIL_MAX_LENGTH ||
|
||||
!this.EMAIL_REGEX.test(attrs.email)
|
||||
) { errors.email = gettext("Please enter a valid email address"); }
|
||||
|
||||
submit: function( event ) {
|
||||
var $email = $('#new-email'),
|
||||
$password = $('#password'),
|
||||
data = {
|
||||
new_email: $email.val(),
|
||||
password: $password.val()
|
||||
};
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
_fn.form.validate( $('#email-change-form') );
|
||||
|
||||
if ( _fn.form.isValid ) {
|
||||
_fn.ajax.put( 'email_change_request', data );
|
||||
}
|
||||
},
|
||||
|
||||
validate: function( $form ) {
|
||||
_fn.form.isValid = true;
|
||||
$form.find('input').each( _fn.valid.input );
|
||||
}
|
||||
},
|
||||
|
||||
regex: {
|
||||
email: function() {
|
||||
// taken from http://parsleyjs.org/
|
||||
return /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i;
|
||||
}
|
||||
},
|
||||
|
||||
valid: {
|
||||
email: function( str ) {
|
||||
var valid = false,
|
||||
len = str ? str.length : 0,
|
||||
regex = _fn.regex.email();
|
||||
|
||||
if ( 0 < len && len < 254 ) {
|
||||
valid = regex.test( str );
|
||||
}
|
||||
|
||||
return valid;
|
||||
},
|
||||
|
||||
input: function() {
|
||||
var $el = $(this),
|
||||
validation = $el.data('validate'),
|
||||
value = $el.val(),
|
||||
valid = true;
|
||||
|
||||
|
||||
if ( validation && validation.length > 0 ) {
|
||||
$el.removeClass('error')
|
||||
.css('border-color', '#c8c8c8'); // temp. for development
|
||||
|
||||
// Required field
|
||||
if ( validation.indexOf('required') > -1 ) {
|
||||
valid = _fn.valid.required( value );
|
||||
}
|
||||
|
||||
// Email address
|
||||
if ( valid && validation.indexOf('email') > -1 ) {
|
||||
valid = _fn.valid.email( value );
|
||||
}
|
||||
|
||||
if ( !valid ) {
|
||||
$el.addClass('error')
|
||||
.css('border-color', '#f00'); // temp. for development
|
||||
_fn.form.isValid = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
required: function( str ) {
|
||||
return ( str && str.length > 0 ) ? true : false;
|
||||
}
|
||||
if (attrs.password.length < this.PASSWORD_MIN_LENGTH || attrs.password.length > this.PASSWORD_MAX_LENGTH) {
|
||||
errors.password = gettext("Please enter a valid password");
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
init: _fn.init
|
||||
};
|
||||
})();
|
||||
if (!$.isEmptyObject(errors)) {
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
edx.student.account.init();
|
||||
edx.student.account.AccountView = Backbone.View.extend({
|
||||
|
||||
})(jQuery);
|
||||
events: {
|
||||
'submit': 'submit',
|
||||
'change': 'change'
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
_.bindAll(this, 'render', 'submit', 'change', 'clearStatus', 'invalid', 'error', 'sync');
|
||||
this.model = new edx.student.account.AccountModel();
|
||||
this.model.on('invalid', this.invalid);
|
||||
this.model.on('error', this.error);
|
||||
this.model.on('sync', this.sync);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
this.$el.html(_.template($('#account-tpl').html(), {}));
|
||||
this.$email = $('#new-email', this.$el);
|
||||
this.$password = $('#password', this.$el);
|
||||
this.$emailStatus = $('#new-email-status', this.$el);
|
||||
this.$passwordStatus = $('#password-status', this.$el);
|
||||
this.$requestStatus = $('#request-email-status', this.$el);
|
||||
return this;
|
||||
},
|
||||
|
||||
submit: function(event) {
|
||||
event.preventDefault();
|
||||
this.clearStatus();
|
||||
this.model.save();
|
||||
},
|
||||
|
||||
change: function() {
|
||||
this.model.set({
|
||||
email: this.$email.val(),
|
||||
password: this.$password.val()
|
||||
});
|
||||
},
|
||||
|
||||
invalid: function(model) {
|
||||
var errors = model.validationError;
|
||||
|
||||
if (errors.hasOwnProperty('email')) {
|
||||
this.$emailStatus
|
||||
.addClass('validation-error')
|
||||
.text(errors.email);
|
||||
}
|
||||
|
||||
if (errors.hasOwnProperty('password')) {
|
||||
this.$passwordStatus
|
||||
.addClass('validation-error')
|
||||
.text(errors.password);
|
||||
}
|
||||
},
|
||||
|
||||
error: function(error) {
|
||||
this.$requestStatus
|
||||
.addClass('error')
|
||||
.text(error);
|
||||
},
|
||||
|
||||
sync: function() {
|
||||
this.$requestStatus
|
||||
.addClass('success')
|
||||
.text(gettext("Please check your email to confirm the change"));
|
||||
},
|
||||
|
||||
clearStatus: function() {
|
||||
this.$emailStatus
|
||||
.removeClass('validation-error')
|
||||
.text("");
|
||||
|
||||
this.$passwordStatus
|
||||
.removeClass('validation-error')
|
||||
.text("");
|
||||
|
||||
this.$requestStatus
|
||||
.removeClass('error')
|
||||
.text("");
|
||||
},
|
||||
});
|
||||
|
||||
return new edx.student.account.AccountView({
|
||||
el: $('#account-container')
|
||||
}).render();
|
||||
|
||||
})(jQuery, _, Backbone, gettext);
|
||||
|
||||
@@ -1,97 +1,205 @@
|
||||
var edx = edx || {};
|
||||
|
||||
(function($) {
|
||||
(function($, _, Backbone, gettext) {
|
||||
'use strict';
|
||||
|
||||
edx.student = edx.student || {};
|
||||
edx.student.profile = {};
|
||||
|
||||
edx.student.profile = (function() {
|
||||
var syncErrorMessage = gettext("The data could not be saved.");
|
||||
|
||||
var _fn = {
|
||||
init: function() {
|
||||
_fn.ajax.init();
|
||||
_fn.eventHandlers.init();
|
||||
},
|
||||
edx.student.profile.reloadPage = function() {
|
||||
location.reload();
|
||||
};
|
||||
|
||||
eventHandlers: {
|
||||
init: function() {
|
||||
_fn.eventHandlers.submit();
|
||||
_fn.eventHandlers.click();
|
||||
},
|
||||
edx.student.profile.ProfileModel = Backbone.Model.extend({
|
||||
defaults: {
|
||||
fullName: ''
|
||||
},
|
||||
|
||||
submit: function() {
|
||||
$('#name-change-form').on( 'submit', _fn.update.name );
|
||||
},
|
||||
urlRoot: '',
|
||||
|
||||
click: function() {
|
||||
$('#language-change-form .submit-button').on( 'click', _fn.update.language );
|
||||
}
|
||||
},
|
||||
sync: function(method, model) {
|
||||
var headers = {
|
||||
'X-CSRFToken': $.cookie('csrftoken')
|
||||
};
|
||||
|
||||
update: {
|
||||
name: function( event ) {
|
||||
_fn.form.submit( event, '#new-name', 'new_name', 'name_change' );
|
||||
},
|
||||
$.ajax({
|
||||
url: model.urlRoot,
|
||||
type: 'PUT',
|
||||
data: model.attributes,
|
||||
headers: headers
|
||||
})
|
||||
.done(function() {
|
||||
model.trigger('sync');
|
||||
})
|
||||
.fail(function() {
|
||||
model.trigger('error', syncErrorMessage);
|
||||
});
|
||||
},
|
||||
|
||||
language: function( event ) {
|
||||
/**
|
||||
* The onSuccess argument here means: take `window.location.reload`
|
||||
* and return a function that will use `window.location` as the
|
||||
* `this` reference inside `reload()`.
|
||||
*/
|
||||
_fn.form.submit( event, '#new-language', 'new_language', 'language_change', window.location.reload.bind(window.location) );
|
||||
}
|
||||
},
|
||||
validate: function(attrs) {
|
||||
var errors = {};
|
||||
if (attrs.fullName.length < 1) {
|
||||
errors.fullName = gettext("Full name cannot be blank");
|
||||
}
|
||||
|
||||
form: {
|
||||
submit: function( event, idSelector, key, url, onSuccess ) {
|
||||
var $selection = $(idSelector),
|
||||
data = {};
|
||||
if (!$.isEmptyObject(errors)) {
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
data[key] = $selection.val();
|
||||
edx.student.profile.PreferencesModel = Backbone.Model.extend({
|
||||
defaults: {
|
||||
language: 'en'
|
||||
},
|
||||
|
||||
event.preventDefault();
|
||||
_fn.ajax.put( url, data, onSuccess );
|
||||
}
|
||||
},
|
||||
urlRoot: 'preferences',
|
||||
|
||||
ajax: {
|
||||
init: function() {
|
||||
var csrftoken = _fn.cookie.get( 'csrftoken' );
|
||||
sync: function(method, model) {
|
||||
var headers = {
|
||||
'X-CSRFToken': $.cookie('csrftoken')
|
||||
};
|
||||
|
||||
$.ajaxSetup({
|
||||
beforeSend: function( xhr, settings ) {
|
||||
if ( settings.type === 'PUT' ) {
|
||||
xhr.setRequestHeader( 'X-CSRFToken', csrftoken );
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
$.ajax({
|
||||
url: model.urlRoot,
|
||||
type: 'PUT',
|
||||
data: model.attributes,
|
||||
headers: headers
|
||||
})
|
||||
.done(function() {
|
||||
model.trigger('sync');
|
||||
edx.student.profile.reloadPage();
|
||||
})
|
||||
.fail(function() {
|
||||
model.trigger('error', syncErrorMessage);
|
||||
});
|
||||
},
|
||||
|
||||
put: function( url, data, onSuccess ) {
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'PUT',
|
||||
data: data,
|
||||
success: onSuccess ? onSuccess : ''
|
||||
});
|
||||
}
|
||||
},
|
||||
validate: function(attrs) {
|
||||
var errors = {};
|
||||
if (attrs.language.length < 1) {
|
||||
errors.language = gettext("Language cannot be blank");
|
||||
}
|
||||
|
||||
cookie: {
|
||||
get: function( name ) {
|
||||
return $.cookie(name);
|
||||
}
|
||||
},
|
||||
if (!$.isEmptyObject(errors)) {
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
edx.student.profile.ProfileView = Backbone.View.extend({
|
||||
|
||||
return {
|
||||
init: _fn.init
|
||||
};
|
||||
events: {
|
||||
'submit': 'submit',
|
||||
'change': 'change'
|
||||
},
|
||||
|
||||
})();
|
||||
initialize: function() {
|
||||
_.bindAll(this, 'render', 'change', 'submit', 'invalidProfile', 'invalidPreference', 'error', 'sync', 'clearStatus');
|
||||
|
||||
this.profileModel = new edx.student.profile.ProfileModel();
|
||||
this.profileModel.on('invalid', this.invalidProfile);
|
||||
this.profileModel.on('error', this.error);
|
||||
this.profileModel.on('sync', this.sync);
|
||||
|
||||
edx.student.profile.init();
|
||||
this.preferencesModel = new edx.student.profile.PreferencesModel();
|
||||
this.preferencesModel.on('invalid', this.invalidPreference);
|
||||
this.preferencesModel.on('error', this.error);
|
||||
this.preferencesModel.on('sync', this.sync);
|
||||
},
|
||||
|
||||
})(jQuery);
|
||||
render: function() {
|
||||
this.$el.html(_.template($('#profile-tpl').html()));
|
||||
|
||||
this.$nameField = $('#profile-name', this.$el);
|
||||
this.$nameStatus = $('#profile-name-status', this.$el);
|
||||
|
||||
this.$languageChoices = $('#preference-language', this.$el);
|
||||
this.$languageStatus = $('#preference-language-status', this.$el);
|
||||
|
||||
this.$submitStatus = $('#submit-status', this.$el);
|
||||
|
||||
var self = this;
|
||||
$.getJSON('preferences/languages')
|
||||
.done(function(json) {
|
||||
/** Asynchronously populate the language choices. */
|
||||
self.$languageChoices.html(_.template($('#languages-tpl').html(), {languageInfo: json}));
|
||||
})
|
||||
.fail(function() {
|
||||
self.$languageStatus
|
||||
.addClass('language-list-error')
|
||||
.text(gettext("We couldn't populate the list of language choices."));
|
||||
});
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
change: function() {
|
||||
this.profileModel.set({
|
||||
fullName: this.$nameField.val()
|
||||
});
|
||||
|
||||
this.preferencesModel.set({
|
||||
language: this.$languageChoices.val()
|
||||
});
|
||||
},
|
||||
|
||||
submit: function(event) {
|
||||
event.preventDefault();
|
||||
this.clearStatus();
|
||||
this.profileModel.save();
|
||||
this.preferencesModel.save();
|
||||
},
|
||||
|
||||
invalidProfile: function(model) {
|
||||
var errors = model.validationError;
|
||||
if (errors.hasOwnProperty('fullName')) {
|
||||
this.$nameStatus
|
||||
.addClass('validation-error')
|
||||
.text(errors.fullName);
|
||||
}
|
||||
},
|
||||
|
||||
invalidPreference: function(model) {
|
||||
var errors = model.validationError;
|
||||
if (errors.hasOwnProperty('language')) {
|
||||
this.$languageStatus
|
||||
.addClass('validation-error')
|
||||
.text(errors.language);
|
||||
}
|
||||
},
|
||||
|
||||
error: function(error) {
|
||||
this.$submitStatus
|
||||
.addClass('error')
|
||||
.text(error);
|
||||
},
|
||||
|
||||
sync: function() {
|
||||
this.$submitStatus
|
||||
.addClass('success')
|
||||
.text(gettext("Saved"));
|
||||
},
|
||||
|
||||
clearStatus: function() {
|
||||
this.$nameStatus
|
||||
.removeClass('validation-error')
|
||||
.text("");
|
||||
|
||||
this.$languageStatus
|
||||
.removeClass('validation-error')
|
||||
.text("");
|
||||
|
||||
this.$submitStatus
|
||||
.removeClass('error')
|
||||
.text("");
|
||||
}
|
||||
});
|
||||
|
||||
return new edx.student.profile.ProfileView({
|
||||
el: $('#profile-container')
|
||||
}).render();
|
||||
|
||||
})(jQuery, _, Backbone, gettext);
|
||||
|
||||
@@ -72,6 +72,8 @@ spec_paths:
|
||||
fixture_paths:
|
||||
- templates/instructor/instructor_dashboard_2
|
||||
- templates/dashboard
|
||||
- templates/student_account
|
||||
- templates/student_profile
|
||||
|
||||
requirejs:
|
||||
paths:
|
||||
|
||||
14
lms/templates/student_account/account.underscore
Normal file
14
lms/templates/student_account/account.underscore
Normal file
@@ -0,0 +1,14 @@
|
||||
<form id="email-change-form" method="post">
|
||||
<label for="new-email"><%- gettext('New Address') %></label>
|
||||
<input id="new-email" type="text" name="new-email" value="" placeholder="xsy@edx.org" data-validate="required email"/>
|
||||
<div id="new-email-status" />
|
||||
|
||||
<label for="password"><%- gettext('Password') %></label>
|
||||
<input id="password" type="password" name="password" value="" data-validate="required"/>
|
||||
<div id="password-status" />
|
||||
|
||||
<div class="submit-button">
|
||||
<input type="submit" id="email-change-submit" value="<%- gettext('Change My Email Address') %>">
|
||||
</div>
|
||||
<div id="request-email-status" />
|
||||
</form>
|
||||
@@ -18,9 +18,9 @@ ${_("There was recently a request to change the email address associated "
|
||||
|
||||
## Confirmation link
|
||||
% if is_secure:
|
||||
https://${site}/account/email_change_confirm/${key}
|
||||
https://${site}/account/email/confirmation/${key}
|
||||
% else:
|
||||
http://${site}/account/email_change_confirm/${key}
|
||||
http://${site}/account/email/confirmation/${key}
|
||||
% endif
|
||||
|
||||
## Closing
|
||||
|
||||
@@ -6,23 +6,21 @@
|
||||
<%block name="pagetitle">${_("Student Account")}</%block>
|
||||
|
||||
<%block name="js_extra">
|
||||
<script type="text/javascript" src="${static.url('js/vendor/underscore-min.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/vendor/backbone-min.js')}"></script>
|
||||
<%static:js group='student_account'/>
|
||||
</%block>
|
||||
|
||||
<%block name="header_extras">
|
||||
% for template_name in ["account"]:
|
||||
<script type="text/template" id="${template_name}-tpl">
|
||||
<%static:include path="student_account/${template_name}.underscore" />
|
||||
</script>
|
||||
% endfor
|
||||
</%block>
|
||||
|
||||
<h1>Student Account</h1>
|
||||
|
||||
<p>This is a placeholder for the student's account page.</p>
|
||||
|
||||
<form id="email-change-form" method="post">
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value="${csrf_token}">
|
||||
|
||||
<label for="new-email">${_('New Address')}</label>
|
||||
<input id="new-email" type="text" name="new-email" value="" placeholder="xsy@edx.org" data-validate="required email"/>
|
||||
|
||||
<label for="password">${_('Password')}</label>
|
||||
<input id="password" type="password" name="password" value="" data-validate="required"/>
|
||||
|
||||
<div class="submit-button">
|
||||
<input type="submit" id="email-change-submit" value="${_('Change My Email Address')}">
|
||||
</div>
|
||||
</form>
|
||||
<div id="account-container" />
|
||||
|
||||
@@ -6,55 +6,24 @@
|
||||
<%block name="pagetitle">${_("Student Profile")}</%block>
|
||||
|
||||
<%block name="js_extra">
|
||||
<script type="text/javascript" src="${static.url('js/vendor/underscore-min.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/vendor/backbone-min.js')}"></script>
|
||||
<%static:js group='student_profile'/>
|
||||
</%block>
|
||||
|
||||
<h1>Student Profile</h1>
|
||||
<%block name="header_extras">
|
||||
% for template_name in ["profile", "languages"]:
|
||||
<script type="text/template" id="${template_name}-tpl">
|
||||
<%static:include path="student_profile/${template_name}.underscore" />
|
||||
</script>
|
||||
% endfor
|
||||
</%block>
|
||||
|
||||
<h1>${_("Student Profile")}</h1>
|
||||
|
||||
<p>This is a placeholder for the student's profile page.</p>
|
||||
|
||||
<form id="name-change-form">
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value="${csrf_token}">
|
||||
|
||||
<label for="new-name">${_("Full Name")}</label>
|
||||
<input id="new-name" type="text" name="new-name" value="" placeholder="Xsy" />
|
||||
|
||||
<div class="submit-button">
|
||||
<input type="submit" id="name-change-submit" value="${_("Change My Name")}">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div id="language-change-body">
|
||||
<form id="language-change-form">
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value="${csrf_token}">
|
||||
|
||||
<label for="new-language">${_("Please choose your preferred language")}</label>
|
||||
<select id="new-language" name="language">
|
||||
% for language in released_languages:
|
||||
% if language.name is preferred_language:
|
||||
<option value="${language.code}" selected="selected">${language.name}</option>
|
||||
% else:
|
||||
<option value="${language.code}">${language.name}</option>
|
||||
% endif
|
||||
% endfor
|
||||
</select>
|
||||
|
||||
<div class="submit-button">
|
||||
<input type="submit" id="language-change-submit" value="${_("Change Preferred Language")}" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<ul class="list list-actions actions-supplemental">
|
||||
<li class="list-actions-item">
|
||||
${_("Don't see your preferred language? {link_start}Volunteer to become a translator!{link_end}").format(
|
||||
link_start='<a class=" action action-volunteer" rel="external" target="_blank" href={translators_guide}>'.format(
|
||||
translators_guide=settings.TRANSLATORS_GUIDE
|
||||
),
|
||||
link_end="</a>"
|
||||
)}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="profile-container"></div>
|
||||
|
||||
% if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH'):
|
||||
<%include file="third_party_auth.html" />
|
||||
|
||||
7
lms/templates/student_profile/languages.underscore
Normal file
7
lms/templates/student_profile/languages.underscore
Normal file
@@ -0,0 +1,7 @@
|
||||
<% _.each( languageInfo.languages, function( language ){ %>
|
||||
<% if ( language.name === languageInfo.preferredLanguage.name ){ %>
|
||||
<option value=<%= language.code %> selected="selected"><%= language.name %></option>
|
||||
<% } else { %>
|
||||
<option value=<%= language.code %>><%= language.name %></option>
|
||||
<% } %>
|
||||
<% }); %>
|
||||
14
lms/templates/student_profile/profile.underscore
Normal file
14
lms/templates/student_profile/profile.underscore
Normal file
@@ -0,0 +1,14 @@
|
||||
<form id="profile-form">
|
||||
<label for="profile-name"><%- gettext("Full Name") %></label>
|
||||
<input id="profile-name" type="text" name="profile-name" value="" placeholder="Xsy" />
|
||||
<div id="profile-name-status" />
|
||||
|
||||
<label for="preference-language"><%- gettext('Preferred Language') %></label>
|
||||
<select id="preference-language" name="preference-language"></select>
|
||||
<div id="preference-language-status" />
|
||||
|
||||
<div class="profile-submit">
|
||||
<input type="submit" id="submit-button" value="<%- gettext('Update Profile') %>">
|
||||
</div>
|
||||
<div id="submit-status" />
|
||||
</form>
|
||||
Reference in New Issue
Block a user