From 00d976b85d6a85777522619aaee3a2031bb18ca0 Mon Sep 17 00:00:00 2001 From: Renzo Lucioni Date: Mon, 29 Sep 2014 09:16:38 -0400 Subject: [PATCH] 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. --- common/djangoapps/lang_pref/api.py | 10 +- common/djangoapps/lang_pref/tests/test_api.py | 6 +- common/djangoapps/user_api/api/profile.py | 1 + .../user_api/tests/test_account_api.py | 2 + conf/locale/eo/LC_MESSAGES/django.mo | Bin 549288 -> 553696 bytes conf/locale/eo/LC_MESSAGES/django.po | 250 +++++++++++++---- conf/locale/eo/LC_MESSAGES/djangojs.mo | Bin 96476 -> 98348 bytes conf/locale/eo/LC_MESSAGES/djangojs.po | 106 ++++--- .../student_account/test/test_views.py | 34 +-- lms/djangoapps/student_account/urls.py | 4 +- lms/djangoapps/student_account/views.py | 27 +- .../student_profile/test/test_views.py | 76 ++--- lms/djangoapps/student_profile/urls.py | 4 +- lms/djangoapps/student_profile/views.py | 113 +++++--- lms/static/js/spec/main.js | 10 + lms/static/js/spec/student_account/account.js | 196 +++++++++++++ lms/static/js/spec/student_profile/profile.js | 178 ++++++++++++ lms/static/js/student_account/account.js | 260 +++++++++--------- lms/static/js/student_profile/profile.js | 254 ++++++++++++----- lms/static/js_test.yml | 2 + .../student_account/account.underscore | 14 + .../email_change_request/message_body.txt | 4 +- lms/templates/student_account/index.html | 24 +- lms/templates/student_profile/index.html | 55 +--- .../student_profile/languages.underscore | 7 + .../student_profile/profile.underscore | 14 + 26 files changed, 1202 insertions(+), 449 deletions(-) create mode 100644 lms/static/js/spec/student_account/account.js create mode 100644 lms/static/js/spec/student_profile/profile.js create mode 100644 lms/templates/student_account/account.underscore create mode 100644 lms/templates/student_profile/languages.underscore create mode 100644 lms/templates/student_profile/profile.underscore diff --git a/common/djangoapps/lang_pref/api.py b/common/djangoapps/lang_pref/api.py index 18b98c1e5a..57ceb3c3f4 100644 --- a/common/djangoapps/lang_pref/api.py +++ b/common/djangoapps/lang_pref/api.py @@ -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) diff --git a/common/djangoapps/lang_pref/tests/test_api.py b/common/djangoapps/lang_pref/tests/test_api.py index 2362fbc0ea..79f9c0c0da 100644 --- a/common/djangoapps/lang_pref/tests/test_api.py +++ b/common/djangoapps/lang_pref/tests/test_api.py @@ -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')) diff --git a/common/djangoapps/user_api/api/profile.py b/common/djangoapps/user_api/api/profile.py index b985d3c039..fc14e47273 100644 --- a/common/djangoapps/user_api/api/profile.py +++ b/common/djangoapps/user_api/api/profile.py @@ -6,6 +6,7 @@ email address. """ + from user_api.models import User, UserProfile, UserPreference from user_api.helpers import intercept_errors diff --git a/common/djangoapps/user_api/tests/test_account_api.py b/common/djangoapps/user_api/tests/test_account_api.py index c343749a6f..1bcc1bbce8 100644 --- a/common/djangoapps/user_api/tests/test_account_api.py +++ b/common/djangoapps/user_api/tests/test_account_api.py @@ -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) diff --git a/conf/locale/eo/LC_MESSAGES/django.mo b/conf/locale/eo/LC_MESSAGES/django.mo index b333f49827f69a8a7a131c59a6b9f35b0413ec66..68bfbd2889e1e7dbd707778ad354c9e13c705b17 100644 GIT binary patch delta 72926 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ⅆ2$|hITh9 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 Qr$4YZvOA}^+M4wGe;BSSi2wiq diff --git a/conf/locale/eo/LC_MESSAGES/django.po b/conf/locale/eo/LC_MESSAGES/django.po index 1b2866887d..8e0390e647 100644 --- a/conf/locale/eo/LC_MESSAGES/django.po +++ b/conf/locale/eo/LC_MESSAGES/django.po @@ -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 \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 "" diff --git a/conf/locale/eo/LC_MESSAGES/djangojs.mo b/conf/locale/eo/LC_MESSAGES/djangojs.mo index 56b1b8f521e8c343fe94af0ba9cddfcad2c911af..bec087fa922c225c00469e330b757ac1130eb177 100644 GIT binary patch delta 19534 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 delta 18033 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 diff --git a/conf/locale/eo/LC_MESSAGES/djangojs.po b/conf/locale/eo/LC_MESSAGES/djangojs.po index 2f182cdff5..9ae8bb4d5b 100644 --- a/conf/locale/eo/LC_MESSAGES/djangojs.po +++ b/conf/locale/eo/LC_MESSAGES/djangojs.po @@ -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 \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." diff --git a/lms/djangoapps/student_account/test/test_views.py b/lms/djangoapps/student_account/test/test_views.py index adb8b8b7e2..0b2447c3ea 100644 --- a/lms/djangoapps/student_account/test/test_views.py +++ b/lms/djangoapps/student_account/test/test_views.py @@ -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) diff --git a/lms/djangoapps/student_account/urls.py b/lms/djangoapps/student_account/urls.py index 90e6129267..bb5a0d5690 100644 --- a/lms/djangoapps/student_account/urls.py +++ b/lms/djangoapps/student_account/urls.py @@ -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[^/]*)$', 'email_change_confirmation_handler', name='email_change_confirm'), + url(r'^email$', 'email_change_request_handler', name='email_change_request'), + url(r'^email/confirmation/(?P[^/]*)$', 'email_change_confirmation_handler', name='email_change_confirm'), ) diff --git a/lms/djangoapps/student_account/views.py b/lms/djangoapps/student_account/views.py index d1344e881a..b20e19d051 100644 --- a/lms/djangoapps/student_account/views.py +++ b/lms/djangoapps/student_account/views.py @@ -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 diff --git a/lms/djangoapps/student_profile/test/test_views.py b/lms/djangoapps/student_profile/test/test_views.py index c933242c1d..db30146986 100644 --- a/lms/djangoapps/student_profile/test/test_views.py +++ b/lms/djangoapps/student_profile/test/test_views.py @@ -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' ) diff --git a/lms/djangoapps/student_profile/urls.py b/lms/djangoapps/student_profile/urls.py index 81344a0be3..ba4cebeba1 100644 --- a/lms/djangoapps/student_profile/urls.py +++ b/lms/djangoapps/student_profile/urls.py @@ -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'), ) diff --git a/lms/djangoapps/student_profile/views.py b/lms/djangoapps/student_profile/views.py index c0ec2f57cd..4ec52f3f40 100644 --- a/lms/djangoapps/student_profile/views.py +++ b/lms/djangoapps/student_profile/views.py @@ -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() diff --git a/lms/static/js/spec/main.js b/lms/static/js/spec/main.js index d30440d4d9..30ce47a9e9 100644 --- a/lms/static/js/spec/main.js +++ b/lms/static/js/spec/main.js @@ -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); diff --git a/lms/static/js/spec/student_account/account.js b/lms/static/js/spec/student_account/account.js new file mode 100644 index 0000000000..596107f415 --- /dev/null +++ b/lms/static/js/spec/student_account/account.js @@ -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("
" + fixture + "
"); + + 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"); + }); + }); + } +); diff --git a/lms/static/js/spec/student_profile/profile.js b/lms/static/js/spec/student_profile/profile.js new file mode 100644 index 0000000000..82f354582f --- /dev/null +++ b/lms/static/js/spec/student_profile/profile.js @@ -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("
" + profileFixture + "
"); + appendSetFixtures("
" + languageFixture + "
"); + + // 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"); + }); + }); + } +); diff --git a/lms/static/js/student_account/account.js b/lms/static/js/student_account/account.js index 172f09db71..dda4fba60f 100644 --- a/lms/static/js/student_account/account.js +++ b/lms/static/js/student_account/account.js @@ -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); diff --git a/lms/static/js/student_profile/profile.js b/lms/static/js/student_profile/profile.js index a453dceb45..664b198d65 100644 --- a/lms/static/js/student_profile/profile.js +++ b/lms/static/js/student_profile/profile.js @@ -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); diff --git a/lms/static/js_test.yml b/lms/static/js_test.yml index dbc7f00e07..fa0bb93b48 100644 --- a/lms/static/js_test.yml +++ b/lms/static/js_test.yml @@ -72,6 +72,8 @@ spec_paths: fixture_paths: - templates/instructor/instructor_dashboard_2 - templates/dashboard + - templates/student_account + - templates/student_profile requirejs: paths: diff --git a/lms/templates/student_account/account.underscore b/lms/templates/student_account/account.underscore new file mode 100644 index 0000000000..9a6b91628f --- /dev/null +++ b/lms/templates/student_account/account.underscore @@ -0,0 +1,14 @@ +
+ + +
+ + + +
+ +
+ +
+
+ diff --git a/lms/templates/student_account/emails/email_change_request/message_body.txt b/lms/templates/student_account/emails/email_change_request/message_body.txt index 9c38042a5b..cc58ae814f 100644 --- a/lms/templates/student_account/emails/email_change_request/message_body.txt +++ b/lms/templates/student_account/emails/email_change_request/message_body.txt @@ -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 diff --git a/lms/templates/student_account/index.html b/lms/templates/student_account/index.html index a3403fb473..21ac4f4dda 100644 --- a/lms/templates/student_account/index.html +++ b/lms/templates/student_account/index.html @@ -6,23 +6,21 @@ <%block name="pagetitle">${_("Student Account")} <%block name="js_extra"> + + <%static:js group='student_account'/> +<%block name="header_extras"> +% for template_name in ["account"]: + +% endfor + +

Student Account

This is a placeholder for the student's account page.

-
- - - - - - - - -
- -
-
+
diff --git a/lms/templates/student_profile/index.html b/lms/templates/student_profile/index.html index a6d26ffdfe..aa46c69fcd 100644 --- a/lms/templates/student_profile/index.html +++ b/lms/templates/student_profile/index.html @@ -6,55 +6,24 @@ <%block name="pagetitle">${_("Student Profile")} <%block name="js_extra"> + + <%static:js group='student_profile'/> -

Student Profile

+<%block name="header_extras"> +% for template_name in ["profile", "languages"]: + +% endfor + + +

${_("Student Profile")}

This is a placeholder for the student's profile page.

-
- - - - - -
- -
-
- -
+
% if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH'): <%include file="third_party_auth.html" /> diff --git a/lms/templates/student_profile/languages.underscore b/lms/templates/student_profile/languages.underscore new file mode 100644 index 0000000000..5b11aff4f2 --- /dev/null +++ b/lms/templates/student_profile/languages.underscore @@ -0,0 +1,7 @@ +<% _.each( languageInfo.languages, function( language ){ %> + <% if ( language.name === languageInfo.preferredLanguage.name ){ %> + + <% } else { %> + + <% } %> +<% }); %> diff --git a/lms/templates/student_profile/profile.underscore b/lms/templates/student_profile/profile.underscore new file mode 100644 index 0000000000..76bafbc247 --- /dev/null +++ b/lms/templates/student_profile/profile.underscore @@ -0,0 +1,14 @@ +
+ + +
+ + + +
+ +
+ +
+
+