diff --git a/docs/en_US/images/query_tool_add_to_macro.png b/docs/en_US/images/query_tool_add_to_macro.png new file mode 100644 index 000000000..88a22cb7c Binary files /dev/null and b/docs/en_US/images/query_tool_add_to_macro.png differ diff --git a/docs/en_US/query_tool.rst b/docs/en_US/query_tool.rst index e2ead1c05..dabaf316d 100644 --- a/docs/en_US/query_tool.rst +++ b/docs/en_US/query_tool.rst @@ -485,6 +485,12 @@ To create a macro, select the *Manage Macros* option from the *Macros* menu on t :alt: Query Tool Manage Macros dialogue :align: center +To add a query to macros, write and select your query, then go to the *Macros* menu in the Query Tool and click *Add to macros*. Your query will be automatically saved to macros. + +.. image:: images/query_tool_add_to_macro.png + :alt: Query Tool Add To Macros + :align: center + To delete a macro, select the macro on the *Manage Macros* dialogue, and then click the *Delete* button. The server will prompt you for confirmation to delete the macro. diff --git a/web/migrations/versions/ac2c2e27dc2d_.py b/web/migrations/versions/ac2c2e27dc2d_.py new file mode 100644 index 000000000..ef04174de --- /dev/null +++ b/web/migrations/versions/ac2c2e27dc2d_.py @@ -0,0 +1,61 @@ + +"""empty message + +Revision ID: ac2c2e27dc2d +Revises: ec0f11f9a4e6 +Create Date: 2024-05-17 19:35:03.700104 + +""" +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = 'ac2c2e27dc2d' +down_revision = 'ec0f11f9a4e6' +branch_labels = None +depends_on = None + + +def upgrade(): + meta = sa.MetaData() + meta.reflect(op.get_bind(), only=('user_macros',)) + user_macros_table = sa.Table('user_macros', meta) + + # Create a select statement + stmt = sa.select( + user_macros_table.columns.mid, + user_macros_table.columns.uid, user_macros_table.columns.name, + user_macros_table.columns.sql + ) + # Fetch the data from the user_macros table + results = op.get_bind().execute(stmt).fetchall() + + # Drop and re-create user macro table. + op.drop_table('user_macros') + op.create_table( + 'user_macros', + sa.Column('id', sa.Integer(), autoincrement=True), + sa.Column('mid', sa.Integer(), nullable=True), + sa.Column('uid', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=1024), nullable=False), + sa.Column('sql', sa.String()), + sa.ForeignKeyConstraint(['mid'], ['macros.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['uid'], ['user.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id',)) + + # Reflect the new table structure + meta.reflect(op.get_bind(), only=('user_macros',)) + new_user_macros_table = sa.Table('user_macros', meta) + + # Bulk insert the fetched data into the new user_macros table + op.bulk_insert( + new_user_macros_table, + [ + {'mid': row[0], 'uid': row[1], 'name': row[2], 'sql': row[3]} + for row in results + ] + ) + +def downgrade(): + # pgAdmin only upgrades, downgrade not implemented. + pass diff --git a/web/pgadmin/model/__init__.py b/web/pgadmin/model/__init__.py index 04c2ce00e..c10c7d053 100644 --- a/web/pgadmin/model/__init__.py +++ b/web/pgadmin/model/__init__.py @@ -33,7 +33,7 @@ import config # ########################################################################## -SCHEMA_VERSION = 39 +SCHEMA_VERSION = 40 ########################################################################## # @@ -433,11 +433,12 @@ class Macros(db.Model): class UserMacros(db.Model): """Define the macro for a particular user.""" __tablename__ = 'user_macros' + id = db.Column(db.Integer, primary_key=True, autoincrement=True) mid = db.Column( - db.Integer, db.ForeignKey('macros.id'), primary_key=True + db.Integer, db.ForeignKey('macros.id'), nullable=True ) uid = db.Column( - db.Integer, db.ForeignKey(USER_ID), primary_key=True + db.Integer, db.ForeignKey(USER_ID) ) name = db.Column(db.String(1024), nullable=False) sql = db.Column(db.Text(), nullable=False) diff --git a/web/pgadmin/static/js/SchemaView/DataGridView.jsx b/web/pgadmin/static/js/SchemaView/DataGridView.jsx index 5458e03e3..dc0235387 100644 --- a/web/pgadmin/static/js/SchemaView/DataGridView.jsx +++ b/web/pgadmin/static/js/SchemaView/DataGridView.jsx @@ -518,8 +518,13 @@ export default function DataGridView({ if(!props.canAddRow) { return; } - let newRow = schemaRef.current.getNewData(); + + const current_macros = schemaRef.current?._top?._sessData?.macro || null; + if (current_macros){ + newRow = schemaRef.current.getNewData(current_macros); + } + if(props.expandEditOnAdd && props.canEdit) { newRowIndex.current = rows.length; } diff --git a/web/pgadmin/static/js/components/Menu.jsx b/web/pgadmin/static/js/components/Menu.jsx index 7bd2425ce..00e5dabf0 100644 --- a/web/pgadmin/static/js/components/Menu.jsx +++ b/web/pgadmin/static/js/components/Menu.jsx @@ -107,11 +107,15 @@ export const PgMenuItem = applyStatics(MenuItem)(({hasCheck=false, checked=false props.onClick(e); }; } + const keyVal = shortcutToString(shortcut, accesskey); + const dataLabel = typeof(children) == 'string' ? children : props.datalabel; return {hasCheck && } {children} - {(shortcut || accesskey) &&
({shortcutToString(shortcut, accesskey)})
} +
+ {keyVal ? `(${keyVal})` : ''} +
; }); diff --git a/web/pgadmin/static/js/components/ReactCodeMirror/index.jsx b/web/pgadmin/static/js/components/ReactCodeMirror/index.jsx index a4e56374d..50b25ed3a 100644 --- a/web/pgadmin/static/js/components/ReactCodeMirror/index.jsx +++ b/web/pgadmin/static/js/components/ReactCodeMirror/index.jsx @@ -7,7 +7,7 @@ // ////////////////////////////////////////////////////////////// -import React, { useCallback, useMemo, useRef, useState } from 'react'; +import React, { useCallback, useMemo, useRef, useState, useEffect } from 'react'; import { makeStyles } from '@mui/styles'; import FileCopyRoundedIcon from '@mui/icons-material/FileCopyRounded'; import CheckRoundedIcon from '@mui/icons-material/CheckRounded'; @@ -62,7 +62,7 @@ CopyButton.propTypes = { }; -export default function CodeMirror({className, currEditor, showCopyBtn=false, customKeyMap=[], ...props}) { +export default function CodeMirror({className, currEditor, showCopyBtn=false, customKeyMap=[], onTextSelect, ...props}) { const classes = useStyles(); const editor = useRef(); const [[showFind, isReplace], setShowFind] = useState([false, false]); @@ -111,8 +111,36 @@ export default function CodeMirror({className, currEditor, showCopyBtn=false, cu const onMouseEnter = useCallback(()=>{showCopyBtn && setShowCopy(true);}, []); const onMouseLeave = useCallback(()=>{showCopyBtn && setShowCopy(false);}, []); + // Use to handle text selection events. + useEffect(() => { + if (!onTextSelect) return; + + const handleSelection = () => { + const selectedText = window.getSelection().toString(); + if (selectedText) { + onTextSelect(selectedText); + } else { + onTextSelect(''); // Reset if no text is selected + } + }; + + const handleKeyUp = () => { + handleSelection(); + }; + // Add event listeners for mouseup and keyup events to detect text selection. + document.addEventListener('mouseup', handleSelection); + document.addEventListener('keyup', handleKeyUp); + + // Cleanup function to remove event listeners when component unmounts or onTextSelect changes. + return () => { + document.removeEventListener('mouseup', handleSelection); + document.removeEventListener('keyup', handleKeyUp); + }; + }, [onTextSelect]); + + return ( -
+
{showCopy && } @@ -126,4 +154,5 @@ CodeMirror.propTypes = { className: CustomPropTypes.className, showCopyBtn: PropTypes.bool, customKeyMap: PropTypes.array, + onTextSelect:PropTypes.func }; diff --git a/web/pgadmin/tools/sqleditor/__init__.py b/web/pgadmin/tools/sqleditor/__init__.py index d94868ffe..9b6b61db1 100644 --- a/web/pgadmin/tools/sqleditor/__init__.py +++ b/web/pgadmin/tools/sqleditor/__init__.py @@ -49,7 +49,7 @@ from pgadmin.tools.sqleditor.utils.query_tool_fs_utils import \ read_file_generator from pgadmin.tools.sqleditor.utils.filter_dialog import FilterDialog from pgadmin.tools.sqleditor.utils.query_history import QueryHistory -from pgadmin.tools.sqleditor.utils.macros import get_macros,\ +from pgadmin.tools.sqleditor.utils.macros import get_macros, \ get_user_macros, set_macros from pgadmin.utils.constants import MIMETYPE_APP_JS, \ SERVER_CONNECTION_CLOSED, ERROR_MSG_TRANS_ID_NOT_FOUND, \ @@ -130,6 +130,7 @@ class SqlEditorModule(PgAdminModule): 'sqleditor.clear_query_history', 'sqleditor.get_macro', 'sqleditor.get_macros', + 'sqleditor.get_user_macros', 'sqleditor.set_macros', 'sqleditor.get_new_connection_data', 'sqleditor.get_new_connection_servers', @@ -2692,3 +2693,15 @@ def update_macros(trans_id): _, _, _, _, _ = check_transaction_status(trans_id) return set_macros() + + +@blueprint.route( + '/get_user_macros', + methods=["GET"], endpoint='get_user_macros' +) +@pga_login_required +def user_macros(json_resp=True): + """ + This method is used to fetch all user macros. + """ + return get_user_macros() diff --git a/web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx b/web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx index 4e6e6b6fa..9d72dba1c 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx @@ -50,6 +50,21 @@ function initConnection(api, params, passdata) { return api.post(url_for('NODE-server.connect_id', params), passdata); } +export function getRandomName(existingNames) { + const maxNumber = existingNames.reduce((max, name) => { + const match = name.match(/\d+$/); // Extract the number from the name + if (match) { + const number = parseInt(match[0], 10); + return number > max ? number : max; + } + return max; + }, 0); + + // Generate the new name + const newName = `Macro ${maxNumber + 1}`; + return newName; +} + function setPanelTitle(docker, panelId, title, qtState, dirty=false) { if(qtState.current_file) { title = qtState.current_file.split('\\').pop().split('/').pop(); @@ -194,6 +209,8 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN }], }); + const [selectedText, setSelectedText] = useState(''); + const setQtState = (state)=>{ _setQtState((prev)=>({...prev,...evalFunc(null, state, prev)})); }; @@ -250,7 +267,7 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN { maximizable: true, tabs: [ - LayoutDocker.getPanel({id: PANELS.QUERY, title: gettext('Query'), content: }), + LayoutDocker.getPanel({id: PANELS.QUERY, title: gettext('Query'), content: setSelectedText(text)}/>}), LayoutDocker.getPanel({id: PANELS.HISTORY, title: gettext('Query History'), content: , cached: undefined}), ], @@ -796,7 +813,38 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN }} onClose={onClose}/> }, 850, 500); - }, [qtState.preferences.browser]); + }, [qtState.preferences.browser]); + + const onAddToMacros = () => { + if (selectedText){ + let currMacros = qtState.params.macros; + const existingNames = currMacros.map(macro => macro.name); + const newName = getRandomName(existingNames); + let changed = [{ 'name': newName, 'sql': selectedText }]; + + api.put( + url_for('sqleditor.set_macros', { + 'trans_id': qtState.params.trans_id, + }), + { changed: changed } + ) + .then(({ data: respData }) => { + const filteredData = respData.filter(m => Boolean(m.name)); + setQtState(prev => ({ + ...prev, + params: { + ...prev.params, + macros: filteredData, + }, + })); + }) + .catch(error => { + console.error(error); + }); + } + setSelectedText(''); + }; + const onFilterClick = useCallback(()=>{ const onClose = ()=>docker.current.close('filter-dialog'); @@ -884,8 +932,9 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN ), [containerRef.current, onManageMacros, onFilterClick])} + />), [containerRef.current, onManageMacros, onFilterClick, onAddToMacros])} docker.current=obj} defaultLayout={defaultLayout} diff --git a/web/pgadmin/tools/sqleditor/static/js/components/dialogs/MacrosDialog.jsx b/web/pgadmin/tools/sqleditor/static/js/components/dialogs/MacrosDialog.jsx index c3bcb9665..e6183facb 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/dialogs/MacrosDialog.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/dialogs/MacrosDialog.jsx @@ -11,7 +11,7 @@ import React from 'react'; import SchemaView from '../../../../../../static/js/SchemaView'; import BaseUISchema from '../../../../../../static/js/SchemaView/base_schema.ui'; import gettext from 'sources/gettext'; -import { QueryToolContext } from '../QueryToolComponent'; +import { QueryToolContext, getRandomName } from '../QueryToolComponent'; import url_for from 'sources/url_for'; import _ from 'lodash'; import PropTypes from 'prop-types'; @@ -23,17 +23,36 @@ class MacrosCollection extends BaseUISchema { } get idAttribute() { - return 'mid'; + return 'id'; + } + + /* Returns the new data row for the schema based on defaults and input */ + getNewData(current_macros, data={}) { + let newRow = {}; + this.fields.forEach((field)=>{ + newRow[field.id] = this.defaults[field.id]; + }); + newRow = { + ...newRow, + ...data, + }; + if (current_macros){ + // Extract an array of existing names from the 'macro' collection + const existingNames = current_macros.map(macro => macro.name); + const newName = getRandomName(existingNames); + newRow.name = newName; + } + return newRow; } get baseFields() { let obj = this; return [ { - id: 'id', label: gettext('Key'), cell: 'select', noEmpty: true, + id: 'mid', label: gettext('Key'), cell: 'select', noEmpty: false, width: 100, options: obj.keyOptions, optionsReloadBasis: obj.keyOptions.length, controlProps: { - allowClear: false, + allowClear: true, } }, { @@ -73,10 +92,14 @@ class MacrosSchema extends BaseUISchema { } validate(state, setError) { - let allKeys = state.macro.map((m)=>m.id.toString()); + let allKeys = state.macro.map((m) => m.mid ? m.mid.toString() : null).filter(key => key !== null); + let allNames = state.macro.map((m) => m.name ? m.name.toLowerCase() : null); if(allKeys.length != new Set(allKeys).size) { setError('macro', gettext('Key must be unique.')); return true; + } else if(allNames.length != new Set(allNames).size) { + setError('macro', gettext('Name must be unique.')); + return true; } return false; } @@ -89,26 +112,28 @@ const useStyles = makeStyles((theme)=>({ }, })); -function getChangedMacros(macrosData, changeData) { +function getChangedMacros(userMacrosData, changeData) { /* For backend, added, removed is changed. Convert all added, removed to changed. */ let changed = []; + for (const m of (changeData.macro.changed || [])) { let newM = {...m}; if('id' in m) { - /* if key changed, clear prev and add new */ - changed.push({id: m.mid, name: null, sql: null}); - let em = _.find(macrosData, (d)=>d.mid==m.mid); - newM = {name: em.name, sql: em.sql, ...m}; + let em = _.find(userMacrosData, (d)=>d.id==m.id); + newM = {name: m.name ? (m.name) : em.name , sql: m.sql ? m.sql : em.sql, mid: m.mid ? m.mid : em.mid, ...m}; } else { newM.id = m.mid; } - delete newM.mid; changed.push(newM); } for (const m of (changeData.macro.deleted || [])) { changed.push({id: m.id, name: null, sql: null}); } for (const m of (changeData.macro.added || [])) { + if (m.id && m.id !== 0){ + m.mid = m.id; + delete m.id; + } changed.push(m); } return changed; @@ -118,30 +143,46 @@ export default function MacrosDialog({onClose, onSave}) { const classes = useStyles(); const queryToolCtx = React.useContext(QueryToolContext); const [macrosData, setMacrosData] = React.useState([]); + const [userMacrosData, setUserMacrosData] = React.useState([]); const [macrosErr, setMacrosErr] = React.useState(null); React.useEffect(async ()=>{ try { - let {data: respData} = await queryToolCtx.api.get(url_for('sqleditor.get_macros', { - 'trans_id': queryToolCtx.params.trans_id, - })); - /* Copying id to mid to track key id changes */ - setMacrosData(respData.macro.map((m)=>({...m, mid: m.id}))); + // Fetch user macros data + let { data: userMacroRespData } = await queryToolCtx.api.get(url_for('sqleditor.get_user_macros')); + setUserMacrosData(userMacroRespData); + } catch (error) { setMacrosErr(error); } }, []); + + React.useEffect(async ()=>{ + try { + // Fetch macros data + let {data: respData} = await queryToolCtx.api.get(url_for('sqleditor.get_macros', { + 'trans_id': queryToolCtx.params.trans_id, + })); + /* Copying id to mid to track key id changes */ + setMacrosData(respData.macro.map((m)=>({...m, mid: m.id}))); + + } catch (error) { + setMacrosErr(error); + } + }, []); + + const onSaveClick = (_isNew, changeData)=>{ return new Promise((resolve, reject)=>{ const setMacros = async ()=>{ try { - let changed = getChangedMacros(macrosData, changeData); + let changed = getChangedMacros(userMacrosData, changeData); let {data: respData} = await queryToolCtx.api.put(url_for('sqleditor.set_macros', { 'trans_id': queryToolCtx.params.trans_id, }), {changed: changed}); resolve(); - onSave(respData.macro?.filter((m)=>Boolean(m.name))); + onSave(respData.filter((m) => Boolean(m.name))); onClose(); } catch (error) { reject(error); @@ -159,6 +200,7 @@ export default function MacrosDialog({onClose, onSave}) { if(keyOptions.length <= 0) { return <>; } + return ( Boolean(m.name))}); + return Promise.resolve({macro: userMacrosData.filter((m)=>Boolean(m.name))}); }} schema={new MacrosSchema(keyOptions)} viewHelperProps={{ diff --git a/web/pgadmin/tools/sqleditor/static/js/components/sections/MainToolBar.jsx b/web/pgadmin/tools/sqleditor/static/js/components/sections/MainToolBar.jsx index b66143877..62549db9d 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/sections/MainToolBar.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/sections/MainToolBar.jsx @@ -54,7 +54,7 @@ function autoCommitRollback(type, api, transId, value) { return api.post(url, JSON.stringify(value)); } -export function MainToolBar({containerRef, onFilterClick, onManageMacros}) { +export function MainToolBar({containerRef, onFilterClick, onManageMacros, onAddToMacros}) { const classes = useStyles(); const eventBus = useContext(QueryToolEventsContext); const queryToolCtx = useContext(QueryToolContext); @@ -633,6 +633,7 @@ export function MainToolBar({containerRef, onFilterClick, onManageMacros}) { label={gettext('Macros Menu')} > {gettext('Manage macros')} + {gettext('Add to macros')} {queryToolCtx.params?.macros?.map((m)=>{ return ( @@ -656,4 +657,5 @@ MainToolBar.propTypes = { containerRef: CustomPropTypes.ref, onFilterClick: PropTypes.func, onManageMacros: PropTypes.func, + onAddToMacros: PropTypes.func }; diff --git a/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx b/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx index 119186ecc..a857e586e 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx @@ -23,6 +23,7 @@ import { usePgAdmin } from '../../../../../../static/js/BrowserComponent'; import ConfirmPromotionContent from '../dialogs/ConfirmPromotionContent'; import usePreferences from '../../../../../../preferences/static/js/store'; import { getTitle } from '../../sqleditor_title'; +import PropTypes from 'prop-types'; const useStyles = makeStyles(()=>({ @@ -61,7 +62,7 @@ async function registerAutocomplete(editor, api, transId) { }); } -export default function Query() { +export default function Query({onTextSelect}) { const classes = useStyles(); const editor = React.useRef(); const eventBus = useContext(QueryToolEventsContext); @@ -390,7 +391,6 @@ export default function Query() { const change = useCallback(()=>{ eventBus.fireEvent(QUERY_TOOL_EVENTS.QUERY_CHANGED, editor.current.isDirty()); - if(!queryToolCtx.params.is_query_tool && editor.current.isDirty()){ if(queryToolCtx.preferences.sqleditor.view_edit_promotion_warning){ checkViewEditDataPromotion(); @@ -480,5 +480,11 @@ export default function Query() { onChange={change} autocomplete={true} customKeyMap={shortcutOverrideKeys} + onTextSelect={onTextSelect} />; } + + +Query.propTypes = { + onTextSelect: PropTypes.func, +}; \ No newline at end of file diff --git a/web/pgadmin/tools/sqleditor/tests/test_macros.py b/web/pgadmin/tools/sqleditor/tests/test_macros.py index d3ad4af19..2872e4330 100644 --- a/web/pgadmin/tools/sqleditor/tests/test_macros.py +++ b/web/pgadmin/tools/sqleditor/tests/test_macros.py @@ -32,15 +32,15 @@ class TestMacros(BaseTestGenerator): operation='set', data={ 'changed': [ - {'id': 1, + {'mid': 1, 'name': 'Test Macro 1', 'sql': 'SELECT 1;' }, - {'id': 2, + {'mid': 2, 'name': 'Test Macro 2', 'sql': 'SELECT 2;' }, - {'id': 3, + {'mid': 3, 'name': 'Test Macro 3', 'sql': 'SELECT 3;' }, @@ -129,10 +129,11 @@ class TestMacros(BaseTestGenerator): self.assertEqual(response.status_code, 200) for m in self.data['changed']: + if self.operation == 'set': + m['id'] = m['mid'] url = '/sqleditor/get_macros/{0}/{1}'.format(m['id'], self.trans_id) response = self.tester.get(url) - if self.operation == 'clear': self.assertEqual(response.status_code, 410) elif self.operation == 'set': diff --git a/web/pgadmin/tools/sqleditor/utils/macros.py b/web/pgadmin/tools/sqleditor/utils/macros.py index cf1d89aba..4612e018c 100644 --- a/web/pgadmin/tools/sqleditor/utils/macros.py +++ b/web/pgadmin/tools/sqleditor/utils/macros.py @@ -27,7 +27,7 @@ def get_macros(macro_id, json_resp): :param json_resp: Set True to return json response """ if macro_id: - macro = UserMacros.query.filter_by(mid=macro_id, + macro = UserMacros.query.filter_by(id=macro_id, uid=current_user.id).first() if macro is None: return make_json_response( @@ -37,7 +37,8 @@ def get_macros(macro_id, json_resp): ) else: return ajax_response( - response={'id': macro.mid, + response={'id': macro.id, + 'mid':macro.mid, 'name': macro.name, 'sql': macro.sql}, status=200 @@ -45,13 +46,12 @@ def get_macros(macro_id, json_resp): else: macros = db.session.query(Macros.id, Macros.alt, Macros.control, Macros.key, Macros.key_code, - UserMacros.name, UserMacros.sql - ).outerjoin( + UserMacros.name, UserMacros.sql, + UserMacros.id).outerjoin( UserMacros, and_(Macros.id == UserMacros.mid, UserMacros.uid == current_user.id)).all() data = [] - for m in macros: key_label = 'Ctrl + ' + m[3] if m[2] is True else 'Alt + ' + m[3] data.append({'id': m[0], 'alt': m[1], @@ -74,7 +74,8 @@ def get_user_macros(): This method is used to get all the user macros. """ - macros = db.session.query(UserMacros.name, + macros = db.session.query(UserMacros.id, + UserMacros.name, Macros.id, Macros.alt, Macros.control, Macros.key, Macros.key_code, @@ -86,11 +87,17 @@ def get_user_macros(): data = [] for m in macros: - key_label = 'Ctrl + ' + m[4] if m[3] is True else 'Alt + ' + m[4] - data.append({'name': m[0], 'id': m[1], 'key': m[4], - 'key_label': key_label, 'alt': 1 if m[2] else 0, - 'control': 1 if m[3] else 0, 'key_code': m[5], - 'sql': m[6]}) + key_label = ( + 'Ctrl + ' + str(m[5]) + if m[4] is True + else 'Alt + ' + str(m[5]) + if m[5] is not None + else '' + ) + data.append({'id': m[0], 'name': m[1], 'mid': m[2], 'key': m[5], + 'key_label': key_label, 'alt': 1 if m[3] else 0, + 'control': 1 if m[4] else 0, 'key_code': m[6], + 'sql': m[7]}) return data @@ -111,21 +118,21 @@ def set_macros(): ) for m in data['changed']: - if m['id']: + if m.get('id'): macro = UserMacros.query.filter_by( uid=current_user.id, - mid=m['id']).first() + id=m['id']).first() if macro: status, msg = update_macro(m, macro) - else: - status, msg = create_macro(m) + else: + status, msg = create_macro(m) if not status: return make_json_response( status=410, success=0, errormsg=msg ) - return get_macros(None, True) + return get_user_macros() def create_macro(macro): @@ -146,7 +153,7 @@ def create_macro(macro): try: new_macro = UserMacros( uid=current_user.id, - mid=macro['id'], + mid=macro['mid'] if macro.get('mid') else None, name=macro['name'], sql=macro['sql'] ) @@ -168,6 +175,7 @@ def update_macro(data, macro): name = data.get('name', None) sql = data.get('sql', None) + mid = data.get('mid', None) if (name or sql) and macro.sql and 'name' in data and name is None: return False, gettext( @@ -177,11 +185,12 @@ def update_macro(data, macro): "Could not find the required parameter (sql).") try: - if name or sql: + if name or sql or mid: if name: macro.name = name if sql: macro.sql = sql + macro.mid = mid if mid != 0 else None else: db.session.delete(macro)