Skip to content

Commit

Permalink
feat: 210 - page refinements + cleanup (#223)
Browse files Browse the repository at this point in the history
  • Loading branch information
tim738745 authored Mar 14, 2024
1 parent de6b1dd commit 3ea275e
Show file tree
Hide file tree
Showing 16 changed files with 130 additions and 64 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ The Clean Transportation Data Hub provides an evidence base for the Clean Transp
- This is where you can make changes to your package.json
- You can technically make changes to your packages without going into your container, but you'll need npm installed into your system

# Rebasing Guide
- To rebase your branch onto the latest release branch:
- ```git fetch upstream```
- ```git checkout your_branch```
- ```git rebase --onto A B```
- Where `upstream` is the remote containing the release branch, and `A` is the hash of the latest commit to the release branch, and `B` is the hash of the commit in `your_branch` such that every commit after `B` ought to be rebased onto the release branch.
- If you run into conflicts while rebasing, you can resolve them in your IDE, and `git add` the resolved changes before finishing the rebase (committing).
- The rebased commits will have different hashes than the old ones, so if you previously pushed `your_branch` to a remote you will have to `git push --force` in order not to end up with additional commits in your remote branch.
- On Github, you can modify the base branch of a PR if you're rebasing from a branch based on a previous release branch to the latest release branch.

# License
The code is a fork from Richard's personal project. Please do not clone, copy or replicate this project unless you're authorized to do so.

Expand Down
3 changes: 3 additions & 0 deletions django/api/decorators/permission.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from rest_framework.response import Response
from rest_framework import status
from api.services.permissions import create_permission_list
from api.models.permission import Permission
from api.models.user import User
from api.models.user_permission import UserPermission

def check_upload_permission():
def wrapper(func):
Expand Down
47 changes: 25 additions & 22 deletions django/api/serializers/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
Further reading:
https://www.django-rest-framework.org/api-guide/serializers/
"""
from rest_framework.serializers import ModelSerializer, SerializerMethodField
from rest_framework.serializers import ModelSerializer, SerializerMethodField, ValidationError

from api.models.user import User
from api.models.user_permission import UserPermission
from api.serializers.permission import PermissionSerializer
from api.services.permissions import get_permissions_representation

class UserSerializer(ModelSerializer):
"""
Expand All @@ -15,30 +15,33 @@ class UserSerializer(ModelSerializer):
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)
admin = False
uploader = False
for i in permissions.data:
if i['description'] == 'admin':
admin = True
if i['description'] == 'uploader':
uploader = True

return {'admin': admin, 'uploader': uploader}
user_permission = UserPermission.objects.select_related("permission").filter(user_id=obj.id)
permissions = []
for each in user_permission:
permissions.append(each.permission)
return get_permissions_representation(permissions)

def validate_idir(self, value):
if isinstance(value, str) and value.strip():
return value.strip().upper()
raise ValidationError("IDIR error!")

def create(self, validated_data):
return User.objects.create(**validated_data)

class Meta:
model = User
fields = ('idir', 'user_permissions')

# requires permissions_map object
class UserListSerializer(ModelSerializer):
user_permissions = SerializerMethodField()

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
def get_user_permissions(self, obj):
permissions_map = self.context.get("permissions_map")
permissions = permissions_map.get(obj)
return get_permissions_representation(permissions)

#update user_permissions

class Meta:
model = User
fields = ('idir', 'user_permissions')
8 changes: 8 additions & 0 deletions django/api/services/generic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# gets a map of specified model values to model instances
def get_objects_map(qs, key_field):
result = {}
for object in qs:
key = getattr(object, key_field, None)
if key:
result[key] = object
return result
22 changes: 21 additions & 1 deletion django/api/services/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,24 @@ def create_permission_list(user):
for each in user_permission:
permission = Permission.objects.get(id=each.permission_id)
permissions.append(permission.description)
return permissions
return permissions


def get_permissions_map(users):
result = {}
user_permissions = UserPermission.objects.select_related("user", "permission").filter(user__in=users)
for each in user_permissions:
user = each.user
permission = each.permission
if not user in result:
result[user] = []
result[user].append(permission)
return result


def get_permissions_representation(permissions):
result = {}
if permissions is not None:
for permission in permissions:
result[permission.description] = True
return result
34 changes: 18 additions & 16 deletions django/api/services/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,24 @@
from api.models.user_permission import UserPermission
from api.models.permission import Permission
from api.models.user import User
from api.services.generic import get_objects_map


