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
650a9a9b
Commit
650a9a9b
authored
Mar 18, 2015
by
Andy Armstrong
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Implement parental controls for user profiles
TNL-1606
parent
8e08ff52
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
212 additions
and
61 deletions
+212
-61
cms/envs/common.py
+1
-1
cms/envs/test.py
+3
-0
common/djangoapps/student/models.py
+45
-0
common/djangoapps/student/tests/test_enrollment.py
+1
-1
common/djangoapps/student/tests/test_parental_controls.py
+86
-0
lms/envs/common.py
+7
-1
lms/envs/test.py
+3
-0
openedx/core/djangoapps/user_api/accounts/api.py
+4
-4
openedx/core/djangoapps/user_api/accounts/tests/test_helpers.py
+7
-1
openedx/core/djangoapps/user_api/helpers.py
+1
-1
openedx/core/djangoapps/user_api/models.py
+1
-1
openedx/core/djangoapps/user_api/preferences/api.py
+33
-38
openedx/core/djangoapps/user_api/preferences/tests/test_api.py
+7
-0
openedx/core/djangoapps/user_api/views.py
+13
-13
No files found.
cms/envs/common.py
View file @
650a9a9b
...
...
@@ -36,7 +36,7 @@ import lms.envs.common
# Although this module itself may not use these imported variables, other dependent modules may.
from
lms.envs.common
import
(
USE_TZ
,
TECH_SUPPORT_EMAIL
,
PLATFORM_NAME
,
BUGS_EMAIL
,
DOC_STORE_CONFIG
,
DATA_DIR
,
ALL_LANGUAGES
,
WIKI_ENABLED
,
update_module_store_settings
,
ASSET_IGNORE_REGEX
,
COPYRIGHT_YEAR
,
update_module_store_settings
,
ASSET_IGNORE_REGEX
,
COPYRIGHT_YEAR
,
PARENTAL_CONSENT_AGE_LIMIT
,
# The following PROFILE_IMAGE_* settings are included as they are
# indirectly accessed through the email opt-in API, which is
# technically accessible through the CMS via legacy URLs.
...
...
cms/envs/test.py
View file @
650a9a9b
...
...
@@ -249,6 +249,9 @@ FEATURES['USE_MICROSITES'] = True
# the one in lms/envs/test.py
FEATURES
[
'ENABLE_DISCUSSION_SERVICE'
]
=
False
# Enable a parental consent age limit for testing
PARENTAL_CONSENT_AGE_LIMIT
=
13
# Enable content libraries code for the tests
FEATURES
[
'ENABLE_CONTENT_LIBRARIES'
]
=
True
...
...
common/djangoapps/student/models.py
View file @
650a9a9b
...
...
@@ -28,6 +28,7 @@ from django.contrib.auth.hashers import make_password
from
django.contrib.auth.signals
import
user_logged_in
,
user_logged_out
from
django.db
import
models
,
IntegrityError
from
django.db.models
import
Count
from
django.db.models.signals
import
pre_save
from
django.dispatch
import
receiver
,
Signal
from
django.core.exceptions
import
ObjectDoesNotExist
from
django.utils.translation
import
ugettext_noop
...
...
@@ -278,6 +279,50 @@ class UserProfile(models.Model):
self
.
set_meta
(
meta
)
self
.
save
()
def
requires_parental_consent
(
self
,
date
=
None
,
age_limit
=
None
,
default_requires_consent
=
True
):
"""Returns true if this user requires parental consent.
Args:
date (Date): The date for which consent needs to be tested (defaults to now).
age_limit (int): The age limit at which parental consent is no longer required.
This defaults to the value of the setting 'PARENTAL_CONTROL_AGE_LIMIT'.
default_requires_consent (bool): True if users require parental consent if they
have no specified year of birth (default is True).
Returns:
True if the user requires parental consent.
"""
if
age_limit
is
None
:
age_limit
=
getattr
(
settings
,
'PARENTAL_CONSENT_AGE_LIMIT'
,
None
)
if
age_limit
is
None
:
return
False
# Return True if either:
# a) The user has a year of birth specified and that year is fewer years in the past than the limit.
# b) The user has no year of birth specified and the default is to require consent.
#
# Note: we have to be conservative using the user's year of birth as their birth date could be
# December 31st. This means that if the number of years since their birth year is exactly equal
# to the age limit then we have to assume that they might still not be old enough.
year_of_birth
=
self
.
year_of_birth
if
year_of_birth
is
None
:
return
default_requires_consent
if
date
is
None
:
date
=
datetime
.
now
(
UTC
)
return
date
.
year
-
year_of_birth
<=
age_limit
# pylint: disable=maybe-no-member
@receiver
(
pre_save
,
sender
=
UserProfile
)
def
user_profile_pre_save_callback
(
sender
,
**
kwargs
):
"""
Ensure consistency of a user profile before saving it.
"""
user_profile
=
kwargs
[
'instance'
]
# Remove profile images for users who require parental consent
if
user_profile
.
requires_parental_consent
()
and
user_profile
.
has_profile_image
:
user_profile
.
has_profile_image
=
False
class
UserSignupSource
(
models
.
Model
):
"""
...
...
common/djangoapps/student/tests/test_enrollment.py
View file @
650a9a9b
...
...
@@ -194,7 +194,7 @@ class EnrollmentTest(UrlResetMixin, ModuleStoreTestCase):
"""Change the student's enrollment status in a course.
Args:
action (str
ing
): The action to perform (either "enroll" or "unenroll")
action (str): The action to perform (either "enroll" or "unenroll")
Keyword Args:
course_id (unicode): If provided, use this course ID. Otherwise, use the
...
...
common/djangoapps/student/tests/test_parental_controls.py
0 → 100644
View file @
650a9a9b
"""Unit tests for parental controls."""
import
datetime
from
django.test
import
TestCase
from
django.test.utils
import
override_settings
from
student.models
import
UserProfile
from
student.tests.factories
import
UserFactory
class
ProfileParentalControlsTest
(
TestCase
):
"""Unit tests for requires_parental_consent."""
password
=
"test"
def
setUp
(
self
):
super
(
ProfileParentalControlsTest
,
self
)
.
setUp
()
self
.
user
=
UserFactory
.
create
(
password
=
self
.
password
)
self
.
profile
=
UserProfile
.
objects
.
get
(
id
=
self
.
user
.
id
)
def
set_year_of_birth
(
self
,
year_of_birth
):
"""
Helper method that creates a mock profile for the specified user.
"""
self
.
profile
.
year_of_birth
=
year_of_birth
self
.
profile
.
save
()
def
test_no_year_of_birth
(
self
):
"""Verify the behavior for users with no specified year of birth."""
self
.
assertTrue
(
self
.
profile
.
requires_parental_consent
())
self
.
assertTrue
(
self
.
profile
.
requires_parental_consent
(
default_requires_consent
=
True
))
self
.
assertFalse
(
self
.
profile
.
requires_parental_consent
(
default_requires_consent
=
False
))
@override_settings
(
PARENTAL_CONSENT_AGE_LIMIT
=
None
)
def
test_no_parental_controls
(
self
):
"""Verify the behavior for all users when parental controls are not enabled."""
self
.
assertFalse
(
self
.
profile
.
requires_parental_consent
())
self
.
assertFalse
(
self
.
profile
.
requires_parental_consent
(
default_requires_consent
=
True
))
self
.
assertFalse
(
self
.
profile
.
requires_parental_consent
(
default_requires_consent
=
False
))
# Verify that even a child does not require parental consent
current_year
=
datetime
.
datetime
.
now
()
.
year
self
.
set_year_of_birth
(
current_year
-
10
)
self
.
assertFalse
(
self
.
profile
.
requires_parental_consent
())
def
test_adult_user
(
self
):
"""Verify the behavior for an adult."""
current_year
=
datetime
.
datetime
.
now
()
.
year
self
.
set_year_of_birth
(
current_year
-
20
)
self
.
assertFalse
(
self
.
profile
.
requires_parental_consent
())
self
.
assertTrue
(
self
.
profile
.
requires_parental_consent
(
age_limit
=
21
))
def
test_child_user
(
self
):
"""Verify the behavior for a child."""
current_year
=
datetime
.
datetime
.
now
()
.
year
# Verify for a child born 13 years agp
self
.
set_year_of_birth
(
current_year
-
13
)
self
.
assertTrue
(
self
.
profile
.
requires_parental_consent
())
self
.
assertTrue
(
self
.
profile
.
requires_parental_consent
(
date
=
datetime
.
date
(
current_year
,
12
,
31
)))
self
.
assertFalse
(
self
.
profile
.
requires_parental_consent
(
date
=
datetime
.
date
(
current_year
+
1
,
1
,
1
)))
# Verify for a child born 14 years ago
self
.
set_year_of_birth
(
current_year
-
14
)
self
.
assertFalse
(
self
.
profile
.
requires_parental_consent
())
self
.
assertFalse
(
self
.
profile
.
requires_parental_consent
(
date
=
datetime
.
date
(
current_year
,
1
,
1
)))
def
test_profile_image
(
self
):
"""Verify that a profile's image obeys parental controls."""
# Verify that an image cannot be set for a user with no year of birth set
self
.
profile
.
has_profile_image
=
True
self
.
profile
.
save
()
self
.
assertFalse
(
self
.
profile
.
has_profile_image
)
# Verify that an image can be set for an adult user
current_year
=
datetime
.
datetime
.
now
()
.
year
self
.
set_year_of_birth
(
current_year
-
20
)
self
.
profile
.
has_profile_image
=
True
self
.
profile
.
save
()
self
.
assertTrue
(
self
.
profile
.
has_profile_image
)
# verify that a user's profile image is removed when they switch to requiring parental controls
self
.
set_year_of_birth
(
current_year
-
10
)
self
.
profile
.
save
()
self
.
assertFalse
(
self
.
profile
.
has_profile_image
)
lms/envs/common.py
View file @
650a9a9b
...
...
@@ -987,6 +987,12 @@ EDXNOTES_INTERFACE = {
'url'
:
'http://localhost:8120/api/v1'
,
}
########################## Parental controls config #######################
# The age at which a learner no longer requires parental consent, or None
# if parental consent is never required.
PARENTAL_CONSENT_AGE_LIMIT
=
13
################################# Jasmine ##################################
JASMINE_TEST_DIRECTORY
=
PROJECT_ROOT
+
'/static/coffee'
...
...
@@ -1544,7 +1550,7 @@ BULK_EMAIL_RETRY_DELAY_BETWEEN_SENDS = 0.02
############################# Email Opt In ####################################
# Minimum age for organization-wide email opt in
EMAIL_OPTIN_MINIMUM_AGE
=
13
EMAIL_OPTIN_MINIMUM_AGE
=
PARENTAL_CONSENT_AGE_LIMIT
############################## Video ##########################################
...
...
lms/envs/test.py
View file @
650a9a9b
...
...
@@ -74,6 +74,9 @@ FEATURES['ENABLE_COMBINED_LOGIN_REGISTRATION'] = True
# Need wiki for courseware views to work. TODO (vshnayder): shouldn't need it.
WIKI_ENABLED
=
True
# Enable a parental consent age limit for testing
PARENTAL_CONSENT_AGE_LIMIT
=
13
# Makes the tests run much faster...
SOUTH_TESTS_MIGRATE
=
False
# To disable migrations and use syncdb instead
...
...
openedx/core/djangoapps/user_api/accounts/api.py
View file @
650a9a9b
...
...
@@ -104,7 +104,7 @@ def update_account_settings(requesting_user, update, username=None):
requesting_user (User): The user requesting to modify account information. Only the user with username
'username' has permissions to modify account information.
update (dict): The updated account field values.
username (str
ing
): Optional username specifying which account should be updated. If not specified,
username (str): Optional username specifying which account should be updated. If not specified,
`requesting_user.username` is assumed.
Raises:
...
...
@@ -372,9 +372,9 @@ def request_password_change(email, orig_host, is_secure):
Users must confirm the password change before we update their information.
Args:
email (str
ing
): An email address
orig_host (str
ing
): An originating host, extracted from a request with get_host
is_secure (
Boolean
): Whether the request was made with HTTPS
email (str): An email address
orig_host (str): An originating host, extracted from a request with get_host
is_secure (
bool
): Whether the request was made with HTTPS
Returns:
None
...
...
openedx/core/djangoapps/user_api/accounts/tests/test_helpers.py
View file @
650a9a9b
...
...
@@ -9,9 +9,11 @@ from unittest import skipUnless
from
django.conf
import
settings
from
django.test
import
TestCase
from
openedx.core.djangoapps.user_api.accounts.helpers
import
get_profile_image_url_for_user
from
student.tests.factories
import
UserFactory
from
...models
import
UserProfile
from
..helpers
import
get_profile_image_url_for_user
@ddt
@patch
(
'openedx.core.djangoapps.user_api.accounts.helpers._PROFILE_IMAGE_SIZES'
,
[
50
,
10
])
...
...
@@ -27,6 +29,10 @@ class ProfileImageUrlTestCase(TestCase):
super
(
ProfileImageUrlTestCase
,
self
)
.
setUp
()
self
.
user
=
UserFactory
()
# Ensure that parental controls don't apply to this user
self
.
user
.
profile
.
year_of_birth
=
1980
self
.
user
.
profile
.
save
()
def
verify_url
(
self
,
user
,
pixels
,
filename
):
"""
Helper method to verify that we're correctly generating profile
...
...
openedx/core/djangoapps/user_api/helpers.py
View file @
650a9a9b
...
...
@@ -308,7 +308,7 @@ class FormDescription(object):
Field properties not in `OVERRIDE_FIELD_PROPERTIES` will be ignored.
Arguments:
field_name (str
ing
): The name of the field to override.
field_name (str): The name of the field to override.
Keyword Args:
Same as to `add_field()`.
...
...
openedx/core/djangoapps/user_api/models.py
View file @
650a9a9b
...
...
@@ -35,7 +35,7 @@ class UserPreference(models.Model):
Arguments:
user (User): The user whose preference should be set.
preference_key (str
ing
): The key for the user preference.
preference_key (str): The key for the user preference.
Returns:
The user preference value, or None if one is not set.
...
...
openedx/core/djangoapps/user_api/preferences/api.py
View file @
650a9a9b
"""
API for managing user preferences.
"""
import
datetime
import
logging
import
string
import
analytics
from
eventtracking
import
tracker
from
pytz
import
UTC
from
django.conf
import
settings
from
django.contrib.auth.models
import
User
from
django.core.exceptions
import
ObjectDoesNotExist
from
django.db
import
IntegrityError
from
django.utils.translation
import
ugettext
as
_
from
student.models
import
User
,
UserProfile
from
django.utils.translation
import
ugettext_noop
from
student.models
import
UserProfile
from
..errors
import
(
UserAPIInternalError
,
UserAPIRequestError
,
UserNotFound
,
UserNotAuthorized
,
PreferenceValidationError
,
PreferenceUpdateError
...
...
@@ -35,7 +30,7 @@ def get_user_preference(requesting_user, preference_key, username=None):
Args:
requesting_user (User): The user requesting the user preferences. Only the user with username
`username` or users with "is_staff" privileges can access the preferences.
preference_key (str
ing
): The key for the user preference.
preference_key (str): The key for the user preference.
username (str): Optional username for which to look up the preferences. If not specified,
`requesting_user.username` is assumed.
...
...
@@ -92,7 +87,7 @@ def update_user_preferences(requesting_user, update, username=None):
Some notes:
Values are expected to be strings. Non-string values will be converted to strings.
Null values for a preference will be treated as a request to delete the key in question.
username (str
ing
): Optional username specifying which account should be updated. If not specified,
username (str): Optional username specifying which account should be updated. If not specified,
`requesting_user.username` is assumed.
Raises:
...
...
@@ -148,9 +143,9 @@ def set_user_preference(requesting_user, preference_key, preference_value, usern
Arguments:
requesting_user (User): The user requesting to modify account information. Only the user with username
'username' has permissions to modify account information.
preference_key (str
ing
): The key for the user preference.
preference_value (str
ing
): The value to be stored. Non-string values will be converted to strings.
username (str
ing
): Optional username specifying which account should be updated. If not specified,
preference_key (str): The key for the user preference.
preference_value (str): The value to be stored. Non-string values will be converted to strings.
username (str): Optional username specifying which account should be updated. If not specified,
`requesting_user.username` is assumed.
Raises:
...
...
@@ -182,8 +177,8 @@ def delete_user_preference(requesting_user, preference_key, username=None):
Arguments:
requesting_user (User): The user requesting to delete the preference. Only the user with username
'username' has permissions to delete their own preference.
preference_key (str
ing
): The key for the user preference.
username (str
ing
): Optional username specifying which account should be updated. If not specified,
preference_key (str): The key for the user preference.
username (str): Optional username specifying which account should be updated. If not specified,
`requesting_user.username` is assumed.
Returns:
...
...
@@ -218,7 +213,7 @@ def delete_user_preference(requesting_user, preference_key, username=None):
@intercept_errors
(
UserAPIInternalError
,
ignore_errors
=
[
UserAPIRequestError
])
def
update_email_opt_in
(
user
,
org
,
optin
):
def
update_email_opt_in
(
user
,
org
,
opt
_
in
):
"""Updates a user's preference for receiving org-wide emails.
Sets a User Org Tag defining the choice to opt in or opt out of organization-wide
...
...
@@ -227,48 +222,48 @@ def update_email_opt_in(user, org, optin):
Arguments:
user (User): The user to set a preference for.
org (str): The org is used to determine the organization this setting is related to.
opt
in (Boolean): True if the user is choosing to receive emails for this organization. If the user is not
the correct age to receive emails,
email-optin is set to False regardless.
opt
_in (bool): True if the user is choosing to receive emails for this organization.
If the user requires parental consent then
email-optin is set to False regardless.
Returns:
None
Raises:
UserNotFound: no user profile exists for the specified user.
"""
# Avoid calling get_account_settings because it introduces circularity for many callers who need both
# preferences and account information.
preference
,
_
=
UserOrgTag
.
objects
.
get_or_create
(
user
=
user
,
org
=
org
,
key
=
'email-optin'
)
# If the user requires parental consent, then don't allow opt-in
try
:
user_profile
=
UserProfile
.
objects
.
get
(
user
=
user
)
except
ObjectDoesNotExist
:
raise
UserNotFound
()
year_of_birth
=
user_profile
.
year_of_birth
of_age
=
(
year_of_birth
is
None
or
# If year of birth is not set, we assume user is of age.
datetime
.
datetime
.
now
(
UTC
)
.
year
-
year_of_birth
>
# pylint: disable=maybe-no-member
getattr
(
settings
,
'EMAIL_OPTIN_MINIMUM_AGE'
,
13
)
)
if
user_profile
.
requires_parental_consent
(
age_limit
=
getattr
(
settings
,
'EMAIL_OPTIN_MINIMUM_AGE'
,
13
),
default_requires_consent
=
False
,
):
opt_in
=
False
# Update the preference and save it
preference
.
value
=
str
(
opt_in
)
try
:
preference
,
_
=
UserOrgTag
.
objects
.
get_or_create
(
user
=
user
,
org
=
org
,
key
=
'email-optin'
)
preference
.
value
=
str
(
optin
and
of_age
)
preference
.
save
()
if
settings
.
FEATURES
.
get
(
'SEGMENT_IO_LMS'
)
and
settings
.
SEGMENT_IO_LMS_KEY
:
_track_update_email_opt_in
(
user
.
id
,
org
,
optin
)
_track_update_email_opt_in
(
user
.
id
,
org
,
opt_in
)
except
IntegrityError
as
err
:
log
.
warn
(
u"Could not update organization wide preference due to IntegrityError: {}"
.
format
(
err
.
message
))
def
_track_update_email_opt_in
(
user_id
,
organization
,
opt_in
):
"""Track an email opt-in preference change.
Arguments:
user_id (str): The ID of the user making the preference change.
organization (str): The organization whose emails are being opted into or out of by the user.
opt_in (
Boolean
): Whether the user has chosen to opt-in to emails from the organization.
opt_in (
bool
): Whether the user has chosen to opt-in to emails from the organization.
Returns:
None
...
...
@@ -317,8 +312,8 @@ def create_user_preference_serializer(user, preference_key, preference_value):
Arguments:
user (User): The user whose preference is being serialized.
preference_key (str
ing
): The key for the user preference.
preference_value (str
ing
): The value to be stored. Non-string values will be converted to strings.
preference_key (str): The key for the user preference.
preference_value (str): The value to be stored. Non-string values will be converted to strings.
Returns:
A serializer that can be used to save the user preference.
...
...
@@ -344,8 +339,8 @@ def validate_user_preference_serializer(serializer, preference_key, preference_v
Arguments:
serializer (UserPreferenceSerializer): The serializer to be validated.
preference_key (str
ing
): The key for the user preference.
preference_value (str
ing
): The value to be stored. Non-string values will be converted to strings.
preference_key (str): The key for the user preference.
preference_value (str): The value to be stored. Non-string values will be converted to strings.
Raises:
PreferenceValidationError: the supplied key and/or value for a user preference are invalid.
...
...
openedx/core/djangoapps/user_api/preferences/tests/test_api.py
View file @
650a9a9b
...
...
@@ -344,6 +344,13 @@ class UpdateEmailOptInTests(ModuleStoreTestCase):
result_obj
=
UserOrgTag
.
objects
.
get
(
user
=
user
,
org
=
course
.
id
.
org
,
key
=
'email-optin'
)
self
.
assertEqual
(
result_obj
.
value
,
u"True"
)
def
test_update_email_optin_anonymous_user
(
self
):
"""Verify that the API raises an exception for a user with no profile."""
course
=
CourseFactory
.
create
()
no_profile_user
,
__
=
User
.
objects
.
get_or_create
(
username
=
"no_profile_user"
,
password
=
self
.
PASSWORD
)
with
self
.
assertRaises
(
UserNotFound
):
update_email_opt_in
(
no_profile_user
,
course
.
id
.
org
,
True
)
@ddt.data
(
# Check that a 27 year old can opt-in, then out.
(
27
,
True
,
False
,
u"False"
),
...
...
openedx/core/djangoapps/user_api/views.py
View file @
650a9a9b
...
...
@@ -309,7 +309,7 @@ class RegistrationView(APIView):
form_desc: A form description
Keyword Arguments:
required (
Boolean
): Whether this field is required; defaults to True
required (
bool
): Whether this field is required; defaults to True
"""
# Translators: This label appears above a field on the registration form
...
...
@@ -339,7 +339,7 @@ class RegistrationView(APIView):
form_desc: A form description
Keyword Arguments:
required (
Boolean
): Whether this field is required; defaults to True
required (
bool
): Whether this field is required; defaults to True
"""
# Translators: This label appears above a field on the registration form
...
...
@@ -372,7 +372,7 @@ class RegistrationView(APIView):
form_desc: A form description
Keyword Arguments:
required (
Boolean
): Whether this field is required; defaults to True
required (
bool
): Whether this field is required; defaults to True
"""
# Translators: This label appears above a field on the registration form
...
...
@@ -409,7 +409,7 @@ class RegistrationView(APIView):
form_desc: A form description
Keyword Arguments:
required (
Boolean
): Whether this field is required; defaults to True
required (
bool
): Whether this field is required; defaults to True
"""
# Translators: This label appears above a field on the registration form
...
...
@@ -434,7 +434,7 @@ class RegistrationView(APIView):
form_desc: A form description
Keyword Arguments:
required (
Boolean
): Whether this field is required; defaults to True
required (
bool
): Whether this field is required; defaults to True
"""
# Translators: This label appears above a dropdown menu on the registration
...
...
@@ -457,7 +457,7 @@ class RegistrationView(APIView):
form_desc: A form description
Keyword Arguments:
required (
Boolean
): Whether this field is required; defaults to True
required (
bool
): Whether this field is required; defaults to True
"""
# Translators: This label appears above a dropdown menu on the registration
...
...
@@ -480,7 +480,7 @@ class RegistrationView(APIView):
form_desc: A form description
Keyword Arguments:
required (
Boolean
): Whether this field is required; defaults to True
required (
bool
): Whether this field is required; defaults to True
"""
# Translators: This label appears above a dropdown menu on the registration
...
...
@@ -504,7 +504,7 @@ class RegistrationView(APIView):
form_desc: A form description
Keyword Arguments:
required (
Boolean
): Whether this field is required; defaults to True
required (
bool
): Whether this field is required; defaults to True
"""
# Translators: This label appears above a field on the registration form
...
...
@@ -525,7 +525,7 @@ class RegistrationView(APIView):
form_desc: A form description
Keyword Arguments:
required (
Boolean
): Whether this field is required; defaults to True
required (
bool
): Whether this field is required; defaults to True
"""
# Translators: This phrase appears above a field on the registration form
...
...
@@ -548,7 +548,7 @@ class RegistrationView(APIView):
form_desc: A form description
Keyword Arguments:
required (
Boolean
): Whether this field is required; defaults to True
required (
bool
): Whether this field is required; defaults to True
"""
# Translators: This label appears above a field on the registration form
...
...
@@ -568,7 +568,7 @@ class RegistrationView(APIView):
form_desc: A form description
Keyword Arguments:
required (
Boolean
): Whether this field is required; defaults to True
required (
bool
): Whether this field is required; defaults to True
"""
# Translators: This label appears above a dropdown menu on the registration
...
...
@@ -604,7 +604,7 @@ class RegistrationView(APIView):
form_desc: A form description
Keyword Arguments:
required (
Boolean
): Whether this field is required; defaults to True
required (
bool
): Whether this field is required; defaults to True
"""
# Separate terms of service and honor code checkboxes
...
...
@@ -658,7 +658,7 @@ class RegistrationView(APIView):
form_desc: A form description
Keyword Arguments:
required (
Boolean
): Whether this field is required; defaults to True
required (
bool
): Whether this field is required; defaults to True
"""
# Translators: This is a legal document users must agree to
...
...
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