refactor: Ran pyupgrade on openedx/core/djangoapps/user_api

Co-authored-by: Muhammad Soban Javed <58461728+iamsobanjaved@users.noreply.github.com>
This commit is contained in:
Usama Sadiq
2021-03-11 17:24:52 +05:00
parent 24272e5caa
commit e9f9fcb17b
33 changed files with 525 additions and 554 deletions

View File

@@ -40,58 +40,58 @@ VISIBILITY_PREFIX = 'visibility.'
# It is shown to users who attempt to create a new account using invalid characters
# in the username.
USERNAME_INVALID_CHARS_ASCII = _(
u"Usernames can only contain letters (A-Z, a-z), numerals (0-9), underscores (_), and hyphens (-)."
"Usernames can only contain letters (A-Z, a-z), numerals (0-9), underscores (_), and hyphens (-)."
)
# Translators: This message is shown only when the Unicode usernames are allowed.
# It is shown to users who attempt to create a new account using invalid characters
# in the username.
USERNAME_INVALID_CHARS_UNICODE = _(
u"Usernames can only contain letters, numerals, and @/./+/-/_ characters."
"Usernames can only contain letters, numerals, and @/./+/-/_ characters."
)
# Translators: This message is shown to users who attempt to create a new account using
# an invalid email format.
EMAIL_INVALID_MSG = _(u'"{email}" is not a valid email address.')
EMAIL_INVALID_MSG = _('"{email}" is not a valid email address.')
# Translators: This message is shown to users who attempt to create a new
# account using an username/email associated with an existing account.
EMAIL_CONFLICT_MSG = _(
u"It looks like {email_address} belongs to an existing account. "
u"Try again with a different email address."
"It looks like {email_address} belongs to an existing account. "
"Try again with a different email address."
)
USERNAME_CONFLICT_MSG = _(
u"It looks like {username} belongs to an existing account. "
u"Try again with a different username."
"It looks like {username} belongs to an existing account. "
"Try again with a different username."
)
# Translators: This message is shown to users who enter a username/email/password
# with an inappropriate length (too short or too long).
USERNAME_BAD_LENGTH_MSG = format_lazy(
_(u"Username must be between {min} and {max} characters long."),
_("Username must be between {min} and {max} characters long."),
min=USERNAME_MIN_LENGTH,
max=USERNAME_MAX_LENGTH,
)
EMAIL_BAD_LENGTH_MSG = format_lazy(
_(u"Enter a valid email address that contains at least {min} characters."),
_("Enter a valid email address that contains at least {min} characters."),
min=EMAIL_MIN_LENGTH,
)
# These strings are normally not user-facing.
USERNAME_BAD_TYPE_MSG = u"Username must be a string."
EMAIL_BAD_TYPE_MSG = u"Email must be a string."
PASSWORD_BAD_TYPE_MSG = u"Password must be a string."
USERNAME_BAD_TYPE_MSG = "Username must be a string."
EMAIL_BAD_TYPE_MSG = "Email must be a string."
PASSWORD_BAD_TYPE_MSG = "Password must be a string."
# Translators: These messages are shown to users who do not enter information
# into the required field or enter it incorrectly.
REQUIRED_FIELD_NAME_MSG = _(u"Enter your full name.")
REQUIRED_FIELD_CONFIRM_EMAIL_MSG = _(u"The email addresses do not match.")
REQUIRED_FIELD_COUNTRY_MSG = _(u"Select your country or region of residence.")
REQUIRED_FIELD_PROFESSION_SELECT_MSG = _(u"Select your profession.")
REQUIRED_FIELD_SPECIALTY_SELECT_MSG = _(u"Select your specialty.")
REQUIRED_FIELD_PROFESSION_TEXT_MSG = _(u"Enter your profession.")
REQUIRED_FIELD_SPECIALTY_TEXT_MSG = _(u"Enter your specialty.")
REQUIRED_FIELD_CITY_MSG = _(u"Enter your city.")
REQUIRED_FIELD_GOALS_MSG = _(u"Tell us your goals.")
REQUIRED_FIELD_LEVEL_OF_EDUCATION_MSG = _(u"Select the highest level of education you have completed.")
REQUIRED_FIELD_MAILING_ADDRESS_MSG = _(u"Enter your mailing address.")
REQUIRED_FIELD_NAME_MSG = _("Enter your full name.")
REQUIRED_FIELD_CONFIRM_EMAIL_MSG = _("The email addresses do not match.")
REQUIRED_FIELD_COUNTRY_MSG = _("Select your country or region of residence.")
REQUIRED_FIELD_PROFESSION_SELECT_MSG = _("Select your profession.")
REQUIRED_FIELD_SPECIALTY_SELECT_MSG = _("Select your specialty.")
REQUIRED_FIELD_PROFESSION_TEXT_MSG = _("Enter your profession.")
REQUIRED_FIELD_SPECIALTY_TEXT_MSG = _("Enter your specialty.")
REQUIRED_FIELD_CITY_MSG = _("Enter your city.")
REQUIRED_FIELD_GOALS_MSG = _("Tell us your goals.")
REQUIRED_FIELD_LEVEL_OF_EDUCATION_MSG = _("Select the highest level of education you have completed.")
REQUIRED_FIELD_MAILING_ADDRESS_MSG = _("Enter your mailing address.")

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# pylint: disable=missing-docstring
"""
Programmatic integration point for User API Accounts sub-application
@@ -7,14 +6,12 @@ Programmatic integration point for User API Accounts sub-application
import datetime
import six
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.core.validators import ValidationError, validate_email
from django.utils.translation import override as override_language
from django.utils.translation import ugettext as _
from pytz import UTC
from six import text_type # pylint: disable=ungrouped-imports
from common.djangoapps.student import views as student_views
from common.djangoapps.student.models import (
AccountRecovery,
@@ -166,7 +163,7 @@ def update_account_settings(requesting_user, update, username=None):
raise err
except Exception as err:
raise AccountUpdateError( # lint-amnesty, pylint: disable=raise-missing-from
u"Error thrown when saving account updates: '{}'".format(text_type(err))
"Error thrown when saving account updates: '{}'".format(str(err))
)
_send_email_change_requests_if_needed(update, user)
@@ -176,15 +173,15 @@ def _validate_read_only_fields(user, data, field_errors):
# Check for fields that are not editable. Marking them read-only causes them to be ignored, but we wish to 400.
read_only_fields = set(data.keys()).intersection(
# Remove email since it is handled separately below when checking for changing_email.
(set(AccountUserSerializer.get_read_only_fields()) - set(["email"])) |
(set(AccountUserSerializer.get_read_only_fields()) - {"email"}) |
set(AccountLegacyProfileSerializer.get_read_only_fields() or set()) |
get_enterprise_readonly_account_fields(user)
)
for read_only_field in read_only_fields:
field_errors[read_only_field] = {
"developer_message": u"This field is not editable via this API",
"user_message": _(u"The '{field_name}' field cannot be edited.").format(field_name=read_only_field)
"developer_message": "This field is not editable via this API",
"user_message": _("The '{field_name}' field cannot be edited.").format(field_name=read_only_field)
}
del data[read_only_field]
@@ -196,15 +193,15 @@ def _validate_email_change(user, data, field_errors):
return
if not settings.FEATURES['ALLOW_EMAIL_ADDRESS_CHANGE']:
raise AccountUpdateError(u"Email address changes have been disabled by the site operators.")
raise AccountUpdateError("Email address changes have been disabled by the site operators.")
new_email = data["email"]
try:
student_views.validate_new_email(user, new_email)
except ValueError as err:
field_errors["email"] = {
"developer_message": u"Error thrown from validate_new_email: '{}'".format(text_type(err)),
"user_message": text_type(err)
"developer_message": "Error thrown from validate_new_email: '{}'".format(str(err)),
"user_message": str(err)
}
return
@@ -225,8 +222,8 @@ def _validate_secondary_email(user, data, field_errors):
student_views.validate_secondary_email(user, secondary_email)
except ValueError as err:
field_errors["secondary_email"] = {
"developer_message": u"Error thrown from validate_secondary_email: '{}'".format(text_type(err)),
"user_message": text_type(err)
"developer_message": "Error thrown from validate_secondary_email: '{}'".format(str(err)),
"user_message": str(err)
}
else:
# Don't process with sending email to given new email, if it is already associated with
@@ -247,7 +244,7 @@ def _validate_name_change(user_profile, data, field_errors):
validate_name(data['name'])
except ValidationError as err:
field_errors["name"] = {
"developer_message": u"Error thrown from validate_name: '{}'".format(err.message),
"developer_message": f"Error thrown from validate_name: '{err.message}'",
"user_message": err.message
}
return None
@@ -310,7 +307,7 @@ def _store_old_name_if_needed(old_name, user_profile, requesting_user):
meta['old_names'] = []
meta['old_names'].append([
old_name,
u"Name change requested through account API by {0}".format(requesting_user.username),
f"Name change requested through account API by {requesting_user.username}",
datetime.datetime.now(UTC).isoformat()
])
user_profile.set_meta(meta)
@@ -324,8 +321,8 @@ def _send_email_change_requests_if_needed(data, user):
student_views.do_email_change_request(user, new_email)
except ValueError as err:
raise AccountUpdateError( # lint-amnesty, pylint: disable=raise-missing-from
u"Error thrown from do_email_change_request: '{}'".format(text_type(err)),
user_message=text_type(err)
"Error thrown from do_email_change_request: '{}'".format(str(err)),
user_message=str(err)
)
new_secondary_email = data.get("secondary_email")
@@ -338,8 +335,8 @@ def _send_email_change_requests_if_needed(data, user):
)
except ValueError as err:
raise AccountUpdateError( # lint-amnesty, pylint: disable=raise-missing-from
u"Error thrown from do_email_change_request: '{}'".format(text_type(err)),
user_message=text_type(err)
"Error thrown from do_email_change_request: '{}'".format(str(err)),
user_message=str(err)
)
@@ -480,7 +477,7 @@ def _validate(validation_func, err, *args):
try:
validation_func(*args)
except err as validation_err:
return text_type(validation_err)
return str(validation_err)
return ''
@@ -499,7 +496,7 @@ def _validate_username(username):
"""
try:
_validate_unicode(username)
_validate_type(username, six.string_types, accounts.USERNAME_BAD_TYPE_MSG)
_validate_type(username, str, accounts.USERNAME_BAD_TYPE_MSG)
_validate_length(
username,
accounts.USERNAME_MIN_LENGTH,
@@ -511,7 +508,7 @@ def _validate_username(username):
# message by convention.
validate_username(username)
except (UnicodeError, errors.AccountDataBadType, errors.AccountDataBadLength) as username_err:
raise errors.AccountUsernameInvalid(text_type(username_err))
raise errors.AccountUsernameInvalid(str(username_err))
except ValidationError as validation_err:
raise errors.AccountUsernameInvalid(validation_err.message)
@@ -531,12 +528,12 @@ def _validate_email(email):
"""
try:
_validate_unicode(email)
_validate_type(email, six.string_types, accounts.EMAIL_BAD_TYPE_MSG)
_validate_type(email, str, accounts.EMAIL_BAD_TYPE_MSG)
_validate_length(email, accounts.EMAIL_MIN_LENGTH, accounts.EMAIL_MAX_LENGTH, accounts.EMAIL_BAD_LENGTH_MSG)
validate_email.message = accounts.EMAIL_INVALID_MSG.format(email=email)
validate_email(email)
except (UnicodeError, errors.AccountDataBadType, errors.AccountDataBadLength) as invalid_email_err:
raise errors.AccountEmailInvalid(text_type(invalid_email_err))
raise errors.AccountEmailInvalid(str(invalid_email_err))
except ValidationError as validation_err:
raise errors.AccountEmailInvalid(validation_err.message)
@@ -573,11 +570,11 @@ def _validate_password(password, username=None, email=None):
"""
try:
_validate_type(password, six.string_types, accounts.PASSWORD_BAD_TYPE_MSG)
_validate_type(password, str, accounts.PASSWORD_BAD_TYPE_MSG)
temp_user = User(username=username, email=email) if username else None
validate_password(password, user=temp_user)
except errors.AccountDataBadType as invalid_password_err:
raise errors.AccountPasswordInvalid(text_type(invalid_password_err))
raise errors.AccountPasswordInvalid(str(invalid_password_err))
except ValidationError as validation_err:
raise errors.AccountPasswordInvalid(' '.join(validation_err.messages))
@@ -680,7 +677,7 @@ def _validate_length(data, min, max, err): # lint-amnesty, pylint: disable=rede
raise errors.AccountDataBadLength(err)
def _validate_unicode(data, err=u"Input not valid unicode"):
def _validate_unicode(data, err="Input not valid unicode"):
"""Checks whether the input data is valid unicode or not.
:param data: The data to check for unicode validity.
@@ -690,9 +687,9 @@ def _validate_unicode(data, err=u"Input not valid unicode"):
"""
try:
if not isinstance(data, str) and not isinstance(data, six.text_type):
if not isinstance(data, str) and not isinstance(data, str):
raise UnicodeError(err)
# In some cases we pass the above, but it's still inappropriate utf-8.
six.text_type(data)
str(data)
except UnicodeError:
raise UnicodeError(err) # lint-amnesty, pylint: disable=raise-missing-from

View File

@@ -26,8 +26,8 @@ class RetirementQueueDeletionForm(forms.Form):
None,
# Translators: 'current_state' is a string from an enumerated list indicating the learner's retirement
# state. Example: FORUMS_COMPLETE
u"Retirement requests can only be cancelled for users in the PENDING state."
u" Current request state for '{original_username}': {current_state}".format(
"Retirement requests can only be cancelled for users in the PENDING state."
" Current request state for '{original_username}': {current_state}".format(
original_username=retirement.original_username,
current_state=retirement.current_state.state_name
)

View File

@@ -43,7 +43,7 @@ def _get_profile_image_filename(name, size, file_extension=PROFILE_IMAGE_FILE_EX
"""
Returns the full filename for a profile image, given the name and size.
"""
return '{name}_{size}.{file_extension}'.format(name=name, size=size, file_extension=file_extension)
return f'{name}_{size}.{file_extension}'
def _get_profile_image_urls(name, storage, file_extension=PROFILE_IMAGE_FILE_EXTENSION, version=None):
@@ -60,7 +60,7 @@ def _get_profile_image_urls(name, storage, file_extension=PROFILE_IMAGE_FILE_EXT
# query string (such as signed S3 URLs), append to the query
# string with "&v=" instead.
separator = '&' if '?' in url else '?'
return '{}{}v={}'.format(url, separator, version) if version is not None else url
return f'{url}{separator}v={version}' if version is not None else url
return {size_display_name: _make_url(size) for size_display_name, size in settings.PROFILE_IMAGE_SIZES_MAP.items()}

View File

@@ -12,7 +12,6 @@ from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imp
from django.core.exceptions import ObjectDoesNotExist
from django.urls import reverse
from rest_framework import serializers
from six import text_type
from common.djangoapps.student.models import UserPasswordToggleHistory
from lms.djangoapps.badges.utils import badges_enabled
@@ -54,7 +53,7 @@ class LanguageProficiencySerializer(serializers.ModelSerializer):
Class that serializes the LanguageProficiency model for account
information.
"""
class Meta(object):
class Meta:
model = LanguageProficiency
fields = ("code",)
@@ -75,7 +74,7 @@ class SocialLinkSerializer(serializers.ModelSerializer):
"""
Class that serializes the SocialLink model for the UserProfile object.
"""
class Meta(object):
class Meta:
model = SocialLink
fields = ("platform", "social_link")
@@ -86,7 +85,7 @@ class SocialLinkSerializer(serializers.ModelSerializer):
valid_platforms = ["facebook", "twitter", "linkedin"]
if platform not in valid_platforms:
raise serializers.ValidationError(
u"The social platform must be facebook, twitter or linkedin"
"The social platform must be facebook, twitter or linkedin"
)
return platform
@@ -105,7 +104,7 @@ class UserReadOnlySerializer(serializers.Serializer): # lint-amnesty, pylint: d
# Don't pass the 'custom_fields' arg up to the superclass
self.custom_fields = kwargs.pop('custom_fields', [])
super(UserReadOnlySerializer, self).__init__(*args, **kwargs) # lint-amnesty, pylint: disable=super-with-arguments
super().__init__(*args, **kwargs)
def to_representation(self, user): # lint-amnesty, pylint: disable=arguments-differ
"""
@@ -117,7 +116,7 @@ class UserReadOnlySerializer(serializers.Serializer): # lint-amnesty, pylint: d
user_profile = user.profile
except ObjectDoesNotExist:
user_profile = None
LOGGER.warning(u"user profile for the user [%s] does not exist", user.username)
LOGGER.warning("user profile for the user [%s] does not exist", user.username)
try:
account_recovery = user.account_recovery
@@ -226,7 +225,7 @@ class UserAccountDisableHistorySerializer(serializers.ModelSerializer):
"""
created_by = serializers.SerializerMethodField()
class Meta(object):
class Meta:
model = UserPasswordToggleHistory
fields = ("created", "comment", "disabled", "created_by")
@@ -240,7 +239,7 @@ class AccountUserSerializer(serializers.HyperlinkedModelSerializer, ReadOnlyFiel
"""
password_toggle_history = UserAccountDisableHistorySerializer(many=True, required=False)
class Meta(object):
class Meta:
model = User
fields = ("username", "email", "date_joined", "is_active", "password_toggle_history")
read_only_fields = fields
@@ -257,7 +256,7 @@ class AccountLegacyProfileSerializer(serializers.HyperlinkedModelSerializer, Rea
social_links = SocialLinkSerializer(many=True, required=False)
phone_number = PhoneNumberSerializer(required=False)
class Meta(object):
class Meta:
model = UserProfile
fields = (
"name", "gender", "goals", "year_of_birth", "level_of_education", "country", "state", "social_links",
@@ -272,7 +271,7 @@ class AccountLegacyProfileSerializer(serializers.HyperlinkedModelSerializer, Rea
""" Enforce maximum length for bio. """
if len(new_bio) > BIO_MAX_LENGTH:
raise serializers.ValidationError(
u"The about me field must be at most {} characters long.".format(BIO_MAX_LENGTH)
f"The about me field must be at most {BIO_MAX_LENGTH} characters long."
)
return new_bio
@@ -280,7 +279,7 @@ class AccountLegacyProfileSerializer(serializers.HyperlinkedModelSerializer, Rea
""" Enforce minimum length for name. """
if len(new_name) < NAME_MIN_LENGTH:
raise serializers.ValidationError(
u"The name field must be at least {} character long.".format(NAME_MIN_LENGTH)
f"The name field must be at least {NAME_MIN_LENGTH} character long."
)
return new_name
@@ -289,7 +288,7 @@ class AccountLegacyProfileSerializer(serializers.HyperlinkedModelSerializer, Rea
Enforce all languages are unique.
"""
language_proficiencies = [language for language in value] # lint-amnesty, pylint: disable=unnecessary-comprehension
unique_language_proficiencies = set(language["code"] for language in language_proficiencies)
unique_language_proficiencies = {language["code"] for language in language_proficiencies}
if len(language_proficiencies) != len(unique_language_proficiencies):
raise serializers.ValidationError("The language_proficiencies field must consist of unique languages.")
return value
@@ -299,7 +298,7 @@ class AccountLegacyProfileSerializer(serializers.HyperlinkedModelSerializer, Rea
Enforce only one entry for a particular social platform.
"""
social_links = [social_link for social_link in value] # lint-amnesty, pylint: disable=unnecessary-comprehension
unique_social_links = set(social_link["platform"] for social_link in social_links)
unique_social_links = {social_link["platform"] for social_link in social_links}
if len(social_links) != len(unique_social_links):
raise serializers.ValidationError("The social_links field must consist of unique social platforms.")
return value
@@ -349,7 +348,7 @@ class AccountLegacyProfileSerializer(serializers.HyperlinkedModelSerializer, Rea
data = {'has_image': user_profile.has_profile_image}
urls = get_profile_image_urls_for_user(user, request)
data.update({
'{image_key_prefix}_{size}'.format(image_key_prefix=PROFILE_IMAGE_KEY_PREFIX, size=size_display_name): url
f'{PROFILE_IMAGE_KEY_PREFIX}_{size_display_name}': url
for size_display_name, url in urls.items()
})
return data
@@ -408,8 +407,8 @@ class AccountLegacyProfileSerializer(serializers.HyperlinkedModelSerializer, Rea
# If we have encountered any validation errors, return them to the user.
raise errors.AccountValidationError({
'social_links': {
"developer_message": u"Error when adding new social link: '{}'".format(text_type(err)),
"user_message": text_type(err)
"developer_message": "Error when adding new social link: '{}'".format(str(err)),
"user_message": str(err)
}
})
@@ -425,7 +424,7 @@ class AccountLegacyProfileSerializer(serializers.HyperlinkedModelSerializer, Rea
# Update all fields on the user profile that are writeable,
# except for "language_proficiencies" and "social_links", which we'll update separately
update_fields = set(self.get_writeable_fields()) - set(["language_proficiencies"]) - set(["social_links"])
update_fields = set(self.get_writeable_fields()) - {"language_proficiencies"} - {"social_links"}
for field_name in update_fields:
default = getattr(instance, field_name)
field_value = validated_data.get(field_name, default)
@@ -452,7 +451,7 @@ class RetirementUserProfileSerializer(serializers.ModelSerializer):
"""
Serialize a small subset of UserProfile data for use in RetirementStatus APIs
"""
class Meta(object):
class Meta:
model = UserProfile
fields = ('id', 'name')
@@ -463,7 +462,7 @@ class RetirementUserSerializer(serializers.ModelSerializer):
"""
profile = RetirementUserProfileSerializer(read_only=True)
class Meta(object):
class Meta:
model = User
fields = ('id', 'username', 'email', 'profile')
@@ -472,7 +471,7 @@ class RetirementStateSerializer(serializers.ModelSerializer):
"""
Serialize a small subset of RetirementState data for use in RetirementStatus APIs
"""
class Meta(object):
class Meta:
model = RetirementState
fields = ('id', 'state_name', 'state_execution_order')
@@ -485,7 +484,7 @@ class UserRetirementStatusSerializer(serializers.ModelSerializer):
current_state = RetirementStateSerializer(read_only=True)
last_state = RetirementStateSerializer(read_only=True)
class Meta(object):
class Meta:
model = UserRetirementStatus
exclude = ['responses', ]
@@ -580,6 +579,6 @@ def _visible_fields_from_custom_preferences(user, configuration):
preferences = UserPreference.get_all_preferences(user)
fields_shared_with_all_users = [
field_name for field_name in configuration.get('custom_shareable_fields')
if preferences.get('{}{}'.format(VISIBILITY_PREFIX, field_name)) == 'all_users'
if preferences.get(f'{VISIBILITY_PREFIX}{field_name}') == 'all_users'
]
return set(fields_shared_with_all_users + configuration.get('public_fields'))

View File

@@ -2,9 +2,9 @@
import logging
import urllib
from datetime import datetime
import six
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
@@ -64,7 +64,7 @@ def account_settings(request):
if duplicate_provider:
url = '{url}?{params}'.format(
url=url,
params=six.moves.urllib.parse.urlencode({
params=urllib.parse.urlencode({
'duplicate_provider': duplicate_provider,
}),
)
@@ -87,7 +87,7 @@ def account_settings_context(request):
"""
user = request.user
year_of_birth_options = [(six.text_type(year), six.text_type(year)) for year in UserProfile.VALID_YEARS]
year_of_birth_options = [(str(year), str(year)) for year in UserProfile.VALID_YEARS]
try:
user_orders = get_user_orders(user)
except: # pylint: disable=bare-except
@@ -231,19 +231,19 @@ def _get_extended_profile_fields():
'gender', 'year_of_birth', 'language_proficiencies', 'social_links']
field_labels_map = {
"first_name": _(u"First Name"),
"last_name": _(u"Last Name"),
"city": _(u"City"),
"state": _(u"State/Province/Region"),
"company": _(u"Company"),
"title": _(u"Title"),
"job_title": _(u"Job Title"),
"mailing_address": _(u"Mailing address"),
"goals": _(u"Tell us why you're interested in {platform_name}").format(
"first_name": _("First Name"),
"last_name": _("Last Name"),
"city": _("City"),
"state": _("State/Province/Region"),
"company": _("Company"),
"title": _("Title"),
"job_title": _("Job Title"),
"mailing_address": _("Mailing address"),
"goals": _("Tell us why you're interested in {platform_name}").format(
platform_name=configuration_helpers.get_value("PLATFORM_NAME", settings.PLATFORM_NAME)
),
"profession": _(u"Profession"),
"specialty": _(u"Specialty")
"profession": _("Profession"),
"specialty": _("Specialty")
}
extended_profile_field_names = configuration_helpers.get_value('extended_profile_fields', [])

View File

@@ -4,15 +4,13 @@ Utility methods for the account settings.
import re
from urllib.parse import urlparse # pylint: disable=import-error
import waffle
from completion.waffle import ENABLE_COMPLETION_TRACKING_SWITCH
from completion.models import BlockCompletion
from django.conf import settings
from django.utils.translation import ugettext as _
from six import text_type
from six.moves import range # lint-amnesty, pylint: disable=unused-import
from six.moves.urllib.parse import urlparse # pylint: disable=import-error
from common.djangoapps.third_party_auth.config.waffle import ENABLE_MULTIPLE_SSO_ACCOUNTS_ASSOCIATION_TO_SAML_USER
from openedx.core.djangoapps.site_configuration.models import SiteConfiguration
@@ -63,7 +61,7 @@ def format_social_link(platform_name, new_social_link):
return None
# For security purposes, always build up the url rather than using input from user.
return 'https://www.{}{}'.format(url_stub, username)
return f'https://www.{url_stub}{username}'
def _get_username_from_social_link(platform_name, new_social_link):
@@ -173,10 +171,10 @@ def retrieve_last_sitewide_block_completed(user):
if not (lms_root and item):
return
return u"{lms_root}/courses/{course_key}/jump_to/{location}".format(
return "{lms_root}/courses/{course_key}/jump_to/{location}".format(
lms_root=lms_root,
course_key=text_type(item.location.course_key),
location=text_type(item.location),
course_key=str(item.location.course_key),
location=str(item.location),
)

View File

@@ -34,7 +34,6 @@ from rest_framework.response import Response
from rest_framework.serializers import ValidationError
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSet
from six import iteritems, text_type
from social_django.models import UserSocialAuth
from wiki.models import ArticleRevision
from wiki.models.pluginbase import RevisionPluginRevision
@@ -115,7 +114,7 @@ def request_requires_username(function):
if not username:
return Response(
status=status.HTTP_404_NOT_FOUND,
data={'message': text_type('The user was not specified.')}
data={'message': 'The user was not specified.'}
)
return function(self, request)
return wrapper
@@ -469,16 +468,16 @@ class DeactivateLogoutView(APIView):
logout(request)
return Response(status=status.HTTP_204_NO_CONTENT)
except KeyError:
log.exception('Username not specified {}'.format(request.user))
return Response(u'Username not specified.', status=status.HTTP_404_NOT_FOUND)
log.exception(f'Username not specified {request.user}')
return Response('Username not specified.', status=status.HTTP_404_NOT_FOUND)
except user_model.DoesNotExist:
log.exception('The user "{}" does not exist.'.format(request.user.username))
log.exception(f'The user "{request.user.username}" does not exist.')
return Response(
u'The user "{}" does not exist.'.format(request.user.username), status=status.HTTP_404_NOT_FOUND
f'The user "{request.user.username}" does not exist.', status=status.HTTP_404_NOT_FOUND
)
except Exception as exc: # pylint: disable=broad-except
log.exception('500 error deactivating account {}'.format(exc))
return Response(text_type(exc), status=status.HTTP_500_INTERNAL_SERVER_ERROR)
log.exception(f'500 error deactivating account {exc}')
return Response(str(exc), status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def _verify_user_password(self, request):
"""
@@ -499,11 +498,11 @@ class DeactivateLogoutView(APIView):
self._handle_failed_authentication(request.user)
except AuthFailedError as err:
log.exception(
"The user password to deactivate was incorrect. {}".format(request.user.username)
f"The user password to deactivate was incorrect. {request.user.username}"
)
return Response(text_type(err), status=status.HTTP_403_FORBIDDEN)
return Response(str(err), status=status.HTTP_403_FORBIDDEN)
except Exception as err: # pylint: disable=broad-except
return Response(u"Could not verify user password: {}".format(err), status=status.HTTP_400_BAD_REQUEST)
return Response(f"Could not verify user password: {err}", status=status.HTTP_400_BAD_REQUEST)
def _check_excessive_login_attempts(self, user):
"""
@@ -713,9 +712,9 @@ class AccountRetirementPartnerReportView(ViewSet):
# to disambiguate them in Python, which will respect case in the comparison.
if len(usernames) != len(retirement_statuses_clean):
return Response(
u'{} original_usernames given, {} found!\n'
u'Given usernames:\n{}\n'
u'Found UserRetirementReportingStatuses:\n{}'.format(
'{} original_usernames given, {} found!\n'
'Given usernames:\n{}\n'
'Found UserRetirementReportingStatuses:\n{}'.format(
len(usernames),
len(retirement_statuses_clean),
usernames,
@@ -758,7 +757,7 @@ class AccountRetirementStatusView(ViewSet):
state_objs = RetirementState.objects.filter(state_name__in=states)
if state_objs.count() != len(states):
found = [s.state_name for s in state_objs]
raise RetirementStateError(u'Unknown state. Requested: {} Found: {}'.format(states, found))
raise RetirementStateError(f'Unknown state. Requested: {states} Found: {found}')
earliest_datetime = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=cool_off_days)
@@ -775,10 +774,10 @@ class AccountRetirementStatusView(ViewSet):
except ValueError:
return Response('Invalid cool_off_days, should be integer.', status=status.HTTP_400_BAD_REQUEST)
except KeyError as exc:
return Response(u'Missing required parameter: {}'.format(text_type(exc)),
return Response('Missing required parameter: {}'.format(str(exc)),
status=status.HTTP_400_BAD_REQUEST)
except RetirementStateError as exc:
return Response(text_type(exc), status=status.HTTP_400_BAD_REQUEST)
return Response(str(exc), status=status.HTTP_400_BAD_REQUEST)
def retirements_by_status_and_date(self, request):
"""
@@ -815,14 +814,14 @@ class AccountRetirementStatusView(ViewSet):
return Response(serializer.data)
# This should only occur on the datetime conversion of the start / end dates.
except ValueError as exc:
return Response(u'Invalid start or end date: {}'.format(text_type(exc)), status=status.HTTP_400_BAD_REQUEST)
return Response('Invalid start or end date: {}'.format(str(exc)), status=status.HTTP_400_BAD_REQUEST)
except KeyError as exc:
return Response(u'Missing required parameter: {}'.format(text_type(exc)),
return Response('Missing required parameter: {}'.format(str(exc)),
status=status.HTTP_400_BAD_REQUEST)
except RetirementState.DoesNotExist:
return Response('Unknown retirement state.', status=status.HTTP_400_BAD_REQUEST)
except RetirementStateError as exc:
return Response(text_type(exc), status=status.HTTP_400_BAD_REQUEST)
return Response(str(exc), status=status.HTTP_400_BAD_REQUEST)
def retrieve(self, request, username): # pylint: disable=unused-argument
"""
@@ -886,9 +885,9 @@ class AccountRetirementStatusView(ViewSet):
except UserRetirementStatus.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
except RetirementStateError as exc:
return Response(text_type(exc), status=status.HTTP_400_BAD_REQUEST)
return Response(str(exc), status=status.HTTP_400_BAD_REQUEST)
except Exception as exc: # pylint: disable=broad-except
return Response(text_type(exc), status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return Response(str(exc), status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def cleanup(self, request):
"""
@@ -921,9 +920,9 @@ class AccountRetirementStatusView(ViewSet):
retirements.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
except (RetirementStateError, UserRetirementStatus.DoesNotExist, TypeError) as exc:
return Response(text_type(exc), status=status.HTTP_400_BAD_REQUEST)
return Response(str(exc), status=status.HTTP_400_BAD_REQUEST)
except Exception as exc: # pylint: disable=broad-except
return Response(text_type(exc), status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return Response(str(exc), status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class LMSAccountRetirementView(ViewSet):
@@ -975,9 +974,9 @@ class LMSAccountRetirementView(ViewSet):
except UserRetirementStatus.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
except RetirementStateError as exc:
return Response(text_type(exc), status=status.HTTP_400_BAD_REQUEST)
return Response(str(exc), status=status.HTTP_400_BAD_REQUEST)
except Exception as exc: # pylint: disable=broad-except
return Response(text_type(exc), status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return Response(str(exc), status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return Response(status=status.HTTP_204_NO_CONTENT)
@@ -1045,9 +1044,9 @@ class AccountRetirementView(ViewSet):
except UserRetirementStatus.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
except RetirementStateError as exc:
return Response(text_type(exc), status=status.HTTP_400_BAD_REQUEST)
return Response(str(exc), status=status.HTTP_400_BAD_REQUEST)
except Exception as exc: # pylint: disable=broad-except
return Response(text_type(exc), status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return Response(str(exc), status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return Response(status=status.HTTP_204_NO_CONTENT)
@@ -1057,7 +1056,7 @@ class AccountRetirementView(ViewSet):
For the given user, sets all of the user's profile fields to some retired value.
This also deletes all ``SocialLink`` objects associated with this user's profile.
"""
for model_field, value_to_assign in iteritems(USER_PROFILE_PII):
for model_field, value_to_assign in USER_PROFILE_PII.items():
setattr(user.profile, model_field, value_to_assign)
user.profile.save()
@@ -1260,7 +1259,7 @@ class UsernameReplacementView(APIView):
)
except Exception as exc: # pylint: disable=broad-except
log.exception(
u"Unable to change username from %s to %s. Failed on table %s because %s",
"Unable to change username from %s to %s. Failed on table %s because %s",
current_username,
new_username,
model.__class__.__name__, # Retrieves the model name that it failed on
@@ -1269,14 +1268,14 @@ class UsernameReplacementView(APIView):
return False
if num_rows_changed == 0:
log.info(
u"Unable to change username from %s to %s because %s doesn't exist.",
"Unable to change username from %s to %s because %s doesn't exist.",
current_username,
new_username,
current_username,
)
else:
log.info(
u"Successfully changed username from %s to %s.",
"Successfully changed username from %s to %s.",
current_username,
new_username,
)

View File

@@ -26,7 +26,7 @@ class RetirementStateAdmin(admin.ModelAdmin):
list_filter = ('is_dead_end_state', 'required',)
search_fields = ('state_name',)
class Meta(object):
class Meta:
model = RetirementState
@@ -87,7 +87,7 @@ class UserRetirementStatusAdmin(admin.ModelAdmin):
"""
Adds our custom URL to the admin
"""
urls = super(UserRetirementStatusAdmin, self).get_urls() # lint-amnesty, pylint: disable=super-with-arguments
urls = super().get_urls()
custom_urls = [
url(
r'^(?P<retirement_id>.+)/cancel_retirement/$',
@@ -105,7 +105,7 @@ class UserRetirementStatusAdmin(admin.ModelAdmin):
try:
if obj.current_state.state_name == 'PENDING':
return format_html(
u'<a class="button" href="{}">{}</a>&nbsp;',
'<a class="button" href="{}">{}</a>&nbsp;',
reverse('admin:cancel-retirement', args=[obj.pk]),
_('Cancel')
)
@@ -121,7 +121,7 @@ class UserRetirementStatusAdmin(admin.ModelAdmin):
Removes the default bulk delete option provided by Django,
it doesn't do what we need for this model.
"""
actions = super(UserRetirementStatusAdmin, self).get_actions(request) # lint-amnesty, pylint: disable=super-with-arguments
actions = super().get_actions(request)
if 'delete_selected' in actions:
del actions['delete_selected']
return actions
@@ -138,7 +138,7 @@ class UserRetirementStatusAdmin(admin.ModelAdmin):
"""
return False
class Meta(object):
class Meta:
model = UserRetirementStatus
@@ -150,7 +150,7 @@ class UserRetirementRequestAdmin(admin.ModelAdmin):
list_display = ('user', 'created')
raw_id_fields = ('user',)
class Meta(object):
class Meta:
model = UserRetirementRequest
@@ -172,7 +172,7 @@ class UserRetirementPartnerReportingStatusAdmin(admin.ModelAdmin):
'reset_state', # See reset_state() below.
]
class Meta(object):
class Meta:
model = UserRetirementPartnerReportingStatus
def user_id(self, obj):
@@ -192,7 +192,7 @@ class UserRetirementPartnerReportingStatusAdmin(admin.ModelAdmin):
if rows_updated == 1:
message_bit = "one user was"
else:
message_bit = u"%s users were" % rows_updated
self.message_user(request, u"%s successfully reset." % message_bit)
message_bit = "%s users were" % rows_updated
self.message_user(request, "%s successfully reset." % message_bit)
reset_state.short_description = 'Reset is_being_processed to False'

View File

@@ -7,7 +7,7 @@ from django.utils.translation import ugettext_lazy as _
from edx_toggles.toggles import WaffleSwitch
SYSTEM_MAINTENANCE_MSG = _(u'System maintenance in progress. Please try again later.')
SYSTEM_MAINTENANCE_MSG = _('System maintenance in progress. Please try again later.')
# .. toggle_name: user_api.enable_multiple_user_enterprises_feature
# .. toggle_implementation: WaffleSwitch

View File

@@ -20,8 +20,8 @@ from ..models import UserCourseTag
COURSE_SCOPE = 'course'
class BulkCourseTags(object): # lint-amnesty, pylint: disable=missing-class-docstring
CACHE_NAMESPACE = u'user_api.course_tag.api'
class BulkCourseTags: # lint-amnesty, pylint: disable=missing-class-docstring
CACHE_NAMESPACE = 'user_api.course_tag.api'
@classmethod
def prefetch(cls, course_id, users):
@@ -53,7 +53,7 @@ class BulkCourseTags(object): # lint-amnesty, pylint: disable=missing-class-doc
@classmethod
def _cache_key(cls, course_id):
return u'course_tag.{}'.format(course_id)
return f'course_tag.{course_id}'
def get_course_tag(user, course_id, key):

View File

@@ -108,7 +108,7 @@ class PreferenceValidationError(PreferenceRequestError):
"""
def __init__(self, preference_errors):
self.preference_errors = preference_errors
super(PreferenceValidationError, self).__init__(preference_errors) # lint-amnesty, pylint: disable=super-with-arguments
super().__init__(preference_errors)
class PreferenceUpdateError(PreferenceRequestError):
@@ -119,7 +119,7 @@ class PreferenceUpdateError(PreferenceRequestError):
def __init__(self, developer_message, user_message=None):
self.developer_message = developer_message
self.user_message = user_message
super(PreferenceUpdateError, self).__init__(developer_message) # lint-amnesty, pylint: disable=super-with-arguments
super().__init__(developer_message)
class CountryCodeError(ValueError):

View File

@@ -10,7 +10,6 @@ import traceback
from collections import defaultdict
from functools import wraps
import six
from django import forms
from django.conf import settings
from django.core.serializers.json import DjangoJSONEncoder
@@ -57,9 +56,9 @@ def intercept_errors(api_error, ignore_errors=None):
for ignored in ignore_errors or []:
if isinstance(ex, ignored):
msg = (
u"A handled error occurred when calling '{func_name}' "
u"with arguments '{args}' and keyword arguments '{kwargs}': "
u"{exception}"
"A handled error occurred when calling '{func_name}' "
"with arguments '{args}' and keyword arguments '{kwargs}': "
"{exception}"
).format(
func_name=func.__name__,
args=args,
@@ -73,9 +72,9 @@ def intercept_errors(api_error, ignore_errors=None):
# Otherwise, log the error and raise the API-specific error
msg = (
u"An unexpected error occurred when calling '{func_name}' "
u"with arguments '{args}' and keyword arguments '{kwargs}' from {caller}: "
u"{exception}"
"An unexpected error occurred when calling '{func_name}' "
"with arguments '{args}' and keyword arguments '{kwargs}' from {caller}: "
"{exception}"
).format(
func_name=func.__name__,
args=args,
@@ -93,7 +92,7 @@ class InvalidFieldError(Exception):
"""The provided field definition is not valid. """
class FormDescription(object):
class FormDescription:
"""Generate a JSON representation of a form. """
ALLOWED_TYPES = ["text", "email", "select", "textarea", "checkbox", "plaintext", "password", "hidden"]
@@ -135,10 +134,10 @@ class FormDescription(object):
self._field_overrides = defaultdict(dict)
def add_field(
self, name, label=u"", field_type=u"text", default=u"",
placeholder=u"", instructions=u"", required=True, restrictions=None,
self, name, label="", field_type="text", default="",
placeholder="", instructions="", required=True, restrictions=None,
options=None, include_default_option=False, error_messages=None,
supplementalLink=u"", supplementalText=u""
supplementalLink="", supplementalText=""
):
"""Add a field to the form description.
@@ -190,7 +189,7 @@ class FormDescription(object):
"""
if field_type not in self.ALLOWED_TYPES:
msg = u"Field type '{field_type}' is not a valid type. Allowed types are: {allowed}.".format(
msg = "Field type '{field_type}' is not a valid type. Allowed types are: {allowed}.".format(
field_type=field_type,
allowed=", ".join(self.ALLOWED_TYPES)
)
@@ -240,14 +239,11 @@ class FormDescription(object):
if restrictions is not None:
allowed_restrictions = self.ALLOWED_RESTRICTIONS.get(field_type, [])
for key, val in six.iteritems(restrictions):
for key, val in restrictions.items():
if key in allowed_restrictions:
field_dict["restrictions"][key] = val
else:
msg = u"Restriction '{restriction}' is not allowed for field type '{field_type}'".format(
restriction=key,
field_type=field_type
)
msg = f"Restriction '{key}' is not allowed for field type '{field_type}'"
raise InvalidFieldError(msg)
if error_messages is not None:
@@ -337,7 +333,7 @@ class FormDescription(object):
self._field_overrides[field_name].update({
property_name: property_value
for property_name, property_value in six.iteritems(kwargs)
for property_name, property_value in kwargs.items()
if property_name in self.OVERRIDE_FIELD_PROPERTIES
})
@@ -353,7 +349,7 @@ class LocalizedJSONEncoder(DjangoJSONEncoder):
"""
if isinstance(obj, Promise):
return force_text(obj)
super(LocalizedJSONEncoder, self).default(obj) # lint-amnesty, pylint: disable=super-with-arguments
super().default(obj)
def serializer_is_dirty(preference_serializer):

View File

@@ -19,7 +19,7 @@ urlpatterns = [
url(r'^account/settings$', account_settings, name='account_settings'),
url(r'^user_api/v1/', include(USER_API_ROUTER.urls)),
url(
r'^user_api/v1/preferences/(?P<pref_key>{})/users/$'.format(UserPreference.KEY_REGEX),
fr'^user_api/v1/preferences/(?P<pref_key>{UserPreference.KEY_REGEX})/users/$',
user_api_views.PreferenceUsersListView.as_view()
),
url(

View File

@@ -14,7 +14,7 @@ class DeletionNotificationMessage(BaseMessageType):
Message to notify learners that their account is queued for deletion.
"""
def __init__(self, *args, **kwargs):
super(DeletionNotificationMessage, self).__init__(*args, **kwargs) # lint-amnesty, pylint: disable=super-with-arguments
super().__init__(*args, **kwargs)
self.options['transactional'] = True # pylint: disable=unsupported-assignment-operation
self.options['from_address'] = helpers.get_value( # pylint: disable=unsupported-assignment-operation

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
import django.core.validators
import django.utils.timezone
import model_utils.fields
@@ -42,21 +39,21 @@ class Migration(migrations.Migration):
name='UserPreference',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('key', models.CharField(db_index=True, max_length=255, validators=[django.core.validators.RegexValidator(u'[-_a-zA-Z0-9]+')])),
('key', models.CharField(db_index=True, max_length=255, validators=[django.core.validators.RegexValidator('[-_a-zA-Z0-9]+')])),
('value', models.TextField()),
('user', models.ForeignKey(related_name='preferences', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
],
),
migrations.AlterUniqueTogether(
name='userpreference',
unique_together=set([('user', 'key')]),
unique_together={('user', 'key')},
),
migrations.AlterUniqueTogether(
name='userorgtag',
unique_together=set([('user', 'org', 'key')]),
unique_together={('user', 'org', 'key')},
),
migrations.AlterUniqueTogether(
name='usercoursetag',
unique_together=set([('user', 'course_id', 'key')]),
unique_together={('user', 'course_id', 'key')},
),
]

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.12 on 2018-04-19 17:55

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.13 on 2018-05-14 20:37

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.13 on 2018-06-13 20:54

View File

@@ -45,12 +45,12 @@ class UserPreference(models.Model):
.. no_pii: Stores arbitrary key/value pairs, currently none are PII. If that changes, update this annotation.
"""
KEY_REGEX = u"[-_a-zA-Z0-9]+"
KEY_REGEX = "[-_a-zA-Z0-9]+"
user = models.ForeignKey(User, db_index=True, related_name="preferences", on_delete=models.CASCADE)
key = models.CharField(max_length=255, db_index=True, validators=[RegexValidator(KEY_REGEX)])
value = models.TextField()
class Meta(object):
class Meta:
unique_together = ("user", "key")
@staticmethod
@@ -61,7 +61,7 @@ class UserPreference(models.Model):
Returns: Set of (preference type, value) pairs for each of the user's preferences
"""
return dict([(pref.key, pref.value) for pref in user.preferences.all()]) # lint-amnesty, pylint: disable=consider-using-dict-comprehension
return {pref.key: pref.value for pref in user.preferences.all()}
@classmethod
def get_value(cls, user, preference_key, default=None):
@@ -158,7 +158,7 @@ class UserCourseTag(models.Model):
course_id = CourseKeyField(max_length=255, db_index=True)
value = models.TextField()
class Meta(object):
class Meta:
unique_together = ("user", "course_id", "key")
@@ -177,7 +177,7 @@ class UserOrgTag(TimeStampedModel, DeletableByUserValue):
org = models.CharField(max_length=255, db_index=True)
value = models.TextField()
class Meta(object):
class Meta:
unique_together = ("user", "org", "key")
@@ -195,9 +195,9 @@ class RetirementState(models.Model):
required = models.BooleanField(default=False)
def __str__(self):
return '{} (step {})'.format(self.state_name, self.state_execution_order)
return f'{self.state_name} (step {self.state_execution_order})'
class Meta(object):
class Meta:
ordering = ('state_execution_order',)
@classmethod
@@ -232,12 +232,12 @@ class UserRetirementPartnerReportingStatus(TimeStampedModel):
original_name = models.CharField(max_length=255, blank=True, db_index=True)
is_being_processed = models.BooleanField(default=False)
class Meta(object):
class Meta:
verbose_name = 'User Retirement Reporting Status'
verbose_name_plural = 'User Retirement Reporting Statuses'
def __str__(self):
return u'UserRetirementPartnerReportingStatus: {} is being processed: {}'.format(
return 'UserRetirementPartnerReportingStatus: {} is being processed: {}'.format(
self.user,
self.is_being_processed
)
@@ -254,7 +254,7 @@ class UserRetirementRequest(TimeStampedModel):
"""
user = models.OneToOneField(User, on_delete=models.CASCADE)
class Meta(object):
class Meta:
verbose_name = 'User Retirement Request'
verbose_name_plural = 'User Retirement Requests'
@@ -264,7 +264,7 @@ class UserRetirementRequest(TimeStampedModel):
Creates a UserRetirementRequest for the specified user.
"""
if cls.has_user_requested_retirement(user):
raise RetirementStateError(u'User {} already has a retirement request row!'.format(user))
raise RetirementStateError(f'User {user} already has a retirement request row!')
return cls.objects.create(user=user)
@classmethod
@@ -275,10 +275,9 @@ class UserRetirementRequest(TimeStampedModel):
return cls.objects.filter(user=user).exists()
def __str__(self):
return u'User: {} Requested: {}'.format(self.user.id, self.created)
return f'User: {self.user.id} Requested: {self.created}'
@python_2_unicode_compatible
class UserRetirementStatus(TimeStampedModel):
"""
Tracks the progress of a user's retirement request
@@ -297,7 +296,7 @@ class UserRetirementStatus(TimeStampedModel):
last_state = models.ForeignKey(RetirementState, blank=True, related_name='last_state', on_delete=models.CASCADE)
responses = models.TextField()
class Meta(object):
class Meta:
verbose_name = 'User Retirement Status'
verbose_name_plural = 'User Retirement Statuses'
@@ -308,16 +307,14 @@ class UserRetirementStatus(TimeStampedModel):
dead_end_states = list(RetirementState.get_dead_end_state_names_list())
states = list(RetirementState.get_state_names_list())
if self.current_state in dead_end_states:
raise RetirementStateError(u'RetirementStatus: Unable to move user from {}'.format(self.current_state))
raise RetirementStateError(f'RetirementStatus: Unable to move user from {self.current_state}')
try:
new_state_index = states.index(new_state)
if new_state_index <= states.index(self.current_state.state_name):
raise ValueError()
except ValueError:
err = u'{} does not exist or is an eariler state than current state {}'.format(
new_state, self.current_state
)
err = f'{new_state} does not exist or is an eariler state than current state {self.current_state}'
raise RetirementStateError(err) # lint-amnesty, pylint: disable=raise-missing-from
def _validate_update_data(self, data):
@@ -330,13 +327,11 @@ class UserRetirementStatus(TimeStampedModel):
for required_key in required_keys:
if required_key not in data:
raise RetirementStateError(u'RetirementStatus: Required key {} missing from update'.format(
required_key
))
raise RetirementStateError(f'RetirementStatus: Required key {required_key} missing from update')
for key in data:
if key not in known_keys:
raise RetirementStateError(u'RetirementStatus: Unknown key {} in update'.format(key))
raise RetirementStateError(f'RetirementStatus: Unknown key {key} in update')
@classmethod
def create_retirement(cls, user):
@@ -350,7 +345,7 @@ class UserRetirementStatus(TimeStampedModel):
raise RetirementStateError('Default state does not exist! Populate retirement states to retire users.') # lint-amnesty, pylint: disable=raise-missing-from
if cls.objects.filter(user=user).exists():
raise RetirementStateError(u'User {} already has a retirement status row!'.format(user))
raise RetirementStateError(f'User {user} already has a retirement status row!')
retired_username = get_retired_username_by_username(user.username)
retired_email = get_retired_email_by_email(user.email)
@@ -366,7 +361,7 @@ class UserRetirementStatus(TimeStampedModel):
retired_email=retired_email,
current_state=pending,
last_state=pending,
responses=u'Created in state {} by create_retirement'.format(pending)
responses=f'Created in state {pending} by create_retirement'
)
def update_state(self, update):
@@ -383,7 +378,7 @@ class UserRetirementStatus(TimeStampedModel):
old_state = self.current_state
self.current_state = RetirementState.objects.get(state_name=update['new_state'])
self.last_state = old_state
self.responses += u"\n Moved from {} to {}:\n{}\n".format(old_state, self.current_state, update['response'])
self.responses += "\n Moved from {} to {}:\n{}\n".format(old_state, self.current_state, update['response'])
self.save()
@classmethod
@@ -411,19 +406,19 @@ class UserRetirementStatus(TimeStampedModel):
break
if retirement is None:
raise UserRetirementStatus.DoesNotExist(u'{} does not have an exact match in UserRetirementStatus. '
u'{} similar rows found.'.format(username, len(retirements)))
raise UserRetirementStatus.DoesNotExist('{} does not have an exact match in UserRetirementStatus. '
'{} similar rows found.'.format(username, len(retirements)))
state = retirement.current_state
if state.required or state.state_name.endswith('_COMPLETE'):
raise RetirementStateError(u'{} is in {}, not a valid state to perform retirement '
u'actions on.'.format(retirement, state.state_name))
raise RetirementStateError('{} is in {}, not a valid state to perform retirement '
'actions on.'.format(retirement, state.state_name))
return retirement
def __str__(self):
return u'User: {} State: {} Last Updated: {}'.format(self.user.id, self.current_state, self.modified)
return f'User: {self.user.id} State: {self.current_state} Last Updated: {self.modified}'
@receiver(models.signals.post_delete, sender=UserRetirementStatus)

View File

@@ -14,7 +14,7 @@ from xmodule.partitions.partitions import NoSuchUserPartitionGroupError, UserPar
log = logging.getLogger(__name__)
class NotImplementedPartitionScheme(object):
class NotImplementedPartitionScheme:
"""
This "scheme" allows previously-defined schemes to be purged, while giving existing
course data definitions a safe entry point to load.
@@ -30,7 +30,7 @@ class NotImplementedPartitionScheme(object):
return None
class ReturnGroup1PartitionScheme(object):
class ReturnGroup1PartitionScheme:
"""
This scheme is needed to allow verification partitions to be killed, see EDUCATOR-199
"""
@@ -43,7 +43,7 @@ class ReturnGroup1PartitionScheme(object):
return user_partition.get_group(1)
class RandomUserPartitionScheme(object):
class RandomUserPartitionScheme:
"""
This scheme randomly assigns users into the partition's groups.
"""
@@ -66,7 +66,7 @@ class RandomUserPartitionScheme(object):
except NoSuchUserPartitionGroupError:
# jsa: we can turn off warnings here if this is an expected case.
log.warning(
u"group not found in RandomUserPartitionScheme: %r",
"group not found in RandomUserPartitionScheme: %r",
{
"requested_partition_id": user_partition.id,
"requested_group_id": group_id,
@@ -74,7 +74,7 @@ class RandomUserPartitionScheme(object):
exc_info=True
)
except ValueError:
log.error(u"Bad group_id %r for user: %r", group_id, user)
log.error("Bad group_id %r for user: %r", group_id, user)
if group is None and assign and not course_tag_api.BulkCourseTags.is_prefetched(course_key):
if not user_partition.groups:
@@ -115,4 +115,4 @@ class RandomUserPartitionScheme(object):
"""
Returns the key to use to look up and save the user's group for a given user partition.
"""
return 'xblock.partition_service.partition_{0}'.format(user_partition.id)
return f'xblock.partition_service.partition_{user_partition.id}'

View File

@@ -5,7 +5,6 @@ API for managing user preferences.
import logging
import six
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.db import IntegrityError
@@ -134,7 +133,7 @@ def update_user_preferences(requesting_user, update, user=None):
PreferenceUpdateError: the operation failed when performing the update.
UserAPIInternalError: the operation failed due to an unexpected error.
"""
if not user or isinstance(user, six.string_types):
if not user or isinstance(user, str):
user = _get_authorized_user(requesting_user, user)
else:
_check_authorized(requesting_user, user.username)
@@ -145,7 +144,7 @@ def update_user_preferences(requesting_user, update, user=None):
for preference_key in update.keys():
preference_value = update[preference_key]
if preference_value is not None:
preference_value = six.text_type(preference_value)
preference_value = str(preference_value)
try:
serializer = create_user_preference_serializer(user, preference_key, preference_value)
validate_user_preference_serializer(serializer, preference_key, preference_value)
@@ -162,7 +161,7 @@ def update_user_preferences(requesting_user, update, user=None):
for preference_key in update.keys():
preference_value = update[preference_key]
if preference_value is not None:
preference_value = six.text_type(preference_value)
preference_value = str(preference_value)
try:
serializer = serializers[preference_key]
@@ -201,7 +200,7 @@ def set_user_preference(requesting_user, preference_key, preference_value, usern
"""
existing_user = _get_authorized_user(requesting_user, username)
if preference_value is not None:
preference_value = six.text_type(preference_value)
preference_value = str(preference_value)
serializer = create_user_preference_serializer(existing_user, preference_key, preference_value)
validate_user_preference_serializer(serializer, preference_key, preference_value)
@@ -248,10 +247,10 @@ def delete_user_preference(requesting_user, preference_key, username=None):
user_preference.delete()
except Exception as error:
raise PreferenceUpdateError( # lint-amnesty, pylint: disable=raise-missing-from
developer_message=u"Delete failed for user preference '{preference_key}': {error}".format(
developer_message="Delete failed for user preference '{preference_key}': {error}".format(
preference_key=preference_key, error=error
),
user_message=_(u"Delete failed for user preference '{preference_key}'.").format(
user_message=_("Delete failed for user preference '{preference_key}'.").format(
preference_key=preference_key
),
)
@@ -300,7 +299,7 @@ def update_email_opt_in(user, org, opt_in):
_track_update_email_opt_in(user.id, org, opt_in)
except IntegrityError as err:
log.warning(
u"Could not update organization wide preference due to IntegrityError: {}".format(six.text_type(err))
"Could not update organization wide preference due to IntegrityError: {}".format(str(err))
)
@@ -399,8 +398,8 @@ def validate_user_preference_serializer(serializer, preference_key, preference_v
Raises:
PreferenceValidationError: the supplied key and/or value for a user preference are invalid.
"""
if preference_value is None or six.text_type(preference_value).strip() == '':
format_string = ugettext_noop(u"Preference '{preference_key}' cannot be set to an empty value.")
if preference_value is None or str(preference_value).strip() == '':
format_string = ugettext_noop("Preference '{preference_key}' cannot be set to an empty value.")
raise PreferenceValidationError({
preference_key: {
"developer_message": format_string.format(preference_key=preference_key),
@@ -412,16 +411,16 @@ def validate_user_preference_serializer(serializer, preference_key, preference_v
# DRF error messages are of type ErrorDetail and serialize out as such. We want to coerce those
# messages into the strings only.
for key in errors:
errors[key] = [six.text_type(el) for el in errors[key]]
developer_message = u"Value '{preference_value}' not valid for preference '{preference_key}': {error}".format(
errors[key] = [str(el) for el in errors[key]]
developer_message = "Value '{preference_value}' not valid for preference '{preference_key}': {error}".format(
preference_key=preference_key, preference_value=preference_value, error=errors
)
if "key" in serializer.errors:
user_message = _(u"Invalid user preference key '{preference_key}'.").format(
user_message = _("Invalid user preference key '{preference_key}'.").format(
preference_key=preference_key
)
else:
user_message = _(u"Value '{preference_value}' is not valid for user preference '{preference_key}'.").format(
user_message = _("Value '{preference_value}' is not valid for user preference '{preference_key}'.").format(
preference_key=preference_key, preference_value=preference_value
)
raise PreferenceValidationError({
@@ -431,8 +430,8 @@ def validate_user_preference_serializer(serializer, preference_key, preference_v
}
})
if preference_key == "time_zone" and preference_value not in common_timezones_set:
developer_message = ugettext_noop(u"Value '{preference_value}' not valid for preference '{preference_key}': Not in timezone set.") # pylint: disable=line-too-long
user_message = ugettext_noop(u"Value '{preference_value}' is not a valid time zone selection.")
developer_message = ugettext_noop("Value '{preference_value}' not valid for preference '{preference_key}': Not in timezone set.") # pylint: disable=line-too-long
user_message = ugettext_noop("Value '{preference_value}' is not a valid time zone selection.")
raise PreferenceValidationError({
preference_key: {
"developer_message": developer_message.format(
@@ -446,10 +445,10 @@ def validate_user_preference_serializer(serializer, preference_key, preference_v
def _create_preference_update_error(preference_key, preference_value, error):
""" Creates a PreferenceUpdateError with developer_message and user_message. """
return PreferenceUpdateError(
developer_message=u"Save failed for user preference '{key}' with value '{value}': {error}".format(
developer_message="Save failed for user preference '{key}' with value '{value}': {error}".format(
key=preference_key, value=preference_value, error=error
),
user_message=_(u"Save failed for user preference '{key}' with value '{value}'.").format(
user_message=_("Save failed for user preference '{key}' with value '{value}'.").format(
key=preference_key, value=preference_value
),
)

View File

@@ -34,7 +34,7 @@ class UserSerializer(serializers.HyperlinkedModelSerializer):
"""
return UserPreference.get_all_preferences(user)
class Meta(object):
class Meta:
model = User
# This list is the minimal set required by the notification service
fields = ("id", "url", "email", "name", "username", "preferences")
@@ -49,7 +49,7 @@ class UserPreferenceSerializer(serializers.HyperlinkedModelSerializer):
"""
user = UserSerializer()
class Meta(object):
class Meta:
model = UserPreference
depth = 1
fields = ('user', 'key', 'value', 'url')
@@ -61,13 +61,13 @@ class RawUserPreferenceSerializer(serializers.ModelSerializer):
"""
user = serializers.PrimaryKeyRelatedField(queryset=User.objects.all())
class Meta(object):
class Meta:
model = UserPreference
depth = 1
fields = ('user', 'key', 'value', 'url')
class ReadOnlyFieldsSerializerMixin(object):
class ReadOnlyFieldsSerializerMixin:
"""
Mixin for use with Serializers that provides a method
`get_read_only_fields`, which returns a tuple of all read-only

View File

@@ -13,7 +13,7 @@ from ..models import UserCourseTag, UserOrgTag, UserPreference
# Factories are self documenting
# pylint: disable=missing-docstring
class UserPreferenceFactory(DjangoModelFactory):
class Meta(object):
class Meta:
model = UserPreference
user = None
@@ -22,7 +22,7 @@ class UserPreferenceFactory(DjangoModelFactory):
class UserCourseTagFactory(DjangoModelFactory):
class Meta(object):
class Meta:
model = UserCourseTag
user = SubFactory(UserFactory)
@@ -33,7 +33,7 @@ class UserCourseTagFactory(DjangoModelFactory):
class UserOrgTagFactory(DjangoModelFactory):
""" Simple factory class for generating UserOrgTags """
class Meta(object):
class Meta:
model = UserOrgTag
user = SubFactory(UserFactory)

View File

@@ -1,254 +1,253 @@
# -*- coding: utf-8 -*-
"""Constants used in the test suite. """
SORTED_COUNTRIES = [
(u"AF", u"Afghanistan"),
(u"AX", u"\xc5land Islands"),
(u"AL", u"Albania"),
(u"DZ", u"Algeria"),
(u"AS", u"American Samoa"),
(u"AD", u"Andorra"),
(u"AO", u"Angola"),
(u"AI", u"Anguilla"),
(u"AQ", u"Antarctica"),
(u"AG", u"Antigua and Barbuda"),
(u"AR", u"Argentina"),
(u"AM", u"Armenia"),
(u"AW", u"Aruba"),
(u"AU", u"Australia"),
(u"AT", u"Austria"),
(u"AZ", u"Azerbaijan"),
(u"BS", u"Bahamas"),
(u"BH", u"Bahrain"),
(u"BD", u"Bangladesh"),
(u"BB", u"Barbados"),
(u"BY", u"Belarus"),
(u"BE", u"Belgium"),
(u"BZ", u"Belize"),
(u"BJ", u"Benin"),
(u"BM", u"Bermuda"),
(u"BT", u"Bhutan"),
(u"BO", u"Bolivia"),
(u"BQ", u"Bonaire, Sint Eustatius and Saba"),
(u"BA", u"Bosnia and Herzegovina"),
(u"BW", u"Botswana"),
(u"BV", u"Bouvet Island"),
(u"BR", u"Brazil"),
(u"IO", u"British Indian Ocean Territory"),
(u"BN", u"Brunei"),
(u"BG", u"Bulgaria"),
(u"BF", u"Burkina Faso"),
(u"BI", u"Burundi"),
(u"CV", u"Cabo Verde"),
(u"KH", u"Cambodia"),
(u"CM", u"Cameroon"),
(u"CA", u"Canada"),
(u"KY", u"Cayman Islands"),
(u"CF", u"Central African Republic"),
(u"TD", u"Chad"),
(u"CL", u"Chile"),
(u"CN", u"China"),
(u"CX", u"Christmas Island"),
(u"CC", u"Cocos (Keeling) Islands"),
(u"CO", u"Colombia"),
(u"KM", u"Comoros"),
(u"CG", u"Congo"),
(u"CD", u"Congo (the Democratic Republic of the)"),
(u"CK", u"Cook Islands"),
(u"CR", u"Costa Rica"),
(u"CI", u"C\xf4te d'Ivoire"),
(u"HR", u"Croatia"),
(u"CU", u"Cuba"),
(u"CW", u"Cura\xe7ao"),
(u"CY", u"Cyprus"),
(u"CZ", u"Czechia"),
(u"DK", u"Denmark"),
(u"DJ", u"Djibouti"),
(u"DM", u"Dominica"),
(u"DO", u"Dominican Republic"),
(u"EC", u"Ecuador"),
(u"EG", u"Egypt"),
(u"SV", u"El Salvador"),
(u"GQ", u"Equatorial Guinea"),
(u"ER", u"Eritrea"),
(u"EE", u"Estonia"),
(u"SZ", u"Eswatini"),
(u"ET", u"Ethiopia"),
(u"FK", u"Falkland Islands [Malvinas]"),
(u"FO", u"Faroe Islands"),
(u"FJ", u"Fiji"),
(u"FI", u"Finland"),
(u"FR", u"France"),
(u"GF", u"French Guiana"),
(u"PF", u"French Polynesia"),
(u"TF", u"French Southern Territories"),
(u"GA", u"Gabon"),
(u"GM", u"Gambia"),
(u"GE", u"Georgia"),
(u"DE", u"Germany"),
(u"GH", u"Ghana"),
(u"GI", u"Gibraltar"),
(u"GR", u"Greece"),
(u"GL", u"Greenland"),
(u"GD", u"Grenada"),
(u"GP", u"Guadeloupe"),
(u"GU", u"Guam"),
(u"GT", u"Guatemala"),
(u"GG", u"Guernsey"),
(u"GN", u"Guinea"),
(u"GW", u"Guinea-Bissau"),
(u"GY", u"Guyana"),
(u"HT", u"Haiti"),
(u"HM", u"Heard Island and McDonald Islands"),
(u"VA", u"Holy See"),
(u"HN", u"Honduras"),
(u"HK", u"Hong Kong"),
(u"HU", u"Hungary"),
(u"IS", u"Iceland"),
(u"IN", u"India"),
(u"ID", u"Indonesia"),
(u"IR", u"Iran"),
(u"IQ", u"Iraq"),
(u"IE", u"Ireland"),
(u"IM", u"Isle of Man"),
(u"IL", u"Israel"),
(u"IT", u"Italy"),
(u"JM", u"Jamaica"),
(u"JP", u"Japan"),
(u"JE", u"Jersey"),
(u"JO", u"Jordan"),
(u"KZ", u"Kazakhstan"),
(u"KE", u"Kenya"),
(u"KI", u"Kiribati"),
(u"XK", u"Kosovo"),
(u"KW", u"Kuwait"),
(u"KG", u"Kyrgyzstan"),
(u"LA", u"Laos"),
(u"LV", u"Latvia"),
(u"LB", u"Lebanon"),
(u"LS", u"Lesotho"),
(u"LR", u"Liberia"),
(u"LY", u"Libya"),
(u"LI", u"Liechtenstein"),
(u"LT", u"Lithuania"),
(u"LU", u"Luxembourg"),
(u"MO", u"Macao"),
(u"MG", u"Madagascar"),
(u"MW", u"Malawi"),
(u"MY", u"Malaysia"),
(u"MV", u"Maldives"),
(u"ML", u"Mali"),
(u"MT", u"Malta"),
(u"MH", u"Marshall Islands"),
(u"MQ", u"Martinique"),
(u"MR", u"Mauritania"),
(u"MU", u"Mauritius"),
(u"YT", u"Mayotte"),
(u"MX", u"Mexico"),
(u"FM", u"Micronesia (Federated States of)"),
(u"MD", u"Moldova"),
(u"MC", u"Monaco"),
(u"MN", u"Mongolia"),
(u"ME", u"Montenegro"),
(u"MS", u"Montserrat"),
(u"MA", u"Morocco"),
(u"MZ", u"Mozambique"),
(u"MM", u"Myanmar"),
(u"NA", u"Namibia"),
(u"NR", u"Nauru"),
(u"NP", u"Nepal"),
(u"NL", u"Netherlands"),
(u"NC", u"New Caledonia"),
(u"NZ", u"New Zealand"),
(u"NI", u"Nicaragua"),
(u"NE", u"Niger"),
(u"NG", u"Nigeria"),
(u"NU", u"Niue"),
(u"NF", u"Norfolk Island"),
(u"KP", u"North Korea"),
(u"MK", u"North Macedonia"),
(u"MP", u"Northern Mariana Islands"),
(u"NO", u"Norway"),
(u"OM", u"Oman"),
(u"PK", u"Pakistan"),
(u"PW", u"Palau"),
(u"PS", u"Palestine, State of"),
(u"PA", u"Panama"),
(u"PG", u"Papua New Guinea"),
(u"PY", u"Paraguay"),
(u"PE", u"Peru"),
(u"PH", u"Philippines"),
(u"PN", u"Pitcairn"),
(u"PL", u"Poland"),
(u"PT", u"Portugal"),
(u"PR", u"Puerto Rico"),
(u"QA", u"Qatar"),
(u"RE", u"R\xe9union"),
(u"RO", u"Romania"),
(u"RU", u"Russia"),
(u"RW", u"Rwanda"),
(u"BL", u"Saint Barth\xe9lemy"),
(u"SH", u"Saint Helena, Ascension and Tristan da Cunha"),
(u"KN", u"Saint Kitts and Nevis"),
(u"LC", u"Saint Lucia"),
(u"MF", u"Saint Martin (French part)"),
(u"PM", u"Saint Pierre and Miquelon"),
(u"VC", u"Saint Vincent and the Grenadines"),
(u"WS", u"Samoa"),
(u"SM", u"San Marino"),
(u"ST", u"Sao Tome and Principe"),
(u"SA", u"Saudi Arabia"),
(u"SN", u"Senegal"),
(u"RS", u"Serbia"),
(u"SC", u"Seychelles"),
(u"SL", u"Sierra Leone"),
(u"SG", u"Singapore"),
(u"SX", u"Sint Maarten (Dutch part)"),
(u"SK", u"Slovakia"),
(u"SI", u"Slovenia"),
(u"SB", u"Solomon Islands"),
(u"SO", u"Somalia"),
(u"ZA", u"South Africa"),
(u"GS", u"South Georgia and the South Sandwich Islands"),
(u"KR", u"South Korea"),
(u"SS", u"South Sudan"),
(u"ES", u"Spain"),
(u"LK", u"Sri Lanka"),
(u"SD", u"Sudan"),
(u"SR", u"Suriname"),
(u"SJ", u"Svalbard and Jan Mayen"),
(u"SE", u"Sweden"),
(u"CH", u"Switzerland"),
(u"SY", u"Syria"),
(u"TW", u"Taiwan"),
(u"TJ", u"Tajikistan"),
(u"TZ", u"Tanzania"),
(u"TH", u"Thailand"),
(u"TL", u"Timor-Leste"),
(u"TG", u"Togo"),
(u"TK", u"Tokelau"),
(u"TO", u"Tonga"),
(u"TT", u"Trinidad and Tobago"),
(u"TN", u"Tunisia"),
(u"TR", u"Turkey"),
(u"TM", u"Turkmenistan"),
(u"TC", u"Turks and Caicos Islands"),
(u"TV", u"Tuvalu"),
(u"UG", u"Uganda"),
(u"UA", u"Ukraine"),
(u"AE", u"United Arab Emirates"),
(u"GB", u"United Kingdom"),
(u"UM", u"United States Minor Outlying Islands"),
(u"US", u"United States of America"),
(u"UY", u"Uruguay"),
(u"UZ", u"Uzbekistan"),
(u"VU", u"Vanuatu"),
(u"VE", u"Venezuela"),
(u"VN", u"Vietnam"),
(u"VG", u"Virgin Islands (British)"),
(u"VI", u"Virgin Islands (U.S.)"),
(u"WF", u"Wallis and Futuna"),
(u"EH", u"Western Sahara"),
(u"YE", u"Yemen"),
(u"ZM", u"Zambia"),
(u"ZW", u"Zimbabwe")
("AF", "Afghanistan"),
("AX", "\xc5land Islands"),
("AL", "Albania"),
("DZ", "Algeria"),
("AS", "American Samoa"),
("AD", "Andorra"),
("AO", "Angola"),
("AI", "Anguilla"),
("AQ", "Antarctica"),
("AG", "Antigua and Barbuda"),
("AR", "Argentina"),
("AM", "Armenia"),
("AW", "Aruba"),
("AU", "Australia"),
("AT", "Austria"),
("AZ", "Azerbaijan"),
("BS", "Bahamas"),
("BH", "Bahrain"),
("BD", "Bangladesh"),
("BB", "Barbados"),
("BY", "Belarus"),
("BE", "Belgium"),
("BZ", "Belize"),
("BJ", "Benin"),
("BM", "Bermuda"),
("BT", "Bhutan"),
("BO", "Bolivia"),
("BQ", "Bonaire, Sint Eustatius and Saba"),
("BA", "Bosnia and Herzegovina"),
("BW", "Botswana"),
("BV", "Bouvet Island"),
("BR", "Brazil"),
("IO", "British Indian Ocean Territory"),
("BN", "Brunei"),
("BG", "Bulgaria"),
("BF", "Burkina Faso"),
("BI", "Burundi"),
("CV", "Cabo Verde"),
("KH", "Cambodia"),
("CM", "Cameroon"),
("CA", "Canada"),
("KY", "Cayman Islands"),
("CF", "Central African Republic"),
("TD", "Chad"),
("CL", "Chile"),
("CN", "China"),
("CX", "Christmas Island"),
("CC", "Cocos (Keeling) Islands"),
("CO", "Colombia"),
("KM", "Comoros"),
("CG", "Congo"),
("CD", "Congo (the Democratic Republic of the)"),
("CK", "Cook Islands"),
("CR", "Costa Rica"),
("CI", "C\xf4te d'Ivoire"),
("HR", "Croatia"),
("CU", "Cuba"),
("CW", "Cura\xe7ao"),
("CY", "Cyprus"),
("CZ", "Czechia"),
("DK", "Denmark"),
("DJ", "Djibouti"),
("DM", "Dominica"),
("DO", "Dominican Republic"),
("EC", "Ecuador"),
("EG", "Egypt"),
("SV", "El Salvador"),
("GQ", "Equatorial Guinea"),
("ER", "Eritrea"),
("EE", "Estonia"),
("SZ", "Eswatini"),
("ET", "Ethiopia"),
("FK", "Falkland Islands [Malvinas]"),
("FO", "Faroe Islands"),
("FJ", "Fiji"),
("FI", "Finland"),
("FR", "France"),
("GF", "French Guiana"),
("PF", "French Polynesia"),
("TF", "French Southern Territories"),
("GA", "Gabon"),
("GM", "Gambia"),
("GE", "Georgia"),
("DE", "Germany"),
("GH", "Ghana"),
("GI", "Gibraltar"),
("GR", "Greece"),
("GL", "Greenland"),
("GD", "Grenada"),
("GP", "Guadeloupe"),
("GU", "Guam"),
("GT", "Guatemala"),
("GG", "Guernsey"),
("GN", "Guinea"),
("GW", "Guinea-Bissau"),
("GY", "Guyana"),
("HT", "Haiti"),
("HM", "Heard Island and McDonald Islands"),
("VA", "Holy See"),
("HN", "Honduras"),
("HK", "Hong Kong"),
("HU", "Hungary"),
("IS", "Iceland"),
("IN", "India"),
("ID", "Indonesia"),
("IR", "Iran"),
("IQ", "Iraq"),
("IE", "Ireland"),
("IM", "Isle of Man"),
("IL", "Israel"),
("IT", "Italy"),
("JM", "Jamaica"),
("JP", "Japan"),
("JE", "Jersey"),
("JO", "Jordan"),
("KZ", "Kazakhstan"),
("KE", "Kenya"),
("KI", "Kiribati"),
("XK", "Kosovo"),
("KW", "Kuwait"),
("KG", "Kyrgyzstan"),
("LA", "Laos"),
("LV", "Latvia"),
("LB", "Lebanon"),
("LS", "Lesotho"),
("LR", "Liberia"),
("LY", "Libya"),
("LI", "Liechtenstein"),
("LT", "Lithuania"),
("LU", "Luxembourg"),
("MO", "Macao"),
("MG", "Madagascar"),
("MW", "Malawi"),
("MY", "Malaysia"),
("MV", "Maldives"),
("ML", "Mali"),
("MT", "Malta"),
("MH", "Marshall Islands"),
("MQ", "Martinique"),
("MR", "Mauritania"),
("MU", "Mauritius"),
("YT", "Mayotte"),
("MX", "Mexico"),
("FM", "Micronesia (Federated States of)"),
("MD", "Moldova"),
("MC", "Monaco"),
("MN", "Mongolia"),
("ME", "Montenegro"),
("MS", "Montserrat"),
("MA", "Morocco"),
("MZ", "Mozambique"),
("MM", "Myanmar"),
("NA", "Namibia"),
("NR", "Nauru"),
("NP", "Nepal"),
("NL", "Netherlands"),
("NC", "New Caledonia"),
("NZ", "New Zealand"),
("NI", "Nicaragua"),
("NE", "Niger"),
("NG", "Nigeria"),
("NU", "Niue"),
("NF", "Norfolk Island"),
("KP", "North Korea"),
("MK", "North Macedonia"),
("MP", "Northern Mariana Islands"),
("NO", "Norway"),
("OM", "Oman"),
("PK", "Pakistan"),
("PW", "Palau"),
("PS", "Palestine, State of"),
("PA", "Panama"),
("PG", "Papua New Guinea"),
("PY", "Paraguay"),
("PE", "Peru"),
("PH", "Philippines"),
("PN", "Pitcairn"),
("PL", "Poland"),
("PT", "Portugal"),
("PR", "Puerto Rico"),
("QA", "Qatar"),
("RE", "R\xe9union"),
("RO", "Romania"),
("RU", "Russia"),
("RW", "Rwanda"),
("BL", "Saint Barth\xe9lemy"),
("SH", "Saint Helena, Ascension and Tristan da Cunha"),
("KN", "Saint Kitts and Nevis"),
("LC", "Saint Lucia"),
("MF", "Saint Martin (French part)"),
("PM", "Saint Pierre and Miquelon"),
("VC", "Saint Vincent and the Grenadines"),
("WS", "Samoa"),
("SM", "San Marino"),
("ST", "Sao Tome and Principe"),
("SA", "Saudi Arabia"),
("SN", "Senegal"),
("RS", "Serbia"),
("SC", "Seychelles"),
("SL", "Sierra Leone"),
("SG", "Singapore"),
("SX", "Sint Maarten (Dutch part)"),
("SK", "Slovakia"),
("SI", "Slovenia"),
("SB", "Solomon Islands"),
("SO", "Somalia"),
("ZA", "South Africa"),
("GS", "South Georgia and the South Sandwich Islands"),
("KR", "South Korea"),
("SS", "South Sudan"),
("ES", "Spain"),
("LK", "Sri Lanka"),
("SD", "Sudan"),
("SR", "Suriname"),
("SJ", "Svalbard and Jan Mayen"),
("SE", "Sweden"),
("CH", "Switzerland"),
("SY", "Syria"),
("TW", "Taiwan"),
("TJ", "Tajikistan"),
("TZ", "Tanzania"),
("TH", "Thailand"),
("TL", "Timor-Leste"),
("TG", "Togo"),
("TK", "Tokelau"),
("TO", "Tonga"),
("TT", "Trinidad and Tobago"),
("TN", "Tunisia"),
("TR", "Turkey"),
("TM", "Turkmenistan"),
("TC", "Turks and Caicos Islands"),
("TV", "Tuvalu"),
("UG", "Uganda"),
("UA", "Ukraine"),
("AE", "United Arab Emirates"),
("GB", "United Kingdom"),
("UM", "United States Minor Outlying Islands"),
("US", "United States of America"),
("UY", "Uruguay"),
("UZ", "Uzbekistan"),
("VU", "Vanuatu"),
("VE", "Venezuela"),
("VN", "Vietnam"),
("VG", "Virgin Islands (British)"),
("VI", "Virgin Islands (U.S.)"),
("WF", "Wallis and Futuna"),
("EH", "Western Sahara"),
("YE", "Yemen"),
("ZM", "Zambia"),
("ZW", "Zimbabwe")
]

View File

@@ -5,13 +5,12 @@ Tests for helper functions.
import json
import re
from unittest import mock
import ddt # lint-amnesty, pylint: disable=unused-import
import mock
import pytest
from django import forms
from django.test import TestCase
from six import text_type
from ..helpers import FormDescription, InvalidFieldError, intercept_errors
@@ -57,17 +56,17 @@ class InterceptErrorsTest(TestCase):
self.maxDiff = None
exception = 'openedx.core.djangoapps.user_api.tests.test_helpers.FakeInputException'
expected_log_msg = (
u"An unexpected error occurred when calling 'intercepted_function' with arguments '()' and "
u"keyword arguments '{{'raise_error': <class '{}'>}}' "
u"from File \"{}\", line XXX, in test_logs_errors\n"
u" intercepted_function(raise_error=FakeInputException): FakeInputException()"
"An unexpected error occurred when calling 'intercepted_function' with arguments '()' and "
"keyword arguments '{{'raise_error': <class '{}'>}}' "
"from File \"{}\", line XXX, in test_logs_errors\n"
" intercepted_function(raise_error=FakeInputException): FakeInputException()"
).format(exception, __file__.rstrip('c'))
# Verify that the raised exception has the error message
try:
intercepted_function(raise_error=FakeInputException)
except FakeOutputException as ex:
actual_message = re.sub(r'line \d+', 'line XXX', text_type(ex), flags=re.MULTILINE)
actual_message = re.sub(r'line \d+', 'line XXX', str(ex), flags=re.MULTILINE)
assert actual_message == expected_log_msg
# Verify that the error logger is called
@@ -159,7 +158,7 @@ class FormDescriptionTest(TestCase):
{'default': True, 'name': 'Pakistan', 'value': 'PK'}]
class DummyRegistrationExtensionModel(object):
class DummyRegistrationExtensionModel:
"""
Dummy registration object
"""
@@ -190,8 +189,8 @@ class TestCaseForm(forms.Form):
favorite_movie = forms.CharField(
label="Fav Flick", min_length=MOVIE_MIN_LEN, max_length=MOVIE_MAX_LEN, error_messages={
"required": u"Please tell us your favorite movie.",
"invalid": u"We're pretty sure you made that movie up."
"required": "Please tell us your favorite movie.",
"invalid": "We're pretty sure you made that movie up."
}
)
favorite_editor = forms.ChoiceField(label="Favorite Editor", choices=FAVORITE_EDITOR, required=False, initial='cat')
@@ -207,7 +206,7 @@ class TestCaseForm(forms.Form):
dummy_model = DummyRegistrationExtensionModel()
return dummy_model
class Meta(object):
class Meta:
"""
Set options for fields which can't be conveyed in their definition.
"""

View File

@@ -1,10 +1,10 @@
"""Tests for user API middleware"""
from unittest.mock import Mock, patch
from django.http import HttpResponse
from django.test import TestCase
from django.test.client import RequestFactory
from mock import Mock, patch
from common.djangoapps.student.tests.factories import AnonymousUserFactory, UserFactory
@@ -17,7 +17,7 @@ class TagsMiddlewareTest(TestCase):
Test the UserTagsEventContextMiddleware
"""
def setUp(self):
super(TagsMiddlewareTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.middleware = UserTagsEventContextMiddleware()
self.user = UserFactory.create()
self.other_user = UserFactory.create()
@@ -27,7 +27,7 @@ class TagsMiddlewareTest(TestCase):
# TODO: Make it so we can use reverse. Appears to fail depending on the order in which tests are run
#self.request = RequestFactory().get(reverse('courseware', kwargs={'course_id': self.course_id}))
self.request = RequestFactory().get('/courses/{}/courseware'.format(self.course_id))
self.request = RequestFactory().get(f'/courses/{self.course_id}/courseware')
self.request.user = self.user
self.response = Mock(spec=HttpResponse)

View File

@@ -123,7 +123,7 @@ class TestUserPreferenceEvents(UserSettingsEventTestMixin, TestCase):
Mixin for verifying that user preference events are fired correctly.
"""
def setUp(self):
super(TestUserPreferenceEvents, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.table = "user_api_userpreference"
self.user = UserFactory.create()
self.TEST_KEY = "test key"

View File

@@ -4,11 +4,10 @@ Test the user api's partition extensions.
from collections import defaultdict
from unittest.mock import patch
import pytest
from django.test import TestCase
from mock import patch
from six.moves import range
from openedx.core.djangoapps.user_api.partition_schemes import RandomUserPartitionScheme, UserPartitionError
from common.djangoapps.student.tests.factories import UserFactory
@@ -16,7 +15,7 @@ from xmodule.partitions.partitions import Group, UserPartition
from xmodule.partitions.tests.test_partitions import PartitionTestCase
class MemoryCourseTagAPI(object):
class MemoryCourseTagAPI:
"""
An implementation of a user service that uses an in-memory dictionary for storage
"""
@@ -31,7 +30,7 @@ class MemoryCourseTagAPI(object):
"""Gets the value of ``key``"""
self._tags[course_id][key] = value
class BulkCourseTags(object):
class BulkCourseTags:
@classmethod
def is_prefetched(self, course_id): # lint-amnesty, pylint: disable=bad-classmethod-argument, unused-argument
return False
@@ -46,7 +45,7 @@ class TestRandomUserPartitionScheme(PartitionTestCase):
MOCK_COURSE_ID = "mock-course-id"
def setUp(self):
super(TestRandomUserPartitionScheme, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
# Patch in a memory-based user service instead of using the persistent version
course_tag_api = MemoryCourseTagAPI()
self.user_service_patcher = patch(

View File

@@ -3,11 +3,10 @@
import json # lint-amnesty, pylint: disable=unused-import
from unittest import skipUnless # lint-amnesty, pylint: disable=unused-import
from unittest import mock # lint-amnesty, pylint: disable=unused-import
import pytest
import ddt
import httpretty # lint-amnesty, pylint: disable=unused-import
import mock # lint-amnesty, pylint: disable=unused-import
import six
from django.conf import settings # lint-amnesty, pylint: disable=unused-import
from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user, unused-import
from django.core import mail # lint-amnesty, pylint: disable=unused-import
@@ -17,8 +16,6 @@ from django.test.utils import override_settings
from django.urls import reverse
from opaque_keys.edx.keys import CourseKey
from pytz import UTC, common_timezones_set # lint-amnesty, pylint: disable=unused-import
from six import text_type
from six.moves import range
from social_django.models import Partial, UserSocialAuth # lint-amnesty, pylint: disable=unused-import
from openedx.core.djangoapps.django_comment_common import models
@@ -90,9 +87,8 @@ class UserAPITestCase(ApiTestCase):
def assertUserIsValid(self, user):
"""Assert that the given user result is valid"""
six.assertCountEqual(self, list(user.keys()), ["email", "id", "name", "username", "preferences", "url"])
six.assertCountEqual(
self,
self.assertCountEqual(list(user.keys()), ["email", "id", "name", "username", "preferences", "url"])
self.assertCountEqual(
list(user["preferences"].items()),
[(pref.key, pref.value) for pref in self.prefs if pref.user.id == user["id"]] # lint-amnesty, pylint: disable=no-member
)
@@ -102,7 +98,7 @@ class UserAPITestCase(ApiTestCase):
"""
Assert that the given preference is acknowledged by the system
"""
six.assertCountEqual(self, list(pref.keys()), ["user", "key", "value", "url"])
self.assertCountEqual(list(pref.keys()), ["user", "key", "value", "url"])
self.assertSelfReferential(pref)
self.assertUserIsValid(pref["user"])
@@ -124,7 +120,7 @@ class EmptyUserTestCase(UserAPITestCase):
class EmptyRoleTestCase(UserAPITestCase):
"""Test that the endpoint supports empty result sets"""
course_id = CourseKey.from_string("org/course/run")
LIST_URI = ROLE_LIST_URI + "?course_id=" + six.text_type(course_id)
LIST_URI = ROLE_LIST_URI + "?course_id=" + str(course_id)
def test_get_list_empty(self):
"""Test that the endpoint properly returns empty result sets"""
@@ -140,11 +136,11 @@ class UserApiTestCase(UserAPITestCase):
Generalized test case class for specific implementations below
"""
def setUp(self):
super(UserApiTestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.users = [
UserFactory.create(
email="test{0}@test.org".format(i),
profile__name=u"Test {0}".format(i)
email=f"test{i}@test.org",
profile__name=f"Test {i}"
)
for i in range(5)
]
@@ -161,10 +157,10 @@ class RoleTestCase(UserApiTestCase):
Test cases covering Role-related views and their behaviors
"""
course_id = CourseKey.from_string("org/course/run")
LIST_URI = ROLE_LIST_URI + "?course_id=" + six.text_type(course_id)
LIST_URI = ROLE_LIST_URI + "?course_id=" + str(course_id)
def setUp(self):
super(RoleTestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
(role, _) = models.Role.objects.get_or_create(
name=models.FORUM_ROLE_MODERATOR,
course_id=self.course_id
@@ -222,7 +218,7 @@ class RoleTestCase(UserApiTestCase):
def test_get_list_pagination(self):
first_page = self.get_json(self.LIST_URI, data={
"page_size": 3,
"course_id": text_type(self.course_id),
"course_id": str(self.course_id),
})
assert first_page['count'] == 5
first_page_next_uri = first_page["next"]
@@ -253,7 +249,7 @@ class UserViewSetTest(UserApiTestCase):
LIST_URI = USER_LIST_URI
def setUp(self):
super(UserViewSetTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.detail_uri = self.get_uri_for_user(self.users[0])
# List view tests
@@ -362,7 +358,7 @@ class UserPreferenceViewSetTest(CacheIsolationTestCase, UserApiTestCase):
ENABLED_CACHES = ['default']
def setUp(self):
super(UserPreferenceViewSetTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.detail_uri = self.get_uri_for_pref(self.prefs[0])
# List view tests
@@ -555,30 +551,30 @@ class UpdateEmailOptInTestCase(UserAPITestCase, SharedModuleStoreTestCase):
@classmethod
def setUpClass(cls):
super(UpdateEmailOptInTestCase, cls).setUpClass()
super().setUpClass()
cls.course = CourseFactory.create()
cls.url = reverse("preferences_email_opt_in")
def setUp(self):
""" Create a course and user, then log in. """
super(UpdateEmailOptInTestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.user = UserFactory.create(username=self.USERNAME, email=self.EMAIL, password=self.PASSWORD)
self.client.login(username=self.USERNAME, password=self.PASSWORD)
@ddt.data(
(u"True", u"True"),
(u"true", u"True"),
(u"TrUe", u"True"),
(u"Banana", u"False"),
(u"strawberries", u"False"),
(u"False", u"False"),
("True", "True"),
("true", "True"),
("TrUe", "True"),
("Banana", "False"),
("strawberries", "False"),
("False", "False"),
)
@ddt.unpack
def test_update_email_opt_in(self, opt, result):
"""Tests the email opt in preference"""
# Register, which should trigger an activation email
response = self.client.post(self.url, {
"course_id": six.text_type(self.course.id),
"course_id": str(self.course.id),
"email_opt_in": opt
})
self.assertHttpOK(response)
@@ -597,9 +593,9 @@ class UpdateEmailOptInTestCase(UserAPITestCase, SharedModuleStoreTestCase):
"""Tests the email opt in preference"""
params = {}
if use_course_id:
params["course_id"] = six.text_type(self.course.id)
params["course_id"] = str(self.course.id)
if use_opt_in:
params["email_opt_in"] = u"True"
params["email_opt_in"] = "True"
response = self.client.post(self.url, params)
self.assertHttpBadRequest(response)
@@ -610,14 +606,14 @@ class UpdateEmailOptInTestCase(UserAPITestCase, SharedModuleStoreTestCase):
self.user.save()
# Register, which should trigger an activation email
response = self.client.post(self.url, {
"course_id": six.text_type(self.course.id),
"email_opt_in": u"True"
"course_id": str(self.course.id),
"email_opt_in": "True"
})
self.assertHttpOK(response)
preference = UserOrgTag.objects.get(
user=self.user, org=self.course.id.org, key="email-optin"
)
assert preference.value == u'True'
assert preference.value == 'True'
def test_update_email_opt_in_anonymous_user(self):
"""
@@ -626,8 +622,8 @@ class UpdateEmailOptInTestCase(UserAPITestCase, SharedModuleStoreTestCase):
"""
self.client.logout()
response = self.client.post(self.url, {
"course_id": six.text_type(self.course.id),
"email_opt_in": u"True"
"course_id": str(self.course.id),
"email_opt_in": "True"
})
assert response.status_code == 403
@@ -638,7 +634,7 @@ class UpdateEmailOptInTestCase(UserAPITestCase, SharedModuleStoreTestCase):
"""
response = self.client.post(self.url, {
"course_id": 'invalid',
"email_opt_in": u"True"
"email_opt_in": "True"
})
self.assertHttpBadRequest(response)
with pytest.raises(UserOrgTag.DoesNotExist):

View File

@@ -89,17 +89,17 @@ urlpatterns = [
name='accounts_detail_api'
),
url(
r'^v1/accounts/{}$'.format(settings.USERNAME_PATTERN),
fr'^v1/accounts/{settings.USERNAME_PATTERN}$',
ACCOUNT_DETAIL,
name='accounts_api'
),
url(
r'^v1/accounts/{}/image$'.format(settings.USERNAME_PATTERN),
fr'^v1/accounts/{settings.USERNAME_PATTERN}/image$',
ProfileImageView.as_view(),
name='accounts_profile_image_api'
),
url(
r'^v1/accounts/{}/deactivate/$'.format(settings.USERNAME_PATTERN),
fr'^v1/accounts/{settings.USERNAME_PATTERN}/deactivate/$',
AccountDeactivationView.as_view(),
name='accounts_deactivation'
),
@@ -109,17 +109,17 @@ urlpatterns = [
name='deactivate_logout'
),
url(
r'^v1/accounts/{}/verification_status/$'.format(settings.USERNAME_PATTERN),
fr'^v1/accounts/{settings.USERNAME_PATTERN}/verification_status/$',
IDVerificationStatusView.as_view(),
name='verification_status'
),
url(
r'^v1/accounts/{}/verifications/$'.format(settings.USERNAME_PATTERN),
fr'^v1/accounts/{settings.USERNAME_PATTERN}/verifications/$',
IDVerificationStatusDetailsView.as_view(),
name='verification_details'
),
url(
r'^v1/accounts/{}/retirement_status/$'.format(settings.USERNAME_PATTERN),
fr'^v1/accounts/{settings.USERNAME_PATTERN}/retirement_status/$',
RETIREMENT_RETRIEVE,
name='accounts_retirement_retrieve'
),
@@ -169,12 +169,12 @@ urlpatterns = [
name='username_replacement'
),
url(
r'^v1/preferences/{}$'.format(settings.USERNAME_PATTERN),
fr'^v1/preferences/{settings.USERNAME_PATTERN}$',
PreferencesView.as_view(),
name='preferences_api'
),
url(
r'^v1/preferences/{}/(?P<preference_key>[a-zA-Z0-9_]+)$'.format(settings.USERNAME_PATTERN),
fr'^v1/preferences/{settings.USERNAME_PATTERN}/(?P<preference_key>[a-zA-Z0-9_]+)$',
PreferencesDetailView.as_view(),
name='preferences_detail_api'
),
@@ -183,7 +183,7 @@ urlpatterns = [
# Moved from user_api/legacy_urls.py
url(
r'^v1/preferences/(?P<pref_key>{})/users/$'.format(UserPreference.KEY_REGEX),
fr'^v1/preferences/(?P<pref_key>{UserPreference.KEY_REGEX})/users/$',
user_api_views.PreferenceUsersListView.as_view()
),

View File

@@ -17,7 +17,6 @@ from rest_framework import authentication, generics, status, viewsets
from rest_framework.exceptions import ParseError
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView
from six import text_type # lint-amnesty, pylint: disable=unused-import
from openedx.core.djangoapps.django_comment_common.models import Role
from openedx.core.djangoapps.user_api import accounts # lint-amnesty, pylint: disable=unused-import
@@ -128,7 +127,7 @@ class UpdateEmailOptInPreference(APIView):
except InvalidKeyError:
return HttpResponse(
status=400,
content=u"No course '{course_id}' found".format(course_id=course_id),
content=f"No course '{course_id}' found",
content_type="text/plain"
)
# Only check for true. All other values are False.