mirror of
				https://github.com/encode/django-rest-framework.git
				synced 2025-11-04 01:47:59 +03:00 
			
		
		
		
	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 <dgraves@lrtcapitalgroup.com> Co-authored-by: Asif Saif Uddin <auvipy@gmail.com>
This commit is contained in:
		
							parent
							
								
									b79099f7ba
								
							
						
					
					
						commit
						48a21aa0eb
					
				| 
						 | 
					@ -52,12 +52,24 @@ class BaseRouter:
 | 
				
			||||||
    def register(self, prefix, viewset, basename=None):
 | 
					    def register(self, prefix, viewset, basename=None):
 | 
				
			||||||
        if basename is None:
 | 
					        if basename is None:
 | 
				
			||||||
            basename = self.get_default_basename(viewset)
 | 
					            basename = self.get_default_basename(viewset)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.is_already_registered(basename):
 | 
				
			||||||
 | 
					            msg = (f'Router with basename "{basename}" is already registered. '
 | 
				
			||||||
 | 
					                   f'Please provide a unique basename for viewset "{viewset}"')
 | 
				
			||||||
 | 
					            raise ImproperlyConfigured(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.registry.append((prefix, viewset, basename))
 | 
					        self.registry.append((prefix, viewset, basename))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # invalidate the urls cache
 | 
					        # invalidate the urls cache
 | 
				
			||||||
        if hasattr(self, '_urls'):
 | 
					        if hasattr(self, '_urls'):
 | 
				
			||||||
            del self._urls
 | 
					            del self._urls
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def is_already_registered(self, new_basename):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Check if `basename` is already registered
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return any(basename == new_basename for _prefix, _viewset, basename in self.registry)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_default_basename(self, viewset):
 | 
					    def get_default_basename(self, viewset):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        If `basename` is not specified, attempt to automatically determine
 | 
					        If `basename` is not specified, attempt to automatically determine
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,6 +21,11 @@ class RouterTestModel(models.Model):
 | 
				
			||||||
    text = models.CharField(max_length=200)
 | 
					    text = models.CharField(max_length=200)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BasenameTestModel(models.Model):
 | 
				
			||||||
 | 
					    uuid = models.CharField(max_length=20)
 | 
				
			||||||
 | 
					    text = models.CharField(max_length=200)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class NoteSerializer(serializers.HyperlinkedModelSerializer):
 | 
					class NoteSerializer(serializers.HyperlinkedModelSerializer):
 | 
				
			||||||
    url = serializers.HyperlinkedIdentityField(view_name='routertestmodel-detail', lookup_field='uuid')
 | 
					    url = serializers.HyperlinkedIdentityField(view_name='routertestmodel-detail', lookup_field='uuid')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -42,6 +47,11 @@ class KWargedNoteViewSet(viewsets.ModelViewSet):
 | 
				
			||||||
    lookup_url_kwarg = 'text'
 | 
					    lookup_url_kwarg = 'text'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BasenameViewSet(viewsets.ModelViewSet):
 | 
				
			||||||
 | 
					    queryset = BasenameTestModel.objects.all()
 | 
				
			||||||
 | 
					    serializer_class = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MockViewSet(viewsets.ModelViewSet):
 | 
					class MockViewSet(viewsets.ModelViewSet):
 | 
				
			||||||
    queryset = None
 | 
					    queryset = None
 | 
				
			||||||
    serializer_class = None
 | 
					    serializer_class = None
 | 
				
			||||||
| 
						 | 
					@ -156,7 +166,7 @@ class TestSimpleRouter(URLPatternsTestCase, TestCase):
 | 
				
			||||||
    def test_register_after_accessing_urls(self):
 | 
					    def test_register_after_accessing_urls(self):
 | 
				
			||||||
        self.router.register(r'notes', NoteViewSet)
 | 
					        self.router.register(r'notes', NoteViewSet)
 | 
				
			||||||
        assert len(self.router.urls) == 2  # list and detail
 | 
					        assert len(self.router.urls) == 2  # list and detail
 | 
				
			||||||
        self.router.register(r'notes_bis', NoteViewSet)
 | 
					        self.router.register(r'notes_bis', NoteViewSet, basename='notes_bis')
 | 
				
			||||||
        assert len(self.router.urls) == 4
 | 
					        assert len(self.router.urls) == 4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -481,3 +491,103 @@ class TestViewInitkwargs(URLPatternsTestCase, TestCase):
 | 
				
			||||||
        initkwargs = match.func.initkwargs
 | 
					        initkwargs = match.func.initkwargs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert initkwargs['basename'] == 'routertestmodel'
 | 
					        assert initkwargs['basename'] == 'routertestmodel'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BasenameTestCase:
 | 
				
			||||||
 | 
					    def test_conflicting_autogenerated_basenames(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Ensure 2 routers with the same model, and no basename specified
 | 
				
			||||||
 | 
					        throws an ImproperlyConfigured exception
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.router.register(r'notes', NoteViewSet)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with pytest.raises(ImproperlyConfigured):
 | 
				
			||||||
 | 
					            self.router.register(r'notes_kwduplicate', KWargedNoteViewSet)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with pytest.raises(ImproperlyConfigured):
 | 
				
			||||||
 | 
					            self.router.register(r'notes_duplicate', NoteViewSet)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_conflicting_mixed_basenames(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Ensure 2 routers with the same model, and no basename specified on 1
 | 
				
			||||||
 | 
					        throws an ImproperlyConfigured exception
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.router.register(r'notes', NoteViewSet)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with pytest.raises(ImproperlyConfigured):
 | 
				
			||||||
 | 
					            self.router.register(r'notes_kwduplicate', KWargedNoteViewSet, basename='routertestmodel')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with pytest.raises(ImproperlyConfigured):
 | 
				
			||||||
 | 
					            self.router.register(r'notes_duplicate', NoteViewSet, basename='routertestmodel')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_nonconflicting_mixed_basenames(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Ensure 2 routers with the same model, and a distinct basename
 | 
				
			||||||
 | 
					        specified on the second router does not fail
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.router.register(r'notes', NoteViewSet)
 | 
				
			||||||
 | 
					        self.router.register(r'notes_kwduplicate', KWargedNoteViewSet, basename='routertestmodel_kwduplicate')
 | 
				
			||||||
 | 
					        self.router.register(r'notes_duplicate', NoteViewSet, basename='routertestmodel_duplicate')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_conflicting_specified_basename(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Ensure 2 routers with the same model, and the same basename specified
 | 
				
			||||||
 | 
					        on both throws an ImproperlyConfigured exception
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.router.register(r'notes', NoteViewSet, basename='notes')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with pytest.raises(ImproperlyConfigured):
 | 
				
			||||||
 | 
					            self.router.register(r'notes_kwduplicate', KWargedNoteViewSet, basename='notes')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with pytest.raises(ImproperlyConfigured):
 | 
				
			||||||
 | 
					            self.router.register(r'notes_duplicate', KWargedNoteViewSet, basename='notes')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_nonconflicting_specified_basename(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Ensure 2 routers with the same model, and a distinct basename specified
 | 
				
			||||||
 | 
					        on each does not throw an exception
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.router.register(r'notes', NoteViewSet, basename='notes')
 | 
				
			||||||
 | 
					        self.router.register(r'notes_kwduplicate', KWargedNoteViewSet, basename='notes_kwduplicate')
 | 
				
			||||||
 | 
					        self.router.register(r'notes_duplicate', NoteViewSet, basename='notes_duplicate')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_nonconflicting_specified_basename_different_models(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Ensure 2 routers with different models, and a distinct basename specified
 | 
				
			||||||
 | 
					        on each does not throw an exception
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.router.register(r'notes', NoteViewSet, basename='notes')
 | 
				
			||||||
 | 
					        self.router.register(r'notes_basename', BasenameViewSet, basename='notes_basename')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_conflicting_specified_basename_different_models(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Ensure 2 routers with different models, and a conflicting basename specified
 | 
				
			||||||
 | 
					        throws an exception
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.router.register(r'notes', NoteViewSet)
 | 
				
			||||||
 | 
					        with pytest.raises(ImproperlyConfigured):
 | 
				
			||||||
 | 
					            self.router.register(r'notes_basename', BasenameViewSet, basename='routertestmodel')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_nonconflicting_autogenerated_basename_different_models(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Ensure 2 routers with different models, and a distinct basename specified
 | 
				
			||||||
 | 
					        on each does not throw an exception
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.router.register(r'notes', NoteViewSet)
 | 
				
			||||||
 | 
					        self.router.register(r'notes_basename', BasenameViewSet)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestDuplicateBasenameSimpleRouter(BasenameTestCase, TestCase):
 | 
				
			||||||
 | 
					    def setUp(self):
 | 
				
			||||||
 | 
					        self.router = SimpleRouter(trailing_slash=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestDuplicateBasenameDefaultRouter(BasenameTestCase, TestCase):
 | 
				
			||||||
 | 
					    def setUp(self):
 | 
				
			||||||
 | 
					        self.router = DefaultRouter()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestDuplicateBasenameDefaultRouterRootViewName(BasenameTestCase, TestCase):
 | 
				
			||||||
 | 
					    def setUp(self):
 | 
				
			||||||
 | 
					        self.router = DefaultRouter()
 | 
				
			||||||
 | 
					        self.router.root_view_name = 'nameable-root'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user