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
291004de
Commit
291004de
authored
Feb 10, 2015
by
Greg Price
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Factor create_account param validation into a form
parent
ad86ef3b
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
221 additions
and
34 deletions
+221
-34
common/djangoapps/student/forms.py
+164
-0
common/djangoapps/student/management/commands/create_random_users.py
+12
-8
common/djangoapps/student/management/commands/create_user.py
+11
-9
common/djangoapps/student/tests/test_create_account.py
+34
-17
common/djangoapps/student/views.py
+0
-0
No files found.
common/djangoapps/student/forms.py
View file @
291004de
...
@@ -2,16 +2,23 @@
...
@@ -2,16 +2,23 @@
Utility functions for validating forms
Utility functions for validating forms
"""
"""
from
django
import
forms
from
django
import
forms
from
django.core.exceptions
import
ValidationError
from
django.contrib.auth.models
import
User
from
django.contrib.auth.models
import
User
from
django.contrib.auth.forms
import
PasswordResetForm
from
django.contrib.auth.forms
import
PasswordResetForm
from
django.contrib.auth.hashers
import
UNUSABLE_PASSWORD
from
django.contrib.auth.hashers
import
UNUSABLE_PASSWORD
from
django.contrib.auth.tokens
import
default_token_generator
from
django.contrib.auth.tokens
import
default_token_generator
from
django.utils.http
import
int_to_base36
from
django.utils.http
import
int_to_base36
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.template
import
loader
from
django.template
import
loader
from
django.conf
import
settings
from
django.conf
import
settings
from
microsite_configuration
import
microsite
from
microsite_configuration
import
microsite
from
util.password_policy_validators
import
(
validate_password_length
,
validate_password_complexity
,
validate_password_dictionary
,
)
class
PasswordResetFormNoActive
(
PasswordResetForm
):
class
PasswordResetFormNoActive
(
PasswordResetForm
):
...
@@ -70,3 +77,160 @@ class PasswordResetFormNoActive(PasswordResetForm):
...
@@ -70,3 +77,160 @@ class PasswordResetFormNoActive(PasswordResetForm):
subject
=
subject
.
replace
(
'
\n
'
,
''
)
subject
=
subject
.
replace
(
'
\n
'
,
''
)
email
=
loader
.
render_to_string
(
email_template_name
,
context
)
email
=
loader
.
render_to_string
(
email_template_name
,
context
)
send_mail
(
subject
,
email
,
from_email
,
[
user
.
email
])
send_mail
(
subject
,
email
,
from_email
,
[
user
.
email
])
class
TrueField
(
forms
.
BooleanField
):
"""
A boolean field that only accepts "true" (case-insensitive) as true
"""
def
to_python
(
self
,
value
):
# CheckboxInput converts string to bool by case-insensitive match to "true" or "false"
if
value
is
True
:
return
value
else
:
return
None
_USERNAME_TOO_SHORT_MSG
=
_
(
"Username must be minimum of two characters long"
)
_EMAIL_INVALID_MSG
=
_
(
"A properly formatted e-mail is required"
)
_PASSWORD_INVALID_MSG
=
_
(
"A valid password is required"
)
_NAME_TOO_SHORT_MSG
=
_
(
"Your legal name must be a minimum of two characters long"
)
class
AccountCreationForm
(
forms
.
Form
):
"""
A form to for account creation data. It is currently only used for
validation, not rendering.
"""
# TODO: Resolve repetition
username
=
forms
.
SlugField
(
min_length
=
2
,
max_length
=
30
,
error_messages
=
{
"required"
:
_USERNAME_TOO_SHORT_MSG
,
"invalid"
:
_
(
"Username should only consist of A-Z and 0-9, with no spaces."
),
"min_length"
:
_USERNAME_TOO_SHORT_MSG
,
"max_length"
:
_
(
"Username cannot be more than
%(limit_value)
s characters long"
),
}
)
email
=
forms
.
EmailField
(
max_length
=
75
,
# Limit per RFCs is 254, but User's email field in django 1.4 only takes 75
error_messages
=
{
"required"
:
_EMAIL_INVALID_MSG
,
"invalid"
:
_EMAIL_INVALID_MSG
,
"max_length"
:
_
(
"Email cannot be more than
%(limit_value)
s characters long"
),
}
)
password
=
forms
.
CharField
(
min_length
=
2
,
error_messages
=
{
"required"
:
_PASSWORD_INVALID_MSG
,
"min_length"
:
_PASSWORD_INVALID_MSG
,
}
)
name
=
forms
.
CharField
(
min_length
=
2
,
error_messages
=
{
"required"
:
_NAME_TOO_SHORT_MSG
,
"min_length"
:
_NAME_TOO_SHORT_MSG
,
}
)
def
__init__
(
self
,
data
=
None
,
extra_fields
=
None
,
extended_profile_fields
=
None
,
enforce_username_neq_password
=
False
,
enforce_password_policy
=
False
,
tos_required
=
True
):
super
(
AccountCreationForm
,
self
)
.
__init__
(
data
)
extra_fields
=
extra_fields
or
{}
self
.
extended_profile_fields
=
extended_profile_fields
or
{}
self
.
enforce_username_neq_password
=
enforce_username_neq_password
self
.
enforce_password_policy
=
enforce_password_policy
if
tos_required
:
self
.
fields
[
"terms_of_service"
]
=
TrueField
(
error_messages
=
{
"required"
:
_
(
"You must accept the terms of service."
)}
)
# TODO: These messages don't say anything about minimum length
error_message_dict
=
{
"level_of_education"
:
_
(
"A level of education is required"
),
"gender"
:
_
(
"Your gender is required"
),
"year_of_birth"
:
_
(
"Your year of birth is required"
),
"mailing_address"
:
_
(
"Your mailing address is required"
),
"goals"
:
_
(
"A description of your goals is required"
),
"city"
:
_
(
"A city is required"
),
"country"
:
_
(
"A country is required"
)
}
for
field_name
,
field_value
in
extra_fields
.
items
():
if
field_name
not
in
self
.
fields
:
if
field_name
==
"honor_code"
:
if
field_value
==
"required"
:
self
.
fields
[
field_name
]
=
TrueField
(
error_messages
=
{
"required"
:
_
(
"To enroll, you must follow the honor code."
)
}
)
else
:
required
=
field_value
==
"required"
min_length
=
1
if
field_name
in
(
"gender"
,
"level_of_education"
)
else
2
error_message
=
error_message_dict
.
get
(
field_name
,
_
(
"You are missing one or more required fields"
)
)
self
.
fields
[
field_name
]
=
forms
.
CharField
(
required
=
required
,
min_length
=
min_length
,
error_messages
=
{
"required"
:
error_message
,
"min_length"
:
error_message
,
}
)
for
field
in
self
.
extended_profile_fields
:
if
field
not
in
self
.
fields
:
self
.
fields
[
field
]
=
forms
.
CharField
(
required
=
False
)
def
clean_password
(
self
):
"""Enforce password policies (if applicable)"""
password
=
self
.
cleaned_data
[
"password"
]
if
(
self
.
enforce_username_neq_password
and
"username"
in
self
.
cleaned_data
and
self
.
cleaned_data
[
"username"
]
==
password
):
raise
ValidationError
(
_
(
"Username and password fields cannot match"
))
if
self
.
enforce_password_policy
:
try
:
validate_password_length
(
password
)
validate_password_complexity
(
password
)
validate_password_dictionary
(
password
)
except
ValidationError
,
err
:
raise
ValidationError
(
_
(
"Password: "
)
+
"; "
.
join
(
err
.
messages
))
return
password
def
clean_year_of_birth
(
self
):
"""
Parse year_of_birth to an integer, but just use None instead of raising
an error if it is malformed
"""
try
:
year_str
=
self
.
cleaned_data
[
"year_of_birth"
]
return
int
(
year_str
)
if
year_str
is
not
None
else
None
except
ValueError
:
return
None
@property
def
cleaned_extended_profile
(
self
):
"""
Return a dictionary containing the extended_profile_fields and values
"""
return
{
key
:
value
for
key
,
value
in
self
.
cleaned_data
.
items
()
if
key
in
self
.
extended_profile_fields
and
value
is
not
None
}
common/djangoapps/student/management/commands/create_random_users.py
View file @
291004de
...
@@ -8,26 +8,30 @@ from student.models import CourseEnrollment
...
@@ -8,26 +8,30 @@ from student.models import CourseEnrollment
from
opaque_keys
import
InvalidKeyError
from
opaque_keys
import
InvalidKeyError
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
student.forms
import
AccountCreationForm
from
student.views
import
_do_create_account
from
student.views
import
_do_create_account
def
get_random_post_override
():
def
make_random_form
():
"""
"""
Generate unique user data for dummy users.
Generate unique user data for dummy users.
"""
"""
identification
=
uuid
.
uuid4
()
.
hex
[:
8
]
identification
=
uuid
.
uuid4
()
.
hex
[:
8
]
return
{
return
AccountCreationForm
(
'username'
:
'user_{id}'
.
format
(
id
=
identification
),
data
=
{
'email'
:
'email_{id}@example.com'
.
format
(
id
=
identification
),
'username'
:
'user_{id}'
.
format
(
id
=
identification
),
'password'
:
'12345'
,
'email'
:
'email_{id}@example.com'
.
format
(
id
=
identification
),
'name'
:
'User {id}'
.
format
(
id
=
identification
),
'password'
:
'12345'
,
}
'name'
:
'User {id}'
.
format
(
id
=
identification
),
},
tos_required
=
False
)
def
create
(
num
,
course_key
):
def
create
(
num
,
course_key
):
"""Create num users, enrolling them in course_key if it's not None"""
"""Create num users, enrolling them in course_key if it's not None"""
for
idx
in
range
(
num
):
for
idx
in
range
(
num
):
(
user
,
user_profile
,
__
)
=
_do_create_account
(
get_random_post_override
())
(
user
,
_
,
_
)
=
_do_create_account
(
make_random_form
())
if
course_key
is
not
None
:
if
course_key
is
not
None
:
CourseEnrollment
.
enroll
(
user
,
course_key
)
CourseEnrollment
.
enroll
(
user
,
course_key
)
...
...
common/djangoapps/student/management/commands/create_user.py
View file @
291004de
...
@@ -8,6 +8,7 @@ from django.utils import translation
...
@@ -8,6 +8,7 @@ from django.utils import translation
from
opaque_keys
import
InvalidKeyError
from
opaque_keys
import
InvalidKeyError
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
student.forms
import
AccountCreationForm
from
student.models
import
CourseEnrollment
,
Registration
,
create_comments_service_user
from
student.models
import
CourseEnrollment
,
Registration
,
create_comments_service_user
from
student.views
import
_do_create_account
,
AccountValidationError
from
student.views
import
_do_create_account
,
AccountValidationError
from
track.management.tracked_command
import
TrackedCommand
from
track.management.tracked_command
import
TrackedCommand
...
@@ -80,21 +81,22 @@ class Command(TrackedCommand):
...
@@ -80,21 +81,22 @@ class Command(TrackedCommand):
except
InvalidKeyError
:
except
InvalidKeyError
:
course
=
SlashSeparatedCourseKey
.
from_deprecated_string
(
options
[
'course'
])
course
=
SlashSeparatedCourseKey
.
from_deprecated_string
(
options
[
'course'
])
post_data
=
{
form
=
AccountCreationForm
(
'username'
:
username
,
data
=
{
'email'
:
options
[
'email'
],
'username'
:
username
,
'password'
:
options
[
'password'
],
'email'
:
options
[
'email'
],
'name'
:
name
,
'password'
:
options
[
'password'
],
'honor_code'
:
u'true'
,
'name'
:
name
,
'terms_of_service'
:
u'true'
,
},
}
tos_required
=
False
)
# django.utils.translation.get_language() will be used to set the new
# django.utils.translation.get_language() will be used to set the new
# user's preferred language. This line ensures that the result will
# user's preferred language. This line ensures that the result will
# match this installation's default locale. Otherwise, inside a
# match this installation's default locale. Otherwise, inside a
# management command, it will always return "en-us".
# management command, it will always return "en-us".
translation
.
activate
(
settings
.
LANGUAGE_CODE
)
translation
.
activate
(
settings
.
LANGUAGE_CODE
)
try
:
try
:
user
,
profile
,
reg
=
_do_create_account
(
post_data
)
user
,
_
,
reg
=
_do_create_account
(
form
)
if
options
[
'staff'
]:
if
options
[
'staff'
]:
user
.
is_staff
=
True
user
.
is_staff
=
True
user
.
save
()
user
.
save
()
...
...
common/djangoapps/student/tests/test_create_account.py
View file @
291004de
...
@@ -93,13 +93,19 @@ class TestCreateAccount(TestCase):
...
@@ -93,13 +93,19 @@ class TestCreateAccount(TestCase):
def
test_profile_saved_no_optional_fields
(
self
):
def
test_profile_saved_no_optional_fields
(
self
):
profile
=
self
.
create_account_and_fetch_profile
()
profile
=
self
.
create_account_and_fetch_profile
()
self
.
assertEqual
(
profile
.
name
,
self
.
params
[
"name"
])
self
.
assertEqual
(
profile
.
name
,
self
.
params
[
"name"
])
self
.
assert
IsNone
(
profile
.
level_of_education
)
self
.
assert
Equal
(
profile
.
level_of_education
,
""
)
self
.
assert
IsNone
(
profile
.
gender
)
self
.
assert
Equal
(
profile
.
gender
,
""
)
self
.
assert
IsNone
(
profile
.
mailing_address
)
self
.
assert
Equal
(
profile
.
mailing_address
,
""
)
self
.
assert
IsNone
(
profile
.
city
)
self
.
assert
Equal
(
profile
.
city
,
""
)
self
.
assertEqual
(
profile
.
country
,
""
)
self
.
assertEqual
(
profile
.
country
,
""
)
self
.
assertIsNone
(
profile
.
goals
)
self
.
assertEqual
(
profile
.
goals
,
""
)
self
.
assertEqual
(
profile
.
meta
,
""
)
self
.
assertEqual
(
profile
.
get_meta
(),
{
"extra1"
:
""
,
"extra2"
:
""
,
}
)
self
.
assertIsNone
(
profile
.
year_of_birth
)
self
.
assertIsNone
(
profile
.
year_of_birth
)
@unittest.skipUnless
(
@unittest.skipUnless
(
...
@@ -267,7 +273,7 @@ class TestCreateAccountValidation(TestCase):
...
@@ -267,7 +273,7 @@ class TestCreateAccountValidation(TestCase):
# Missing
# Missing
del
params
[
"username"
]
del
params
[
"username"
]
assert_username_error
(
"
Error (401 username). E-mail us.
"
)
assert_username_error
(
"
Username must be minimum of two characters long
"
)
# Empty, too short
# Empty, too short
for
username
in
[
""
,
"a"
]:
for
username
in
[
""
,
"a"
]:
...
@@ -282,10 +288,6 @@ class TestCreateAccountValidation(TestCase):
...
@@ -282,10 +288,6 @@ class TestCreateAccountValidation(TestCase):
params
[
"username"
]
=
"invalid username"
params
[
"username"
]
=
"invalid username"
assert_username_error
(
"Username should only consist of A-Z and 0-9, with no spaces."
)
assert_username_error
(
"Username should only consist of A-Z and 0-9, with no spaces."
)
# Matching password
params
[
"username"
]
=
params
[
"password"
]
=
"test_username_and_password"
assert_username_error
(
"Username and password fields cannot match"
)
def
test_email
(
self
):
def
test_email
(
self
):
params
=
dict
(
self
.
minimal_params
)
params
=
dict
(
self
.
minimal_params
)
...
@@ -298,7 +300,7 @@ class TestCreateAccountValidation(TestCase):
...
@@ -298,7 +300,7 @@ class TestCreateAccountValidation(TestCase):
# Missing
# Missing
del
params
[
"email"
]
del
params
[
"email"
]
assert_email_error
(
"
Error (401 email). E-mail us.
"
)
assert_email_error
(
"
A properly formatted e-mail is required
"
)
# Empty, too short
# Empty, too short
for
email
in
[
""
,
"a"
]:
for
email
in
[
""
,
"a"
]:
...
@@ -311,7 +313,7 @@ class TestCreateAccountValidation(TestCase):
...
@@ -311,7 +313,7 @@ class TestCreateAccountValidation(TestCase):
# Invalid
# Invalid
params
[
"email"
]
=
"not_an_email_address"
params
[
"email"
]
=
"not_an_email_address"
assert_email_error
(
"
Valid e-mail is required.
"
)
assert_email_error
(
"
A properly formatted e-mail is required
"
)
def
test_password
(
self
):
def
test_password
(
self
):
params
=
dict
(
self
.
minimal_params
)
params
=
dict
(
self
.
minimal_params
)
...
@@ -325,7 +327,7 @@ class TestCreateAccountValidation(TestCase):
...
@@ -325,7 +327,7 @@ class TestCreateAccountValidation(TestCase):
# Missing
# Missing
del
params
[
"password"
]
del
params
[
"password"
]
assert_password_error
(
"
Error (401 password). E-mail us.
"
)
assert_password_error
(
"
A valid password is required
"
)
# Empty, too short
# Empty, too short
for
password
in
[
""
,
"a"
]:
for
password
in
[
""
,
"a"
]:
...
@@ -334,6 +336,10 @@ class TestCreateAccountValidation(TestCase):
...
@@ -334,6 +336,10 @@ class TestCreateAccountValidation(TestCase):
# Password policy is tested elsewhere
# Password policy is tested elsewhere
# Matching username
params
[
"username"
]
=
params
[
"password"
]
=
"test_username_and_password"
assert_password_error
(
"Username and password fields cannot match"
)
def
test_name
(
self
):
def
test_name
(
self
):
params
=
dict
(
self
.
minimal_params
)
params
=
dict
(
self
.
minimal_params
)
...
@@ -346,7 +352,7 @@ class TestCreateAccountValidation(TestCase):
...
@@ -346,7 +352,7 @@ class TestCreateAccountValidation(TestCase):
# Missing
# Missing
del
params
[
"name"
]
del
params
[
"name"
]
assert_name_error
(
"
Error (401 name). E-mail us.
"
)
assert_name_error
(
"
Your legal name must be a minimum of two characters long
"
)
# Empty, too short
# Empty, too short
for
name
in
[
""
,
"a"
]:
for
name
in
[
""
,
"a"
]:
...
@@ -369,13 +375,20 @@ class TestCreateAccountValidation(TestCase):
...
@@ -369,13 +375,20 @@ class TestCreateAccountValidation(TestCase):
assert_honor_code_error
(
"To enroll, you must follow the honor code."
)
assert_honor_code_error
(
"To enroll, you must follow the honor code."
)
# Empty, invalid
# Empty, invalid
for
honor_code
in
[
""
,
"false"
,
"
True
"
]:
for
honor_code
in
[
""
,
"false"
,
"
not_boolean
"
]:
params
[
"honor_code"
]
=
honor_code
params
[
"honor_code"
]
=
honor_code
assert_honor_code_error
(
"To enroll, you must follow the honor code."
)
assert_honor_code_error
(
"To enroll, you must follow the honor code."
)
# True
params
[
"honor_code"
]
=
"tRUe"
self
.
assert_success
(
params
)
with
override_settings
(
REGISTRATION_EXTRA_FIELDS
=
{
"honor_code"
:
"optional"
}):
with
override_settings
(
REGISTRATION_EXTRA_FIELDS
=
{
"honor_code"
:
"optional"
}):
# Missing
# Missing
del
params
[
"honor_code"
]
del
params
[
"honor_code"
]
# Need to change username/email because user was created above
params
[
"username"
]
=
"another_test_username"
params
[
"email"
]
=
"another_test_email@example.com"
self
.
assert_success
(
params
)
self
.
assert_success
(
params
)
def
test_terms_of_service
(
self
):
def
test_terms_of_service
(
self
):
...
@@ -393,10 +406,14 @@ class TestCreateAccountValidation(TestCase):
...
@@ -393,10 +406,14 @@ class TestCreateAccountValidation(TestCase):
assert_terms_of_service_error
(
"You must accept the terms of service."
)
assert_terms_of_service_error
(
"You must accept the terms of service."
)
# Empty, invalid
# Empty, invalid
for
terms_of_service
in
[
""
,
"false"
,
"
True
"
]:
for
terms_of_service
in
[
""
,
"false"
,
"
not_boolean
"
]:
params
[
"terms_of_service"
]
=
terms_of_service
params
[
"terms_of_service"
]
=
terms_of_service
assert_terms_of_service_error
(
"You must accept the terms of service."
)
assert_terms_of_service_error
(
"You must accept the terms of service."
)
# True
params
[
"terms_of_service"
]
=
"tRUe"
self
.
assert_success
(
params
)
@ddt.data
(
@ddt.data
(
(
"level_of_education"
,
1
,
"A level of education is required"
),
(
"level_of_education"
,
1
,
"A level of education is required"
),
(
"gender"
,
1
,
"Your gender is required"
),
(
"gender"
,
1
,
"Your gender is required"
),
...
...
common/djangoapps/student/views.py
View file @
291004de
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