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 @@
Utility functions for validating forms
"""
from
django
import
forms
from
django.core.exceptions
import
ValidationError
from
django.contrib.auth.models
import
User
from
django.contrib.auth.forms
import
PasswordResetForm
from
django.contrib.auth.hashers
import
UNUSABLE_PASSWORD
from
django.contrib.auth.tokens
import
default_token_generator
from
django.utils.http
import
int_to_base36
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.template
import
loader
from
django.conf
import
settings
from
microsite_configuration
import
microsite
from
util.password_policy_validators
import
(
validate_password_length
,
validate_password_complexity
,
validate_password_dictionary
,
)
class
PasswordResetFormNoActive
(
PasswordResetForm
):
...
...
@@ -70,3 +77,160 @@ class PasswordResetFormNoActive(PasswordResetForm):
subject
=
subject
.
replace
(
'
\n
'
,
''
)
email
=
loader
.
render_to_string
(
email_template_name
,
context
)
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
from
opaque_keys
import
InvalidKeyError
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
student.forms
import
AccountCreationForm
from
student.views
import
_do_create_account
def
get_random_post_override
():
def
make_random_form
():
"""
Generate unique user data for dummy users.
"""
identification
=
uuid
.
uuid4
()
.
hex
[:
8
]
return
{
'username'
:
'user_{id}'
.
format
(
id
=
identification
),
'email'
:
'email_{id}@example.com'
.
format
(
id
=
identification
),
'password'
:
'12345'
,
'name'
:
'User {id}'
.
format
(
id
=
identification
),
}
return
AccountCreationForm
(
data
=
{
'username'
:
'user_{id}'
.
format
(
id
=
identification
),
'email'
:
'email_{id}@example.com'
.
format
(
id
=
identification
),
'password'
:
'12345'
,
'name'
:
'User {id}'
.
format
(
id
=
identification
),
},
tos_required
=
False
)
def
create
(
num
,
course_key
):
"""Create num users, enrolling them in course_key if it's not None"""
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
:
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
from
opaque_keys
import
InvalidKeyError
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
student.forms
import
AccountCreationForm
from
student.models
import
CourseEnrollment
,
Registration
,
create_comments_service_user
from
student.views
import
_do_create_account
,
AccountValidationError
from
track.management.tracked_command
import
TrackedCommand
...
...
@@ -80,21 +81,22 @@ class Command(TrackedCommand):
except
InvalidKeyError
:
course
=
SlashSeparatedCourseKey
.
from_deprecated_string
(
options
[
'course'
])
post_data
=
{
'username'
:
username
,
'email'
:
options
[
'email'
],
'password'
:
options
[
'password'
],
'name'
:
name
,
'honor_code'
:
u'true'
,
'terms_of_service'
:
u'true'
,
}
form
=
AccountCreationForm
(
data
=
{
'username'
:
username
,
'email'
:
options
[
'email'
],
'password'
:
options
[
'password'
],
'name'
:
name
,
},
tos_required
=
False
)
# django.utils.translation.get_language() will be used to set the new
# user's preferred language. This line ensures that the result will
# match this installation's default locale. Otherwise, inside a
# management command, it will always return "en-us".
translation
.
activate
(
settings
.
LANGUAGE_CODE
)
try
:
user
,
profile
,
reg
=
_do_create_account
(
post_data
)
user
,
_
,
reg
=
_do_create_account
(
form
)
if
options
[
'staff'
]:
user
.
is_staff
=
True
user
.
save
()
...
...
common/djangoapps/student/tests/test_create_account.py
View file @
291004de
...
...
@@ -93,13 +93,19 @@ class TestCreateAccount(TestCase):
def
test_profile_saved_no_optional_fields
(
self
):
profile
=
self
.
create_account_and_fetch_profile
()
self
.
assertEqual
(
profile
.
name
,
self
.
params
[
"name"
])
self
.
assert
IsNone
(
profile
.
level_of_education
)
self
.
assert
IsNone
(
profile
.
gender
)
self
.
assert
IsNone
(
profile
.
mailing_address
)
self
.
assert
IsNone
(
profile
.
city
)
self
.
assert
Equal
(
profile
.
level_of_education
,
""
)
self
.
assert
Equal
(
profile
.
gender
,
""
)
self
.
assert
Equal
(
profile
.
mailing_address
,
""
)
self
.
assert
Equal
(
profile
.
city
,
""
)
self
.
assertEqual
(
profile
.
country
,
""
)
self
.
assertIsNone
(
profile
.
goals
)
self
.
assertEqual
(
profile
.
meta
,
""
)
self
.
assertEqual
(
profile
.
goals
,
""
)
self
.
assertEqual
(
profile
.
get_meta
(),
{
"extra1"
:
""
,
"extra2"
:
""
,
}
)
self
.
assertIsNone
(
profile
.
year_of_birth
)
@unittest.skipUnless
(
...
...
@@ -267,7 +273,7 @@ class TestCreateAccountValidation(TestCase):
# Missing
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
for
username
in
[
""
,
"a"
]:
...
...
@@ -282,10 +288,6 @@ class TestCreateAccountValidation(TestCase):
params
[
"username"
]
=
"invalid username"
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
):
params
=
dict
(
self
.
minimal_params
)
...
...
@@ -298,7 +300,7 @@ class TestCreateAccountValidation(TestCase):
# Missing
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
for
email
in
[
""
,
"a"
]:
...
...
@@ -311,7 +313,7 @@ class TestCreateAccountValidation(TestCase):
# Invalid
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
):
params
=
dict
(
self
.
minimal_params
)
...
...
@@ -325,7 +327,7 @@ class TestCreateAccountValidation(TestCase):
# Missing
del
params
[
"password"
]
assert_password_error
(
"
Error (401 password). E-mail us.
"
)
assert_password_error
(
"
A valid password is required
"
)
# Empty, too short
for
password
in
[
""
,
"a"
]:
...
...
@@ -334,6 +336,10 @@ class TestCreateAccountValidation(TestCase):
# 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
):
params
=
dict
(
self
.
minimal_params
)
...
...
@@ -346,7 +352,7 @@ class TestCreateAccountValidation(TestCase):
# Missing
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
for
name
in
[
""
,
"a"
]:
...
...
@@ -369,13 +375,20 @@ class TestCreateAccountValidation(TestCase):
assert_honor_code_error
(
"To enroll, you must follow the honor code."
)
# Empty, invalid
for
honor_code
in
[
""
,
"false"
,
"
True
"
]:
for
honor_code
in
[
""
,
"false"
,
"
not_boolean
"
]:
params
[
"honor_code"
]
=
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"
}):
# Missing
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
)
def
test_terms_of_service
(
self
):
...
...
@@ -393,10 +406,14 @@ class TestCreateAccountValidation(TestCase):
assert_terms_of_service_error
(
"You must accept the terms of service."
)
# Empty, invalid
for
terms_of_service
in
[
""
,
"false"
,
"
True
"
]:
for
terms_of_service
in
[
""
,
"false"
,
"
not_boolean
"
]:
params
[
"terms_of_service"
]
=
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
(
(
"level_of_education"
,
1
,
"A level of education 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