{"id":6718,"date":"2023-08-22T09:53:35","date_gmt":"2023-08-22T09:53:35","guid":{"rendered":"https:\/\/www.pythontutorial.net\/?page_id=6718"},"modified":"2023-08-22T10:00:37","modified_gmt":"2023-08-22T10:00:37","slug":"django-rest-framework-permissions","status":"publish","type":"page","link":"https:\/\/www.pythontutorial.net\/django-tutorial\/django-rest-framework-permissions\/","title":{"rendered":"Django REST Framework Permissions"},"content":{"rendered":"\n<p><strong>Summary<\/strong>: in this tutorial, you&#8217;ll explore Django REST Framework permissions and learn how to define a custom permission class for the todo list project.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id='introduction-to-the-django-rest-framework-permissions'>Introduction to the Django REST Framework permissions <a href=\"#introduction-to-the-django-rest-framework-permissions\" class=\"anchor\" id=\"introduction-to-the-django-rest-framework-permissions\" title=\"Anchor for Introduction to the Django REST Framework permissions\">#<\/a><\/h2>\n\n\n\n<p>Django REST Framework has some built-in permission settings that you can use to secure API. Django REST Framework allows you to set the permissions at three levels:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Project-level<\/li>\n\n\n\n<li>View-level<\/li>\n\n\n\n<li>Model-level<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id='project-level-permissions'>Project-level permissions <a href=\"#project-level-permissions\" class=\"anchor\" id=\"project-level-permissions\" title=\"Anchor for Project-level permissions\">#<\/a><\/h3>\n\n\n\n<p>The project-level permissions are set in the single Django setting called <code>REST_FRAMEWORK<\/code> in the <code>settings.py<\/code> file of the Django project.<\/p>\n\n\n\n<p>By default, Django REST Framework allows unrestricted access to the API. It&#8217;s equivalent to the following:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\">REST_FRAMEWORK = {\n    <span class=\"hljs-string\">'DEFAULT_PERMISSION_CLASSES'<\/span>: &#91;\n        <span class=\"hljs-string\">'rest_framework.permissions.AllowAny'<\/span>,\n    ]\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Besides <code>AllowAny<\/code> permission classes, Django REST Framework also offers other built-in project-level permissions:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>IsAuthenticated<\/code>: only authenticated users have access.<\/li>\n\n\n\n<li><code>IsAdminUser<\/code>: only the admin\/superusers have access.<\/li>\n\n\n\n<li><code>IsAuthenticatedOrReadOnly<\/code>: unauthenticated users can call any API but only authenticated users have to create, update, and delete privileges.<\/li>\n<\/ul>\n\n\n\n<p>Let&#8217;s use the <code>IsAuthenticated<\/code> permission class:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\">REST_FRAMEWORK = {\n    <span class=\"hljs-string\">'DEFAULT_PERMISSION_CLASSES'<\/span>: &#91;\n        <span class=\"hljs-string\">'rest_framework.permissions.IsAuthenticated'<\/span>,\n    ]\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>If you log out as a superuser and access the API endpoint that shows all the todos <code>\/api\/v1\/todos\/<\/code>, you&#8217;ll get the following HTTP 403 forbidden error message:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\">HTTP <span class=\"hljs-number\">403<\/span> Forbidden\nAllow: GET, POST, HEAD, OPTIONS\nContent-Type: application\/json\nVary: Accept\n\n{\n    <span class=\"hljs-string\">\"detail\"<\/span>: <span class=\"hljs-string\">\"Authentication credentials were not provided.\"<\/span>\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The reason is that the API requires authenticated user.<\/p>\n\n\n\n<p>Let&#8217;s create a regular user called <code>testapi<\/code> using the admin site <code>http:\/\/localhost:8000\/admin\/1<\/code>. Note that a regular user is not a superuser.<\/p>\n\n\n\n<p>To enable the regular user <code>testapi<\/code> to log in and log out, you need to update the project-level  URLconf to create the login\/logout views:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\"><span class=\"hljs-keyword\">from<\/span> django.contrib <span class=\"hljs-keyword\">import<\/span> admin\n<span class=\"hljs-keyword\">from<\/span> django.urls <span class=\"hljs-keyword\">import<\/span> path, include\n\nurlpatterns = &#91;\n    path(<span class=\"hljs-string\">'admin\/'<\/span>, admin.site.urls),\n    path(<span class=\"hljs-string\">'api\/v1\/'<\/span>, include(<span class=\"hljs-string\">'api.urls'<\/span>)),\n    path(<span class=\"hljs-string\">\"auth\/\"<\/span>, include(<span class=\"hljs-string\">\"rest_framework.urls\"<\/span>)), <span class=\"hljs-comment\"># added<\/span>\n]<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Once the <code>rest_framework.urls<\/code> is set, you can access the API endpoint <code>\/api\/v1\/todos\/<\/code>. A minor change is the login link on the right side:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"740\" height=\"572\" src=\"https:\/\/www.pythontutorial.net\/wp-content\/uploads\/2023\/08\/django-rest-framework-permissions.png\" alt=\"\" class=\"wp-image-6720\" srcset=\"https:\/\/www.pythontutorial.net\/wp-content\/uploads\/2023\/08\/django-rest-framework-permissions.png 740w, https:\/\/www.pythontutorial.net\/wp-content\/uploads\/2023\/08\/django-rest-framework-permissions-300x232.png 300w\" sizes=\"auto, (max-width: 740px) 100vw, 740px\" \/><\/figure>\n\n\n\n<p>If you click the Log In link, you&#8217;ll see the login form as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"535\" height=\"449\" src=\"https:\/\/www.pythontutorial.net\/wp-content\/uploads\/2023\/08\/django-rest-framework-login-form.png\" alt=\"\" class=\"wp-image-6721\" srcset=\"https:\/\/www.pythontutorial.net\/wp-content\/uploads\/2023\/08\/django-rest-framework-login-form.png 535w, https:\/\/www.pythontutorial.net\/wp-content\/uploads\/2023\/08\/django-rest-framework-login-form-300x252.png 300w\" sizes=\"auto, (max-width: 535px) 100vw, 535px\" \/><\/figure>\n\n\n\n<p>Sign in to the browseable API using a regular user <code>testapi<\/code> and password. Once logged in successfully with the <code>testapi<\/code> user, you&#8217;ll see the following response:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"724\" height=\"658\" src=\"https:\/\/www.pythontutorial.net\/wp-content\/uploads\/2023\/08\/django-rest-framework-authenticated-user.png\" alt=\"\" class=\"wp-image-6722\" srcset=\"https:\/\/www.pythontutorial.net\/wp-content\/uploads\/2023\/08\/django-rest-framework-authenticated-user.png 724w, https:\/\/www.pythontutorial.net\/wp-content\/uploads\/2023\/08\/django-rest-framework-authenticated-user-300x273.png 300w\" sizes=\"auto, (max-width: 724px) 100vw, 724px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\" id='view-level-permissions'>View-level permissions <a href=\"#view-level-permissions\" class=\"anchor\" id=\"view-level-permissions\" title=\"Anchor for View-level permissions\">#<\/a><\/h3>\n\n\n\n<p>Django REST Framework allows you to add permissions at the view level for more granular control. <\/p>\n\n\n\n<p>For example, you can update the <code>TodoDetail<\/code> view so that only admin users can view it but regular users cannot access it.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\"><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">TodoDetail<\/span><span class=\"hljs-params\">(generics.RetrieveUpdateDestroyAPIView)<\/span>:<\/span>\n    permission_classes = (permissions.IsAdminUser,) <span class=\"hljs-comment\"># added<\/span>\n    queryset = Todo.objects.all()\n    serializer_class = TodoSerializer<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The <code><code>TodoDetail<\/code><\/code> view has the <code>permission_classes<\/code> which is initialized to the <code>permissions.IsAdminUser<\/code>. <\/p>\n\n\n\n<p>By doing this, Django REST Framework only allows the admin user to access the <code><code>TodoDetail<\/code><\/code> view, which maps to the API endpoint <code>\/api\/v1\/todos\/id<\/code>.<\/p>\n\n\n\n<p>If you use the <code>testapi<\/code> user (regular user) to access the Todo detail API, you&#8217;ll get the HTTP 403 forbidden:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"731\" height=\"434\" src=\"https:\/\/www.pythontutorial.net\/wp-content\/uploads\/2023\/08\/django-rest-framework-permission-view-level.png\" alt=\"\" class=\"wp-image-6723\" srcset=\"https:\/\/www.pythontutorial.net\/wp-content\/uploads\/2023\/08\/django-rest-framework-permission-view-level.png 731w, https:\/\/www.pythontutorial.net\/wp-content\/uploads\/2023\/08\/django-rest-framework-permission-view-level-300x178.png 300w\" sizes=\"auto, (max-width: 731px) 100vw, 731px\" \/><\/figure>\n\n\n\n<p>To log the <code>testapi<\/code> user out, you click the username link and select click logout link:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"250\" height=\"116\" src=\"https:\/\/www.pythontutorial.net\/wp-content\/uploads\/2023\/08\/django-rest-framework-permission-logout.png\" alt=\"\" class=\"wp-image-6724\"\/><\/figure>\n\n\n\n<p>And login to the browsable API using the super admin (<code>john<\/code>). <\/p>\n\n\n\n<p>Since the <code>TodoDetail<\/code> view allows the super admin to access, you can see the API response:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"720\" height=\"536\" src=\"https:\/\/www.pythontutorial.net\/wp-content\/uploads\/2023\/08\/django-rest-framework-permission-view-level-superuser.png\" alt=\"\" class=\"wp-image-6725\" srcset=\"https:\/\/www.pythontutorial.net\/wp-content\/uploads\/2023\/08\/django-rest-framework-permission-view-level-superuser.png 720w, https:\/\/www.pythontutorial.net\/wp-content\/uploads\/2023\/08\/django-rest-framework-permission-view-level-superuser-300x223.png 300w\" sizes=\"auto, (max-width: 720px) 100vw, 720px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id='custom-permissions'>Custom Permissions <a href=\"#custom-permissions\" class=\"anchor\" id=\"custom-permissions\" title=\"Anchor for Custom Permissions\">#<\/a><\/h2>\n\n\n\n<p>In the Todo project, you can restrict access so that only owners of todos can view, edit, update, and delete. But they cannot access the todos of other users. Also, the superuser will have access to the todo list of their own as well as manage the todos of other users.<\/p>\n\n\n\n<p>To accommodate these permission requirements, you need to use Django REST Framework&#8217;s custom permissions. <\/p>\n\n\n\n<p>To define custom permission, you use the <code>BasePermission<\/code> class of the Django REST Framework. Here&#8217;s the source code of the <code>BasePermission<\/code> class:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\"><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">BasePermission<\/span><span class=\"hljs-params\">(metaclass=BasePermissionMetaclass)<\/span>:<\/span>\n    <span class=\"hljs-string\">\"\"\"\n    A base class from which all permission classes should inherit.\n    \"\"\"<\/span>\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">has_permission<\/span><span class=\"hljs-params\">(self, request, view)<\/span>:<\/span>\n        <span class=\"hljs-string\">\"\"\"\n        Return `True` if permission is granted, `False` otherwise.\n        \"\"\"<\/span>\n        <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-literal\">True<\/span>\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">has_object_permission<\/span><span class=\"hljs-params\">(self, request, view, obj)<\/span>:<\/span>\n        <span class=\"hljs-string\">\"\"\"\n        Return `True` if permission is granted, `False` otherwise.\n        \"\"\"<\/span>\n        <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-literal\">True<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The <code>BasePermission<\/code> has two methods:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>has_permission(self, request, view)<\/code> &#8211; Return <code>True<\/code> if permission is granted or <code>False<\/code> otherwise. The list views will call the <code><code>has_permission<\/code><\/code> method to check for permissions.<\/li>\n\n\n\n<li><code>has_object_permission(self, requests, view, obj)<\/code> &#8211; Return <code>True<\/code> if permission is granted or <code>False<\/code> otherwise. The detail view method will call <code>has_permission()<\/code> first. If passes, it&#8217;ll call the <code><code>has_object_permission<\/code>()<\/code> method next to check for the permissions.<\/li>\n<\/ul>\n\n\n\n<p>It&#8217;s a good practice to override both methods explicitly to avoid unwanted default settings of the base class.<\/p>\n\n\n\n<p>The following illustrates the steps for defining and using a custom permission class in Django REST Framework:<\/p>\n\n\n\n<p>First, define the <code>IsOwnerOnly<\/code> class that extends the <code>BasePermission<\/code> class in the <code>permissions.py<\/code> file:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\"><span class=\"hljs-keyword\">from<\/span> rest_framework <span class=\"hljs-keyword\">import<\/span> permissions\n\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">IsOwnerOnly<\/span><span class=\"hljs-params\">(permissions.BasePermission)<\/span>:<\/span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">has_permission<\/span><span class=\"hljs-params\">(self, request, view)<\/span>:<\/span>\n        <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-literal\">True<\/span>\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">has_object_permission<\/span><span class=\"hljs-params\">(self, request, view, obj)<\/span>:<\/span>\n        <span class=\"hljs-keyword\">if<\/span> request.user.is_superuser:\n            <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-literal\">True<\/span>\n\n        <span class=\"hljs-keyword\">return<\/span> obj.user == request.user<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The <code>has_permission<\/code> always returns <code>True<\/code>. It means that any user can access the <code>TodoList<\/code> view to get all the todos and create a new todo. Note that to restrict access to the todos of the current user, we&#8217;ll do it in the <code>TodoList<\/code> view later.<\/p>\n\n\n\n<p>The <code>has_object_permission<\/code> method returns <code>True<\/code> if the current user is the super user or the owner of the todo.<\/p>\n\n\n\n<p>Second, update the view classes in the <code>views.py<\/code> of the <code>api<\/code> app to use the custom permission class <code>IsOwnerOnly<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\"><span class=\"hljs-keyword\">from<\/span> rest_framework <span class=\"hljs-keyword\">import<\/span> generics\n<span class=\"hljs-keyword\">from<\/span> .serializers <span class=\"hljs-keyword\">import<\/span> TodoSerializer\n<span class=\"hljs-keyword\">from<\/span> .permissions <span class=\"hljs-keyword\">import<\/span> IsOwnerOnly\n<span class=\"hljs-keyword\">from<\/span> todo.models <span class=\"hljs-keyword\">import<\/span> Todo\n\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">TodoList<\/span><span class=\"hljs-params\">(generics.ListCreateAPIView)<\/span>:<\/span>\n    permission_classes = (IsOwnerOnly,)  <span class=\"hljs-comment\"># added<\/span>\n    queryset = Todo.objects.all()\n    serializer_class = TodoSerializer\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">perform_create<\/span><span class=\"hljs-params\">(self, serializer)<\/span>:<\/span>\n        serializer.save(user=self.request.user)\n\n    <span class=\"hljs-comment\"># added<\/span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">filter_queryset<\/span><span class=\"hljs-params\">(self, queryset)<\/span>:<\/span>\n        queryset = queryset.filter(user=self.request.user)\n        <span class=\"hljs-keyword\">return<\/span> super().filter_queryset(queryset)\n\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">TodoDetail<\/span><span class=\"hljs-params\">(generics.RetrieveUpdateDestroyAPIView)<\/span>:<\/span>\n    permission_classes = (IsOwnerOnly,)  <span class=\"hljs-comment\"># added<\/span>\n    queryset = Todo.objects.all()\n    serializer_class = TodoSerializer<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Both <code>TodoList<\/code> and <code>TodoDetail<\/code> classes use the <code>IsOwnerOnly<\/code> class:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\">permission_classes = (IsOwnerOnly,)  <span class=\"hljs-comment\"># added<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Also, add the <code>filter_queryset<\/code> method to the <code>TodoList<\/code> class to return only todos of the currently authenticated user:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">filter_queryset<\/span><span class=\"hljs-params\">(self, queryset)<\/span>:<\/span>\n    queryset = queryset.filter(user=self.request.user)\n    <span class=\"hljs-keyword\">return<\/span> super().filter_queryset(queryset)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>If you log in to the browsable API using the superuser, you can view, create, edit, update, and delete todos.<\/p>\n\n\n\n<p>But if you log in using the regular user (<code>testapi<\/code>), you can see that the <code>\/api\/v1\/todos\/<\/code> return an empty list. <\/p>\n\n\n\n<p>Also, accessing the API endpoint <code>\/api\/v1\/todos\/1\/<\/code> will result in an HTTP 403 forbidden since the <code>testapi<\/code> user does not have access to the todo that does not belong to the user:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"748\" height=\"460\" src=\"https:\/\/www.pythontutorial.net\/wp-content\/uploads\/2023\/08\/django-rest-framework-HTTP-403.png\" alt=\"\" class=\"wp-image-6728\" srcset=\"https:\/\/www.pythontutorial.net\/wp-content\/uploads\/2023\/08\/django-rest-framework-HTTP-403.png 748w, https:\/\/www.pythontutorial.net\/wp-content\/uploads\/2023\/08\/django-rest-framework-HTTP-403-300x184.png 300w\" sizes=\"auto, (max-width: 748px) 100vw, 748px\" \/><\/figure>\n\n\n\n<p>Now, you can use the <code>testapi<\/code> user to create a new todo with the following information:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\">{\n    <span class=\"hljs-string\">\"title\"<\/span>: <span class=\"hljs-string\">\"Test API\"<\/span>,\n    <span class=\"hljs-string\">\"completed\"<\/span>: false\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Here&#8217;s the response:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\">HTTP <span class=\"hljs-number\">201<\/span> Created\nAllow: GET, POST, HEAD, OPTIONS\nContent-Type: application\/json\nVary: Accept\n\n{\n    <span class=\"hljs-string\">\"id\"<\/span>: <span class=\"hljs-number\">5<\/span>,\n    <span class=\"hljs-string\">\"title\"<\/span>: <span class=\"hljs-string\">\"Test API\"<\/span>,\n    <span class=\"hljs-string\">\"completed\"<\/span>: false,\n    <span class=\"hljs-string\">\"user\"<\/span>: <span class=\"hljs-string\">\"testapi\"<\/span>\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Make an HTTP GET request to the <code>\/api\/v1\/todos\/<\/code> endpoint, you will get the todo list created by the <code>testapi<\/code> user:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\">HTTP <span class=\"hljs-number\">200<\/span> OK\nAllow: GET, POST, HEAD, OPTIONS\nContent-Type: application\/json\nVary: Accept\n\n&#91;\n    {\n        <span class=\"hljs-string\">\"id\"<\/span>: <span class=\"hljs-number\">5<\/span>,\n        <span class=\"hljs-string\">\"title\"<\/span>: <span class=\"hljs-string\">\"Test API\"<\/span>,\n        <span class=\"hljs-string\">\"completed\"<\/span>: false,\n        <span class=\"hljs-string\">\"user\"<\/span>: <span class=\"hljs-string\">\"testapi\"<\/span>\n    }\n]<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Also, you can test the view, update, and delete todo with id 5 using the <code>testapi<\/code> user. It should work as expected.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id='download-the-project-source-code'>Download the project source code <a href=\"#download-the-project-source-code\" class=\"anchor\" id=\"download-the-project-source-code\" title=\"Anchor for Download the project source code\">#<\/a><\/h2>\n\n\n\n<p>Click the following link to download the project source code:<\/p>\n\n\n\n<p><a href=\"https:\/\/www.pythontutorial.net\/wp-content\/uploads\/2023\/08\/todolist3.zip\">Download the project source code<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id='summary'>Summary <a href=\"#summary\" class=\"anchor\" id=\"summary\" title=\"Anchor for Summary\">#<\/a><\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Django REST Framework provides three permission levels including project level, view level, and model level.<\/li>\n\n\n\n<li>Extend the <code>BasePermisssion<\/code> class to define custom permission to suit your requirements.<\/li>\n<\/ul>\n<div class=\"helpful-block-content\" data-title=\"\">\n\t<header>\n\t\t<div class=\"wth-question\">Was this tutorial helpful ?<\/div>\n\t\t<div class=\"wth-thumbs\">\n\t\t\t<button\n\t\t\t\tdata-post=\"6718\"\n\t\t\t\tdata-post-url=\"https:\/\/www.pythontutorial.net\/django-tutorial\/django-rest-framework-permissions\/\"\n\t\t\t\tdata-post-title=\"Django REST Framework Permissions\"\n\t\t\t\tdata-response=\"1\"\n\t\t\t\tclass=\"wth-btn-rounded wth-yes-btn\"\n\t\t\t>\n\t\t\t\t<svg\n\t\t\t\t\txmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\tfill=\"none\"\n\t\t\t\t\tstroke=\"currentColor\"\n\t\t\t\t\tstroke-width=\"2\"\n\t\t\t\t\tstroke-linecap=\"round\"\n\t\t\t\t\tstroke-linejoin=\"round\"\n\t\t\t\t\tclass=\"feather feather-thumbs-up block w-full h-full\"\n\t\t\t\t>\n\t\t\t\t\t<path\n\t\t\t\t\t\td=\"M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3\"\n\t\t\t\t\t><\/path>\n\t\t\t\t<\/svg>\n\t\t\t\t<span class=\"sr-only\"> Yes <\/span>\n\t\t\t<\/button>\n\n\t\t\t<button\n\t\t\t\tdata-response=\"0\"\n\t\t\t\tdata-post=\"6718\"\n\t\t\t\tdata-post-url=\"https:\/\/www.pythontutorial.net\/django-tutorial\/django-rest-framework-permissions\/\"\n\t\t\t\tdata-post-title=\"Django REST Framework Permissions\"\n\t\t\t\tclass=\"wth-btn-rounded wth-no-btn\"\n\t\t\t>\n\t\t\t\t<svg\n\t\t\t\t\txmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\tfill=\"none\"\n\t\t\t\t\tstroke=\"currentColor\"\n\t\t\t\t\tstroke-width=\"2\"\n\t\t\t\t\tstroke-linecap=\"round\"\n\t\t\t\t\tstroke-linejoin=\"round\"\n\t\t\t\t>\n\t\t\t\t\t<path\n\t\t\t\t\t\td=\"M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17\"\n\t\t\t\t\t><\/path>\n\t\t\t\t<\/svg>\n\t\t\t\t<span class=\"sr-only\"> No <\/span>\n\t\t\t<\/button>\n\t\t<\/div>\n\t<\/header>\n\n\t<div class=\"wth-form hidden\">\n\t\t<div class=\"wth-form-wrapper\">\n\t\t\t<div class=\"wth-title\"><\/div>\n\t\t\t<textarea class=\"wth-message\"><\/textarea>\n\t\t\t<input type=\"button\" name=\"wth-submit\" class=\"wth-btn wth-btn-submit\" id=\"wth-submit\" \/>\n\t\t\t<input type=\"button\" class=\"wth-btn wth-btn-cancel\" value=\"Cancel\" \/>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>In this tutorial, you&#8217;ll explore Django REST Framework permissions and learn how to build custom permission for the todo list project.<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":5531,"menu_order":40,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-6718","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/www.pythontutorial.net\/wp-json\/wp\/v2\/pages\/6718","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.pythontutorial.net\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.pythontutorial.net\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.pythontutorial.net\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.pythontutorial.net\/wp-json\/wp\/v2\/comments?post=6718"}],"version-history":[{"count":0,"href":"https:\/\/www.pythontutorial.net\/wp-json\/wp\/v2\/pages\/6718\/revisions"}],"up":[{"embeddable":true,"href":"https:\/\/www.pythontutorial.net\/wp-json\/wp\/v2\/pages\/5531"}],"wp:attachment":[{"href":"https:\/\/www.pythontutorial.net\/wp-json\/wp\/v2\/media?parent=6718"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}