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
Oct 14, 2014
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
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
669 additions
and
23 deletions
+669
-23
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
+0
-0
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):
...
@@ -65,9 +65,15 @@ def profile_info(username):
return
None
return
None
profile_dict
=
{
profile_dict
=
{
u'username'
:
profile
.
user
.
username
,
"username"
:
profile
.
user
.
username
,
u'email'
:
profile
.
user
.
email
,
"email"
:
profile
.
user
.
email
,
u'full_name'
:
profile
.
name
,
"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
return
profile_dict
...
...
common/djangoapps/user_api/helpers.py
View file @
e89afa93
...
@@ -4,6 +4,8 @@ This is NOT part of the public API.
...
@@ -4,6 +4,8 @@ This is NOT part of the public API.
"""
"""
from
functools
import
wraps
from
functools
import
wraps
import
logging
import
logging
import
json
LOGGER
=
logging
.
getLogger
(
__name__
)
LOGGER
=
logging
.
getLogger
(
__name__
)
...
@@ -54,3 +56,242 @@ def intercept_errors(api_error, ignore_errors=[]):
...
@@ -54,3 +56,242 @@ def intercept_errors(api_error, ignore_errors=[]):
raise
api_error
(
msg
)
raise
api_error
(
msg
)
return
_wrapped
return
_wrapped
return
_decorator
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
common/djangoapps/user_api/tests/test_views.py
View file @
e89afa93
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)
...
@@ -10,6 +10,8 @@ user_api_router.register(r'user_prefs', user_api_views.UserPreferenceViewSet)
urlpatterns
=
patterns
(
urlpatterns
=
patterns
(
''
,
''
,
url
(
r'^v1/'
,
include
(
user_api_router
.
urls
)),
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
(
url
(
r'^v1/preferences/(?P<pref_key>{})/users/$'
.
format
(
UserPreference
.
KEY_REGEX
),
r'^v1/preferences/(?P<pref_key>{})/users/$'
.
format
(
UserPreference
.
KEY_REGEX
),
user_api_views
.
PreferenceUsersListView
.
as_view
()
user_api_views
.
PreferenceUsersListView
.
as_view
()
...
...
common/djangoapps/user_api/views.py
View file @
e89afa93
"""TODO"""
from
django.conf
import
settings
from
django.conf
import
settings
from
django.contrib.auth.models
import
User
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
authentication
from
rest_framework
import
filters
from
rest_framework
import
filters
from
rest_framework
import
generics
from
rest_framework
import
generics
from
rest_framework
import
permissions
from
rest_framework
import
permissions
from
rest_framework
import
status
from
rest_framework
import
viewsets
from
rest_framework
import
viewsets
from
rest_framework.views
import
APIView
from
rest_framework.exceptions
import
ParseError
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.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
django_comment_common.models
import
Role
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
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
):
class
ApiKeyHeaderPermission
(
permissions
.
BasePermission
):
def
has_permission
(
self
,
request
,
view
):
def
has_permission
(
self
,
request
,
view
):
...
@@ -31,6 +41,251 @@ class ApiKeyHeaderPermission(permissions.BasePermission):
...
@@ -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
):
class
UserViewSet
(
viewsets
.
ReadOnlyModelViewSet
):
authentication_classes
=
(
authentication
.
SessionAuthentication
,)
authentication_classes
=
(
authentication
.
SessionAuthentication
,)
permission_classes
=
(
ApiKeyHeaderPermission
,)
permission_classes
=
(
ApiKeyHeaderPermission
,)
...
...
lms/djangoapps/student_account/test/test_views.py
View file @
e89afa93
...
@@ -3,6 +3,7 @@
...
@@ -3,6 +3,7 @@
import
re
import
re
from
urllib
import
urlencode
from
urllib
import
urlencode
import
json
from
mock
import
patch
from
mock
import
patch
import
ddt
import
ddt
from
django.test
import
TestCase
from
django.test
import
TestCase
...
@@ -60,6 +61,37 @@ class StudentAccountViewTest(UrlResetMixin, TestCase):
...
@@ -60,6 +61,37 @@ class StudentAccountViewTest(UrlResetMixin, TestCase):
response
=
self
.
client
.
get
(
reverse
(
'account_index'
))
response
=
self
.
client
.
get
(
reverse
(
'account_index'
))
self
.
assertContains
(
response
,
"Student Account"
)
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
):
def
test_change_email
(
self
):
response
=
self
.
_change_email
(
self
.
NEW_EMAIL
,
self
.
PASSWORD
)
response
=
self
.
_change_email
(
self
.
NEW_EMAIL
,
self
.
PASSWORD
)
self
.
assertEquals
(
response
.
status_code
,
200
)
self
.
assertEquals
(
response
.
status_code
,
200
)
...
...
lms/djangoapps/student_account/urls.py
View file @
e89afa93
from
django.conf.urls
import
patterns
,
url
from
django.conf.urls
import
patterns
,
url
from
django.conf
import
settings
urlpatterns
=
patterns
(
urlpatterns
=
patterns
(
'student_account.views'
,
'student_account.views'
,
url
(
r'^$'
,
'index'
,
name
=
'account_index'
),
url
(
r'^login/$'
,
'login_and_registration_form'
,
{
'initial_mode'
:
'login'
},
name
=
'login'
),
url
(
r'^email$'
,
'email_change_request_handler'
,
name
=
'email_change_request'
),
url
(
r'^register/$'
,
'login_and_registration_form'
,
{
'initial_mode'
:
'register'
},
name
=
'register'
),
url
(
r'^email/confirmation/(?P<key>[^/]*)$'
,
'email_change_confirmation_handler'
,
name
=
'email_change_confirm'
),
)
)
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
lms/djangoapps/student_account/views.py
View file @
e89afa93
""" Views for a student's account information. """
""" Views for a student's account information. """
import
json
from
django.conf
import
settings
from
django.conf
import
settings
from
django.http
import
(
from
django.http
import
(
QueryDict
,
HttpResponse
,
HttpResponse
,
HttpResponseBadRequest
,
HttpResponseServerError
HttpResponseBadRequest
,
HttpResponseServerError
)
)
from
django.core.mail
import
send_mail
from
django.core.mail
import
send_mail
from
django_future.csrf
import
ensure_csrf_cookie
from
django_future.csrf
import
ensure_csrf_cookie
from
django.contrib.auth.decorators
import
login_required
from
django.contrib.auth.decorators
import
login_required
from
django.views.decorators.http
import
require_http_methods
from
django.views.decorators.http
import
require_http_methods
from
edxmako.shortcuts
import
render_to_response
,
render_to_string
from
edxmako.shortcuts
import
render_to_response
,
render_to_string
import
third_party_auth
from
microsite_configuration
import
microsite
from
microsite_configuration
import
microsite
from
user_api.api
import
account
as
account_api
from
user_api.api
import
account
as
account_api
...
@@ -41,6 +42,38 @@ def index(request):
...
@@ -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
@login_required
@require_http_methods
([
'POST'
])
@require_http_methods
([
'POST'
])
@ensure_csrf_cookie
@ensure_csrf_cookie
...
...
lms/djangoapps/student_profile/urls.py
View file @
e89afa93
from
django.conf.urls
import
patterns
,
url
from
django.conf.urls
import
patterns
,
url
from
django.conf
import
settings
urlpatterns
=
patterns
(
'student_profile.views'
,
urlpatterns
=
[]
url
(
r'^$'
,
'index'
,
name
=
'profile_index'
),
url
(
r'^preferences$'
,
'preference_handler'
,
name
=
'preference_handler'
),
url
(
r'^preferences/languages$'
,
'language_info'
,
name
=
'language_info'
),
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'
),
)
lms/envs/test.py
View file @
e89afa93
...
@@ -203,6 +203,17 @@ simplefilter('ignore') # Change to "default" to see the first instance of each
...
@@ -203,6 +203,17 @@ simplefilter('ignore') # Change to "default" to see the first instance of each
######### Third-party auth ##########
######### Third-party auth ##########
FEATURES
[
'ENABLE_THIRD_PARTY_AUTH'
]
=
True
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 #####################################
################################## OPENID #####################################
FEATURES
[
'AUTH_USE_OPENID'
]
=
True
FEATURES
[
'AUTH_USE_OPENID'
]
=
True
FEATURES
[
'AUTH_USE_OPENID_PROVIDER'
]
=
True
FEATURES
[
'AUTH_USE_OPENID_PROVIDER'
]
=
True
...
...
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}"
/>
lms/urls.py
View file @
e89afa93
...
@@ -375,6 +375,10 @@ if settings.COURSEWARE_ENABLED:
...
@@ -375,6 +375,10 @@ if settings.COURSEWARE_ENABLED:
# LTI endpoints listing
# LTI endpoints listing
url
(
r'^courses/{}/lti_rest_endpoints/'
.
format
(
settings
.
COURSE_ID_PATTERN
),
url
(
r'^courses/{}/lti_rest_endpoints/'
.
format
(
settings
.
COURSE_ID_PATTERN
),
'courseware.views.get_course_lti_endpoints'
,
name
=
'lti_rest_endpoints'
),
'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
# allow course staff to change to student view of courseware
...
@@ -537,12 +541,6 @@ if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH'):
...
@@ -537,12 +541,6 @@ if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH'):
url
(
r''
,
include
(
'third_party_auth.urls'
)),
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
)
urlpatterns
=
patterns
(
*
urlpatterns
)
...
...
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