Skip to content

Commit 48a21aa

Browse files
dgravitateDavid Gravesauvipy
authored
raise ImproperlyConfigured exception if basename is not unique (#8438)
* raise ImproperlyConfigured if basename already exists * rename already_registered function; return True/False * additional basename tests * additional basename tests * Update rest_framework/routers.py Co-authored-by: David Graves <[email protected]> Co-authored-by: Asif Saif Uddin <[email protected]>
1 parent b79099f commit 48a21aa

File tree

2 files changed

+123
-1
lines changed

2 files changed

+123
-1
lines changed

rest_framework/routers.py

+12
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,24 @@ def __init__(self):
5252
def register(self, prefix, viewset, basename=None):
5353
if basename is None:
5454
basename = self.get_default_basename(viewset)
55+
56+
if self.is_already_registered(basename):
57+
msg = (f'Router with basename "{basename}" is already registered. '
58+
f'Please provide a unique basename for viewset "{viewset}"')
59+
raise ImproperlyConfigured(msg)
60+
5561
self.registry.append((prefix, viewset, basename))
5662

5763
# invalidate the urls cache
5864
if hasattr(self, '_urls'):
5965
del self._urls
6066

67+
def is_already_registered(self, new_basename):
68+
"""
69+
Check if `basename` is already registered
70+
"""
71+
return any(basename == new_basename for _prefix, _viewset, basename in self.registry)
72+
6173
def get_default_basename(self, viewset):
6274
"""
6375
If `basename` is not specified, attempt to automatically determine

tests/test_routers.py

+111-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ class RouterTestModel(models.Model):
2121
text = models.CharField(max_length=200)
2222

2323

24+
class BasenameTestModel(models.Model):
25+
uuid = models.CharField(max_length=20)
26+
text = models.CharField(max_length=200)
27+
28+
2429
class NoteSerializer(serializers.HyperlinkedModelSerializer):
2530
url = serializers.HyperlinkedIdentityField(view_name='routertestmodel-detail', lookup_field='uuid')
2631

@@ -42,6 +47,11 @@ class KWargedNoteViewSet(viewsets.ModelViewSet):
4247
lookup_url_kwarg = 'text'
4348

4449

50+
class BasenameViewSet(viewsets.ModelViewSet):
51+
queryset = BasenameTestModel.objects.all()
52+
serializer_class = None
53+
54+
4555
class MockViewSet(viewsets.ModelViewSet):
4656
queryset = None
4757
serializer_class = None
@@ -156,7 +166,7 @@ def test_multiple_action_handlers(self):
156166
def test_register_after_accessing_urls(self):
157167
self.router.register(r'notes', NoteViewSet)
158168
assert len(self.router.urls) == 2 # list and detail
159-
self.router.register(r'notes_bis', NoteViewSet)
169+
self.router.register(r'notes_bis', NoteViewSet, basename='notes_bis')
160170
assert len(self.router.urls) == 4
161171

162172

@@ -481,3 +491,103 @@ def test_basename(self):
481491
initkwargs = match.func.initkwargs
482492

483493
assert initkwargs['basename'] == 'routertestmodel'
494+
495+
496+
class BasenameTestCase:
497+
def test_conflicting_autogenerated_basenames(self):
498+
"""
499+
Ensure 2 routers with the same model, and no basename specified
500+
throws an ImproperlyConfigured exception
501+
"""
502+
self.router.register(r'notes', NoteViewSet)
503+
504+
with pytest.raises(ImproperlyConfigured):
505+
self.router.register(r'notes_kwduplicate', KWargedNoteViewSet)
506+
507+
with pytest.raises(ImproperlyConfigured):
508+
self.router.register(r'notes_duplicate', NoteViewSet)
509+
510+
def test_conflicting_mixed_basenames(self):
511+
"""
512+
Ensure 2 routers with the same model, and no basename specified on 1
513+
throws an ImproperlyConfigured exception
514+
"""
515+
self.router.register(r'notes', NoteViewSet)
516+
517+
with pytest.raises(ImproperlyConfigured):
518+
self.router.register(r'notes_kwduplicate', KWargedNoteViewSet, basename='routertestmodel')
519+
520+
with pytest.raises(ImproperlyConfigured):
521+
self.router.register(r'notes_duplicate', NoteViewSet, basename='routertestmodel')
522+
523+
def test_nonconflicting_mixed_basenames(self):
524+
"""
525+
Ensure 2 routers with the same model, and a distinct basename
526+
specified on the second router does not fail
527+
"""
528+
self.router.register(r'notes', NoteViewSet)
529+
self.router.register(r'notes_kwduplicate', KWargedNoteViewSet, basename='routertestmodel_kwduplicate')
530+
self.router.register(r'notes_duplicate', NoteViewSet, basename='routertestmodel_duplicate')
531+
532+
def test_conflicting_specified_basename(self):
533+
"""
534+
Ensure 2 routers with the same model, and the same basename specified
535+
on both throws an ImproperlyConfigured exception
536+
"""
537+
self.router.register(r'notes', NoteViewSet, basename='notes')
538+
539+
with pytest.raises(ImproperlyConfigured):
540+
self.router.register(r'notes_kwduplicate', KWargedNoteViewSet, basename='notes')
541+
542+
with pytest.raises(ImproperlyConfigured):
543+
self.router.register(r'notes_duplicate', KWargedNoteViewSet, basename='notes')
544+
545+
def test_nonconflicting_specified_basename(self):
546+
"""
547+
Ensure 2 routers with the same model, and a distinct basename specified
548+
on each does not throw an exception
549+
"""
550+
self.router.register(r'notes', NoteViewSet, basename='notes')
551+
self.router.register(r'notes_kwduplicate', KWargedNoteViewSet, basename='notes_kwduplicate')
552+
self.router.register(r'notes_duplicate', NoteViewSet, basename='notes_duplicate')
553+
554+
def test_nonconflicting_specified_basename_different_models(self):
555+
"""
556+
Ensure 2 routers with different models, and a distinct basename specified
557+
on each does not throw an exception
558+
"""
559+
self.router.register(r'notes', NoteViewSet, basename='notes')
560+
self.router.register(r'notes_basename', BasenameViewSet, basename='notes_basename')
561+
562+
def test_conflicting_specified_basename_different_models(self):
563+
"""
564+
Ensure 2 routers with different models, and a conflicting basename specified
565+
throws an exception
566+
"""
567+
self.router.register(r'notes', NoteViewSet)
568+
with pytest.raises(ImproperlyConfigured):
569+
self.router.register(r'notes_basename', BasenameViewSet, basename='routertestmodel')
570+
571+
def test_nonconflicting_autogenerated_basename_different_models(self):
572+
"""
573+
Ensure 2 routers with different models, and a distinct basename specified
574+
on each does not throw an exception
575+
"""
576+
self.router.register(r'notes', NoteViewSet)
577+
self.router.register(r'notes_basename', BasenameViewSet)
578+
579+
580+
class TestDuplicateBasenameSimpleRouter(BasenameTestCase, TestCase):
581+
def setUp(self):
582+
self.router = SimpleRouter(trailing_slash=False)
583+
584+
585+
class TestDuplicateBasenameDefaultRouter(BasenameTestCase, TestCase):
586+
def setUp(self):
587+
self.router = DefaultRouter()
588+
589+
590+
class TestDuplicateBasenameDefaultRouterRootViewName(BasenameTestCase, TestCase):
591+
def setUp(self):
592+
self.router = DefaultRouter()
593+
self.router.root_view_name = 'nameable-root'

0 commit comments

Comments
 (0)