Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding support for custom actions per table #244

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Actions for multiple rows and styling
  • Loading branch information
haffi96 committed Nov 19, 2022
commit 1e89e746abed04c74e4c5380ce33427d647682f9
55 changes: 0 additions & 55 deletions admin_ui/src/components/CallbackButton.vue

This file was deleted.

10 changes: 8 additions & 2 deletions admin_ui/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ export interface DeleteRow {
rowID: number
}

export interface ExecuteCallback {
export interface ExecuteAction {
tableName: string
rowID: number
rowIDs: number
actionId: number
}

export interface UpdateRow {
Expand Down Expand Up @@ -149,3 +150,8 @@ export interface FormConfig {
slug: string
description: string
}

export interface Action {
actionID: number
actionName: string
}
19 changes: 15 additions & 4 deletions admin_ui/src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ export default new Vuex.Store({
tableNames: [],
formConfigs: [] as i.FormConfig[],
user: undefined,
loadingStatus: false
loadingStatus: false,
actions: [] as i.Action[]
},
mutations: {
updateTableNames(state, value) {
Expand Down Expand Up @@ -66,6 +67,9 @@ export default new Vuex.Store({
updateSortBy(state, config: i.SortByConfig) {
state.sortBy = config
},
updateActions(state, actions) {
state.actions = actions
},
reset(state) {
state.sortBy = null
state.filterParams = {}
Expand Down Expand Up @@ -229,12 +233,19 @@ export default new Vuex.Store({
)
return response
},
async executeCallback(context, config: i.ExecuteCallback) {
async fetchActions(context, tableName: string) {
const response = await axios.get(
`${BASE_URL}tables/${tableName}/actions`
)
context.commit("updateActions", response.data)
return response
},
async executeAction(context, config: i.ExecuteAction) {
const response = await axios.post(
`${BASE_URL}tables/${config.tableName}/callback/execute`,
`${BASE_URL}tables/${config.tableName}/actions/${config.actionId}/execute`,
{
table_name: config.tableName,
row_id: config.rowID
row_ids: config.rowIDs
}
)
return response
Expand Down
62 changes: 37 additions & 25 deletions admin_ui/src/views/RowListing.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,20 @@
/>
</div>
<div class="buttons">
<a class="button" v-on:click.prevent="showActionDropDown = !showActionDropDown">
<font-awesome-icon icon="angle-right" v-if="!showActionDropDown"/>
<font-awesome-icon icon="angle-down" v-if="showActionDropDown"/>
Actions
<a class="actions-dropdown">
<DropDownMenu v-if="showActionDropDown">
<li v-for="action in allActions">
<a href="#" class="button" v-on:click="executeAction(action.action_id)">
<span>{{ action.action_name }}</span>
</a>
</li>
</DropDownMenu>
</a>
</a>
<a
class="button"
href="#"
Expand All @@ -27,7 +41,8 @@
v-if="selectedRows.length > 0"
v-on:triggered="deleteRows"
/>



<router-link
:to="{
name: 'addRow',
Expand All @@ -40,6 +55,7 @@
<span>{{ $t("Add Row") }}</span>
</router-link>


<a
class="button"
href="#"
Expand Down Expand Up @@ -299,17 +315,6 @@
"
/>
</li>
<li>
<CallbackButton
:includeTitle="true"
class=""
v-on:triggered="
executeCallback(
row[pkName]
)
"
/>
</li>
</DropDownMenu>
</span>
</td>
Expand Down Expand Up @@ -381,7 +386,6 @@ import BulkUpdateModal from "../components/BulkUpdateModal.vue"
import BulkDeleteButton from "../components/BulkDeleteButton.vue"
import CSVButton from "../components/CSVButton.vue"
import DeleteButton from "../components/DeleteButton.vue"
import CallbackButton from "../components/CallbackButton.vue"
import DropDownMenu from "../components/DropDownMenu.vue"
import ChangePageSize from "../components/ChangePageSize.vue"
import MediaViewer from "../components/MediaViewer.vue"
Expand Down Expand Up @@ -409,7 +413,8 @@ export default Vue.extend({
showUpdateModal: false,
visibleDropdown: null,
showMediaViewer: false,
mediaViewerConfig: null as MediaViewerConfig
mediaViewerConfig: null as MediaViewerConfig,
showActionDropDown: false,
}
},
components: {
Expand All @@ -420,7 +425,6 @@ export default Vue.extend({
ChangePageSize,
CSVButton,
DeleteButton,
CallbackButton,
DropDownMenu,
MediaViewer,
Pagination,
Expand Down Expand Up @@ -448,6 +452,9 @@ export default Vue.extend({
schema() {
return this.$store.state.schema
},
allActions() {
return this.$store.state.actions
},
rowCount() {
return this.$store.state.rowCount
},
Expand Down Expand Up @@ -579,24 +586,22 @@ export default Vue.extend({
this.showSuccess("Successfully deleted row")
}
},
async executeCallback(rowID) {
if (confirm(`Are you sure you want to run callback for this row?`)) {
console.log("Requesting to run callback!")
async executeAction(action_id) {
if (confirm(`Are you sure you want to run action for this row?`)) {
try {
let response = await this.$store.dispatch("executeCallback", {
let response = await this.$store.dispatch("executeAction", {
tableName: this.tableName,
rowID
rowIDs: this.selectedRows,
actionId: action_id
})
this.showSuccess(`Successfully ran callback and got response: ${response.data}`, 10000)
this.showSuccess(`Successfully ran action and got response: ${response.data}`, 10000)
} catch (error) {
if (error.response.status == 404) {
console.log(error.response.status)
this.$store.commit("updateApiResponseMessage", {
contents: "This table is not configured for callback action",
contents: "This table is not configured for any actions",
type: "error"
})
} else {
console.log(error.response.status)
this.$store.commit("updateApiResponseMessage", {
contents: "Something went wrong",
type: "error"
Expand All @@ -623,6 +628,9 @@ export default Vue.extend({
},
async fetchSchema() {
await this.$store.dispatch("fetchSchema", this.tableName)
},
async fetchActions() {
await this.$store.dispatch("fetchActions", this.tableName)
}
},
watch: {
Expand Down Expand Up @@ -650,7 +658,7 @@ export default Vue.extend({
this.$router.currentRoute.query
)

await Promise.all([this.fetchRows(), this.fetchSchema()])
await Promise.all([this.fetchRows(), this.fetchSchema(), this.fetchActions()])
}
})
</script>
Expand Down Expand Up @@ -727,6 +735,10 @@ div.wrapper {
&:first-child {
margin-left: 0;
}

a.actions-dropdown {
position: relative;
}
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions piccolo_admin/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ async def manager_only(
validators=Validators(post_single=manager_only)
)
)
:param custom_actions: Optional list of custom actions handler function

"""

Expand All @@ -164,7 +165,7 @@ async def manager_only(
hooks: t.Optional[t.List[Hook]] = None
media_storage: t.Optional[t.Sequence[MediaStorage]] = None
validators: t.Optional[Validators] = None
custom_callback: t.Optional[t.Callable] = None
custom_actions: t.Optional[t.List[t.Callable]] = None

def __post_init__(self):
if self.visible_columns and self.exclude_visible_columns:
Expand Down Expand Up @@ -464,7 +465,7 @@ def __init__(
"tags": [f"{table_class._meta.tablename.capitalize()}"]
},
),
callback=table_config.custom_callback,
actions=table_config.custom_actions,
)

private_app.add_api_route(
Expand Down
22 changes: 16 additions & 6 deletions piccolo_admin/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
from piccolo_api.session_auth.tables import SessionsBase
from pydantic import BaseModel, validator
from starlette.responses import JSONResponse
from piccolo_api.fastapi.endpoints import TableRowDataSchema

from piccolo_admin.endpoints import FormConfig, TableConfig, create_admin
from piccolo_admin.example_data import (
Expand Down Expand Up @@ -164,6 +165,7 @@ class Genre(int, enum.Enum):
romance = 7
musical = 8

id = BigInt
name = Varchar(length=300)
rating = Real(help_text="The rating on IMDB.")
duration = Interval()
Expand Down Expand Up @@ -259,11 +261,6 @@ async def booking_endpoint(request, data):

return "Booking complete"

async def my_callback_fn(**kwargs) -> JSONResponse:
request_data = kwargs['request_params']
table_name = request_data['table_name']
row_id = request_data['row_id']
return JSONResponse(f"My API received the row_id: {row_id} from table: {table_name}")

TABLE_CLASSES: t.Tuple[t.Type[Table], ...] = (
Director,
Expand All @@ -275,6 +272,19 @@ async def my_callback_fn(**kwargs) -> JSONResponse:
)


# CUSTOM ACTIONS
async def my_custom_action(data: TableRowDataSchema) -> JSONResponse:
row_ids = data.row_ids

# Use the selected row ids to fetch rows from db
movies = await Movie.select().where(Movie.id == row_ids[0])

Check notice

Code scanning / CodeQL

Unused local variable

Variable movies is not used.

# Return a JSONResponse which can be displayed back to the frontend
return JSONResponse(f"My API received the row_id: {row_ids}")

async def custom_action_2(data) -> JSONResponse:
return JSONResponse("This is the second action")

movie_config = TableConfig(
table_class=Movie,
visible_columns=[
Expand Down Expand Up @@ -307,7 +317,7 @@ async def my_callback_fn(**kwargs) -> JSONResponse:
media_path=os.path.join(MEDIA_ROOT, "movie_screenshots"),
),
),
custom_callback=my_callback_fn
custom_actions=[my_custom_action, custom_action_2]
)

director_config = TableConfig(
Expand Down