Skip to content

Commit

Permalink
added bulk edit
Browse files Browse the repository at this point in the history
  • Loading branch information
mattjurenka committed Nov 6, 2021
1 parent bb264fa commit 81915db
Show file tree
Hide file tree
Showing 7 changed files with 307 additions and 6 deletions.
7 changes: 5 additions & 2 deletions content_management/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
from dlms import settings
from .views import (
ContentViewSet, MetadataViewSet, MetadataTypeViewSet, UserViewSet,
LibraryFolderViewSet, LibraryVersionViewSet, LibLayoutImageViewSet, LibraryBuildView, metadata_sheet, BulkAddView, get_csrf,
LibraryModuleViewSet, disk_info)
LibraryFolderViewSet, LibraryVersionViewSet, LibLayoutImageViewSet,
LibraryBuildView, metadata_sheet, BulkAddView, get_csrf, bulk_edit,
LibraryModuleViewSet, disk_info
)

router = routers.DefaultRouter()
router.register(r'contents', ContentViewSet)
Expand All @@ -27,4 +29,5 @@
path('api/spreadsheet/metadata/<str:metadata_type>', metadata_sheet),
path('api/disk_info/', disk_info),
path('api/get_csrf/', get_csrf),
path('api/bulk_edit/', bulk_edit),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
12 changes: 12 additions & 0 deletions content_management/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -563,3 +563,15 @@ def disk_info(request):
@renderer_classes((JSONRenderer,))
def get_csrf(request):
return build_response(get_token(request))

@api_view(('POST',))
# @staff_member_required
@renderer_classes((JSONRenderer,))
def bulk_edit(request):
to_remove = request.data.get("to_remove")
to_add = request.data.get("to_add")
for content in Content.objects.filter(id__in=request.data.get("to_edit")):
content.metadata.remove(*to_remove)
content.metadata.add(*to_add)

return build_response()
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"build-bash": "npm run-script clean; webpack --mode=development",
"build-bash-prod": "npm run clean; webpack --mode=production",
"clean": "rm -R ./static",
"watch": "npm run-script clean && webpack --watch --mode=development",
"winclean": "rd /s /q static",
"winbuild": "npm run-script winclean & webpack --mode=production",
"winbuild-dev": "npm run-script winclean & webpack --mode=development",
Expand Down
273 changes: 270 additions & 3 deletions frontend/src/js/content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@ import React, { Component } from 'react';


import { APP_URLS } from './urls';
import { cloneDeep } from 'lodash';
import { cloneDeep, isUndefined } from 'lodash';
import ActionDialog from './reusable/action_dialog';
import {Box, Button, Checkbox, Typography} from '@material-ui/core';
import {Box, Button, Checkbox, TextField, Typography} from '@material-ui/core';
import Axios from 'axios';
import VALIDATORS from './validators';
import { update_state } from './utils';
import ContentModal from './reusable/content_modal';

import { MetadataAPI, SerializedContent, ContentsAPI } from './types';
import { MetadataAPI, SerializedContent, ContentsAPI, SerializedMetadata, metadata_dict } from './types';
import { ViewContentModal } from './reusable/view_content_modal';
import ContentSearch from './reusable/content_search';
import BulkContentModal from "./reusable/bulk_content_modal";
import {Autocomplete, createFilterOptions} from '@material-ui/lab';


interface ContentProps {
Expand Down Expand Up @@ -47,6 +48,11 @@ interface ContentModals {
bulk_add: {
is_open: boolean
}
bulk_edit: {
is_open: boolean
to_add: metadata_dict
to_remove: metadata_dict
}
column_select: {
is_open: boolean
}
Expand All @@ -61,6 +67,7 @@ export default class Content extends Component<ContentProps, ContentState> {

modal_defaults: ContentModals
content_defaults: SerializedContent
auto_complete_filter: any;

constructor(props: ContentProps) {
super(props)
Expand Down Expand Up @@ -104,6 +111,11 @@ export default class Content extends Component<ContentProps, ContentState> {
bulk_add: {
is_open: false,
},
bulk_edit: {
is_open: false,
to_add: {},
to_remove: {},
},
column_select: {
is_open: false,
},
Expand All @@ -116,6 +128,10 @@ export default class Content extends Component<ContentProps, ContentState> {
modals: cloneDeep(this.modal_defaults)
}

this.auto_complete_filter = createFilterOptions<(string | SerializedMetadata)[]>({
ignoreCase: true
})

this.close_modals = this.close_modals.bind(this)
this.update_state = this.update_state.bind(this)
}
Expand Down Expand Up @@ -191,6 +207,21 @@ export default class Content extends Component<ContentProps, ContentState> {
>
Bulk Download
</Button>
<Button
onClick={_ => {
this.update_state(draft => {
draft.modals.bulk_edit.is_open = true
})
}}
style={{
marginLeft: "1em",
marginBottom: "1em",
backgroundColor: "#75b2dd",
color: "#FFFFFF"
}}
>
Bulk Edit
</Button>
<Button
onClick={_ => {
this.update_state(draft => {
Expand Down Expand Up @@ -414,6 +445,242 @@ export default class Content extends Component<ContentProps, ContentState> {
</Box>
})}
</ActionDialog>
<ActionDialog
title={`Bulk Edit ${
this.props.contents_api.state.selection.length
} Items`}
open={this.state.modals.bulk_edit.is_open}
get_actions={focus_ref => [(
<Button
key={2}
onClick={() => {
this.update_state(draft => {
draft.modals.bulk_edit.is_open = false
})
}}
color="secondary"
ref={focus_ref}
>
Close
</Button>
), (
<Button
key={2}
onClick={() => {
this.update_state(draft => {
draft.modals.bulk_edit.is_open = false
})
if (
this.props.contents_api.state.selection.length > 0
) {
this.props.contents_api.bulk_edit(
([] as SerializedMetadata[]).concat(
...Object.values(
this.state.modals.bulk_edit.to_add
)
),
([] as SerializedMetadata[]).concat(
...Object.values(
this.state.modals.bulk_edit.to_remove
)
),
)
}
}}
color="primary"
ref={focus_ref}
>
Edit
</Button>
)]}
>
<Typography variant="h5">Add Metadata</Typography>
{this.props.metadata_api.state.metadata_types
.map(metadata_type => <Autocomplete
multiple
value={this.state.modals.bulk_edit
.to_add[metadata_type.name]}
onChange={(_evt, value: SerializedMetadata[]) => {
//Determine which tokens are real or generated
//by the "Add new metadata ..." option
let valid_meta = value.filter(
to_check => to_check.id !== 0
)
let add_meta_tokens = value.filter(
to_check => to_check.id === 0
)
if (add_meta_tokens.length > 0) {
const to_add = add_meta_tokens[0]
this.props.metadata_api.add_metadata(
to_add.name, metadata_type
).then(res => {
//add the created metadata to
//valid_metadata with its new id
valid_meta.push(res?.data)
add_meta_tokens = []
})
.then(() => {
this.props.metadata_api.refresh_metadata()
})
.then(() => {
this.update_state(draft => {
draft.modals.bulk_edit
.to_add[metadata_type.name]
= valid_meta
})
})
} else {
this.update_state(draft => {
draft.modals.bulk_edit
.to_add[metadata_type.name]
= valid_meta
})
}

}}
filterOptions={(options, params) => {
const filtered = this.auto_complete_filter(
options, params
)
const already_loaded_metadata = this.props
.metadata_api.state
.metadata_by_type[metadata_type.name]
?.find(match =>
match.name == params.inputValue)
if (
params.inputValue !== '' &&
isUndefined(already_loaded_metadata)
) {
filtered.push({
id: 0,
name: params.inputValue,
type: metadata_type.id,
type_name: metadata_type.name
} as SerializedMetadata)
}
return filtered
}}
handleHomeEndKeys
options={this.props.metadata_api.state
.autocomplete_metadata[metadata_type.name] || []}
getOptionLabel={option => {
if (isUndefined(option)) {
return "undefined"
}
return option.id === 0 ?
`Add new Metadata "${option.name}"` :
option.name
}}
renderInput={(params) => (
<TextField
{...params}
variant={"standard"}
label={metadata_type.name}
placeholder={metadata_type.name}
/>
)}
onInputChange={(_, name) => {
this.props.metadata_api
.update_autocomplete(
metadata_type, name
)
}}
/>
)}
<Typography variant="h5">Remove Metadata</Typography>
{this.props.metadata_api.state.metadata_types
.map(metadata_type => <Autocomplete
multiple
value={this.state.modals.bulk_edit
.to_remove[metadata_type.name]}
onChange={(_evt, value: SerializedMetadata[]) => {
//Determine which tokens are real or generated
//by the "Add new metadata ..." option
let valid_meta = value.filter(
to_check => to_check.id !== 0
)
let add_meta_tokens = value.filter(
to_check => to_check.id === 0
)
if (add_meta_tokens.length > 0) {
const to_add = add_meta_tokens[0]
this.props.metadata_api.add_metadata(
to_add.name, metadata_type
).then(res => {
//add the created metadata to
//valid_metadata with its new id
valid_meta.push(res?.data)
add_meta_tokens = []
})
.then(() => {
this.props.metadata_api.refresh_metadata()
})
.then(() => {
this.update_state(draft => {
draft.modals.bulk_edit
.to_remove[metadata_type.name]
= valid_meta
})
})
} else {
this.update_state(draft => {
draft.modals.bulk_edit
.to_remove[metadata_type.name]
= valid_meta
})
}

}}
filterOptions={(options, params) => {
const filtered = this.auto_complete_filter(
options, params
)
const already_loaded_metadata = this.props
.metadata_api.state
.metadata_by_type[metadata_type.name]
?.find(match =>
match.name == params.inputValue)
if (
params.inputValue !== '' &&
isUndefined(already_loaded_metadata)
) {
filtered.push({
id: 0,
name: params.inputValue,
type: metadata_type.id,
type_name: metadata_type.name
} as SerializedMetadata)
}
return filtered
}}
handleHomeEndKeys
options={this.props.metadata_api.state
.autocomplete_metadata[metadata_type.name] || []}
getOptionLabel={option => {
if (isUndefined(option)) {
return "undefined"
}
return option.id === 0 ?
`Add new Metadata "${option.name}"` :
option.name
}}
renderInput={(params) => (
<TextField
{...params}
variant={"standard"}
label={metadata_type.name}
placeholder={metadata_type.name}
/>
)}
onInputChange={(_, name) => {
this.props.metadata_api
.update_autocomplete(
metadata_type, name
)
}}
/>
)}
</ActionDialog>
</React.Fragment>
)
}
Expand Down
Loading

0 comments on commit 81915db

Please sign in to comment.