RecordActionButtonο
Record action buttons are individual elements or modules that make up the profile and managed through a Django Admin model. Each button in the profile serves a specific function, enabling users to perform various actions on records within the profile.
See also
RecordActionButton Database Table Structureο
The RecordActionButton in Django Admin provides a structured way to manage and perform actions on profile records. Each button is meticulously defined with attributes that ensure it functions correctly and provides clear feedback to users.
These actions can include creating, updating, submitting or deleting records among other functions. Understanding the fields and their purposes can significantly enhance the management and usability of profiles in a Django application
The PostgreSQL table RecordActionButton consists of the following fields:
-
-
id
(Integer): -
The unique identifier for the action button. It is auto-incremented by the database.
-
-
-
name
(String): -
The internal name of the action button, used in the back-end code to reference the button.
-
-
-
title
(String): -
The display title of the action button, shown to users in the UI. It describes the action the button performs in a concise manner.
-
-
-
label
(String): -
The short label shown on the button, providing a brief indication was the button does.
-
-
-
type
(String): -
The type of action the button performs, such as single (acting on a single record) or multi (acting on multiple records).
-
-
-
error_message
(String): -
The error message displayed to users if the action cannot be completed. This helps in providing feedback to users about why an action failed.
-
-
-
icon_class
(String): -
The CSS class for the icon associated with the button, providing a visual representation of the buttonβs action and help improve user interface design.
-
-
-
action
(String): -
The specific action performed by the button, often mapped to a function or a URL ndpoint that the action will call.
-
-
-
icon_colour
(String): -
The colour of the icon used for UI consistency and visual cues thereby helping users to quickly identify the type of action.
-
Hint
Click the button below to view the contents
Example records for the ProfileActionButton model, detailing the various actions available within a profile
id | name | title | label | type | error_message | icon_class | action | icon_colour
----+---------------------------------+--------------------------------------------+--------------------------+--------+---------------------------------------------------------------------------------------+-----------------------+--------------------------+-------------
1 | add_record_all | Add new record | Add | | | fa fa-plus | add | blue
2 | edit_record_single | Edit record | Edit | single | Please select a record to edit | fa fa-pencil-square-o | edit | green
3 | delete_record_multi | Delete records | Delete | multi | Please select one or more records to delete | fa fa-trash-can | validate_and_delete | red
4 | submit_assembly_multi | Submit Assembly | Submit | multi | Please select one or more record to submit | fa fa-info | submit_assembly |
5 | submit_annotation_multi | Submit Annotation | Submit | multi | Please select one or more record to submit | fa fa-info | submit_annotation | teal
6 | submit_read_multi | Submit Read | Submit | multi | Please select one or more record to submit | fa fa-info | submit_read | teal
7 | add_local_all | Add new file by browsing local file system | Add | | Add new file by browsing local file system | fa fa-desktop | add_files_locally | blue
8 | add_terminal_all | Add new file by terminal | Add | | | fa fa-terminal | add_files_by_terminal | blue
9 | submit_tagged_seq_multi | Submit Tagged Sequence | Submit | multi | Please select one or more record to submit | fa fa-info | submit_tagged_seq | teal
10 | download_sample_manifest_single | Download Sample Manifest | Download sample manifest | single | Please select one of samples in the manifest to download | fa fa-download | download-sample-manifest | blue
11 | view_images_multiple | View Images | View images | multi | Please select one or more sample records from the table shown to view images for | fa fa-eye | view-images | teal
12 | download_permits_multiple | Download Permits | Download permits | multi | Please select one or more sample records from the table shown to download permits for | fa fa-download | download-permits | orange
13 | releasestudy | Release Study | Release Study | single | | fa fa-globe | release_study | blue
Description of some RecordActionButton records
-
add_record_all: Add new record button (ID: 1)
Allows users to add a new record to the profile. It is labelled Add and uses a blue
fa fa-plus
icon. -
edit_record_single: Edit record button (ID: 2)
Enables users to edit an existing record. This button is labeled Edit and it uses a green
fa fa-pencil-square-o
icon. It shows an error message, Please select a record to edit, if no record is selected. -
delete_record_multi: Delete records button (ID: 3)
Allows users to delete multiple records at once. This multi-action button uses a red
fa fa-trash-can
icon and prompts users to Please select one or more records to delete if no records are selected. -
submit_assembly_multi: Submit Assembly
-
submit_annotation_multi: Submit Sequence Annotation
-
submit_read_multi: Submit Reads
-
add_local_all: Add new file by browsing local file system
-
add_terminal_all: Add new file by terminal
-
submit_tagged_seq_multi: Submit Tagged Sequence
-
download_sample_manifest_single: Download Sample Manifest
-
view_images_multiple: View Images
-
download_permits_multiple: Download Permits
-
releasestudy: Release Study
Referencing Created RecordActionButton in Projectο
Hint
Click the button below to view the contents
In the
views.py
, define the views to render the template containing the buttons
RecordActionButton example views.py
# myapp/views.py
from django.shortcuts import render
from django.views import View
from .models import TitleButton
import pandas as pd
from .utils import get_resolver
class TitleButtonView(View):
def get(self, request):
my_models = TitleButton.objects.all()
return render(request, 'myapp/index.html', {'title_button_def': my_models})
class BrokerVisuals:
def __init__(self, **kwargs):
self.param_dict = kwargs
self.component = self.param_dict.get("component", str())
self.profile_id = self.param_dict.get("profile_id", str())
self.user_id = self.param_dict.get("user_id", str())
self.context = self.param_dict.get("context", dict())
self.request_dict = self.param_dict.get("request_dict", dict())
self.da_object = self.param_dict.get("da_object", DAComponent(self.profile_id, self.component))
def do_server_side_table_data(self):
self.context["component"] = self.component
request_dict = self.param_dict.get("request_dict", dict())
data = generate_server_side_table_records(self.profile_id, component=self.component, request=request_dict)
self.context["draw"] = data["draw"]
self.context["records_total"] = data["records_total"]
self.context["records_filtered"] = data["records_filtered"]
self.context["data_set"] = data["data_set"]
return self.context
class DAComponent:
def __init__(self, profile_id=None, component=str()):
self.profile_id = profile_id
self.component = component
def visualize(request):
context = dict()
task = request.POST.get("task", str())
profile_id = request.session.get("profile_id", str())
component = request.POST.get("component", str())
da_object = DAComponent(profile_id=profile_id, component=request.POST.get("component", str()))
if component in da_dict:
da_object = da_dict[component](profile_id=profile_id)
target_id = request.POST.get("target_id", None)
if component == "read" and target_id:
target_id = target_id.split("_")[0]
broker_visuals = BrokerVisuals(context=context,
profile_id=profile_id,
request=request,
user_id=request.user.id,
component=request.POST.get("component", str()),
target_id=target_id,
da_object=da_object)
task_dict = dict(server_side_table_data=broker_visuals.do_server_side_table_data)
if task in task_dict:
context = task_dict[task]()
out = jsonpickle.encode(context, unpicklable=False)
return HttpResponse(out, content_type='application/json')
def get_not_deleted_flag():
"""
provides a consistent way of setting records as not deleted
:return:
"""
return "0"
def resolve_control_output_apply(data, args):
if args.get("type", str()) == "array": # resolve array data types
resolved_value = list()
for d in data:
resolved_value.append(get_resolver(d, args))
else: # non-array types
resolved_value = get_resolver(data, args)
return resolved_value
def generate_server_side_table_records(profile_id=str(), da_object=None, request=dict()):
# function generates component records for building an UI table using server-side processing
# - please note that for effective data display,
# all array and object-type fields (e.g., characteristics) are deferred to sub-table display.
# please define such in the schema as "show_in_table": false and "show_as_attribute": true
data_set = list()
# assumes 10 records per page if length not set
n_size = int(request.get("length", 10))
draw = int(request.get("draw", 1))
start = int(request.get("start", 0))
return_dict = dict()
records_total = da_object.get_collection_handle().count_documents(
{'profile_id': profile_id, 'deleted': get_not_deleted_flag()})
# retrieve and process records
filter_by = dict()
# get and filter schema elements based on displayable columns
schema = [x for x in da_object.get_schema().get(
"schema_dict") if x.get("show_in_table", True)]
# build db column projection
projection = [(x["id"].split(".")[-1], 1) for x in schema]
# order by
sort_by = request.get('order[0][column]', '0')
sort_by = request.get('columns[' + sort_by + '][data]', '')
sort_direction = request.get('order[0][dir]', 'asc')
sort_by = '_id' if not sort_by else sort_by
sort_direction = 1 if sort_direction == 'asc' else -1
# search
search_term = request.get('search[value]', '').strip()
records = da_object.get_all_records_columns_server(sort_by=sort_by,
sort_direction=sort_direction,
search_term=search_term,
projection=dict(projection),
limit=n_size,
skip=start,
filter_by=filter_by)
records_filtered = records_total
if search_term:
records_filtered = da_object.get_collection_handle().count_documents(
{'profile_id': profile_id, 'deleted': get_not_deleted_flag(),
'name': {'$regex': search_term, "$options": 'i'}})
if records:
df = pd.DataFrame(records)
df['record_id'] = df._id.astype(str)
df["DT_RowId"] = df.record_id
df.DT_RowId = 'row_' + df.DT_RowId
df = df.drop('_id', axis='columns')
for x in schema:
x["id"] = x["id"].split(".")[-1]
df[x["id"]] = df[x["id"]].apply(
resolve_control_output_apply, args=(x,)).astype(str)
data_set = df.to_dict('records')
return_dict["records_total"] = records_total
return_dict["records_filtered"] = records_filtered
return_dict["data_set"] = data_set
return_dict["draw"] = draw
return return_dict
In the template HTML file (
myapp.html
), reference each element from the RecordActionButton table.
RecordActionButton example template
<!-- myapp/templates/myapp/component.html -->
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=10">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>My App</title>
<link rel="stylesheet" href="{% static 'myapp/css/myapp.css' %}">
<script src="{% static 'myapp/js/myapp.js' %}"></script>
<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- DataTables CSS -->
<link rel="stylesheet" href="https://cdn.datatables.net/1.11.3/css/jquery.dataTables.min.css">
<!-- DataTables JS -->
<script src="https://cdn.datatables.net/1.11.3/js/jquery.dataTables.min.js"></script>
<script>
const component_def = {
{% for component in component_def %}
{{ component.name|lower }}: {
component: '{{ component.name }}',
title: '{{ component.title }}',
{% if component.subtitle %}
subtitle: '{{ component.subtitle }}',
{% endif %}
iconClass: '{{ component.widget_icon_class }}',
semanticIcon: '{{ component.widget_icon }}',
buttons: [ {% for button in component.title_buttons.all %} '{{ button.name }}', {% endfor %} ],
sidebarPanels: ['copo-sidebar-info'],
colorClass: '{{ component.widget_colour_class }}',
color: '{{ component.widget_colour }}',
tableID: '{{ component.table_id }}',
recordActions: [ {% for button in component.recordaction_buttons.all %} '{{ button.name }}', {% endfor %} ],
url: "{% if component.reverse_url %}{% url component.reverse_url profile_id='999' %}{% endif %}",
},
{% endfor %}
}
const title_button_def = {
{% autoescape off %}
{% for button in title_button_def %}
{{ button.name }} : {
template: `{{ button.template }}`,
additional_attr: '{{ button.additional_attr }}',
},
{% endfor %}
{% endautoescape %}
}
const profile_type_def = {
{% for profile_type in profile_type_def %}
{{ profile_type.type|lower }}: {
title: '{{ profile_type.type | upper }}',
widget_colour: '{{ profile_type.widget_colour }}',
components: [ {% for component in profile_type.components.all %} '{{ component.name }}', {% endfor %} ]
},
{% endfor %}
}
function get_profile_components(profile_type) {
return profile_type_def[profile_type.toLowerCase()].components.map(component => component_def[component]);
}
</script>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-sm-9 col-md-9 col-lg-9">
<div hidden id="hidden_attrs">
<!-- hidden attributes -->
<input type="hidden" id="nav_component_name" value="component_name"/>
</div>
{% block content %}
<!-- component_table -->
<div id="component_table_loader" class="col-sm-5 col-md-5 col-lg-5 col-xs-offset-5"
data-copo-tour-id="component_name_table">
</div> <!-- div used by the quick tour agent -->
<div class="table-parent-div">
<table id="component_name_table" class="ui celled table hover"
cellspacing="0" width="100%">
</table>
</div>
{% endblock content %}
</div>
</div>
</div>
</body>
</html>
Handle any JavaScript functionality needed for the buttons in the JS file (
myapp.js
)
RecordActionButton example javascript
// record_action_button.js
var visualizeURL = '/myapp/visualize/';
var server_side_select = {}; //holds selected ids for table data - needed in server-side processing
$(document).ready(function () {
var componentName = $('#nav_component_name').val();
// 'component_def' is defined in the 'record_action_button.html' file
var componentMeta = null;
componentMeta = component_def[componentName]
do_render_server_side_table(componentMeta);
});
function place_task_buttons(componentMeta) {
//place custom buttons on table
var is_custom_buttons_needed = false;
var customButtons = $('<span/>', {
style: 'padding-left: 15px;',
class: 'copo-table-cbuttons',
});
if (componentMeta.recordActions.length) {
componentMeta.recordActions.forEach(function (item) {
button_str = record_action_button_def[item].template
var actionBTN = $(button_str);
/*
var actionBTN = $('.record-action-templates')
.find('.' + item)
.clone();
*/
actionBTN.removeClass(item);
actionBTN.attr('data-table', componentMeta.tableID);
customButtons.append(actionBTN);
});
is_custom_buttons_needed = true;
}
$('.components_custom_templates').find('.record-action-custom-template').each(function () {
var actionBTN = $(this).clone();
actionBTN.removeClass('record-action-custom-template');
customButtons.append(actionBTN);
is_custom_buttons_needed = true;
}) ;
if (is_custom_buttons_needed) {
var table = $('#' + componentMeta.tableID).DataTable();
$(table.buttons().container()).append(customButtons);
refresh_tool_tips();
//table action buttons
do_table_buttons_events();
}
}
function do_table_buttons_events_server_side(component) {
//attaches events to table buttons - server-side processing version to function with similar name
$(document)
.off('click', '.copo-dt')
.on('click', '.copo-dt', function (event) {
event.preventDefault();
$('.copo-dt').webuiPopover('destroy');
var elem = $(this);
var task = elem.attr('data-action').toLowerCase(); //action to be performed e.g., 'Edit', 'Delete'
var tableID = elem.attr('data-table'); //get target table
var btntype = elem.attr('data-btntype'); //type of button: single, multi, all
var title = elem.find('.action-label').html();
var message = elem.attr('data-error-message');
if (!title) {
title = 'Record action';
}
if (!message) {
message = 'No records selected for ' + title + ' action';
}
//validate event before passing to handler
var selectedRows = server_side_select[component].length;
var triggerEvent = true;
//do button type validation based on the number of records selected
if (btntype == 'single' || btntype == 'multi') {
if (selectedRows == 0) {
triggerEvent = false;
} else if (selectedRows > 1 && btntype == 'single') {
//sort out 'single record buttons'
triggerEvent = false;
}
}
if (triggerEvent) {
//trigger button event, else deal with error
var event = jQuery.Event('addbuttonevents');
event.tableID = tableID;
event.task = task;
event.title = title;
$('body').trigger(event);
} else {
//alert user
button_event_alert(title, message);
}
});
}
function do_render_server_side_table(componentMeta) {
// Use this function for server-side processing of large tables
var csrftoken = $.cookie('csrftoken');
try {
componentMeta.table_columns = JSON.parse($('#table_columns').val());
} catch (err) {}
var tableID = componentMeta.tableID;
var component = componentMeta.component;
var columnDefs = [];
var table = null;
if ($.fn.dataTable.isDataTable('#' + tableID)) {
// get table instance
table = $('#' + tableID).DataTable();
}
if (table) {
//if table instance already exists, refresh
table.draw();
}
else {
server_side_select[component] = [];
table = $('#' + tableID).DataTable({
paging: true,
processing: true,
serverSide: true,
searchDelay: 850,
columns: componentMeta.table_columns,
ajax: {
url: visualizeURL,
type: 'POST',
headers: {
'X-CSRFToken': csrftoken,
},
data: {
task: 'server_side_table_data',
component: component,
},
dataFilter: function (data) {
var json = jQuery.parseJSON(data);
json.recordsTotal = json.records_total;
json.recordsFiltered = json.records_filtered;
json.data = json.data_set;
return JSON.stringify(json); // return JSON string
},
},
rowCallback: function (row, data) {
if ($.inArray(data.DT_RowId, server_side_select[component]) !== -1) {
$(row).addClass('selected');
}
},
createdRow: function (row, data, index) {
},
fnDrawCallback: function () {
},
buttons: [
{
text: 'Select visible records',
action: function (e, dt, node, config) {
//remove custom select info
$('#' + tableID + '_info')
.find('.select-item-1')
.remove();
dt.rows().select();
var selectedRows = table.rows('.selected').ids().toArray();
for (var i = 0; i < selectedRows.length; ++i) {
var index = $.inArray(
selectedRows[i],
server_side_select[component]
);
if (index === -1) {
server_side_select[component].push(selectedRows[i]);
}
}
$('#' + tableID + '_info')
.find('.select-row-message')
.html(server_side_select[component].length + ' records selected');
},
},
{
text: 'Clear selection',
action: function (e, dt, node, config) {
dt.rows().deselect();
server_side_select[component] = [];
$('#' + tableID + '_info')
.find('.select-item-1')
.remove();
},
},
],
columnDefs: columnDefs,
language: {
select: {
rows: {
_: "<span class='select-row-message'>%d records selected</span>",
0: '',
1: '%d record selected',
},
},
processing: "<div class='copo-i-loader'></div>",
},
dom: 'Bfr<"row"><"row info-rw" i>tlp',
});
table
.buttons()
.nodes()
.each(function (value) {
$(this).removeClass('btn btn-default').addClass('tiny ui button');
});
place_task_buttons(componentMeta); //this will place custom buttons on the table for executing tasks on records
do_table_buttons_events_server_side(component);
table.on('click', 'tr >td', function () {
var classList = [
'annotate-datafile',
'summary-details-control',
'detail-hover-message',
]; //don't select on columns with these classes
var foundClass = false;
var tdList = this.className.split(' ');
for (var i = 0; i < tdList.length; ++i) {
if ($.inArray(tdList[i], classList) > -1) {
foundClass = true;
break;
}
}
if (foundClass) {
return false;
}
var elem = $(this).closest('tr');
var id = elem.attr('id');
var index = $.inArray(id, server_side_select[component]);
if (index === -1) {
server_side_select[component].push(id);
} else {
server_side_select[component].splice(index, 1);
}
elem.toggleClass('selected');
//selected message
$('#' + tableID + '_info')
.find('.select-item-1')
.remove();
var message = '';
if ($('#' + tableID + '_info').find('.select-row-message').length) {
if (server_side_select[component].length > 0) {
message = server_side_select[component].length + ' records selected';
if (server_side_select[component].length == 1) {
message = server_side_select[component].length + ' record selected';
}
$('#' + tableID + '_info')
.find('.select-row-message')
.html(message);
} else {
$('#' + tableID + '_info')
.find('.select-row-message')
.html('');
}
} else {
if (server_side_select[component].length > 0) {
message = server_side_select[component].length + ' records selected';
if (server_side_select[component].length == 1) {
message = server_side_select[component].length + ' record selected';
}
$('#' + tableID + '_info').append(
"<span class='select-item select-item-1'>" + message + '</span>'
);
}
}
});
}
let table_wrapper = $('#' + tableID + '_wrapper');
table_wrapper.find('.dt-buttons').css({ float: 'right' });
table_wrapper
.find('.dataTables_filter')
.find('label')
.css({ padding: '10px 0' })
.find('input')
.removeClass('input-sm')
.attr('placeholder', 'Search ' + componentMeta.title)
.attr('size', 30);
$('<br><br>').insertAfter(table_wrapper.find('.dt-buttons'));
//handle event for table details
//handle event for annotation of datafile
} //end of func
Visualisation of RecordActionButton in Projectο
add_record_all button is labelled
Add
and uses a icon. It is indicated by the blue arrow.edit_record_single button is labelled
Edit
and uses icon. It is indicated by the green arrow.delete_record_multi button is labelled
Delete
and uses a icon. It is indicated by the red arrow. The icon and colour of this button is used on multiple web pages with different actions.-
submit_assembly_multi button is labelled
Submit
and uses a icon. The icon and colour used in for this button, is also used for the submit_annotation_multi, submit_read_multi and submit_tagged_seq_multi buttons.The difference is in the label assigned and the action performed by the button. The button is indicated by the teal arrow in the image above.
add_local_all button is labelled
Add new file by browsing local file system
and uses a icon. It is indicated by the blue arrow on the right in the image above.add_terminal_all button is labelled
Add new file by terminal
and uses a icon. It is indicated by the blue arrow on the left in the image above.
download_sample_manifest_single button is labelled
Download Sample Manifest
and uses a icon. It is indicated by the blue arrow in the image above.view_images_multiple button is labelled
View Images
and uses a icon. It is indicated by the teal arrow.download_permits_multiple button is labelled
Download Permits
and uses a icon. It is indicated by the orange arrow.
releasestudy button is labelled
Release Study
and uses a icon. It is indicated by the blue arrow in the image above.