# this deletes all records in user_permission, and adds the new ones
@transaction.atomic
def update_permissions(self, request):
msg = []
permissions = Permission.objects.all()
def update_permissions(user_permissions):
permissions_map = get_objects_map(Permission.objects.all(), "description")
users_map = get_objects_map(User.objects.all(), "idir")
UserPermission.objects.all().delete()
for each in request.data:
for k, v in each.items():
if k == 'idir':
user = User.objects.get(idir=v)
if k == 'user_permissions':
for permission_description, value in v.items():
if value == True or (user.idir == request.user and permission_description == 'admin'):
## if they are updating permissions then they are already admin user, they cannot remove their own admin
permission = permissions.get(description=permission_description)
try:
UserPermission.objects.create(user_id=user.id, permission_id=permission.id)
except Exception as error:
msg.append("{} permission could not be added to {}".format(permission_description, user.idir))
user_permissions_to_add = []
for each in user_permissions:
idir = each["idir"]
permissions = each["user_permissions"]
user = users_map.get(idir)
permission_objects = []
for description, value in permissions.items():
if value == True:
permission_objects.append(permissions_map.get(description))
for permission_object in permission_objects:
user_permissions_to_add.append(UserPermission(user=user, permission=permission_object))
UserPermission.objects.bulk_create(user_permissions_to_add)
29 changes: 13 additions & 16 deletions django/api/viewsets/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import CreateModelMixin
from api.models.user import User
from api.serializers.user import UserSerializer
from api.serializers.user import UserSerializer, UserListSerializer
from api.decorators.permission import check_admin_permission
from api.services.user import update_permissions
from api.services.permissions import get_permissions_map

class UserViewSet(GenericViewSet):
class UserViewSet(GenericViewSet, CreateModelMixin):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
and `update` actions.
Expand All @@ -30,24 +32,19 @@ def get_serializer_class(self):
return self.serializer_classes['default']


@action(detail=False, methods=['post'])
@method_decorator(check_admin_permission())
def new(self, request):
user_to_insert = request.data['idir'].upper()
try:
User.objects.get_or_create(idir=user_to_insert)
return Response(user_to_insert, status=status.HTTP_200_OK)
except Exception as e:
return Response({"response": str(e)}, status=status.HTTP_400_BAD_REQUEST)
def create(self, request):
return super().create(request)

@action(detail=False, methods=['put'])
@method_decorator(check_admin_permission())
def update_permissions(self, request):
error_msg = update_permissions(self, request)
if error_msg:
return Response(error_msg, status=status.HTTP_400_BAD_REQUEST)
else:
return Response('User permissions were updated!', status=status.HTTP_201_CREATED)
user_permissions = request.data
try:
update_permissions(user_permissions)
except Exception as e:
return Response(str(e), status=status.HTTP_400_BAD_REQUEST)
return Response('User permissions were updated!', status=status.HTTP_201_CREATED)

