diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index ae01bf9a..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,26 +0,0 @@
-language: python
-python:
- - "3.8"
- - "3.9"
- - "3.10"
- - "3.11"
-
-install: pip install tox-travis
-script: tox
-
-cache: pip
-
-matrix:
- fast_finish: true
- include:
- - dist: jammy
- python: "3.11"
-
-notifications:
- webhooks:
- urls:
- - https://webhooks.gitter.im/e/09bd4c7cc8961aff307f
- on_success: change # options: [always|never|change] default: always
- on_failure: always # options: [always|never|change] default: always
- on_start: never # options: [always|never|change] default: always
-
diff --git a/README.md b/README.md
index 59c5880c..df28aa0c 100644
--- a/README.md
+++ b/README.md
@@ -38,10 +38,10 @@ It is not needed to add this library in your Django project's `settings.py` file
The desired URL signatures are:
```
-/domain/ <- Domains list
-/domain/{pk}/ <- One domain, from {pk}
-/domain/{domain_pk}/nameservers/ <- Nameservers of domain from {domain_pk}
-/domain/{domain_pk}/nameservers/{pk} <- Specific nameserver from {pk}, of domain from {domain_pk}
+/domains/ <- Domains list
+/domains/{pk}/ <- One domain, from {pk}
+/domains/{domain_pk}/nameservers/ <- Nameservers of domain from {domain_pk}
+/domains/{domain_pk}/nameservers/{pk} <- Specific nameserver from {pk}, of domain from {domain_pk}
```
How to do it (example):
diff --git a/README.rst b/README.rst
deleted file mode 100644
index 2f074e97..00000000
--- a/README.rst
+++ /dev/null
@@ -1,19 +0,0 @@
-drf-nested-routers
-======================================
-
-|build-status-image| |pypi-version|
-
-Overview
---------
-
-Nested resources for the Django Rest Framework
-
-Documentation
--------------
-
-Please see the README on the Github repo page: ``_
-
-.. |build-status-image| image:: https://secure.travis-ci.org/alanjds/drf-nested-routers.svg?branch=master
- :target: http://travis-ci.org/alanjds/drf-nested-routers?branch=master
-.. |pypi-version| image:: https://img.shields.io/pypi/v/drf-nested-routers.svg
- :target: https://pypi.python.org/pypi/drf-nested-routers
diff --git a/docs/index.md b/docs/index.md
index 25cbbbb7..290f8875 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,6 +1,6 @@
-
-
+
+
diff --git a/requirements-tox.txt b/requirements-tox.txt
index 913f4d8e..07e9b0c3 100644
--- a/requirements-tox.txt
+++ b/requirements-tox.txt
@@ -1,13 +1,14 @@
# Test requirements
-pytest==7.4.2
-pytest-cov==4.1.0
-pytest-django==4.5.2
+
+pytest==8.2.0
+pytest-cov==5.0.0
+pytest-django==4.8.0
flake8==7.0.0
ipdb==0.13.13
-pytz==2023.3
+pytz==2024.1
# wheel for PyPI installs
-wheel==0.42.0
+wheel==0.43.0
# MkDocs for documentation previews/deploys
mkdocs==1.5.2
diff --git a/rest_framework_nested/relations.py b/rest_framework_nested/relations.py
index 2be920fc..3d684f00 100644
--- a/rest_framework_nested/relations.py
+++ b/rest_framework_nested/relations.py
@@ -4,19 +4,12 @@
These fields allow you to specify the style that should be used to represent
model relationships with hyperlinks.
"""
-from __future__ import unicode_literals
-from functools import reduce # import reduce from functools for compatibility with python 3
+from functools import reduce
import rest_framework.relations
from rest_framework.relations import ObjectDoesNotExist, ObjectValueError, ObjectTypeError
from rest_framework.exceptions import ValidationError
-# fix for basestring
-try:
- basestring
-except NameError:
- basestring = str
-
class NestedHyperlinkedRelatedField(rest_framework.relations.HyperlinkedRelatedField):
lookup_field = 'pk'
@@ -26,7 +19,7 @@ class NestedHyperlinkedRelatedField(rest_framework.relations.HyperlinkedRelatedF
def __init__(self, *args, **kwargs):
self.parent_lookup_kwargs = kwargs.pop('parent_lookup_kwargs', self.parent_lookup_kwargs)
- super(NestedHyperlinkedRelatedField, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
def get_url(self, obj, view_name, request, format):
"""
@@ -103,4 +96,4 @@ def __init__(self, view_name=None, **kwargs):
assert view_name is not None, 'The `view_name` argument is required.'
kwargs['read_only'] = True
kwargs['source'] = '*'
- super(NestedHyperlinkedIdentityField, self).__init__(view_name=view_name, **kwargs)
+ super().__init__(view_name=view_name, **kwargs)
diff --git a/rest_framework_nested/routers.py b/rest_framework_nested/routers.py
index bcac8b50..106eb829 100644
--- a/rest_framework_nested/routers.py
+++ b/rest_framework_nested/routers.py
@@ -25,7 +25,6 @@
urlpatterns = router.urls
"""
-from __future__ import unicode_literals
import sys
import re
from rest_framework.routers import SimpleRouter, DefaultRouter # noqa: F401
@@ -37,7 +36,7 @@
IDENTIFIER_REGEX = re.compile(r"^[^\d\W]\w*$", re.UNICODE)
-class LookupMixin(object):
+class LookupMixin:
"""
Deprecated.
@@ -45,14 +44,14 @@ class LookupMixin(object):
"""
-class NestedMixin(object):
+class NestedMixin:
def __init__(self, parent_router, parent_prefix, *args, **kwargs):
self.parent_router = parent_router
self.parent_prefix = parent_prefix
self.nest_count = getattr(parent_router, 'nest_count', 0) + 1
- self.nest_prefix = kwargs.pop('lookup', 'nested_%i' % self.nest_count) + '_'
+ self.nest_prefix = kwargs.pop('lookup', f'nested_{self.nest_count}') + '_'
- super(NestedMixin, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
if 'trailing_slash' not in kwargs:
# Inherit trailing_slash only when not specified explicitly.
@@ -84,10 +83,7 @@ def __init__(self, parent_router, parent_prefix, *args, **kwargs):
nested_routes = []
parent_lookup_regex = parent_router.get_lookup_regex(parent_viewset, self.nest_prefix)
- self.parent_regex = '{parent_prefix}/{parent_lookup_regex}/'.format(
- parent_prefix=parent_prefix,
- parent_lookup_regex=parent_lookup_regex
- )
+ self.parent_regex = f'{parent_prefix}/{parent_lookup_regex}/'
# If there is no parent prefix, the first part of the url is probably
# controlled by the project's urls.py and the router is in an app,
# so a slash in the beginning will (A) cause Django to give warnings
@@ -111,7 +107,7 @@ def __init__(self, parent_router, parent_prefix, *args, **kwargs):
def check_valid_name(self, value):
if IDENTIFIER_REGEX.match(value) is None:
- raise ValueError("lookup argument '{}' needs to be valid python identifier".format(value))
+ raise ValueError(f"lookup argument '{value}' needs to be valid python identifier")
class NestedSimpleRouter(NestedMixin, SimpleRouter):
diff --git a/rest_framework_nested/serializers.py b/rest_framework_nested/serializers.py
index 38c50a80..1a315a2d 100644
--- a/rest_framework_nested/serializers.py
+++ b/rest_framework_nested/serializers.py
@@ -27,10 +27,10 @@ class NestedHyperlinkedModelSerializer(rest_framework.serializers.HyperlinkedMod
def __init__(self, *args, **kwargs):
self.parent_lookup_kwargs = kwargs.pop('parent_lookup_kwargs', self.parent_lookup_kwargs)
- super(NestedHyperlinkedModelSerializer, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
def build_url_field(self, field_name, model_class):
- field_class, field_kwargs = super(NestedHyperlinkedModelSerializer, self).build_url_field(
+ field_class, field_kwargs = super().build_url_field(
field_name,
model_class
)
diff --git a/rest_framework_nested/viewsets.py b/rest_framework_nested/viewsets.py
index 9d3b1dcc..41e9b27a 100644
--- a/rest_framework_nested/viewsets.py
+++ b/rest_framework_nested/viewsets.py
@@ -17,7 +17,7 @@ def _force_mutable(querydict: dict) -> dict:
querydict._mutable = initial_mutability
-class NestedViewSetMixin(object):
+class NestedViewSetMixin:
def _get_parent_lookup_kwargs(self) -> dict:
"""
Locates and returns the `parent_lookup_kwargs` dict informing
@@ -44,7 +44,7 @@ def get_queryset(self):
Filter the `QuerySet` based on its parents as defined in the
`serializer_class.parent_lookup_kwargs` or `viewset.parent_lookup_kwargs`
"""
- queryset = super(NestedViewSetMixin, self).get_queryset()
+ queryset = super().get_queryset()
if getattr(self, 'swagger_fake_view', False):
return queryset
diff --git a/runtests.py b/runtests.py
index 36ef9a86..f130bfab 100755
--- a/runtests.py
+++ b/runtests.py
@@ -1,6 +1,4 @@
#! /usr/bin/env python
-from __future__ import print_function
-
import pytest
import sys
import os
@@ -32,7 +30,7 @@ def flake8_main(args):
def split_class_and_function(string):
class_string, function_string = string.split('.', 1)
- return "%s and %s" % (class_string, function_string)
+ return f"{class_string} and {function_string}"
def is_function(string):
diff --git a/setup.py b/setup.py
index a5106c70..20f384a2 100644
--- a/setup.py
+++ b/setup.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python
-# -*- coding: utf-8 -*-
import re
import os
import sys
@@ -59,8 +58,8 @@ def get_package_data(package):
os.system("python setup.py sdist bdist_wheel")
os.system("twine upload dist/*")
print("You probably want to also tag the version now:")
- print(" git tag -a v{0} -m 'version {0}'".format(version))
- print(" git push origin v{0}".format(version))
+ print(f" git tag -a v{version} -m 'version {version}'")
+ print(f" git push origin v{version}")
sys.exit()
diff --git a/tests/description.py b/tests/description.py
index b46d7f54..4265e496 100644
--- a/tests/description.py
+++ b/tests/description.py
@@ -1,11 +1,3 @@
-# -- coding: utf-8 --
-
-# Apparently there is a python 2.6 issue where docstrings of imported view classes
-# do not retain their encoding information even if a module has a proper
-# encoding declaration at the top of its source file. Therefore for tests
-# to catch unicode related errors, a mock view has to be declared in a separate
-# module.
-
from rest_framework.views import APIView
diff --git a/tests/models.py b/tests/models.py
index 29f4041f..8409ac58 100644
--- a/tests/models.py
+++ b/tests/models.py
@@ -1,4 +1,3 @@
-from __future__ import unicode_literals
from django.db import models
from rest_framework import serializers
@@ -11,7 +10,7 @@ class CustomField(models.CharField):
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 12
- super(CustomField, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
class RESTFrameworkModel(models.Model):
diff --git a/tests/serializers/test_serializers.py b/tests/serializers/test_serializers.py
index f822b7b3..9ed7b449 100644
--- a/tests/serializers/test_serializers.py
+++ b/tests/serializers/test_serializers.py
@@ -44,7 +44,7 @@ def setUpClass(cls):
GrandChild1.objects.create(parent=child2, name='Child2-GrandChild1-C')
Parent.objects.create(name='Parent2')
- return super(TestSerializers, cls).setUpClass()
+ return super().setUpClass()
def test_default(self):
url = reverse('parent1-detail', kwargs={'pk': 1})
diff --git a/tests/test_dummy.py b/tests/test_dummy.py
deleted file mode 100644
index 9f77d41b..00000000
--- a/tests/test_dummy.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from django.test import TestCase
-
-
-class TestDummy(TestCase):
- def test_one_plus_one(self):
- assert 1 + 1 == 2
diff --git a/tests/test_dynamic_routers.py b/tests/test_dynamic_routers.py
index 72b41d1d..b248b1ec 100644
--- a/tests/test_dynamic_routers.py
+++ b/tests/test_dynamic_routers.py
@@ -82,41 +82,41 @@ def test_dynamic_routes(self):
self.assertFalse(hasattr(self.router, 'parent_regex'))
urls = map_by_name(self.router.urls)
self.assertEqual(
- get_regex_pattern(urls['basicmodel-list']), u'^detail/$'
+ get_regex_pattern(urls['basicmodel-list']), '^detail/$'
)
self.assertEqual(
get_regex_pattern(urls['basicmodel-detail']),
- u'^detail/(?P[^/.]+)/$'
+ '^detail/(?P[^/.]+)/$'
)
self.assertEqual(
get_regex_pattern(urls['basicmodel-set-password']),
- u'^detail/(?P[^/.]+)/set_password/$'
+ '^detail/(?P[^/.]+)/set_password/$'
)
def test_nested_parent(self):
self.assertEqual(
self.detail_router.parent_regex,
- u'detail/(?P[^/.]+)/'
+ 'detail/(?P[^/.]+)/'
)
urls = map_by_name(self.detail_router.urls)
self.assertEqual(
get_regex_pattern(urls['basicmodel-list']),
- u'^detail/(?P[^/.]+)/list/$'
+ '^detail/(?P[^/.]+)/list/$'
)
self.assertEqual(
get_regex_pattern(urls['basicmodel-recent-users']),
- u'^detail/(?P[^/.]+)/list/recent_users/$'
+ '^detail/(?P[^/.]+)/list/recent_users/$'
)
self.assertEqual(
get_regex_pattern(urls['basicmodel-detail']),
- u'^detail/(?P[^/.]+)/list/(?P[^/.]+)/$'
+ '^detail/(?P[^/.]+)/list/(?P[^/.]+)/$'
)
def test_nested_child(self):
self.assertEqual(
self.list_router.parent_regex,
- u'detail/(?P[^/.]+)/list/(?P[^/.]+)/'
+ 'detail/(?P[^/.]+)/list/(?P[^/.]+)/'
)
diff --git a/tests/test_routers.py b/tests/test_routers.py
index 179e400d..51ba983a 100644
--- a/tests/test_routers.py
+++ b/tests/test_routers.py
@@ -74,20 +74,20 @@ def test_recursive_nested_simple_routers(self):
self.assertFalse(hasattr(self.router, 'parent_regex'))
urls = self.router.urls
self.assertEqual(len(urls), 2)
- self.assertEqual(get_regex_pattern(urls[0]), u'^a/$')
- self.assertEqual(get_regex_pattern(urls[1]), u'^a/(?P[0-9a-f]{32})/$')
+ self.assertEqual(get_regex_pattern(urls[0]), '^a/$')
+ self.assertEqual(get_regex_pattern(urls[1]), '^a/(?P[0-9a-f]{32})/$')
- self.assertEqual(self.a_router.parent_regex, u'a/(?P[0-9a-f]{32})/')
+ self.assertEqual(self.a_router.parent_regex, 'a/(?P[0-9a-f]{32})/')
urls = self.a_router.urls
self.assertEqual(len(urls), 2)
- self.assertEqual(get_regex_pattern(urls[0]), u'^a/(?P[0-9a-f]{32})/b/$')
- self.assertEqual(get_regex_pattern(urls[1]), u'^a/(?P[0-9a-f]{32})/b/(?P[^/.]+)/$')
+ self.assertEqual(get_regex_pattern(urls[0]), '^a/(?P[0-9a-f]{32})/b/$')
+ self.assertEqual(get_regex_pattern(urls[1]), '^a/(?P[0-9a-f]{32})/b/(?P[^/.]+)/$')
- self.assertEqual(self.b_router.parent_regex, u'a/(?P[0-9a-f]{32})/b/(?P[^/.]+)/')
+ self.assertEqual(self.b_router.parent_regex, 'a/(?P[0-9a-f]{32})/b/(?P[^/.]+)/')
urls = self.b_router.urls
self.assertEqual(len(urls), 2)
- self.assertEqual(get_regex_pattern(urls[0]), u'^a/(?P[0-9a-f]{32})/b/(?P[^/.]+)/c/$')
- self.assertEqual(get_regex_pattern(urls[1]), u'^a/(?P[0-9a-f]{32})/b/(?P[^/.]+)/c/(?P[^/.]+)/$')
+ self.assertEqual(get_regex_pattern(urls[0]), '^a/(?P[0-9a-f]{32})/b/(?P[^/.]+)/c/$')
+ self.assertEqual(get_regex_pattern(urls[1]), '^a/(?P[0-9a-f]{32})/b/(?P[^/.]+)/c/(?P[^/.]+)/$')
class TestEmptyPrefix(TestCase):
@@ -101,8 +101,8 @@ def test_empty_prefix(self):
urls = self.router.urls
urls = self.a_router.urls
self.assertEqual(len(urls), 2)
- self.assertEqual(get_regex_pattern(urls[0]), u'^(?P[0-9a-f]{32})/b/$')
- self.assertEqual(get_regex_pattern(urls[1]), u'^(?P[0-9a-f]{32})/b/(?P[^/.]+)/$')
+ self.assertEqual(get_regex_pattern(urls[0]), '^(?P[0-9a-f]{32})/b/$')
+ self.assertEqual(get_regex_pattern(urls[1]), '^(?P[0-9a-f]{32})/b/(?P[^/.]+)/$')
class TestBadLookupValue(TestCase):
@@ -134,12 +134,12 @@ class TestRouterSettingInheritance(TestCase):
"""
def _assertHasTrailingSlash(self, router):
- self.assertEqual(router.trailing_slash, u'/', "router does not have trailing slash when it should")
+ self.assertEqual(router.trailing_slash, '/', "router does not have trailing slash when it should")
self.assertTrue(pattern_from_url(router.urls[0]).endswith('/$'),
"router created url without trailing slash when it should have")
def _assertDoesNotHaveTrailingSlash(self, router):
- self.assertEqual(router.trailing_slash, u'', "router has trailing slash when it should not")
+ self.assertEqual(router.trailing_slash, '', "router has trailing slash when it should not")
self.assertFalse(pattern_from_url(router.urls[0]).endswith('/$'),
"router created url with trailing slash when it should not have")
@@ -198,6 +198,6 @@ def test_inherits_nonstandard_trailing_slash(self):
a_router = NestedSimpleRouter(router, 'a', lookup='a')
a_router.register('b', BViewSet)
- self.assertEqual(a_router.trailing_slash, u'/?', "router does not have trailing slash when it should")
+ self.assertEqual(a_router.trailing_slash, '/?', "router does not have trailing slash when it should")
self.assertTrue(pattern_from_url(a_router.urls[0]).endswith('/?$'),
"router created url without trailing slash when it should have")