Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
e89afa93
Commit
e89afa93
authored
10 years ago
by
Will Daly
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
WIP: add login and registration end-points to the user API.
parent
80613361
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
1234 additions
and
27 deletions
+1234
-27
common/djangoapps/user_api/api/profile.py
+9
-3
common/djangoapps/user_api/helpers.py
+241
-0
common/djangoapps/user_api/tests/test_views.py
+565
-4
common/djangoapps/user_api/urls.py
+2
-0
common/djangoapps/user_api/views.py
+258
-3
lms/djangoapps/student_account/test/test_views.py
+32
-0
lms/djangoapps/student_account/urls.py
+13
-3
lms/djangoapps/student_account/views.py
+35
-2
lms/djangoapps/student_profile/urls.py
+12
-6
lms/envs/test.py
+11
-0
lms/templates/student_account/login_and_register.html
+52
-0
lms/urls.py
+4
-6
No files found.
common/djangoapps/user_api/api/profile.py
View file @
e89afa93
...
...
@@ -65,9 +65,15 @@ def profile_info(username):
return
None
profile_dict
=
{
u'username'
:
profile
.
user
.
username
,
u'email'
:
profile
.
user
.
email
,
u'full_name'
:
profile
.
name
,
"username"
:
profile
.
user
.
username
,
"email"
:
profile
.
user
.
email
,
"full_name"
:
profile
.
name
,
"level_of_education"
:
profile
.
level_of_education
,
"mailing_address"
:
profile
.
mailing_address
,
"year_of_birth"
:
profile
.
year_of_birth
,
"goals"
:
profile
.
goals
,
"city"
:
profile
.
city
,
"country"
:
profile
.
country
,
}
return
profile_dict
...
...
This diff is collapsed.
Click to expand it.
common/djangoapps/user_api/helpers.py
View file @
e89afa93
...
...
@@ -4,6 +4,8 @@ This is NOT part of the public API.
"""
from
functools
import
wraps
import
logging
import
json
LOGGER
=
logging
.
getLogger
(
__name__
)
...
...
@@ -54,3 +56,242 @@ def intercept_errors(api_error, ignore_errors=[]):
raise
api_error
(
msg
)
return
_wrapped
return
_decorator
class
InvalidFieldError
(
Exception
):
"""The provided field definition is not valid. """
class
FormDescription
(
object
):
"""Generate a JSON representation of a form. """
ALLOWED_TYPES
=
[
"text"
,
"select"
,
"textarea"
]
ALLOWED_RESTRICTIONS
=
{
"text"
:
[
"min_length"
,
"max_length"
],
}
def
__init__
(
self
,
method
,
submit_url
):
"""Configure how the form should be submitted.
Args:
method (unicode): The HTTP method used to submit the form.
submit_url (unicode): The URL where the form should be submitted.
"""
self
.
method
=
method
self
.
submit_url
=
submit_url
self
.
fields
=
[]
def
add_field
(
self
,
name
,
label
=
u""
,
field_type
=
u"text"
,
default
=
u""
,
placeholder
=
u""
,
instructions
=
u""
,
required
=
True
,
restrictions
=
None
,
options
=
None
):
"""Add a field to the form description.
Args:
name (unicode): The name of the field, which is the key for the value
to send back to the server.
Keyword Arguments:
label (unicode): The label for the field (e.g. "E-mail" or "Username")
field_type (unicode): The type of the field. See `ALLOWED_TYPES` for
acceptable values.
default (unicode): The default value for the field.
placeholder (unicode): Placeholder text in the field
(e.g. "user@example.com" for an email field)
instructions (unicode): Short instructions for using the field
(e.g. "This is the email address you used when you registered.")
required (boolean): Whether the field is required or optional.
restrictions (dict): Validation restrictions for the field.
See `ALLOWED_RESTRICTIONS` for acceptable values.
options (list): For "select" fields, a list of tuples
(value, display_name) representing the options available to
the user. `value` is the value of the field to send to the server,
and `display_name` is the name to display to the user.
If the field type is "select", you *must* provide this kwarg.
Raises:
InvalidFieldError
"""
if
field_type
not
in
self
.
ALLOWED_TYPES
:
msg
=
u"Field type '{field_type}' is not a valid type. Allowed types are: {allowed}."
.
format
(
field_type
=
field_type
,
allowed
=
", "
.
join
(
self
.
ALLOWED_TYPES
)
)
raise
InvalidFieldError
(
msg
)
field_dict
=
{
"label"
:
label
,
"name"
:
name
,
"type"
:
field_type
,
"default"
:
default
,
"placeholder"
:
placeholder
,
"instructions"
:
instructions
,
"required"
:
required
,
"restrictions"
:
{}
}
if
field_type
==
"select"
:
if
options
is
not
None
:
field_dict
[
"options"
]
=
[
{
"value"
:
option_value
,
"name"
:
option_name
}
for
option_value
,
option_name
in
options
]
else
:
raise
InvalidFieldError
(
"You must provide options for a select field."
)
if
restrictions
is
not
None
:
allowed_restrictions
=
self
.
ALLOWED_RESTRICTIONS
.
get
(
field_type
,
[])
for
key
,
val
in
restrictions
.
iteritems
():
if
key
in
allowed_restrictions
:
field_dict
[
"restrictions"
][
key
]
=
val
else
:
msg
=
"Restriction '{restriction}' is not allowed for field type '{field_type}'"
.
format
(
restriction
=
key
,
field_type
=
field_type
)
raise
InvalidFieldError
(
msg
)
self
.
fields
.
append
(
field_dict
)
def
to_json
(
self
):
"""Create a JSON representation of the form description.
Here's an example of the output:
{
"method": "post",
"submit_url": "/submit",
"fields": [
{
"name": "cheese_or_wine",
"label": "Cheese or Wine?",
"default": "cheese",
"type": "select",
"required": True,
"placeholder": "",
"instructions": "",
"options": [
{"value": "cheese", "name": "Cheese"},
{"value": "wine", "name": "Wine"}
]
"restrictions": {},
},
{
"name": "comments",
"label": "comments",
"default": "",
"type": "text",
"required": False,
"placeholder": "Any comments?",
"instructions": "Please enter additional comments here."
"restrictions": {
"max_length": 200
}
},
...
]
}
If the field is NOT a "select" type, then the "options"
key will be omitted.
Returns:
unicode
"""
return
json
.
dumps
({
"method"
:
self
.
method
,
"submit_url"
:
self
.
submit_url
,
"fields"
:
self
.
fields
})
def
shim_student_view
(
view_func
,
check_logged_in
=
False
):
"""Create a "shim" view for a view function from the student Django app.
Specifically, we need to:
* Strip out enrollment params, since the client for the new registration/login
page will communicate with the enrollment API to update enrollments.
* Return responses with HTTP status codes indicating success/failure
(instead of always using status 200, but setting "success" to False in
the JSON-serialized content of the response)
* Use status code 302 for redirects instead of
"redirect_url" in the JSON-serialized content of the response.
* Use status code 403 to indicate a login failure.
The shim will preserve any cookies set by the view.
Arguments:
view_func (function): The view function from the student Django app.
Keyword Args:
check_logged_in (boolean): If true, check whether the user successfully
authenticated and if not set the status to 403.
Returns:
function
"""
@wraps
(
view_func
)
def
_inner
(
request
):
# Strip out enrollment action stuff, since we're handling that elsewhere
if
"enrollment_action"
in
request
.
POST
:
del
request
.
POST
[
"enrollment_action"
]
if
"course_id"
in
request
.
POST
:
del
request
.
POST
[
"course_id"
]
# Actually call the function!
# TODO ^^
response
=
view_func
(
request
)
# Most responses from this view are a JSON dict
# TODO -- explain this more
try
:
response_dict
=
json
.
loads
(
response
.
content
)
msg
=
response_dict
.
get
(
"value"
,
u""
)
redirect_url
=
response_dict
.
get
(
"redirect_url"
)
except
(
ValueError
,
TypeError
):
msg
=
response
.
content
redirect_url
=
None
# If the user could not be authenticated
if
check_logged_in
and
not
request
.
user
.
is_authenticated
():
response
.
status_code
=
403
response
.
content
=
msg
# Handle redirects
# TODO -- explain why this is safe
elif
redirect_url
is
not
None
:
response
.
status_code
=
302
response
.
content
=
redirect_url
# Handle errors
elif
response
.
status_code
!=
200
or
not
response_dict
.
get
(
"success"
,
False
):
# TODO -- explain this
if
response
.
status_code
==
200
:
response
.
status_code
=
400
response
.
content
=
msg
# Otherwise, return the response
else
:
response
.
content
=
msg
# Return the response.
# IMPORTANT: this NEEDS to preserve session variables / cookies!
return
response
return
_inner
This diff is collapsed.
Click to expand it.
common/djangoapps/user_api/tests/test_views.py
View file @
e89afa93
"""Tests for the user API at the HTTP request level. """
import
datetime
import
base64
import
json
import
re
from
django.core.urlresolvers
import
reverse
from
django.core
import
mail
from
django.test
import
TestCase
from
django.test.utils
import
override_settings
import
json
import
re
from
student.tests.factories
import
UserFactory
from
unittest
import
SkipTest
from
user_api.models
import
UserPreference
import
ddt
from
pytz
import
UTC
from
django_countries.countries
import
COUNTRIES
from
user_api.api
import
account
as
account_api
,
profile
as
profile_api
from
student.tests.factories
import
UserFactory
from
user_api.tests.factories
import
UserPreferenceFactory
from
django_comment_common
import
models
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
...
...
@@ -532,3 +542,554 @@ class PreferenceUsersListViewTest(UserApiTestCase):
self
.
assertUserIsValid
(
user
)
all_user_uris
=
[
user
[
"url"
]
for
user
in
first_page_users
+
second_page_users
]
self
.
assertEqual
(
len
(
set
(
all_user_uris
)),
2
)
class
LoginSessionViewTest
(
ApiTestCase
):
"""Tests for the login end-points of the user API. """
USERNAME
=
"bob"
EMAIL
=
"bob@example.com"
PASSWORD
=
"password"
def
setUp
(
self
):
super
(
LoginSessionViewTest
,
self
)
.
setUp
()
self
.
url
=
reverse
(
"user_api_login_session"
)
def
test_allowed_methods
(
self
):
self
.
assertAllowedMethods
(
self
.
url
,
[
"GET"
,
"POST"
,
"HEAD"
,
"OPTIONS"
])
def
test_put_not_allowed
(
self
):
response
=
self
.
client
.
put
(
self
.
url
)
self
.
assertHttpMethodNotAllowed
(
response
)
def
test_delete_not_allowed
(
self
):
response
=
self
.
client
.
delete
(
self
.
url
)
self
.
assertHttpMethodNotAllowed
(
response
)
def
test_patch_not_allowed
(
self
):
raise
SkipTest
(
"Django 1.4's test client does not support patch"
)
def
test_login_form
(
self
):
# Retrieve the login form
response
=
self
.
client
.
get
(
self
.
url
,
content_type
=
"application/json"
)
self
.
assertHttpOK
(
response
)
# Verify that the form description matches what we expect
form_desc
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
form_desc
[
"method"
],
"post"
)
self
.
assertEqual
(
form_desc
[
"submit_url"
],
self
.
url
)
self
.
assertEqual
(
form_desc
[
"fields"
],
[
{
u"name"
:
u"email"
,
u"default"
:
u""
,
u"type"
:
u"text"
,
u"required"
:
True
,
u"label"
:
u"E-mail"
,
u"placeholder"
:
u"example: username@domain.com"
,
u"instructions"
:
u"This is the e-mail address you used to register with edX"
,
u"restrictions"
:
{
u"min_length"
:
3
,
u"max_length"
:
254
},
},
{
u"name"
:
u"password"
,
u"default"
:
u""
,
u"type"
:
u"text"
,
u"required"
:
True
,
u"label"
:
u"Password"
,
u"placeholder"
:
u""
,
u"instructions"
:
u""
,
u"restrictions"
:
{
u"min_length"
:
2
,
u"max_length"
:
75
},
},
])
def
test_login
(
self
):
# Create a test user
UserFactory
.
create
(
username
=
self
.
USERNAME
,
email
=
self
.
EMAIL
,
password
=
self
.
PASSWORD
)
# Login
response
=
self
.
client
.
post
(
self
.
url
,
{
"email"
:
self
.
EMAIL
,
"password"
:
self
.
PASSWORD
,
})
self
.
assertHttpOK
(
response
)
# Verify that we logged in successfully by accessing
# a page that requires authentication.
response
=
self
.
client
.
get
(
reverse
(
"dashboard"
))
self
.
assertHttpOK
(
response
)
def
test_invalid_credentials
(
self
):
# Create a test user
UserFactory
.
create
(
username
=
self
.
USERNAME
,
email
=
self
.
EMAIL
,
password
=
self
.
PASSWORD
)
# Invalid password
response
=
self
.
client
.
post
(
self
.
url
,
{
"email"
:
self
.
EMAIL
,
"password"
:
"invalid"
})
self
.
assertHttpForbidden
(
response
)
# Invalid email address
response
=
self
.
client
.
post
(
self
.
url
,
{
"email"
:
"invalid@example.com"
,
"password"
:
self
.
PASSWORD
,
})
self
.
assertHttpForbidden
(
response
)
def
test_missing_login_params
(
self
):
# Create a test user
UserFactory
.
create
(
username
=
self
.
USERNAME
,
email
=
self
.
EMAIL
,
password
=
self
.
PASSWORD
)
# Missing password
response
=
self
.
client
.
post
(
self
.
url
,
{
"email"
:
self
.
EMAIL
,
})
self
.
assertHttpBadRequest
(
response
)
# Missing email
response
=
self
.
client
.
post
(
self
.
url
,
{
"password"
:
self
.
PASSWORD
,
})
self
.
assertHttpBadRequest
(
response
)
# Missing both email and password
response
=
self
.
client
.
post
(
self
.
url
,
{})
@ddt.ddt
class
RegistrationViewTest
(
ApiTestCase
):
"""Tests for the registration end-points of the User API. """
USERNAME
=
"bob"
EMAIL
=
"bob@example.com"
PASSWORD
=
"password"
NAME
=
"Bob Smith"
EDUCATION
=
"m"
YEAR_OF_BIRTH
=
"1998"
ADDRESS
=
"123 Fake Street"
CITY
=
"Springfield"
COUNTRY
=
"us"
GOALS
=
"Learn all the things!"
def
setUp
(
self
):
super
(
RegistrationViewTest
,
self
)
.
setUp
()
self
.
url
=
reverse
(
"user_api_registration"
)
def
test_allowed_methods
(
self
):
self
.
assertAllowedMethods
(
self
.
url
,
[
"GET"
,
"POST"
,
"HEAD"
,
"OPTIONS"
])
def
test_put_not_allowed
(
self
):
response
=
self
.
client
.
put
(
self
.
url
)
self
.
assertHttpMethodNotAllowed
(
response
)
def
test_delete_not_allowed
(
self
):
response
=
self
.
client
.
delete
(
self
.
url
)
self
.
assertHttpMethodNotAllowed
(
response
)
def
test_patch_not_allowed
(
self
):
raise
SkipTest
(
"Django 1.4's test client does not support patch"
)
def
test_register_form_default_fields
(
self
):
no_extra_fields_setting
=
{}
self
.
_assert_reg_field
(
no_extra_fields_setting
,
{
u"name"
:
u"email"
,
u"default"
:
u""
,
u"type"
:
u"text"
,
u"required"
:
True
,
u"label"
:
u"E-mail"
,
u"placeholder"
:
u"example: username@domain.com"
,
u"instructions"
:
u"This is the e-mail address you used to register with edX"
,
u"restrictions"
:
{
u"min_length"
:
3
,
u"max_length"
:
254
},
}
)
self
.
_assert_reg_field
(
no_extra_fields_setting
,
{
u"name"
:
u"name"
,
u"default"
:
u""
,
u"type"
:
u"text"
,
u"required"
:
True
,
u"label"
:
u"Full Name"
,
u"placeholder"
:
u""
,
u"instructions"
:
u"Needed for any certificates you may earn"
,
u"restrictions"
:
{
"max_length"
:
255
,
}
}
)
self
.
_assert_reg_field
(
no_extra_fields_setting
,
{
u"name"
:
u"username"
,
u"default"
:
u""
,
u"type"
:
u"text"
,
u"required"
:
True
,
u"label"
:
u"Public Username"
,
u"placeholder"
:
u""
,
u"instructions"
:
u"Will be shown in any discussions or forums you participate in (cannot be changed)"
,
u"restrictions"
:
{
u"min_length"
:
2
,
u"max_length"
:
30
,
}
}
)
self
.
_assert_reg_field
(
no_extra_fields_setting
,
{
u"name"
:
u"password"
,
u"default"
:
u""
,
u"type"
:
u"text"
,
u"required"
:
True
,
u"label"
:
u"Password"
,
u"placeholder"
:
u""
,
u"instructions"
:
u""
,
u"restrictions"
:
{
u"min_length"
:
2
,
u"max_length"
:
75
},
}
)
def
test_register_form_level_of_education
(
self
):
self
.
_assert_reg_field
(
{
"level_of_education"
:
"optional"
},
{
"name"
:
"level_of_education"
,
"default"
:
""
,
"type"
:
"select"
,
"required"
:
False
,
"label"
:
"Highest Level of Education Completed"
,
"placeholder"
:
""
,
"instructions"
:
""
,
"options"
:
[
{
"value"
:
""
,
"name"
:
"--"
},
{
"value"
:
"p"
,
"name"
:
"Doctorate"
},
{
"value"
:
"m"
,
"name"
:
"Master's or professional degree"
},
{
"value"
:
"b"
,
"name"
:
"Bachelor's degree"
},
{
"value"
:
"a"
,
"name"
:
"Associate's degree"
},
{
"value"
:
"hs"
,
"name"
:
"Secondary/high school"
},
{
"value"
:
"jhs"
,
"name"
:
"Junior secondary/junior high/middle school"
},
{
"value"
:
"el"
,
"name"
:
"Elementary/primary school"
},
{
"value"
:
"none"
,
"name"
:
"None"
},
{
"value"
:
"other"
,
"name"
:
"Other"
},
],
"restrictions"
:
{},
}
)
def
test_register_form_gender
(
self
):
self
.
_assert_reg_field
(
{
"gender"
:
"optional"
},
{
"name"
:
"gender"
,
"default"
:
""
,
"type"
:
"select"
,
"required"
:
False
,
"label"
:
"Gender"
,
"placeholder"
:
""
,
"instructions"
:
""
,
"options"
:
[
{
"value"
:
""
,
"name"
:
"--"
},
{
"value"
:
"m"
,
"name"
:
"Male"
},
{
"value"
:
"f"
,
"name"
:
"Female"
},
{
"value"
:
"o"
,
"name"
:
"Other"
},
],
"restrictions"
:
{},
}
)
def
test_register_form_year_of_birth
(
self
):
this_year
=
datetime
.
datetime
.
now
(
UTC
)
.
year
year_options
=
(
[{
"value"
:
""
,
"name"
:
"--"
}]
+
[
{
"value"
:
unicode
(
year
),
"name"
:
unicode
(
year
)}
for
year
in
range
(
this_year
,
this_year
-
120
,
-
1
)
]
)
self
.
_assert_reg_field
(
{
"year_of_birth"
:
"optional"
},
{
"name"
:
"year_of_birth"
,
"default"
:
""
,
"type"
:
"select"
,
"required"
:
False
,
"label"
:
"Year of Birth"
,
"placeholder"
:
""
,
"instructions"
:
""
,
"options"
:
year_options
,
"restrictions"
:
{},
}
)
def
test_registration_form_mailing_address
(
self
):
self
.
_assert_reg_field
(
{
"mailing_address"
:
"optional"
},
{
"name"
:
"mailing_address"
,
"default"
:
""
,
"type"
:
"textarea"
,
"required"
:
False
,
"label"
:
"Mailing Address"
,
"placeholder"
:
""
,
"instructions"
:
""
,
"restrictions"
:
{},
}
)
def
test_registration_form_goals
(
self
):
self
.
_assert_reg_field
(
{
"goals"
:
"optional"
},
{
"name"
:
"goals"
,
"default"
:
""
,
"type"
:
"textarea"
,
"required"
:
False
,
"label"
:
"Please share with us your reasons for registering with edX"
,
"placeholder"
:
""
,
"instructions"
:
""
,
"restrictions"
:
{},
}
)
def
test_registration_form_city
(
self
):
self
.
_assert_reg_field
(
{
"city"
:
"optional"
},
{
"name"
:
"city"
,
"default"
:
""
,
"type"
:
"text"
,
"required"
:
False
,
"label"
:
"City"
,
"placeholder"
:
""
,
"instructions"
:
""
,
"restrictions"
:
{},
}
)
def
test_registration_form_country
(
self
):
country_options
=
(
[{
"name"
:
"--"
,
"value"
:
""
}]
+
[
{
"value"
:
country_code
,
"name"
:
unicode
(
country_name
)}
for
country_code
,
country_name
in
COUNTRIES
]
)
self
.
_assert_reg_field
(
{
"country"
:
"required"
},
{
"label"
:
"Country"
,
"name"
:
"country"
,
"default"
:
""
,
"type"
:
"select"
,
"required"
:
True
,
"placeholder"
:
""
,
"instructions"
:
""
,
"options"
:
country_options
,
"restrictions"
:
{},
}
)
@override_settings
(
REGISTRATION_EXTRA_FIELDS
=
{
"level_of_education"
:
"optional"
,
"gender"
:
"optional"
,
"year_of_birth"
:
"optional"
,
"mailing_address"
:
"optional"
,
"goals"
:
"optional"
,
"city"
:
"optional"
,
"country"
:
"required"
,
})
def
test_field_order
(
self
):
response
=
self
.
client
.
get
(
self
.
url
)
self
.
assertHttpOK
(
response
)
# Verify that all fields render in the correct order
form_desc
=
json
.
loads
(
response
.
content
)
field_names
=
[
field
[
"name"
]
for
field
in
form_desc
[
"fields"
]]
self
.
assertEqual
(
field_names
,
[
"email"
,
"name"
,
"username"
,
"password"
,
"city"
,
"country"
,
"level_of_education"
,
"gender"
,
"year_of_birth"
,
"mailing_address"
,
"goals"
,
])
def
test_register
(
self
):
# Create a new registration
response
=
self
.
client
.
post
(
self
.
url
,
{
"email"
:
self
.
EMAIL
,
"name"
:
self
.
NAME
,
"username"
:
self
.
USERNAME
,
"password"
:
self
.
PASSWORD
,
})
self
.
assertHttpOK
(
response
)
# Verify that the user exists
self
.
assertEqual
(
account_api
.
account_info
(
self
.
USERNAME
),
{
"username"
:
self
.
USERNAME
,
"email"
:
self
.
EMAIL
,
"is_active"
:
False
}
)
# Verify that the user's full name is set
profile_info
=
profile_api
.
profile_info
(
self
.
USERNAME
)
self
.
assertEqual
(
profile_info
[
"full_name"
],
self
.
NAME
)
# Verify that we've been logged in
# by trying to access a page that requires authentication
response
=
self
.
client
.
get
(
reverse
(
"dashboard"
))
self
.
assertHttpOK
(
response
)
@override_settings
(
REGISTRATION_EXTRA_FIELDS
=
{
"level_of_education"
:
"optional"
,
"gender"
:
"optional"
,
"year_of_birth"
:
"optional"
,
"mailing_address"
:
"optional"
,
"goals"
:
"optional"
,
"city"
:
"optional"
,
"country"
:
"required"
,
})
def
test_register_with_profile_info
(
self
):
# Register, providing lots of demographic info
response
=
self
.
client
.
post
(
self
.
url
,
{
"email"
:
self
.
EMAIL
,
"name"
:
self
.
NAME
,
"username"
:
self
.
USERNAME
,
"password"
:
self
.
PASSWORD
,
"level_of_education"
:
self
.
EDUCATION
,
"mailing_address"
:
self
.
ADDRESS
,
"year_of_birth"
:
self
.
YEAR_OF_BIRTH
,
"goals"
:
self
.
GOALS
,
"city"
:
self
.
CITY
,
"country"
:
self
.
COUNTRY
})
self
.
assertHttpOK
(
response
)
# Verify the profile information
profile_info
=
profile_api
.
profile_info
(
self
.
USERNAME
)
self
.
assertEqual
(
profile_info
[
"level_of_education"
],
self
.
EDUCATION
)
self
.
assertEqual
(
profile_info
[
"mailing_address"
],
self
.
ADDRESS
)
self
.
assertEqual
(
profile_info
[
"year_of_birth"
],
int
(
self
.
YEAR_OF_BIRTH
))
self
.
assertEqual
(
profile_info
[
"goals"
],
self
.
GOALS
)
self
.
assertEqual
(
profile_info
[
"city"
],
self
.
CITY
)
self
.
assertEqual
(
profile_info
[
"country"
],
self
.
COUNTRY
)
def
test_activation_email
(
self
):
# Register, which should trigger an activation email
response
=
self
.
client
.
post
(
self
.
url
,
{
"email"
:
self
.
EMAIL
,
"name"
:
self
.
NAME
,
"username"
:
self
.
USERNAME
,
"password"
:
self
.
PASSWORD
,
})
self
.
assertHttpOK
(
response
)
# Verify that the activation email was sent
self
.
assertEqual
(
len
(
mail
.
outbox
),
1
)
sent_email
=
mail
.
outbox
[
0
]
self
.
assertEqual
(
sent_email
.
to
,
[
self
.
EMAIL
])
self
.
assertEqual
(
sent_email
.
subject
,
"Activate Your edX Account"
)
self
.
assertIn
(
"activate your account"
,
sent_email
.
body
)
@ddt.data
(
{
"email"
:
""
},
{
"email"
:
"invalid"
},
{
"name"
:
""
},
{
"username"
:
""
},
{
"username"
:
"a"
},
{
"password"
:
""
},
)
def
test_register_invalid_input
(
self
,
invalid_fields
):
# Initially, the field values are all valid
data
=
{
"email"
:
self
.
EMAIL
,
"name"
:
self
.
NAME
,
"username"
:
self
.
USERNAME
,
"password"
:
self
.
PASSWORD
,
}
# Override the valid fields, making the input invalid
data
.
update
(
invalid_fields
)
# Attempt to create the account, expecting an error response
response
=
self
.
client
.
post
(
self
.
url
,
data
)
self
.
assertHttpBadRequest
(
response
)
@override_settings
(
REGISTRATION_EXTRA_FIELDS
=
{
"country"
:
"required"
})
@ddt.data
(
"email"
,
"name"
,
"username"
,
"password"
,
"country"
)
def
test_register_missing_required_field
(
self
,
missing_field
):
data
=
{
"email"
:
self
.
EMAIL
,
"name"
:
self
.
NAME
,
"username"
:
self
.
USERNAME
,
"password"
:
self
.
PASSWORD
,
"country"
:
self
.
COUNTRY
,
}
del
data
[
missing_field
]
# Send a request missing a field
response
=
self
.
client
.
post
(
self
.
url
,
data
)
self
.
assertHttpBadRequest
(
response
)
def
test_register_already_authenticated
(
self
):
data
=
{
"email"
:
self
.
EMAIL
,
"name"
:
self
.
NAME
,
"username"
:
self
.
USERNAME
,
"password"
:
self
.
PASSWORD
,
}
# Register once, which will also log us in
response
=
self
.
client
.
post
(
self
.
url
,
data
)
self
.
assertHttpOK
(
response
)
# Try to register again
response
=
self
.
client
.
post
(
self
.
url
,
data
)
self
.
assertHttpBadRequest
(
response
)
def
_assert_reg_field
(
self
,
extra_fields_setting
,
expected_field
):
"""Retrieve the registration form description from the server and
verify that it contains the expected field.
Args:
extra_fields_setting (dict): Override the Django setting controlling
which extra fields are displayed in the form.
expected_field (dict): The field definition we expect to find in the form.
Raises:
AssertionError
"""
# Retrieve the registration form description
with
override_settings
(
REGISTRATION_EXTRA_FIELDS
=
extra_fields_setting
):
response
=
self
.
client
.
get
(
self
.
url
)
self
.
assertHttpOK
(
response
)
# Verify that the form description matches what we'd expect
form_desc
=
json
.
loads
(
response
.
content
)
self
.
assertIn
(
expected_field
,
form_desc
[
"fields"
])
This diff is collapsed.
Click to expand it.
common/djangoapps/user_api/urls.py
View file @
e89afa93
...
...
@@ -10,6 +10,8 @@ user_api_router.register(r'user_prefs', user_api_views.UserPreferenceViewSet)
urlpatterns
=
patterns
(
''
,
url
(
r'^v1/'
,
include
(
user_api_router
.
urls
)),
url
(
r'^v1/account/login_session/$'
,
user_api_views
.
LoginSessionView
.
as_view
(),
name
=
"user_api_login_session"
),
url
(
r'^v1/account/registration/$'
,
user_api_views
.
RegistrationView
.
as_view
(),
name
=
"user_api_registration"
),
url
(
r'^v1/preferences/(?P<pref_key>{})/users/$'
.
format
(
UserPreference
.
KEY_REGEX
),
user_api_views
.
PreferenceUsersListView
.
as_view
()
...
...
This diff is collapsed.
Click to expand it.
common/djangoapps/user_api/views.py
View file @
e89afa93
"""TODO"""
from
django.conf
import
settings
from
django.contrib.auth.models
import
User
from
django.http
import
HttpResponse
,
HttpResponseBadRequest
from
django.core.urlresolvers
import
reverse
from
django.utils.translation
import
ugettext
as
_
from
django.utils.decorators
import
method_decorator
from
django.views.decorators.csrf
import
ensure_csrf_cookie
from
rest_framework
import
authentication
from
rest_framework
import
filters
from
rest_framework
import
generics
from
rest_framework
import
permissions
from
rest_framework
import
status
from
rest_framework
import
viewsets
from
rest_framework.views
import
APIView
from
rest_framework.exceptions
import
ParseError
from
rest_framework.response
import
Response
from
django_countries.countries
import
COUNTRIES
from
user_api.serializers
import
UserSerializer
,
UserPreferenceSerializer
from
user_api.models
import
UserPreference
from
user_api.models
import
UserPreference
,
UserProfile
from
django_comment_common.models
import
Role
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
user_api.api
import
account
as
account_api
,
profile
as
profile_api
from
user_api.helpers
import
FormDescription
,
shim_student_view
class
ApiKeyHeaderPermission
(
permissions
.
BasePermission
):
def
has_permission
(
self
,
request
,
view
):
...
...
@@ -31,6 +41,251 @@ class ApiKeyHeaderPermission(permissions.BasePermission):
)
class
LoginSessionView
(
APIView
):
"""TODO"""
def
get
(
self
,
request
):
"""Render a form for allowing a user to log in.
TODO
"""
form_desc
=
FormDescription
(
"post"
,
reverse
(
"user_api_login_session"
))
form_desc
.
add_field
(
"email"
,
label
=
_
(
u"E-mail"
),
placeholder
=
_
(
u"example: username@domain.com"
),
instructions
=
_
(
u"This is the e-mail address you used to register with {platform}"
)
.
format
(
platform
=
settings
.
PLATFORM_NAME
),
restrictions
=
{
"min_length"
:
account_api
.
EMAIL_MIN_LENGTH
,
"max_length"
:
account_api
.
EMAIL_MAX_LENGTH
,
}
)
form_desc
.
add_field
(
"password"
,
label
=
_
(
u"Password"
),
restrictions
=
{
"min_length"
:
account_api
.
PASSWORD_MIN_LENGTH
,
"max_length"
:
account_api
.
PASSWORD_MAX_LENGTH
,
}
)
return
HttpResponse
(
form_desc
.
to_json
(),
content_type
=
"application/json"
)
@method_decorator
(
ensure_csrf_cookie
)
def
post
(
self
,
request
):
"""Authenticate a user and log them in.
TODO
"""
# Validate the parameters
# If either param is missing, it's a malformed request
email
=
request
.
POST
.
get
(
"email"
)
password
=
request
.
POST
.
get
(
"password"
)
if
email
is
None
or
password
is
None
:
return
HttpResponseBadRequest
()
return
self
.
_login_shim
(
request
)
def
_login_shim
(
self
,
request
):
# Initially, this should be a shim to student views,
# since it will be too much work to re-implement everything there.
# Eventually, we'll want to pull out that functionality into this Django app.
from
student.views
import
login_user
return
shim_student_view
(
login_user
,
check_logged_in
=
True
)(
request
)
class
RegistrationView
(
APIView
):
"""TODO"""
DEFAULT_FIELDS
=
[
"email"
,
"name"
,
"username"
,
"password"
]
EXTRA_FIELDS
=
[
"city"
,
"country"
,
"level_of_education"
,
"gender"
,
"year_of_birth"
,
"mailing_address"
,
"goals"
,
]
def
__init__
(
self
,
*
args
,
**
kwargs
):
super
(
RegistrationView
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
self
.
field_handlers
=
{}
for
field_name
in
(
self
.
DEFAULT_FIELDS
+
self
.
EXTRA_FIELDS
):
handler
=
getattr
(
self
,
"_add_{field_name}_field"
.
format
(
field_name
=
field_name
))
self
.
field_handlers
[
field_name
]
=
handler
def
get
(
self
,
request
):
"""Render a form for allowing the user to register.
TODO
"""
form_desc
=
FormDescription
(
"post"
,
reverse
(
"user_api_registration"
))
# Default fields are always required
for
field_name
in
self
.
DEFAULT_FIELDS
:
self
.
field_handlers
[
field_name
](
form_desc
,
required
=
True
)
# Extra fields from configuration may be required, optional, or hidden
# TODO -- explain error handling here
for
field_name
in
self
.
EXTRA_FIELDS
:
field_setting
=
settings
.
REGISTRATION_EXTRA_FIELDS
.
get
(
field_name
)
handler
=
self
.
field_handlers
[
field_name
]
if
field_setting
in
[
"required"
,
"optional"
]:
handler
(
form_desc
,
required
=
(
field_setting
==
"required"
))
elif
field_setting
!=
"hidden"
:
# TODO -- warning here
pass
return
HttpResponse
(
form_desc
.
to_json
(),
content_type
=
"application/json"
)
def
post
(
self
,
request
):
"""Create the user's account.
TODO
"""
# Backwards compat:
# TODO -- explain this
request
.
POST
[
"honor_code"
]
=
"true"
request
.
POST
[
"terms_of_service"
]
=
"true"
# Initially, this should be a shim to student views.
# Eventually, we'll want to pull that functionality into this API.
from
student.views
import
create_account
return
shim_student_view
(
create_account
)(
request
)
def
_add_email_field
(
self
,
form_desc
,
required
=
True
):
"""TODO """
form_desc
.
add_field
(
"email"
,
label
=
_
(
u"E-mail"
),
placeholder
=
_
(
u"example: username@domain.com"
),
instructions
=
_
(
u"This is the e-mail address you used to register with {platform}"
)
.
format
(
platform
=
settings
.
PLATFORM_NAME
),
restrictions
=
{
"min_length"
:
account_api
.
EMAIL_MIN_LENGTH
,
"max_length"
:
account_api
.
EMAIL_MAX_LENGTH
,
},
required
=
required
)
def
_add_name_field
(
self
,
form_desc
,
required
=
True
):
"""TODO"""
form_desc
.
add_field
(
"name"
,
label
=
_
(
u"Full Name"
),
instructions
=
_
(
u"Needed for any certificates you may earn"
),
restrictions
=
{
"max_length"
:
profile_api
.
FULL_NAME_MAX_LENGTH
,
},
required
=
required
)
def
_add_username_field
(
self
,
form_desc
,
required
=
True
):
"""TODO"""
form_desc
.
add_field
(
"username"
,
label
=
_
(
u"Public Username"
),
instructions
=
_
(
u"Will be shown in any discussions or forums you participate in (cannot be changed)"
),
restrictions
=
{
"min_length"
:
account_api
.
USERNAME_MIN_LENGTH
,
"max_length"
:
account_api
.
USERNAME_MAX_LENGTH
,
},
required
=
required
)
def
_add_password_field
(
self
,
form_desc
,
required
=
True
):
"""TODO"""
form_desc
.
add_field
(
"password"
,
label
=
_
(
u"Password"
),
restrictions
=
{
"min_length"
:
account_api
.
PASSWORD_MIN_LENGTH
,
"max_length"
:
account_api
.
PASSWORD_MAX_LENGTH
,
},
required
=
required
)
def
_add_level_of_education_field
(
self
,
form_desc
,
required
=
True
):
""" TODO """
form_desc
.
add_field
(
"level_of_education"
,
label
=
_
(
"Highest Level of Education Completed"
),
field_type
=
"select"
,
options
=
self
.
_options_with_default
(
UserProfile
.
LEVEL_OF_EDUCATION_CHOICES
),
required
=
required
)
def
_add_gender_field
(
self
,
form_desc
,
required
=
True
):
"""TODO """
form_desc
.
add_field
(
"gender"
,
label
=
_
(
"Gender"
),
field_type
=
"select"
,
options
=
self
.
_options_with_default
(
UserProfile
.
GENDER_CHOICES
),
required
=
required
)
def
_add_year_of_birth_field
(
self
,
form_desc
,
required
=
True
):
"""TODO """
options
=
[(
unicode
(
year
),
unicode
(
year
))
for
year
in
UserProfile
.
VALID_YEARS
]
form_desc
.
add_field
(
"year_of_birth"
,
label
=
_
(
"Year of Birth"
),
field_type
=
"select"
,
options
=
self
.
_options_with_default
(
options
),
required
=
required
)
def
_add_mailing_address_field
(
self
,
form_desc
,
required
=
True
):
"""TODO """
form_desc
.
add_field
(
"mailing_address"
,
label
=
_
(
"Mailing Address"
),
field_type
=
"textarea"
,
required
=
required
)
def
_add_goals_field
(
self
,
form_desc
,
required
=
True
):
"""TODO """
form_desc
.
add_field
(
"goals"
,
label
=
_
(
"Please share with us your reasons for registering with edX"
),
field_type
=
"textarea"
,
required
=
required
)
def
_add_city_field
(
self
,
form_desc
,
required
=
True
):
"""TODO """
form_desc
.
add_field
(
"city"
,
label
=
_
(
"City"
),
required
=
required
)
def
_add_country_field
(
self
,
form_desc
,
required
=
True
):
"""TODO """
options
=
[
(
country_code
,
unicode
(
country_name
))
for
country_code
,
country_name
in
COUNTRIES
]
form_desc
.
add_field
(
"country"
,
label
=
_
(
"Country"
),
field_type
=
"select"
,
options
=
self
.
_options_with_default
(
options
),
required
=
required
)
def
_options_with_default
(
self
,
options
):
"""TODO """
return
(
[(
""
,
"--"
)]
+
list
(
options
)
)
class
UserViewSet
(
viewsets
.
ReadOnlyModelViewSet
):
authentication_classes
=
(
authentication
.
SessionAuthentication
,)
permission_classes
=
(
ApiKeyHeaderPermission
,)
...
...
This diff is collapsed.
Click to expand it.
lms/djangoapps/student_account/test/test_views.py
View file @
e89afa93
...
...
@@ -3,6 +3,7 @@
import
re
from
urllib
import
urlencode
import
json
from
mock
import
patch
import
ddt
from
django.test
import
TestCase
...
...
@@ -60,6 +61,37 @@ class StudentAccountViewTest(UrlResetMixin, TestCase):
response
=
self
.
client
.
get
(
reverse
(
'account_index'
))
self
.
assertContains
(
response
,
"Student Account"
)
@ddt.data
(
(
"login"
,
"login"
),
(
"register"
,
"register"
),
)
@ddt.unpack
def
test_login_and_registration_form
(
self
,
url_name
,
initial_mode
):
response
=
self
.
client
.
get
(
reverse
(
url_name
))
expected_data
=
u"data-initial-mode=
\"
{mode}
\"
"
.
format
(
mode
=
initial_mode
)
self
.
assertContains
(
response
,
expected_data
)
@ddt.data
(
"login"
,
"register"
)
def
test_login_and_registration_third_party_auth_urls
(
self
,
url_name
):
response
=
self
.
client
.
get
(
reverse
(
url_name
))
# This relies on the THIRD_PARTY_AUTH configuration in the test settings
expected_data
=
u"data-third-party-auth-providers=
\"
{providers}
\"
"
.
format
(
providers
=
json
.
dumps
([
{
u'icon_class'
:
u'icon-facebook'
,
u'login_url'
:
u'/auth/login/facebook/?auth_entry=login'
,
u'name'
:
u'Facebook'
},
{
u'icon_class'
:
u'icon-google-plus'
,
u'login_url'
:
u'/auth/login/google-oauth2/?auth_entry=login'
,
u'name'
:
u'Google'
}
])
)
self
.
assertContains
(
response
,
expected_data
)
def
test_change_email
(
self
):
response
=
self
.
_change_email
(
self
.
NEW_EMAIL
,
self
.
PASSWORD
)
self
.
assertEquals
(
response
.
status_code
,
200
)
...
...
This diff is collapsed.
Click to expand it.
lms/djangoapps/student_account/urls.py
View file @
e89afa93
from
django.conf.urls
import
patterns
,
url
from
django.conf
import
settings
urlpatterns
=
patterns
(
'student_account.views'
,
url
(
r'^$'
,
'index'
,
name
=
'account_index'
),
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'
),
url
(
r'^login/$'
,
'login_and_registration_form'
,
{
'initial_mode'
:
'login'
},
name
=
'login'
),
url
(
r'^register/$'
,
'login_and_registration_form'
,
{
'initial_mode'
:
'register'
},
name
=
'register'
),
)
if
settings
.
FEATURES
.
get
(
'ENABLE_NEW_DASHBOARD'
):
urlpatterns
+=
patterns
(
'student_account.views'
,
url
(
r'^$'
,
'index'
,
name
=
'account_index'
),
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'
),
)
\ No newline at end of file
This diff is collapsed.
Click to expand it.
lms/djangoapps/student_account/views.py
View file @
e89afa93
""" Views for a student's account information. """
import
json
from
django.conf
import
settings
from
django.http
import
(
QueryDict
,
HttpResponse
,
HttpResponseBadRequest
,
HttpResponseServerError
HttpResponse
,
HttpResponseBadRequest
,
HttpResponseServerError
)
from
django.core.mail
import
send_mail
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
,
render_to_string
import
third_party_auth
from
microsite_configuration
import
microsite
from
user_api.api
import
account
as
account_api
...
...
@@ -41,6 +42,38 @@ def index(request):
)
@require_http_methods
([
'GET'
])
def
login_and_registration_form
(
request
,
initial_mode
=
"login"
):
"""Render the combined login/registration form, defaulting to login
This relies on the JS to asynchronously load the actual form from
the user_api.
Keyword Args:
initial_mode (string): Either "login" or "registration".
"""
context
=
{
'disable_courseware_js'
:
True
,
'initial_mode'
:
initial_mode
,
'third_party_auth_providers'
:
json
.
dumps
([])
}
if
microsite
.
get_value
(
"ENABLE_THIRD_PARTY_AUTH"
,
settings
.
FEATURES
.
get
(
"ENABLE_THIRD_PARTY_AUTH"
)):
context
[
"third_party_auth_providers"
]
=
json
.
dumps
([
{
"name"
:
enabled
.
NAME
,
"icon_class"
:
enabled
.
ICON_CLASS
,
"login_url"
:
third_party_auth
.
pipeline
.
get_login_url
(
enabled
.
NAME
,
third_party_auth
.
pipeline
.
AUTH_ENTRY_LOGIN
),
}
for
enabled
in
third_party_auth
.
provider
.
Registry
.
enabled
()
])
return
render_to_response
(
'student_account/login_and_register.html'
,
context
)
@login_required
@require_http_methods
([
'POST'
])
@ensure_csrf_cookie
...
...
This diff is collapsed.
Click to expand it.
lms/djangoapps/student_profile/urls.py
View file @
e89afa93
from
django.conf.urls
import
patterns
,
url
from
django.conf
import
settings
urlpatterns
=
patterns
(
'student_profile.views'
,
url
(
r'^$'
,
'index'
,
name
=
'profile_index'
),
url
(
r'^preferences$'
,
'preference_handler'
,
name
=
'preference_handler'
),
url
(
r'^preferences/languages$'
,
'language_info'
,
name
=
'language_info'
),
)
urlpatterns
=
[]
if
settings
.
FEATURES
.
get
(
'ENABLE_NEW_DASHBOARD'
):
urlpatterns
=
patterns
(
'student_profile.views'
,
url
(
r'^$'
,
'index'
,
name
=
'profile_index'
),
url
(
r'^preferences$'
,
'preference_handler'
,
name
=
'preference_handler'
),
url
(
r'^preferences/languages$'
,
'language_info'
,
name
=
'language_info'
),
)
This diff is collapsed.
Click to expand it.
lms/envs/test.py
View file @
e89afa93
...
...
@@ -203,6 +203,17 @@ simplefilter('ignore') # Change to "default" to see the first instance of each
######### Third-party auth ##########
FEATURES
[
'ENABLE_THIRD_PARTY_AUTH'
]
=
True
THIRD_PARTY_AUTH
=
{
"Google"
:
{
"SOCIAL_AUTH_GOOGLE_OAUTH2_KEY"
:
"test"
,
"SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET"
:
"test"
,
},
"Facebook"
:
{
"SOCIAL_AUTH_GOOGLE_OAUTH2_KEY"
:
"test"
,
"SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET"
:
"test"
,
},
}
################################## OPENID #####################################
FEATURES
[
'AUTH_USE_OPENID'
]
=
True
FEATURES
[
'AUTH_USE_OPENID_PROVIDER'
]
=
True
...
...
This diff is collapsed.
Click to expand it.
lms/templates/student_account/login_and_register.html
0 → 100644
View file @
e89afa93
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
%
>
<
%
namespace
name=
'static'
file=
'/static_content.html'
/>
<
%
inherit
file=
"../main.html"
/>
<
%
block
name=
"pagetitle"
>
${_("Login and Register")}
</
%
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>
Login and Registration!
</h1>
<p>
This is a placeholder for the combined login and registration form
</p>
## TODO: Use JavaScript to populate this div with
## the actual registration/login forms (loaded asynchronously from the user API)
## The URLS for the forms are:
## - GET /user_api/v1/registration/
## - GET /user_api/v1/login_session/
##
## You can post back to those URLs with JSON-serialized
## data from the form fields in order to complete the registration
## or login.
##
## Also TODO: we need to figure out how to enroll students in
## a course if they got here from a course about page.
##
## third_party_auth_providers is a JSON-serialized list of
## dictionaries of the form:
## {
## "name": "Facebook",
## "icon_class": "facebook-icon",
## "login_url": "http://api.facebook.com/auth"
## }
##
## Note that this list may be empty.
##
<div
id=
"login-and-registration-container"
data-initial-mode=
"${initial_mode}"
data-third-party-auth-providers=
"${third_party_auth_providers}"
/>
This diff is collapsed.
Click to expand it.
lms/urls.py
View file @
e89afa93
...
...
@@ -375,6 +375,10 @@ if settings.COURSEWARE_ENABLED:
# LTI endpoints listing
url
(
r'^courses/{}/lti_rest_endpoints/'
.
format
(
settings
.
COURSE_ID_PATTERN
),
'courseware.views.get_course_lti_endpoints'
,
name
=
'lti_rest_endpoints'
),
# Student account and profile
url
(
r'^account/'
,
include
(
'student_account.urls'
)),
url
(
r'^profile/'
,
include
(
'student_profile.urls'
)),
)
# allow course staff to change to student view of courseware
...
...
@@ -537,12 +541,6 @@ if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH'):
url
(
r''
,
include
(
'third_party_auth.urls'
)),
)
# If enabled, expose the URLs for the new dashboard, account, and profile pages
if
settings
.
FEATURES
.
get
(
'ENABLE_NEW_DASHBOARD'
):
urlpatterns
+=
(
url
(
r'^profile/'
,
include
(
'student_profile.urls'
)),
url
(
r'^account/'
,
include
(
'student_account.urls'
)),
)
urlpatterns
=
patterns
(
*
urlpatterns
)
...
...
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment