From 9ab7b5b591ec63226bc960ddb3231b7206063c9a Mon Sep 17 00:00:00 2001 From: a2-imeri <Alfret2.imeri@live.uwe.ac.uk> Date: Thu, 20 Jun 2024 18:16:09 +0100 Subject: [PATCH] Add user Dashboard --- .../__pycache__/settings.cpython-310.pyc | Bin 3629 -> 3680 bytes .../__pycache__/settings.cpython-39.pyc | Bin 3537 -> 3708 bytes .../__pycache__/urls.cpython-310.pyc | Bin 885 -> 941 bytes MisplaceAI/MisplaceAI/settings.py | 3 +- MisplaceAI/MisplaceAI/urls.py | 2 + MisplaceAI/placement_rules/utils.py | 71 ++++++++++++++---- MisplaceAI/user_dashboard/__init__.py | 0 MisplaceAI/user_dashboard/admin.py | 3 + MisplaceAI/user_dashboard/apps.py | 6 ++ MisplaceAI/user_dashboard/models.py | 3 + MisplaceAI/user_dashboard/serializers.py | 8 ++ MisplaceAI/user_dashboard/tests.py | 3 + MisplaceAI/user_dashboard/urls.py | 7 ++ MisplaceAI/user_dashboard/views.py | 15 ++++ frontend/src/App.js | 6 ++ frontend/src/layouts/Navbar/Navbar.js | 11 ++- .../src/pages/UserDashboard/UserDashboard.css | 17 +++++ .../src/pages/UserDashboard/UserDashboard.js | 44 +++++++++++ frontend/src/services/api.js | 2 +- frontend/src/services/auth.js | 4 +- 20 files changed, 186 insertions(+), 19 deletions(-) create mode 100644 MisplaceAI/user_dashboard/__init__.py create mode 100644 MisplaceAI/user_dashboard/admin.py create mode 100644 MisplaceAI/user_dashboard/apps.py create mode 100644 MisplaceAI/user_dashboard/models.py create mode 100644 MisplaceAI/user_dashboard/serializers.py create mode 100644 MisplaceAI/user_dashboard/tests.py create mode 100644 MisplaceAI/user_dashboard/urls.py create mode 100644 MisplaceAI/user_dashboard/views.py create mode 100644 frontend/src/pages/UserDashboard/UserDashboard.css create mode 100644 frontend/src/pages/UserDashboard/UserDashboard.js diff --git a/MisplaceAI/MisplaceAI/__pycache__/settings.cpython-310.pyc b/MisplaceAI/MisplaceAI/__pycache__/settings.cpython-310.pyc index 8217cae40ee3843e77dc74817b9b954ad653e373..e960bd634688fe21b9c521f53e0f352cc4d3ddb8 100644 GIT binary patch delta 107 zcmZ20^FW3-pO=@5fq{WxL3Bx4$41@`M)e>@1_mw#-^}8IoW$f*$4q@V(^J1VwWK67 zFTGf=pmOs}#%YX<LYobkpD+sYl@_NK#it||XC&n(7NtZ<Oz!27*sRN1&B4e!c_nWP E0Mbz*zyJUM delta 56 zcmaDLvsQ*TpO=@5fq{WxdbfXCz((E<MizZW28PXh7^g8Z3T_T&e!|EoG5G+8#O4In MY7R!8$#-~L0G&k-LjV8( diff --git a/MisplaceAI/MisplaceAI/__pycache__/settings.cpython-39.pyc b/MisplaceAI/MisplaceAI/__pycache__/settings.cpython-39.pyc index 14ca3d9fd2ec57dde6a87341fbb00d4500a3edf3..31df9d414c9434dccb4753594eba6ed710b7b5d4 100644 GIT binary patch delta 653 zcmca8{YQo`k(ZZ?fq{X+xx6qfKx87H3}ebf?U;J*bjB#b6c8zt!V@K&!W$)$!k5mF z!k)sP&X6Loh%u8fMQ{#FI%AY*icpkTIzx)^BE}SvbjB3XIZRRFDPk$&DH17?DN-rY zDKaUtbJ#K&qa;$~QsgrkQxsAZQ<O3pQ<Ud0rO2hIWHP46rKn~yrn5vzrl>_prKm?q z*Qc;Y$)vDH$);#T$)&QVXi77rvZrWCGo&*`$){*XDWvjFU@S^VVN7ApQH)Zoa!6HN zpp>Gska2<XLWUGwNrn_gNrn_XNrotuRFws)Df$Z;Qw$^-7O18eE@TABMX9CoEl^KY zTgb@B5YCV%!NQQLn5B`WIf1b<j*)?ppMjAfk|B@DfFXrBm_gHMa|h#WMn=KSD$I`= zrJ^K?Qj1IC(~1&vQ_J&<vg3<0a|?1(v&u^*v$JMQ&S6uZJd0Iiax?2oanX#Dk^(Dz z{ha*d#GH)$;u0%k0|NuM&6C&y8Cf)$tBfa0amY<(=J3?9#AT*lVrfZ6YF<fZa$-ql zex9CJc*tg3j&)3oT9e;!Ez%Zo_74h<cl7b`4|jEm_Yd-P_w)<CCE^?$<Q5+i6dD|Y z5Z&C%ZNbQ9!N9;!WHI?PPklWj0|P^G69WSS3l9$?2eSYh3lj??A2SOJ8w-aR2M^PK z7FG~uVPaunVq{`uV&eGE^oQj?ivSBV8ynMqreAFQU~!Nf2>$}h{byofVq*Ev^pAy| q<v$Y(%YUZNETSw-V93J4#Ky?>kA(w7^KdXSF-kE?F!C`9F#-UVoS=LF delta 491 zcmew(b5WWvk(ZZ?fq{WRXQxlvV&RE=GK>)uwPWhpqXbj9ql8j;(iu|NQ+U%EQur1z zW-_Mm&tXYtj1o=}h!RN=Oc6?FN)b+HOc9yG6eXG>nj)4Wo+6PVnIe@Uogy=bEt4@y zEJZd&E|W1uK1Cr#F_SSxX%170Y>ILwV~T8wN+x4EOO$wuYLrBZT9jl8dz4fPdz5sF zdX!9kDtn5CG(#$Til#I}I#ZNvidK|dD(?iwqL38E6y_ZHDETUfRQUx8DcTDe7bq@d zNYRmGNMV#@NYRyKh*C;bTA-Yww~#SKUy@;ga*Dx1Mvz>TN-Ez1)l`**jEoH740#eP z45{*2YFX+N7%PJq89*?SA&<#`A%!`ZnL*QV^AyI}jEn-C4VWJ@PS$13n7oJe@8qeh z>YFdJ1u!yI8BdPlkelqx;ko%Z$9yJ6jmhcUi#D6`m@~4OGcYg|nNKd_t*>WdU|=Y& zV_;xl;o)KAU>0C!VP;|EV_{)qW91Ox;AUZBVPRrqVq{|C_|No*<v)u6nE#K7mFYjz zZ#G`A97rC7f3f^$5oGz##KOeH@}KD+3p<F-@}KE5ix>+N7_zc3u`#m!W8nhP9E?ni N98!!DjC_nji~vWnYBc}= diff --git a/MisplaceAI/MisplaceAI/__pycache__/urls.cpython-310.pyc b/MisplaceAI/MisplaceAI/__pycache__/urls.cpython-310.pyc index 8483a1b21baf23ab9ef05a13e9bf9e1254ecb39f..a1664ddfcf212cf1b6857548f07d35419a15896b 100644 GIT binary patch delta 142 zcmey$ww9eYpO=@5fq{XcJGvxo`9xk>##<A$SDU1=r%0r7q)0Y1HZ!GjMRBJxX7Lnd zr*fw<r!r>orZQ&nrAYNMnKMLjr$`4gfUu^_#(!mus#U^?1)2J##i>Q{DT&1yN%@II gDf(5yr~;GonI15zOb%d{<>Fyv`Nqa4!N~F-01+N3O#lD@ delta 115 zcmZ3>{*{e4pO=@5fq{WxcDH|8!bDzK#&Z+3S8JqmMscSyX7LoIrgEn;r!r>orZQ&n zrAYKLnKMLjr$`1ffUu_2W-i7u#>uOgZmTIVFfi~if}j8+4>O2{U;#z}Mjl3%?`%90 Hj4b~F6Ji#? diff --git a/MisplaceAI/MisplaceAI/settings.py b/MisplaceAI/MisplaceAI/settings.py index 001bfb4..a1a77a2 100644 --- a/MisplaceAI/MisplaceAI/settings.py +++ b/MisplaceAI/MisplaceAI/settings.py @@ -1,4 +1,5 @@ """ + MisplaceAi/MisplaceAI/settings.py Django settings for MisplaceAI project. Generated by 'django-admin startproject' using Django 4.0.2. @@ -51,7 +52,7 @@ INSTALLED_APPS = [ 'process_misplaced_manager.apps.ProcessMisplacedManagerConfig', 'rest_framework', 'corsheaders', - + 'user_dashboard', 'rest_framework_simplejwt', ] diff --git a/MisplaceAI/MisplaceAI/urls.py b/MisplaceAI/MisplaceAI/urls.py index c1e4986..a747898 100644 --- a/MisplaceAI/MisplaceAI/urls.py +++ b/MisplaceAI/MisplaceAI/urls.py @@ -14,6 +14,8 @@ urlpatterns = [ path('api/process_misplaced_manager/', include('process_misplaced_manager.urls', namespace='process_misplaced_manager')), path('api/results_viewer/', include('results_viewer.urls', namespace='results_viewer')), path('api/placement_rules/', include('placement_rules.urls')), + path('api/user_dashboard/', include('user_dashboard.urls')), + ] if settings.DEBUG: diff --git a/MisplaceAI/placement_rules/utils.py b/MisplaceAI/placement_rules/utils.py index e04857c..9bc52a1 100644 --- a/MisplaceAI/placement_rules/utils.py +++ b/MisplaceAI/placement_rules/utils.py @@ -4,64 +4,109 @@ from rules.models import Rule class PlacementRules: def __init__(self): + # Initialize the PlacementRules object and load rules from the database self.rules = self.load_rules_from_db() def load_rules_from_db(self): + """Load rules from the database and store them in a dictionary.""" rules_dict = {} + # Retrieve all Rule objects from the database rules = Rule.objects.all() for rule in rules: + # Extract the name of the item associated with the rule item_name = rule.item.name + # Extract the list of allowed locations for this item allowed_locations = list(rule.locations.values_list('name', flat=True)) + # Store the item name and its allowed locations in the dictionary rules_dict[item_name] = allowed_locations return rules_dict def check_placement(self, dataLocation): - """Check the placement of each object based on the rules.""" + """Check the placement of each object based on the rules. + + Args: + dataLocation (list): A list of dictionaries, each representing an object's data. + + Returns: + list: A list of misplaced objects with their allowed locations. + """ misplaced_objects = [] print("-----------------------------------") print("--CHECK-PLACEMENT-FOR-EACH-OBJECT--") + + # Iterate over each object in the dataLocation list for obj in dataLocation: object_name = obj["class_name"] + # Retrieve the allowed locations for the current object from the rules dictionary allowed_locations = self.rules.get(object_name, []) is_placed_correctly = False print(f"Checking {object_name}, allowed on: {allowed_locations}") + # Check if the object is correctly placed in any of the allowed locations for location in allowed_locations: is_on_top, _ = self.is_on_top_of(dataLocation, object_name, location) if is_on_top: is_placed_correctly = True break + # If the object is not placed correctly and there are allowed locations, it is misplaced if not is_placed_correctly and allowed_locations: - print( - f"{object_name} is misplaced. It should be on {', '.join(allowed_locations)}." - ) - obj["allowed_locations"] = allowed_locations # Add allowed locations to obj + print(f"{object_name} is misplaced. It should be on {', '.join(allowed_locations)}.") + # Add the allowed locations to the object for reference + obj["allowed_locations"] = allowed_locations + # Add the misplaced object to the list misplaced_objects.append(obj) print("-----------------------------------") return misplaced_objects def is_on_top_of(self, dataLocation, top_object_name, bottom_object_name): - # Filter objects by class_name - top_objects = [ - obj for obj in dataLocation if obj["class_name"] == top_object_name - ] - bottom_objects = [ - obj for obj in dataLocation if obj["class_name"] == bottom_object_name - ] + """Check if any object of top_object_name is on top of any object of bottom_object_name. + + Args: + dataLocation (list): A list of dictionaries, each representing an object's data. + top_object_name (str): The class name of the object to be checked if on top. + bottom_object_name (str): The class name of the object to be checked if below. + + Returns: + tuple: A boolean indicating if the top object is on top of the bottom object, and a message. + """ + # Filter objects by their class name + top_objects = [obj for obj in dataLocation if obj["class_name"] == top_object_name] + bottom_objects = [obj for obj in dataLocation if obj["class_name"] == bottom_object_name] # Define a function to check if two objects have horizontal overlap def has_horizontal_overlap(a, b): + """ + Check if two objects have horizontal overlap based on their bounding box coordinates. + + Args: + a (dict): Dictionary containing bounding box coordinates of the first object. + b (dict): Dictionary containing bounding box coordinates of the second object. + + Returns: + bool: True if there is horizontal overlap, False otherwise. + """ + # 'xmax' is the right edge and 'xmin' is the left edge of the bounding box. + # Check if there is no horizontal overlap: + # 1. a["xmax"] <= b["xmin"]: True if the right edge of 'a' is to the left of or exactly at the left edge of 'b' + # 2. a["xmin"] >= b["xmax"]: True if the left edge of 'a' is to the right of or exactly at the right edge of 'b' + # If either of the above conditions is true, there is no overlap. + # 'not' inverts this to check for overlap instead. return not (a["xmax"] <= b["xmin"] or a["xmin"] >= b["xmax"]) - # Check if any of the top objects is on top of any of the bottom objects + + # Check if any top object is on top of any bottom object for top in top_objects: for bottom in bottom_objects: + # Check if the top and bottom objects have horizontal overlap if has_horizontal_overlap(top, bottom): + # Check if the top object's ymax is within the y-range of the bottom object if top["ymax"] <= bottom["ymax"] and top["ymax"] >= bottom["ymin"]: + # Return True if the top object is found on top of the bottom object return ( True, f"Object {top['Object']} ({top['class_name']}) is on top of Object {bottom['Object']} ({bottom['class_name']})", ) + # Return False if no top object is found on top of a bottom object return False, f"No {top_object_name} is on top of any {bottom_object_name}." diff --git a/MisplaceAI/user_dashboard/__init__.py b/MisplaceAI/user_dashboard/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/MisplaceAI/user_dashboard/admin.py b/MisplaceAI/user_dashboard/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/MisplaceAI/user_dashboard/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/MisplaceAI/user_dashboard/apps.py b/MisplaceAI/user_dashboard/apps.py new file mode 100644 index 0000000..4b36bdd --- /dev/null +++ b/MisplaceAI/user_dashboard/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class UserDashboardConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'user_dashboard' diff --git a/MisplaceAI/user_dashboard/models.py b/MisplaceAI/user_dashboard/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/MisplaceAI/user_dashboard/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/MisplaceAI/user_dashboard/serializers.py b/MisplaceAI/user_dashboard/serializers.py new file mode 100644 index 0000000..46b3e49 --- /dev/null +++ b/MisplaceAI/user_dashboard/serializers.py @@ -0,0 +1,8 @@ +# MisplaceAI/user_dashboard/serializers.py +from django.contrib.auth.models import User +from rest_framework import serializers + +class UserSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ['username', 'email', 'first_name', 'last_name'] diff --git a/MisplaceAI/user_dashboard/tests.py b/MisplaceAI/user_dashboard/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/MisplaceAI/user_dashboard/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/MisplaceAI/user_dashboard/urls.py b/MisplaceAI/user_dashboard/urls.py new file mode 100644 index 0000000..93f9170 --- /dev/null +++ b/MisplaceAI/user_dashboard/urls.py @@ -0,0 +1,7 @@ +# MisplaceAI/user_dashboard/urls.py +from django.urls import path +from .views import UserDashboardView + +urlpatterns = [ + path('dashboard/', UserDashboardView.as_view(), name='user-dashboard'), +] diff --git a/MisplaceAI/user_dashboard/views.py b/MisplaceAI/user_dashboard/views.py new file mode 100644 index 0000000..b428d17 --- /dev/null +++ b/MisplaceAI/user_dashboard/views.py @@ -0,0 +1,15 @@ +# MisplaceAI/user_dashboard/views.py +from django.contrib.auth.models import User +from rest_framework import generics +from rest_framework.permissions import IsAuthenticated +from .serializers import UserSerializer + +class UserDashboardView(generics.RetrieveAPIView): + queryset = User.objects.all() + serializer_class = UserSerializer + permission_classes = [IsAuthenticated] + + def get_object(self): + user = self.request.user + print(f'User: {user}') # Debugging statement + return user diff --git a/frontend/src/App.js b/frontend/src/App.js index 1f8039e..c472e5b 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -13,6 +13,7 @@ import ManageItems from './pages/Items/ManageItemsPage'; import ManageRulesPage from './pages/Rules/ManageRulesPage'; import DetectionOptionsPage from './pages/DetectionOptions/DetectionOptionsPage'; import NormalDetectionPage from './pages/NormalDetection/NormalDetectionPage'; +import UserDashboard from './pages/UserDashboard/UserDashboard'; import ProtectedRoute from './firewall/ProtectedRoute'; import RouteProtection from './firewall/RouteProtection'; @@ -71,6 +72,11 @@ function App() { <NormalDetectionPage /> </ProtectedRoute> } /> + <Route path="/user/dashboard" element={ + <ProtectedRoute> + <UserDashboard /> + </ProtectedRoute> + } /> <Route path="*" element={<HomePage />} /> {/* Catch-all route */} </Routes> </div> diff --git a/frontend/src/layouts/Navbar/Navbar.js b/frontend/src/layouts/Navbar/Navbar.js index 12293a3..749ee39 100644 --- a/frontend/src/layouts/Navbar/Navbar.js +++ b/frontend/src/layouts/Navbar/Navbar.js @@ -31,9 +31,14 @@ const Navbar = () => { <Link className="nav-link" to="/">Home</Link> </li> {isAuthenticated && ( - <li className="nav-item"> - <Link className="nav-link" to="/detection-options">Detect Misplaced Items</Link> - </li> + <> + <li className="nav-item"> + <Link className="nav-link" to="/detection-options">Detect Misplaced Items</Link> + </li> + <li className="nav-item"> + <Link className="nav-link" to="/user/dashboard">User Dashboard</Link> + </li> + </> )} {!isAuthenticated && ( <> diff --git a/frontend/src/pages/UserDashboard/UserDashboard.css b/frontend/src/pages/UserDashboard/UserDashboard.css new file mode 100644 index 0000000..e3fcffc --- /dev/null +++ b/frontend/src/pages/UserDashboard/UserDashboard.css @@ -0,0 +1,17 @@ +/* src/pages/UserDashboard/UserDashboard.css */ + +.user-dashboard { + padding: 20px; + background-color: #f9f9f9; + border-radius: 8px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); +} + +.user-dashboard h1 { + margin-bottom: 20px; +} + +.user-dashboard .user-info p { + margin: 10px 0; + font-size: 16px; +} \ No newline at end of file diff --git a/frontend/src/pages/UserDashboard/UserDashboard.js b/frontend/src/pages/UserDashboard/UserDashboard.js new file mode 100644 index 0000000..94af599 --- /dev/null +++ b/frontend/src/pages/UserDashboard/UserDashboard.js @@ -0,0 +1,44 @@ +// src/pages/UserDashboard/UserDashboard.js +import React, { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import api from '../../services/api'; +import './UserDashboard.css'; + +const UserDashboard = () => { + const [userData, setUserData] = useState(null); + const navigate = useNavigate(); + + useEffect(() => { + const fetchUserData = async () => { + try { + const response = await api.get('/api/user_dashboard/dashboard/'); + setUserData(response.data); + } catch (error) { + console.error('Failed to fetch user data', error); + if (error.response && error.response.status === 401) { + navigate('/login'); + } + } + }; + + fetchUserData(); + }, [navigate]); + + if (!userData) { + return <div>Loading...</div>; + } + + return ( + <div className="user-dashboard"> + <h1>User Dashboard</h1> + <div className="user-info"> + <p><strong>Username:</strong> {userData.username}</p> + <p><strong>Email:</strong> {userData.email}</p> + <p><strong>First Name:</strong> {userData.first_name}</p> + <p><strong>Last Name:</strong> {userData.last_name}</p> + </div> + </div> + ); +}; + +export default UserDashboard; diff --git a/frontend/src/services/api.js b/frontend/src/services/api.js index 6ca18ff..5e6e9d1 100644 --- a/frontend/src/services/api.js +++ b/frontend/src/services/api.js @@ -21,7 +21,7 @@ const api = axios.create({ api.interceptors.request.use( config => { - const token = localStorage.getItem('adminToken'); + const token = localStorage.getItem('token') || localStorage.getItem('adminToken'); const csrfToken = getCsrfToken(); if (token) { config.headers.Authorization = `Bearer ${token}`; diff --git a/frontend/src/services/auth.js b/frontend/src/services/auth.js index 8f3f0ea..73b599b 100644 --- a/frontend/src/services/auth.js +++ b/frontend/src/services/auth.js @@ -1,10 +1,11 @@ -// frontend/src/services/auth.js +// src/services/auth.js import api from './api'; export const login = async (credentials) => { const response = await api.post('/api/auth/login/', credentials); if (response.data.access) { localStorage.setItem('token', response.data.access); + localStorage.setItem('isAuthenticated', true); } return response.data; }; @@ -16,6 +17,7 @@ export const register = async (userData) => { export const logout = () => { localStorage.removeItem('token'); + localStorage.removeItem('isAuthenticated'); }; export const getCurrentUser = async () => { -- GitLab