From ff7136d7926cf935cfaa5d63d110bf3b9866acd4 Mon Sep 17 00:00:00 2001
From: Emily <44536222+emi-hi@users.noreply.github.com>
Date: Mon, 4 Mar 2024 12:28:02 -0800
Subject: [PATCH] Feat Cthub 168 PARTIAL admin page (#193)
* feat: adds backend work for retrieving current user and user list
* feat: adds (UNSTYLED) frontend work for listing users and permissions
* chore: removes idir serializermethodField
* feat: adds styling to user admin
---
django/api/serializers/permission.py | 16 ++++
django/api/serializers/user.py | 36 +++++++++
django/api/urls.py | 4 +
django/api/viewsets/user.py | 56 +++++++++++++
react/src/app/styles/App.scss | 41 ++++++++++
react/src/app/styles/Users.scss | 25 ++++++
react/src/app/styles/index.scss | 26 +-----
react/src/uploads/UploadContainer.js | 11 +++
react/src/uploads/components/UploadPage.js | 4 +-
react/src/users/UsersContainer.js | 38 +++++++++
react/src/users/components/UsersPage.js | 93 ++++++++++++++++++++++
react/src/users/routes.js | 9 +++
12 files changed, 333 insertions(+), 26 deletions(-)
create mode 100644 django/api/serializers/permission.py
create mode 100644 django/api/serializers/user.py
create mode 100644 django/api/viewsets/user.py
create mode 100644 react/src/app/styles/Users.scss
create mode 100644 react/src/users/UsersContainer.js
create mode 100644 react/src/users/components/UsersPage.js
create mode 100644 react/src/users/routes.js
diff --git a/django/api/serializers/permission.py b/django/api/serializers/permission.py
new file mode 100644
index 00000000..73efc23e
--- /dev/null
+++ b/django/api/serializers/permission.py
@@ -0,0 +1,16 @@
+from rest_framework.serializers import ModelSerializer, SerializerMethodField
+
+from api.models.permission import Permission
+from api.models.user_permission import UserPermission
+
+class PermissionSerializer(ModelSerializer):
+ description = SerializerMethodField()
+ def get_description(self, obj):
+ permission = Permission.objects.filter(id=obj.permission_id).first()
+ if permission:
+ return permission.description
+ class Meta:
+ model = Permission
+ fields = (
+ 'id', 'description',
+ )
diff --git a/django/api/serializers/user.py b/django/api/serializers/user.py
new file mode 100644
index 00000000..fc2b490f
--- /dev/null
+++ b/django/api/serializers/user.py
@@ -0,0 +1,36 @@
+"""
+Further reading:
+https://www.django-rest-framework.org/api-guide/serializers/
+"""
+from rest_framework.serializers import ModelSerializer, SerializerMethodField
+
+from api.models.user import User
+from api.models.user_permission import UserPermission
+from api.serializers.permission import PermissionSerializer
+
+class UserSerializer(ModelSerializer):
+ """
+ Default Serializer for User
+ """
+ user_permissions = SerializerMethodField()
+
+ def get_user_permissions(self, obj):
+ user_permission = UserPermission.objects.filter(user_id=obj.id)
+ permissions = PermissionSerializer(user_permission, read_only=True, many=True)
+ return permissions.data
+
+ class Meta:
+ model = User
+ fields = ('idir', 'user_permissions')
+
+
+class UserSaveSerializer(ModelSerializer):
+ def update(self, instance, validated_data):
+ request = self.context.get('request')
+ permissions = validated_data.pop('permissions')
+ print(request)
+ print(permissions)
+ #check if user exists, if not add them
+
+ #update user_permissions
+
diff --git a/django/api/urls.py b/django/api/urls.py
index 2ee60438..1d2c48db 100644
--- a/django/api/urls.py
+++ b/django/api/urls.py
@@ -20,6 +20,7 @@
from api.viewsets.icbc_data import IcbcViewset
from api.viewsets.minio import MinioViewSet
from api.viewsets.upload import UploadViewset
+from api.viewsets.user import UserViewSet
ROUTER = routers.SimpleRouter(trailing_slash=False)
@@ -34,6 +35,9 @@
ROUTER.register(
r'minio', MinioViewSet, basename='minio'
)
+ROUTER.register(
+ r'users', UserViewSet
+)
urlpatterns = [
path('admin/', admin.site.urls),
diff --git a/django/api/viewsets/user.py b/django/api/viewsets/user.py
new file mode 100644
index 00000000..13d0629c
--- /dev/null
+++ b/django/api/viewsets/user.py
@@ -0,0 +1,56 @@
+from rest_framework.decorators import action
+from rest_framework.permissions import AllowAny
+from rest_framework.response import Response
+from rest_framework.viewsets import GenericViewSet
+from api.models.user import User
+from api.serializers.user import UserSerializer, UserSaveSerializer
+
+class UserViewSet(GenericViewSet):
+ """
+ This viewset automatically provides `list`, `create`, `retrieve`,
+ and `update` actions.
+ """
+ permission_classes = (AllowAny,)
+ http_method_names = ['get', 'post', 'put', 'patch']
+ queryset = User.objects.all()
+
+ serializer_classes = {
+ 'default': UserSerializer,
+ 'update': UserSaveSerializer,
+ 'create': UserSaveSerializer,
+ }
+
+
+ def get_serializer_class(self):
+ if self.action in list(self.serializer_classes.keys()):
+ return self.serializer_classes[self.action]
+
+ return self.serializer_classes['default']
+
+
+ @action(detail=False)
+ def current(self, request):
+ """
+ Get the current user
+ """
+ user = User.objects.filter(idir=request.user).first()
+ serializer = self.get_serializer(user)
+ return Response(serializer.data)
+
+ def list(self, request):
+ request = self.request
+ ##check if user is admin before producing list of all users
+ users = User.objects.all()
+ current_user = users.filter(idir=request.user).first()
+ if current_user:
+ current_user_serializer = UserSerializer(current_user)
+ current_user_permissions = current_user_serializer.data['user_permissions']
+ is_admin = False
+ if current_user_permissions:
+ for i in current_user_permissions:
+ for v in i.values():
+ if v == 'admin':
+ is_admin = True
+ if is_admin == True:
+ serializer = UserSerializer(users, many=True)
+ return Response(serializer.data)
\ No newline at end of file
diff --git a/react/src/app/styles/App.scss b/react/src/app/styles/App.scss
index 66b0427c..b05e911f 100644
--- a/react/src/app/styles/App.scss
+++ b/react/src/app/styles/App.scss
@@ -2,6 +2,17 @@
Base styling for "App"
ie General Container
*/
+$bg-alt-black: #1E1F21;
+$bg-primary: #244074;
+$bg-black: #000;
+$bg-white: #FFF;
+$default-text-color: #000;
+$default-table-border-color: rgba(249, 249, 249, 0.2);
+$default-table-color: #F9F9F9;
+$highlight: #0078FD;
+$default-link-blue: #568DBA;
+$default-background-grey: #F2F2F2;
+$md: 991px;
.App {
.App-header {
@@ -38,3 +49,33 @@ ie General Container
padding: 0 1rem;
}
}
+
+
+body {
+ background-color: $bg-white;
+ color: $default-text-color;
+ font-family: 'Roboto', 'Open Sans', sans-serif;
+ font-weight: 400;
+ height: 100%;
+ margin: 0;
+}
+
+h2, h3, h4 {
+ font-family: 'Roboto', 'Open Sans', sans-serif;
+ color: #003366;
+ font-weight: 500;
+}
+
+h2 {
+ font-size: 20px;
+}
+
+h3 {
+ display: inline;
+ font-size: 17px;
+}
+
+h4 {
+ font-size: 14px;
+}
+
diff --git a/react/src/app/styles/Users.scss b/react/src/app/styles/Users.scss
new file mode 100644
index 00000000..564814c6
--- /dev/null
+++ b/react/src/app/styles/Users.scss
@@ -0,0 +1,25 @@
+.add-user-box {
+ height: 4rem;
+ background-color: $default-background-grey;
+ margin-bottom: 1rem;
+ width: 40%;
+}
+
+.permissions {
+ background-color: $default-background-grey;
+ align-content: space-around;
+ width: 10%;
+ .checkbox {
+ width: 40%;
+ }
+}
+
+.user-input {
+ margin-left: 10px;
+ margin-right: 10px;
+ background-color: white;
+}
+
+.button-dark-blue {
+ background-color: #003366 !important;
+}
\ No newline at end of file
diff --git a/react/src/app/styles/index.scss b/react/src/app/styles/index.scss
index e9655906..1a7bf42a 100644
--- a/react/src/app/styles/index.scss
+++ b/react/src/app/styles/index.scss
@@ -1,30 +1,6 @@
-/*
-Base Style
-Everything else will generally be controlled via bootstrap
-*/
-$bg-alt-black: #1E1F21;
-$bg-primary: #244074;
-$bg-black: #000;
-$bg-white: #FFF;
-$default-text-color: #000;
-$default-table-border-color: rgba(249, 249, 249, 0.2);
-$default-table-color: #F9F9F9;
-$highlight: #0078FD;
-$default-link-blue: #568DBA;
-$default-background-grey: #F2F2F2;
-$md: 991px;
-
@import 'App.scss';
@import 'Login.scss';
@import 'ReactTable.scss';
@import 'FileUpload.scss';
@import 'Roboto.scss';
-
-body {
- background-color: $bg-white;
- color: $default-text-color;
- font-family: 'Roboto', 'Open Sans', sans-serif;
- font-weight: 700;
- height: 100%;
- margin: 0;
-}
\ No newline at end of file
+@import 'Users.scss';
diff --git a/react/src/uploads/UploadContainer.js b/react/src/uploads/UploadContainer.js
index c312d410..8bcbf52d 100644
--- a/react/src/uploads/UploadContainer.js
+++ b/react/src/uploads/UploadContainer.js
@@ -4,8 +4,10 @@ import CircularProgress from '@mui/material/CircularProgress';
import Alert from '@mui/material/Alert';
import React, { useState, useEffect } from 'react';
import ROUTES_UPLOAD from './routes';
+import ROUTES_USERS from '../users/routes';
import UploadPage from './components/UploadPage';
import AlertDialog from '../app/components/AlertDialog';
+import UsersContainer from '../users/UsersContainer';
const UploadContainer = () => {
const [uploadFiles, setUploadFiles] = useState([]); // array of objects for files to be uploaded
@@ -18,6 +20,7 @@ const UploadContainer = () => {
const [alertSeverity, setAlertSeverity] = useState('');
// existing data with what is being uploaded
const [open, setOpen] = useState(false);
+ const [adminUser, setAdminUser] = useState(false);
const dialogue = 'Selecting replace will delete all previously uploaded records for this dataset';
const leftButtonText = 'Cancel';
const rightButtonText = 'Replace existing data';
@@ -34,6 +37,12 @@ const UploadContainer = () => {
axios.get(ROUTES_UPLOAD.LIST).then((response) => {
setDatasetList(response.data);
setLoading(false);
+ axios.get(ROUTES_USERS.CURRENT).then((currentUserResp) => {
+ const permissions = currentUserResp.data.user_permissions.map((each) => each.description);
+ if (permissions.includes('admin')) {
+ setAdminUser(true);
+ }
+ });
});
};
@@ -137,6 +146,8 @@ const UploadContainer = () => {
handleRadioChange={handleRadioChange}
downloadSpreadsheet={downloadSpreadsheet}
/>
+ {adminUser
+ && }
);
diff --git a/react/src/uploads/components/UploadPage.js b/react/src/uploads/components/UploadPage.js
index 8ea4c15d..81a51c86 100644
--- a/react/src/uploads/components/UploadPage.js
+++ b/react/src/uploads/components/UploadPage.js
@@ -33,7 +33,9 @@ const UploadPage = (props) => {
- Dataset to Upload
+
+ Select Program
+