diff --git a/rest_framework/compat.py b/rest_framework/compat.py
index 4bae7729f..8240f5489 100644
--- a/rest_framework/compat.py
+++ b/rest_framework/compat.py
@@ -140,7 +140,7 @@ if markdown is not None and pygments is not None:
code = m.group(2).replace('\t', ' ')
code = pygments.highlight(code, lexer, self.formatter)
code = code.replace('\n\n', '\n \n').replace('\n', '
').replace('\\@', '@')
- return '\n\n%s\n\n' % code
+ return f'\n\n{code}\n\n'
ret = self.pattern.sub(repl, "\n".join(lines))
return ret.split("\n")
diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py
index 3b572c09e..872b0ee83 100644
--- a/rest_framework/decorators.py
+++ b/rest_framework/decorators.py
@@ -41,7 +41,7 @@ def api_view(http_method_names=None):
# api_view applied with eg. string instead of list of strings
assert isinstance(http_method_names, (list, tuple)), \
- '@api_view expected a list of strings, received %s' % type(http_method_names).__name__
+ f'@api_view expected a list of strings, received {type(http_method_names).__name__}'
allowed_methods = set(http_method_names) | {'options'}
WrappedAPIView.http_method_names = [method.lower() for method in allowed_methods]
@@ -199,7 +199,7 @@ class MethodMapper(dict):
def _map(self, method, func):
assert method not in self, (
- "Method '%s' has already been mapped to '.%s'." % (method, self[method]))
+ f"Method '{method}' has already been mapped to '.{self[method]}'.")
assert func.__name__ != self.action.__name__, (
"Method mapping does not behave like the property decorator. You "
"cannot use the same method name for each mapping declaration.")
diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py
index fee8f024f..5dd6b35fe 100644
--- a/rest_framework/exceptions.py
+++ b/rest_framework/exceptions.py
@@ -84,10 +84,8 @@ class ErrorDetail(str):
return not self.__eq__(other)
def __repr__(self):
- return 'ErrorDetail(string=%r, code=%r)' % (
- str(self),
- self.code,
- )
+ return f"ErrorDetail(string='{str(self)}', code='{self.code}')"
+
def __hash__(self):
return hash(str(self))
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index d7e7816ce..6c50c6662 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -280,7 +280,7 @@ class CreateOnlyDefault:
return self.default
def __repr__(self):
- return '%s(%s)' % (self.__class__.__name__, repr(self.default))
+ return f'{self.__class__.__name__}({repr(self.default)})'
class CurrentUserDefault:
@@ -290,7 +290,7 @@ class CurrentUserDefault:
return serializer_field.context['request'].user
def __repr__(self):
- return '%s()' % self.__class__.__name__
+ return f'{self.__class__.__name__}()'
class SkipField(Exception):
diff --git a/rest_framework/filters.py b/rest_framework/filters.py
index 1ffd9edc0..f83596bbf 100644
--- a/rest_framework/filters.py
+++ b/rest_framework/filters.py
@@ -236,9 +236,9 @@ class OrderingFilter(BaseFilterBackend):
(field.source.replace('.', '__') or field_name, field.label)
for field_name, field in serializer_class(context=context).fields.items()
if (
- not getattr(field, 'write_only', False) and
- not field.source == '*' and
- field.source not in model_property_names
+ not getattr(field, 'write_only', False) and
+ not field.source == '*' and
+ field.source not in model_property_names
)
]
@@ -294,9 +294,9 @@ class OrderingFilter(BaseFilterBackend):
'param': self.ordering_param,
}
for key, label in self.get_valid_fields(queryset, view, context):
- options.append((key, '%s - %s' % (label, _('ascending'))))
- options.append(('-' + key, '%s - %s' % (label, _('descending'))))
- context['options'] = options
+ options.append((key, f'{label} - {_("ascending")}'))
+ options.append(('-' + key, f'{label} - {_("descending")}'))
+ context['options'] = options
return context
def to_html(self, request, queryset, view):
diff --git a/rest_framework/generics.py b/rest_framework/generics.py
index 55cfafda4..55ce2ab25 100644
--- a/rest_framework/generics.py
+++ b/rest_framework/generics.py
@@ -61,9 +61,9 @@ class GenericAPIView(views.APIView):
(Eg. return a list of items that is specific to the user)
"""
assert self.queryset is not None, (
- "'%s' should either include a `queryset` attribute, "
- "or override the `get_queryset()` method."
- % self.__class__.__name__
+ f"'{self.__class__.__name__}' should either include a `queryset` attribute, "
+ f"or override the `get_queryset()` method."
+
)
queryset = self.queryset
@@ -120,9 +120,8 @@ class GenericAPIView(views.APIView):
(Eg. admins get full serialization, others get basic serialization)
"""
assert self.serializer_class is not None, (
- "'%s' should either include a `serializer_class` attribute, "
+ f"'{self.__class__.__name__}' should either include a `serializer_class` attribute, "
"or override the `get_serializer_class()` method."
- % self.__class__.__name__
)
return self.serializer_class
diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py
index fc4eb1428..bb0d03efe 100644
--- a/rest_framework/parsers.py
+++ b/rest_framework/parsers.py
@@ -64,7 +64,7 @@ class JSONParser(BaseParser):
parse_constant = json.strict_constant if self.strict else None
return json.load(decoded_stream, parse_constant=parse_constant)
except ValueError as exc:
- raise ParseError('JSON parse error - %s' % str(exc))
+ raise ParseError(f'JSON parse error - {str(exc)}')
class FormParser(BaseParser):
@@ -109,7 +109,7 @@ class MultiPartParser(BaseParser):
data, files = parser.parse()
return DataAndFiles(data, files)
except MultiPartParserError as exc:
- raise ParseError('Multipart form parse error - %s' % str(exc))
+ raise ParseError(f'Multipart form parse error - {str(exc)}')
class FileUploadParser(BaseParser):
diff --git a/rest_framework/relations.py b/rest_framework/relations.py
index c98700784..b14f5656e 100644
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -46,6 +46,7 @@ class Hyperlink(str):
We use this for hyperlinked URLs that may render as a named link
in some contexts, or render as a plain URL in others.
"""
+
def __new__(cls, url, obj):
ret = super().__new__(cls, url)
ret.obj = obj
@@ -70,11 +71,12 @@ class PKOnlyObject:
instance, but still want to return an object with a .pk attribute,
in order to keep the same interface as a regular model instance.
"""
+
def __init__(self, pk):
self.pk = pk
def __str__(self):
- return "%s" % self.pk
+ return f"{self.pk}"
# We assume that 'validators' are intended for the child serializer,
@@ -376,9 +378,9 @@ class HyperlinkedRelatedField(RelatedField):
def to_representation(self, value):
assert 'request' in self.context, (
- "`%s` requires the request in the serializer"
+ f"`{self.__class__.__name__}` requires the request in the serializer"
" context. Add `context={'request': request}` when instantiating "
- "the serializer." % self.__class__.__name__
+ "the serializer."
)
request = self.context['request']
@@ -409,9 +411,9 @@ class HyperlinkedRelatedField(RelatedField):
if value in ('', None):
value_string = {'': 'the empty string', None: 'None'}[value]
msg += (
- " WARNING: The value of the field on the model instance "
- "was %s, which may be why it didn't match any "
- "entries in your URL conf." % value_string
+ " WARNING: The value of the field on the model instance "
+ "was %s, which may be why it didn't match any "
+ "entries in your URL conf." % value_string
)
raise ImproperlyConfigured(msg % self.view_name)
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
index 8824fa660..a2ac708d6 100644
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -197,7 +197,7 @@ class TemplateHTMLRenderer(BaseRenderer):
return self.resolve_template(template_names)
except Exception:
# Fall back to using eg '404 Not Found'
- body = '%d %s' % (response.status_code, response.status_text.title())
+ body = f'{response.status_code} {response.status_text.title()}'
template = engines['django'].from_string(body)
return template
@@ -414,9 +414,9 @@ class BrowsableAPIRenderer(BaseRenderer):
render_style = getattr(renderer, 'render_style', 'text')
assert render_style in ['text', 'binary'], 'Expected .render_style ' \
- '"text" or "binary", but got "%s"' % render_style
+ f'"text" or "binary", but got "{render_style}"'
if render_style == 'binary':
- return '[%d bytes of binary content]' % len(content)
+ return f'[{len(content)} bytes of binary content]'
return content.decode('utf-8') if isinstance(content, bytes) else content
@@ -660,9 +660,9 @@ class BrowsableAPIRenderer(BaseRenderer):
response_headers = OrderedDict(sorted(response.items()))
renderer_content_type = ''
if renderer:
- renderer_content_type = '%s' % renderer.media_type
+ renderer_content_type = f'{renderer.media_type}'
if renderer.charset:
- renderer_content_type += ' ;%s' % renderer.charset
+ renderer_content_type += f' ;{renderer.charset}'
response_headers['Content-Type'] = renderer_content_type
if getattr(view, 'paginator', None) and view.paginator.display_page_controls:
diff --git a/rest_framework/request.py b/rest_framework/request.py
index 17ceadb08..324188396 100644
--- a/rest_framework/request.py
+++ b/rest_framework/request.py
@@ -154,7 +154,7 @@ class Request:
assert isinstance(request, HttpRequest), (
'The `request` argument must be an instance of '
'`django.http.HttpRequest`, not `{}.{}`.'
- .format(request.__class__.__module__, request.__class__.__name__)
+ .format(request.__class__.__module__, request.__class__.__name__)
)
self._request = request
@@ -180,11 +180,8 @@ class Request:
self.authenticators = (forced_auth,)
def __repr__(self):
- return '<%s.%s: %s %r>' % (
- self.__class__.__module__,
- self.__class__.__name__,
- self.method,
- self.get_full_path())
+ return f'<{self.__class__.__module__}.{self.__class__.__name__}: ' \
+ f"{self.method} '{self.get_full_path()}'>"
def _default_negotiator(self):
return api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS()
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 389680517..10cd79227 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -1105,20 +1105,17 @@ class ModelSerializer(Serializer):
if fields and fields != ALL_FIELDS and not isinstance(fields, (list, tuple)):
raise TypeError(
'The `fields` option must be a list or tuple or "__all__". '
- 'Got %s.' % type(fields).__name__
+ f'Got {type(fields).__name__}.'
)
if exclude and not isinstance(exclude, (list, tuple)):
raise TypeError(
- 'The `exclude` option must be a list or tuple. Got %s.' %
- type(exclude).__name__
+ f'The `exclude` option must be a list or tuple. Got {type(exclude).__name__}.'
)
assert not (fields and exclude), (
- "Cannot set both 'fields' and 'exclude' options on "
- "serializer {serializer_class}.".format(
- serializer_class=self.__class__.__name__
- )
+ f"Cannot set both 'fields' and 'exclude' options on "
+ f"serializer {self.__class__.__name__}."
)
assert not (fields is None and exclude is None), (
diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py
index e262b886b..9a7010735 100644
--- a/rest_framework/throttling.py
+++ b/rest_framework/throttling.py
@@ -91,7 +91,7 @@ class SimpleRateThrottle(BaseThrottle):
try:
return self.THROTTLE_RATES[self.scope]
except KeyError:
- msg = "No default throttle rate set for '%s' scope" % self.scope
+ msg = f"No default throttle rate set for '{self.scope}' scope"
raise ImproperlyConfigured(msg)
def parse_rate(self, rate):
diff --git a/rest_framework/urlpatterns.py b/rest_framework/urlpatterns.py
index bed5708eb..62df08c8b 100644
--- a/rest_framework/urlpatterns.py
+++ b/rest_framework/urlpatterns.py
@@ -9,7 +9,7 @@ def _get_format_path_converter(suffix_kwarg, allowed):
if len(allowed) == 1:
allowed_pattern = allowed[0]
else:
- allowed_pattern = '(?:%s)' % '|'.join(allowed)
+ allowed_pattern = f'(?:{"|".join(allowed)})'
suffix_pattern = r"\.%s/?" % allowed_pattern
else:
suffix_pattern = r"\.[a-z0-9]+/?"
@@ -99,7 +99,7 @@ def format_suffix_patterns(urlpatterns, suffix_required=False, allowed=None):
if len(allowed) == 1:
allowed_pattern = allowed[0]
else:
- allowed_pattern = '(%s)' % '|'.join(allowed)
+ allowed_pattern = f'({"|".join(allowed)})'
suffix_pattern = r'\.(?P<%s>%s)/?$' % (suffix_kwarg, allowed_pattern)
else:
suffix_pattern = r'\.(?P<%s>[a-z0-9]+)/?$' % suffix_kwarg
@@ -107,6 +107,6 @@ def format_suffix_patterns(urlpatterns, suffix_required=False, allowed=None):
converter_name, suffix_converter = _get_format_path_converter(suffix_kwarg, allowed)
register_converter(suffix_converter, converter_name)
- suffix_route = '<%s:%s>' % (converter_name, suffix_kwarg)
+ suffix_route = f'<{converter_name}:{suffix_kwarg}>'
return apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required, suffix_route)
diff --git a/rest_framework/validators.py b/rest_framework/validators.py
index a5cb75a84..8514c8441 100644
--- a/rest_framework/validators.py
+++ b/rest_framework/validators.py
@@ -48,7 +48,7 @@ class UniqueValidator:
"""
Filter the queryset to all instances matching the given attribute.
"""
- filter_kwargs = {'%s__%s' % (field_name, self.lookup): value}
+ filter_kwargs = {f'{field_name}__{self.lookup}': value}
return qs_filter(queryset, **filter_kwargs)
def exclude_current_instance(self, queryset, instance):
@@ -74,10 +74,7 @@ class UniqueValidator:
raise ValidationError(self.message, code='unique')
def __repr__(self):
- return '<%s(queryset=%s)>' % (
- self.__class__.__name__,
- smart_repr(self.queryset)
- )
+ return f'<{self.__class__.__name__}(queryset={smart_repr(self.queryset)})>'
class UniqueTogetherValidator:
@@ -160,11 +157,7 @@ class UniqueTogetherValidator:
raise ValidationError(message, code='unique')
def __repr__(self):
- return '<%s(queryset=%s, fields=%s)>' % (
- self.__class__.__name__,
- smart_repr(self.queryset),
- smart_repr(self.fields)
- )
+ return f'<{self.__class__.__name__}(queryset={smart_repr(self.queryset)}, fields={smart_repr(self.fields)})>'
class ProhibitSurrogateCharactersValidator:
@@ -231,12 +224,8 @@ class BaseUniqueForValidator:
}, code='unique')
def __repr__(self):
- return '<%s(queryset=%s, field=%s, date_field=%s)>' % (
- self.__class__.__name__,
- smart_repr(self.queryset),
- smart_repr(self.field),
- smart_repr(self.date_field)
- )
+ return f'<{self.__class__.__name__}(queryset={smart_repr(self.queryset)}, ' \
+ f'field={smart_repr(self.field)}, date_field={smart_repr(self.date_field)})>'
class UniqueForDateValidator(BaseUniqueForValidator):
@@ -246,11 +235,10 @@ class UniqueForDateValidator(BaseUniqueForValidator):
value = attrs[self.field]
date = attrs[self.date_field]
- filter_kwargs = {}
- filter_kwargs[field_name] = value
- filter_kwargs['%s__day' % date_field_name] = date.day
- filter_kwargs['%s__month' % date_field_name] = date.month
- filter_kwargs['%s__year' % date_field_name] = date.year
+ filter_kwargs = {field_name: value,
+ f'{date_field_name}__day': date.day,
+ f'{date_field_name}__month': date.month,
+ f'{date_field_name}__year': date.year}
return qs_filter(queryset, **filter_kwargs)
@@ -263,7 +251,7 @@ class UniqueForMonthValidator(BaseUniqueForValidator):
filter_kwargs = {}
filter_kwargs[field_name] = value
- filter_kwargs['%s__month' % date_field_name] = date.month
+ filter_kwargs[f'{date_field_name}__month'] = date.month
return qs_filter(queryset, **filter_kwargs)
@@ -274,7 +262,5 @@ class UniqueForYearValidator(BaseUniqueForValidator):
value = attrs[self.field]
date = attrs[self.date_field]
- filter_kwargs = {}
- filter_kwargs[field_name] = value
- filter_kwargs['%s__year' % date_field_name] = date.year
+ filter_kwargs = {field_name: value, f'{date_field_name}__year': date.year}
return qs_filter(queryset, **filter_kwargs)
diff --git a/rest_framework/viewsets.py b/rest_framework/viewsets.py
index 5a1f8acf5..d69d2157c 100644
--- a/rest_framework/viewsets.py
+++ b/rest_framework/viewsets.py
@@ -197,7 +197,7 @@ class ViewSetMixin:
for action in actions:
try:
- url_name = '%s-%s' % (self.basename, action.url_name)
+ url_name = f'{self.basename}-{action.url_name}'
url = reverse(url_name, self.args, self.kwargs, request=self.request)
view = self.__class__(**action.kwargs)
action_urls[view.get_view_name()] = url
diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py
index 9516bfec9..472e27e5f 100644
--- a/tests/test_exceptions.py
+++ b/tests/test_exceptions.py
@@ -70,9 +70,9 @@ class ErrorDetailTests(TestCase):
def test_repr(self):
assert repr(ErrorDetail('msg1')) == \
- 'ErrorDetail(string={!r}, code=None)'.format('msg1')
+ f"ErrorDetail(string='msg1', code='None')"
assert repr(ErrorDetail('msg1', 'code')) == \
- 'ErrorDetail(string={!r}, code={!r})'.format('msg1', 'code')
+ f"ErrorDetail(string='msg1', code='code')"
def test_str(self):
assert str(ErrorDetail('msg1')) == 'msg1'
diff --git a/tests/test_filters.py b/tests/test_filters.py
index 37ae4c7cf..1fa0bd129 100644
--- a/tests/test_filters.py
+++ b/tests/test_filters.py
@@ -208,7 +208,7 @@ class SearchFilterTests(TestCase):
def as_sql(self, compiler, connection):
sql, params = compiler.compile(self.lhs)
- return "trim(%s, 'a')" % sql, params
+ return f"trim({sql}, 'a')", params
with register_lookup(CharField, TrimA):
# Search including `a`
diff --git a/tests/utils.py b/tests/utils.py
index 06e5b9abe..122a0f6c4 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -13,7 +13,7 @@ class MockObject:
'%s=%s' % (key, value)
for key, value in sorted(self._kwargs.items())
])
- return '' % kwargs_str
+ return f''
class MockQueryset: