From 2126012536f13c153f215e04ae99fa17b67a1d3e Mon Sep 17 00:00:00 2001 From: Bondzik-S Date: Fri, 3 Jan 2025 11:06:42 +0200 Subject: [PATCH 1/4] feat: changed links --- book/urls.py | 4 ++-- core/urls.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/book/urls.py b/book/urls.py index d9a9acc..af197e5 100644 --- a/book/urls.py +++ b/book/urls.py @@ -5,6 +5,6 @@ app_name = "book" router = routers.DefaultRouter() -router.register("books", BookViewSet) +router.register("", BookViewSet, basename="books") -urlpatterns = [path("", include(router.urls))] +urlpatterns = router.urls diff --git a/core/urls.py b/core/urls.py index e72c566..a3e84cf 100644 --- a/core/urls.py +++ b/core/urls.py @@ -29,5 +29,5 @@ path("api/schema/", SpectacularAPIView.as_view(), name="schema"), path("api/schema/swagger-ui/", SpectacularSwaggerView.as_view(url_name="schema"), name="swagger-ui"), path("api/schema/redoc/", SpectacularRedocView.as_view(url_name="schema"), name="redoc"), - path("api/book-service/", include("book.urls", namespace="book")), + path("api/books/", include("book.urls", namespace="book")), ] From 4d3f28ffbefac8282fb28f60d037ed0766c3dcd9 Mon Sep 17 00:00:00 2001 From: Bondzik-S Date: Fri, 3 Jan 2025 11:07:17 +0200 Subject: [PATCH 2/4] feat: add permission class and fix tests --- book/permissions.py | 8 +++ book/tests/test_book_view_set.py | 102 +++++++++++++++++++++++-------- book/views.py | 2 + 3 files changed, 85 insertions(+), 27 deletions(-) create mode 100644 book/permissions.py diff --git a/book/permissions.py b/book/permissions.py new file mode 100644 index 0000000..cb036f8 --- /dev/null +++ b/book/permissions.py @@ -0,0 +1,8 @@ +from rest_framework.permissions import SAFE_METHODS, BasePermission + + +class IsAdminOrReadOnly(BasePermission): + def has_permission(self, request, view): + if request.method in SAFE_METHODS: + return True + return request.user and request.user.is_staff diff --git a/book/tests/test_book_view_set.py b/book/tests/test_book_view_set.py index 5101d68..c310ce1 100644 --- a/book/tests/test_book_view_set.py +++ b/book/tests/test_book_view_set.py @@ -1,11 +1,13 @@ +from django.contrib.auth import get_user_model from rest_framework import status -from rest_framework.test import APITestCase +from rest_framework.test import APITestCase, APIClient from book.models import Book +User = get_user_model() class BookViewSetTest(APITestCase): def setUp(self): - """Create a test book entry before tests""" + """Set up test data before running tests""" self.book = Book.objects.create( title="Test Book", author="Test Author", @@ -14,8 +16,32 @@ def setUp(self): daily_fee=10.00, ) - def test_create_book(self): - """Test creating a book via the API""" + # Create a superuser using the custom user model + self.superuser = User.objects.create_superuser( + email="admin@example.com", + password="admin123" + ) + + # Create a regular user using the custom user model + self.user = User.objects.create_user( + email="user@example.com", + password="user123" + ) + + def test_list_books_unauthenticated(self): + """Test: Unauthenticated users can view the list of books""" + response = self.client.get("/api/books/") + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_retrieve_book_unauthenticated(self): + """Test: Unauthenticated users can view book details""" + response = self.client.get(f"/api/books/{self.book.id}/") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data["title"], self.book.title) + + def test_create_book_as_superuser(self): + """Test: Superusers can create new books""" + self.client.force_authenticate(user=self.superuser) data = { "title": "New Book", "author": "New Author", @@ -23,38 +49,60 @@ def test_create_book(self): "inventory": 5, "daily_fee": 12.00, } - response = self.client.post( - "/api/book-service/books/", data, format="json" - ) + response = self.client.post("/api/books/", data, format="json") self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.data["title"], data["title"]) - def test_read_book(self): - """Test retrieving a book via the API""" - response = self.client.get(f"/api/book-service/books/{self.book.id}/") - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.data["title"], self.book.title) + def test_create_book_as_regular_user(self): + """Test: Regular users cannot create new books""" + self.client.force_authenticate(user=self.user) + data = { + "title": "New Book", + "author": "New Author", + "cover": Book.Cover.HARD, + "inventory": 5, + "daily_fee": 12.00, + } + response = self.client.post("/api/books/", data, format="json") + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_update_book(self): - """Test updating a book via the API""" + def test_update_book_as_superuser(self): + """Test: Superusers can update book details""" + self.client.force_authenticate(user=self.superuser) data = { - "inventory": 10, # Перевірка нового значення inventory - "title": self.book.title, # Титул залишається незмінним - "author": self.book.author, # Автор залишається незмінним - "cover": self.book.cover, # Обкладинка залишається незмінною - "daily_fee": self.book.daily_fee, # Залишаємо попереднє значення daily_fee + "inventory": 10, + "title": self.book.title, + "author": self.book.author, + "cover": self.book.cover, + "daily_fee": self.book.daily_fee, } - response = self.client.put( - f"/api/book-service/books/{self.book.id}/", data, format="json" - ) + response = self.client.put(f"/api/books/{self.book.id}/", data, format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) self.book.refresh_from_db() self.assertEqual(self.book.inventory, 10) - def test_delete_book(self): - """Test deleting a book via the API""" - response = self.client.delete( - f"/api/book-service/books/{self.book.id}/" - ) + def test_update_book_as_regular_user(self): + """Test: Regular users cannot update book details""" + self.client.force_authenticate(user=self.user) + data = { + "inventory": 10, + "title": self.book.title, + "author": self.book.author, + "cover": self.book.cover, + "daily_fee": self.book.daily_fee, + } + response = self.client.put(f"/api/books/{self.book.id}/", data, format="json") + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_delete_book_as_superuser(self): + """Test: Superusers can delete books""" + self.client.force_authenticate(user=self.superuser) + response = self.client.delete(f"/api/books/{self.book.id}/") self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertFalse(Book.objects.filter(id=self.book.id).exists()) + + def test_delete_book_as_regular_user(self): + """Test: Regular users cannot delete books""" + self.client.force_authenticate(user=self.user) + response = self.client.delete(f"/api/books/{self.book.id}/") + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) diff --git a/book/views.py b/book/views.py index 828dbe5..b178aa1 100644 --- a/book/views.py +++ b/book/views.py @@ -1,6 +1,7 @@ from rest_framework import viewsets from book.models import Book +from book.permissions import IsAdminOrReadOnly from book.serializers import BookSerializer @@ -13,3 +14,4 @@ class BookViewSet(viewsets.ModelViewSet): queryset = Book.objects.prefetch_related("borrowings") serializer_class = BookSerializer + permission_classes = [IsAdminOrReadOnly,] From e1b1c1d4f481cf010d04e3647faecc579792b040 Mon Sep 17 00:00:00 2001 From: Bondzik-S Date: Fri, 3 Jan 2025 11:13:13 +0200 Subject: [PATCH 3/4] test-commit --- book/permissions.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/book/permissions.py b/book/permissions.py index cb036f8..6680917 100644 --- a/book/permissions.py +++ b/book/permissions.py @@ -1,4 +1,7 @@ -from rest_framework.permissions import SAFE_METHODS, BasePermission +from rest_framework.permissions import ( + SAFE_METHODS, + BasePermission +) class IsAdminOrReadOnly(BasePermission): From 3d8a6cb4720b185f8dae62110c9386426c5a2439 Mon Sep 17 00:00:00 2001 From: Bondzik-S Date: Fri, 3 Jan 2025 11:16:04 +0200 Subject: [PATCH 4/4] feat: fix ruff --- book/tests/test_book_view_set.py | 2 +- book/urls.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/book/tests/test_book_view_set.py b/book/tests/test_book_view_set.py index c310ce1..5f5f303 100644 --- a/book/tests/test_book_view_set.py +++ b/book/tests/test_book_view_set.py @@ -1,6 +1,6 @@ from django.contrib.auth import get_user_model from rest_framework import status -from rest_framework.test import APITestCase, APIClient +from rest_framework.test import APITestCase from book.models import Book User = get_user_model() diff --git a/book/urls.py b/book/urls.py index af197e5..bc8dbb4 100644 --- a/book/urls.py +++ b/book/urls.py @@ -1,4 +1,3 @@ -from django.urls import path, include from rest_framework import routers from book.views import BookViewSet