diff --git a/MisplaceAI/process_misplaced_manager/views.py b/MisplaceAI/process_misplaced_manager/views.py index 0ade34979e883f0ecd2945ed56a4da63cf1bbf4f..9fbceb208be4a1131d8a142d2f0961d880178542 100644 --- a/MisplaceAI/process_misplaced_manager/views.py +++ b/MisplaceAI/process_misplaced_manager/views.py @@ -41,14 +41,11 @@ class UploadedImageViewSet(viewsets.ModelViewSet): serializer_class = UploadedImageSerializer # Serializer class for uploaded images permission_classes = [IsAuthenticated] # Only authenticated users can access -# API view for normal detection on uploaded images +# API view for normal detection on uploaded imagesm to detect misplaced items @api_view(['POST']) @permission_classes([IsAuthenticated]) def normal_detection(request): """Handle image upload, run object detection, and check for misplaced objects.""" - print("Received request for normal detection") - print("Request data:", request.data) - print("Request FILES:", request.FILES) try: if 'capturedImageData' in request.data: # Process base64 encoded image data diff --git a/MisplaceAI/pytest.ini b/MisplaceAI/pytest.ini index 981b79b95e209eaf20014f48c55937978e924c32..78635f9ce5516f17c472cc5b49367078718c0738 100644 --- a/MisplaceAI/pytest.ini +++ b/MisplaceAI/pytest.ini @@ -3,4 +3,5 @@ DJANGO_SETTINGS_MODULE = MisplaceAI.settings addopts = -v --ignore=MisplaceAI/models testpaths = rules/tests - +filterwarnings = + ignore::django.utils.deprecation.RemovedInDjango41Warning diff --git a/MisplaceAI/results_viewer/utils.py b/MisplaceAI/results_viewer/utils.py index a9388e2506f83c20af590c8cd60f11bca926e203..520b69662e062083f0d4ff748d4218c2b853f92b 100644 --- a/MisplaceAI/results_viewer/utils.py +++ b/MisplaceAI/results_viewer/utils.py @@ -98,7 +98,7 @@ def visualize_pil_misplaced_objects(image_pil, detected_objects, misplaced_objec misplaced_names = [obj["class_name"] for obj in misplaced_objects] # Load a font using absolute path to the static directory - font_size = 40 # Set the font size (increase if needed) + font_size = 25 # Set the font size (increase if needed) font_path = os.path.join(settings.BASE_DIR, 'core/static/core/fonts/Arial.ttf') # Path to the font file try: font = ImageFont.truetype(font_path, font_size) diff --git a/MisplaceAI/rules/__pycache__/views.cpython-310.pyc b/MisplaceAI/rules/__pycache__/views.cpython-310.pyc index 640e602c37df9e18fdb78e344c73f3e2edbd9abe..1b6771d072a0121af1c0f8569ec94a78e46e978f 100644 Binary files a/MisplaceAI/rules/__pycache__/views.cpython-310.pyc and b/MisplaceAI/rules/__pycache__/views.cpython-310.pyc differ diff --git a/MisplaceAI/rules/tests/test_permissions.py b/MisplaceAI/rules/tests/test_permissions.py new file mode 100644 index 0000000000000000000000000000000000000000..8286aee61b6ca5f1e40f23407995599f8f59be68 --- /dev/null +++ b/MisplaceAI/rules/tests/test_permissions.py @@ -0,0 +1,145 @@ +from rest_framework.test import APITestCase +from rest_framework import status +from django.urls import reverse +from django.contrib.auth.models import User +from rules.models import Item, Location + +class PermissionsTest(APITestCase): + """ + Test suite for permissions to ensure only authenticated users can access item and location endpoints. + """ + + def setUp(self): + # Create a normal user and an admin user + self.normal_user = User.objects.create_user(username='normaluser', password='testpassword') + self.admin_user = User.objects.create_superuser(username='adminuser', password='adminpassword') + + # Create an item and a location + self.item = Item.objects.create(name='Test Item') + self.location = Location.objects.create(name='Test Location') + + def test_normal_user_cannot_access_items(self): + """ + Ensure a normal user cannot access the item management endpoints. + """ + self.client.force_authenticate(user=self.normal_user) + + # Attempt to access item list + url = reverse('rules:admin_manage_item') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + # Attempt to create an item + data = {'name': 'New Item'} + response = self.client.post(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + # Attempt to update an item + url = reverse('rules:admin_manage_item_detail', args=[self.item.id]) + data = {'name': 'Updated Item'} + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + # Attempt to delete an item + response = self.client.delete(url) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_normal_user_cannot_access_locations(self): + """ + Ensure a normal user cannot access the location management endpoints. + """ + self.client.force_authenticate(user=self.normal_user) + + # Attempt to access location list + url = reverse('rules:admin_manage_location') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + # Attempt to create a location + data = {'name': 'New Location'} + response = self.client.post(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + # Attempt to update a location + url = reverse('rules:admin_manage_location_detail', args=[self.location.id]) + data = {'name': 'Updated Location'} + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + # Attempt to delete a location + response = self.client.delete(url) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_admin_user_can_access_items_and_locations(self): + """ + Ensure an admin user can access the item and location management endpoints. + """ + self.client.force_authenticate(user=self.admin_user) + + # Access item list + url = reverse('rules:admin_manage_item') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + # Create an item + data = {'name': 'New Item'} + response = self.client.post(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + # Update an item + url = reverse('rules:admin_manage_item_detail', args=[self.item.id]) + data = {'name': 'Updated Item'} + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + # Delete an item + response = self.client.delete(url) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + + # Access location list + url = reverse('rules:admin_manage_location') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + # Create a location + data = {'name': 'New Location'} + response = self.client.post(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + # Update a location + url = reverse('rules:admin_manage_location_detail', args=[self.location.id]) + data = {'name': 'Updated Location'} + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + # Delete a location + response = self.client.delete(url) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + + def test_normal_user_can_access_rules(self): + """ + Ensure a normal user can access the rules management endpoints. + """ + self.client.force_authenticate(user=self.normal_user) + + # Access rule list + url = reverse('rules:admin_manage_rule') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + # Create a rule + data = {'item': self.item.id, 'locations': [self.location.id]} + response = self.client.post(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + # Update a rule + url = reverse('rules:admin_manage_rule_detail', args=[self.item.id]) + new_item = Item.objects.create(name='New Item') + new_location = Location.objects.create(name='New Location') + data = {'item': new_item.id, 'locations': [new_location.id]} + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + # Delete a rule + response = self.client.delete(url) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) diff --git a/MisplaceAI/rules/tests/test_views.py b/MisplaceAI/rules/tests/test_views.py index 441e11b0f123ddec2554ec7c4ffacd233a72602e..e501fe619eed1abea98f7fc6422c3b531c011c92 100644 --- a/MisplaceAI/rules/tests/test_views.py +++ b/MisplaceAI/rules/tests/test_views.py @@ -1,18 +1,49 @@ -# misplaceAI/rules/tests/test_views.py - from rest_framework.test import APITestCase from rest_framework import status from django.urls import reverse from django.contrib.auth.models import User -from rules.models import Item +from rules.models import Location, Item, Rule -class AdminManageItemViewTest(APITestCase): +class UserListViewTest(APITestCase): + """ + Test suite for the UserListView. + """ def setUp(self): # Create a user self.user = User.objects.create_user(username='testuser', password='testpassword') self.client.force_authenticate(user=self.user) + def test_list_users(self): + """ + Ensure we can list all users. + """ + url = reverse('rules:user-list') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + self.assertEqual(response.data[0]['username'], 'testuser') + +class AdminManageItemViewTest(APITestCase): + """ + Test suite for the AdminManageItemView. + """ + + def setUp(self): + self.user = User.objects.create_user(username='testuser', password='testpassword') + self.client.force_authenticate(user=self.user) + self.item = Item.objects.create(name='Test Item') + + def test_get_items(self): + """ + Ensure we can retrieve the list of items. + """ + url = reverse('rules:admin_manage_item') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + self.assertEqual(response.data[0]['name'], 'Test Item') + def test_create_item(self): """ Ensure we can create a new item. @@ -20,9 +51,132 @@ class AdminManageItemViewTest(APITestCase): url = reverse('rules:admin_manage_item') data = {'name': 'New Item'} response = self.client.post(url, data, format='json') - - # Check if the response status is 201 Created self.assertEqual(response.status_code, status.HTTP_201_CREATED) - # Check if the item was actually created - self.assertEqual(Item.objects.count(), 1) - self.assertEqual(Item.objects.get().name, 'New Item') + self.assertEqual(Item.objects.count(), 2) + self.assertEqual(Item.objects.get(id=response.data['id']).name, 'New Item') + + def test_update_item(self): + """ + Ensure we can update an existing item. + """ + url = reverse('rules:admin_manage_item_detail', args=[self.item.id]) + data = {'name': 'Updated Item'} + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(Item.objects.get(id=self.item.id).name, 'Updated Item') + + def test_delete_item(self): + """ + Ensure we can delete an existing item. + """ + url = reverse('rules:admin_manage_item_detail', args=[self.item.id]) + response = self.client.delete(url) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + self.assertEqual(Item.objects.count(), 0) + +class AdminManageLocationViewTest(APITestCase): + """ + Test suite for the AdminManageLocationView. + """ + + def setUp(self): + self.user = User.objects.create_user(username='testuser', password='testpassword') + self.client.force_authenticate(user=self.user) + self.location = Location.objects.create(name='Test Location') + + def test_get_locations(self): + """ + Ensure we can retrieve the list of locations. + """ + url = reverse('rules:admin_manage_location') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + self.assertEqual(response.data[0]['name'], 'Test Location') + + def test_create_location(self): + """ + Ensure we can create a new location. + """ + url = reverse('rules:admin_manage_location') + data = {'name': 'New Location'} + response = self.client.post(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(Location.objects.count(), 2) + self.assertEqual(Location.objects.get(id=response.data['id']).name, 'New Location') + + def test_update_location(self): + """ + Ensure we can update an existing location. + """ + url = reverse('rules:admin_manage_location_detail', args=[self.location.id]) + data = {'name': 'Updated Location'} + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(Location.objects.get(id=self.location.id).name, 'Updated Location') + + def test_delete_location(self): + """ + Ensure we can delete an existing location. + """ + url = reverse('rules:admin_manage_location_detail', args=[self.location.id]) + response = self.client.delete(url) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + self.assertEqual(Location.objects.count(), 0) + +class AdminManageRuleViewTest(APITestCase): + """ + Test suite for the AdminManageRuleView. + """ + + def setUp(self): + self.user = User.objects.create_user(username='testuser', password='testpassword') + self.client.force_authenticate(user=self.user) + self.item = Item.objects.create(name='Test Item') + self.location = Location.objects.create(name='Test Location') + self.rule = Rule.objects.create(user=self.user, item=self.item) + self.rule.locations.add(self.location) + + def test_get_rules(self): + """ + Ensure we can retrieve the list of rules. + """ + url = reverse('rules:admin_manage_rule') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + self.assertEqual(response.data[0]['item']['id'], self.item.id) + + def test_create_rule(self): + """ + Ensure we can create a new rule. + """ + url = reverse('rules:admin_manage_rule') + data = {'item': self.item.id, 'locations': [self.location.id]} + response = self.client.post(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(Rule.objects.count(), 2) + self.assertEqual(Rule.objects.get(id=response.data['id']).item.id, self.item.id) + + def test_update_rule(self): + """ + Ensure we can update an existing rule. + """ + url = reverse('rules:admin_manage_rule_detail', args=[self.rule.id]) + new_item = Item.objects.create(name='New Item') + new_location = Location.objects.create(name='New Location') + data = {'item': new_item.id, 'locations': [new_location.id]} + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + updated_rule = Rule.objects.get(id=self.rule.id) + self.assertEqual(updated_rule.item.id, new_item.id) + self.assertEqual(updated_rule.locations.first().id, new_location.id) + + def test_delete_rule(self): + """ + Ensure we can delete an existing rule. + """ + url = reverse('rules:admin_manage_rule_detail', args=[self.rule.id]) + response = self.client.delete(url) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + self.assertEqual(Rule.objects.count(), 0) \ No newline at end of file diff --git a/MisplaceAI/rules/views.py b/MisplaceAI/rules/views.py index 615067a6c01355a62f8cf19c99513e99054583c3..94223ea7f9925e8aee8dc750d3577f4de2ae1ea1 100644 --- a/MisplaceAI/rules/views.py +++ b/MisplaceAI/rules/views.py @@ -7,18 +7,19 @@ from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status -from rest_framework.permissions import IsAuthenticated +from rest_framework.permissions import IsAuthenticated, IsAdminUser from django.contrib.auth.models import User from .models import Location, Item, Rule from .serializers import LocationSerializer, ItemSerializer, RuleSerializer, UserSerializer from django.shortcuts import get_object_or_404 +from rest_framework.decorators import permission_classes + class UserListView(APIView): """ View to list all users. Only accessible by authenticated users. """ permission_classes = [IsAuthenticated] - def get(self, request, *args, **kwargs): # Retrieve all user objects from the database users = User.objects.all() @@ -27,12 +28,13 @@ class UserListView(APIView): # Return the serialized data with a 200 OK status return Response(serializer.data, status=status.HTTP_200_OK) + + class AdminManageItemView(APIView): """ View to manage items. Only accessible by authenticated users. """ - permission_classes = [IsAuthenticated] - + @permission_classes([IsAuthenticated]) def get(self, request, *args, **kwargs): # Retrieve all item objects from the database, ordered by name items = Item.objects.all().order_by('name') @@ -40,7 +42,8 @@ class AdminManageItemView(APIView): serializer = ItemSerializer(items, many=True) # Return the serialized data with a 200 OK status return Response(serializer.data, status=status.HTTP_200_OK) - + + @permission_classes([IsAdminUser]) def post(self, request, *args, **kwargs): # Deserialize the incoming data into an ItemSerializer serializer = ItemSerializer(data=request.data) @@ -53,6 +56,7 @@ class AdminManageItemView(APIView): # Return validation errors with a 400 Bad Request status return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + @permission_classes([IsAdminUser]) def put(self, request, item_id, *args, **kwargs): # Retrieve the existing item object from the database item = get_object_or_404(Item, id=item_id) @@ -66,7 +70,8 @@ class AdminManageItemView(APIView): return Response(serializer.data, status=status.HTTP_200_OK) # Return validation errors with a 400 Bad Request status return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - + + @permission_classes([IsAdminUser]) def delete(self, request, item_id, *args, **kwargs): # Retrieve the existing item object from the database item = get_object_or_404(Item, id=item_id) @@ -123,6 +128,7 @@ class AdminManageLocationView(APIView): # Return a 204 No Content status to indicate successful deletion return Response(status=status.HTTP_204_NO_CONTENT) +# misplaceAI/rules/views.py class AdminManageRuleView(APIView): """ View to manage rules. Only accessible by authenticated users. diff --git a/frontend/src/services/locationApi.js b/frontend/src/services/locationApi.js index 58e95a761df8cd2354d1680b9dbe9173c57062c9..e29fde8ad319dea18ef389d343a15ebb9119dbb1 100644 --- a/frontend/src/services/locationApi.js +++ b/frontend/src/services/locationApi.js @@ -1,3 +1,4 @@ +// src/services/locationApi.js import api, { getCsrfToken } from './api'; export const addLocation = async (locationData) => {