@action(detail=False)
def current(self, request):
Expand All @@ -61,5 +58,5 @@ def current(self, request):
@method_decorator(check_admin_permission())
def list(self, request):
users = User.objects.all().order_by('idir')
serializer = UserSerializer(users, many=True)
serializer = UserListSerializer(users, many=True, context={"permissions_map": get_permissions_map(users)})
return Response(serializer.data)
5 changes: 5 additions & 0 deletions react/src/app/styles/App.scss
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ h4 {
.text-button {
color: #1a5a96 !important;
text-decoration: underline !important;
text-transform: none !important;
}

.button-lowercase {
text-transform: none !important;
}

.layout {
Expand Down
6 changes: 6 additions & 0 deletions react/src/app/styles/FileUpload.scss
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,9 @@
padding-bottom: 1rem;
margin-top: 2rem;
}

.file-upload {
&.disabled {
background-color: $default-background-grey;
}
}
5 changes: 3 additions & 2 deletions react/src/uploads/UploadContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,11 @@ const UploadContainer = () => {
return <Loading />
}

const alertElement = alert && alertContent && alertSeverity ? <Alert severity={alertSeverity}>{alertContent}</Alert> : null

return (
<div className="row">
<div className="col-12 mr-2">
{alert && alertContent && alertSeverity
&& <Alert severity={alertSeverity}>{alertContent}</Alert>}
{open && (
<AlertDialog
open={open}
Expand All @@ -134,6 +134,7 @@ const UploadContainer = () => {
<Stack direction="column" spacing={2}>
<Paper square variant="outlined">
<UploadPage
alertElement={alertElement}
uploadFiles={uploadFiles}
datasetList={datasetList}
doUpload={doUpload}
Expand Down
6 changes: 4 additions & 2 deletions react/src/uploads/components/FileDrop.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useDropzone } from 'react-dropzone';

const FileDrop = (props) => {
const {
disabled,
setFiles,
setAlert,
} = props;
Expand All @@ -16,10 +17,11 @@ const FileDrop = (props) => {
setFiles(files);
}, []);
const { getRootProps, getInputProps } = useDropzone({ onDrop });
const uploadBoxClassNames = disabled ? "file-upload disabled" : "file-upload"
return (
<div {...getRootProps()}>
<input {...getInputProps()} />
<div className="file-upload">
<input disabled={disabled} {...getInputProps()} />
<div className={uploadBoxClassNames}>
<UploadIcon />
<br />
Drag and Drop files here or
Expand Down
3 changes: 3 additions & 0 deletions react/src/uploads/components/FileDropArea.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import getFileSize from '../../app/utilities/getFileSize';

const FileDropArea = (props) => {
const {
disabled,
setUploadFiles,
uploadFiles,
setAlert,
Expand Down Expand Up @@ -49,6 +50,7 @@ const FileDropArea = (props) => {
<div className="content">
<Box p={2}>
<FileDrop
disabled={disabled}
setAlert={setAlert}
setFiles={setUploadFiles}
/>
Expand All @@ -75,6 +77,7 @@ const FileDropArea = (props) => {
);
};
FileDropArea.propTypes = {
disabled: PropTypes.bool.isRequired,
setUploadFiles: PropTypes.func.isRequired,
uploadFiles: PropTypes.arrayOf(PropTypes.shape()).isRequired,
setAlert: PropTypes.func.isRequired,
Expand Down
10 changes: 8 additions & 2 deletions react/src/uploads/components/UploadPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import FileDropArea from './FileDropArea';

const UploadPage = (props) => {
const {
alertElement,
datasetList,
datasetSelected,
doUpload,
Expand All @@ -28,6 +29,7 @@ const UploadPage = (props) => {
<>
<Box p={3}>
<h2>Upload Program Data</h2>
{alertElement}
<div id="dataset-select">
<span>
<h3>
Expand All @@ -41,7 +43,7 @@ const UploadPage = (props) => {
>
{selectionList}
</Select>
<Button className="text-button" onClick={downloadSpreadsheet}>Download Spreadsheet</Button>
<Button className="text-button" onClick={downloadSpreadsheet}>Download Excel File (program data upload template)</Button>

</div>
<div id="replace-data-select">
Expand All @@ -53,13 +55,15 @@ const UploadPage = (props) => {
onChange={handleRadioChange}
>
<FormControlLabel
disabled={datasetSelected ? false: true}
value
control={(
<Radio />
)}
label="Replace existing data"
/>
<FormControlLabel
disabled={datasetSelected ? false: true}
value={false}
control={<Radio />}
label="Add to existing data"
Expand All @@ -69,6 +73,7 @@ const UploadPage = (props) => {
</div>
<div>
<FileDropArea
disabled={datasetSelected ? false: true}
setAlert={setAlert}
setUploadFiles={setUploadFiles}
uploadFiles={uploadFiles}
Expand All @@ -77,7 +82,7 @@ const UploadPage = (props) => {
<Box pt={2} className="upload-bar" alignItems="center" padding={2} display="flex" justifyContent="flex-end">
<Button
disabled={uploadFiles.length === 0 || !datasetSelected}
className="button-dark-blue"
className="button-dark-blue button-lowercase"
onClick={() => doUpload()}
type="button"
variant="contained"
Expand All @@ -95,6 +100,7 @@ UploadPage.defaultProps = {
};

UploadPage.propTypes = {
alertElement: PropTypes.element,
datasetSelected: PropTypes.string,
datasetList: PropTypes.arrayOf(PropTypes.shape()).isRequired,
uploadFiles: PropTypes.arrayOf(PropTypes.shape()).isRequired,
Expand Down
2 changes: 1 addition & 1 deletion react/src/users/UsersContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const UsersContainer = (props) => {
const handleAddNewUser = () => {
axios.post(ROUTES_USERS.CREATE, { idir: newUser })
.then((response) => {
const userAdded = response.data;
const userAdded = response.data.idir;
setMessageSeverity('success');
setPermissionMessage(`${userAdded} was added to the user list`);
const userObject = { idir: userAdded, user_permissions: { admin: false, uploader: false } };
Expand Down
Loading

0 comments on commit 3ea275e

Please sign in to comment.