Home > Software design >  Trying to pass a Python function stored in an SQL database to a menu in TKinter
Trying to pass a Python function stored in an SQL database to a menu in TKinter

Time:12-17

I am writing an application in python using tkinter as the GUI framework. I am saving lists menu commands in an SQL table and iterating through all of the selected rows to build the menu. So far this is working well but I cannot get the command to work when the menu item is clicked. The SQL table has 4 columns ID, Label, Command, Parent. so and example row for the table would look like

1, New, placeholder(), rootFile

When I click on the "New" command, nothing happens. If I replace row[2] with placeholder(), it works as expected, unfortunately this does not allow me to specify different commands for each menu item stored in the SQL database.

import pyodbc #needed for database connectivity
from tkinter import * #imports GUI objects
from tkinter import ttk #imports themed widgets for GUI
from database_connect import SQL_CONNECT

setupSQL = SQL_CONNECT('Server=MILLER2021;','Database=DRAWINGTOOL_DB;') #establish connection to SQL database that contains startup info for applicaton
setupCursor = setupSQL.cursor() #creates a cursor object for the SQL connection for setting up application

root = Tk() #creates top level window widget
root.title("Drawing Tool") #defines the title of root
root.geometry("400x300") #define the size of root

def placeholder():
    print("This is an ouptut from a placeholder function")

##Define Menu Bar##
rootMenuBar = Menu(root)
rootFile = Menu(rootMenuBar, tearoff=0)
rootMenuBar.add_cascade(label="File", menu=rootFile)    #displays item on menu
setupCursor.execute("SELECT * FROM MENU_COMMANDS WHERE PARENT LIKE 'rootFile'") #selects all commands from SQL database to be added to rootFile menu
for row in setupCursor: #iterate through all rows selected from previous cursor execute query
    rootFile.add_command(label=row[1], command=row[2]) #adds new item to the file dropdown
root.config(menu=rootMenuBar) #displays menuBar in root menu
root.mainloop() #keeps code running

CodePudding user response:

The idea of taking function names stored in the database and looking them up in globals() sounds like unfortunate design; sooner or later, this approach will lead to a security problem (Unsafe Reflection), because there are a lot of things in globals() that you don't want people to call. Much better to avoid it if at all possible.

A better approach is to look up the names in a table written directly in the code; something like:

COMMAND_TABLE = {
    'placeholder': placeholder,
    ...: ...,
}
...
for row in setupCursor:
    rootFile.add_command(label=row[1], command=COMMAND_TABLE[row[2]])

(plus appropriate handling for when the lookup raises KeyError)

In addition to being more secure, this will also be more flexible, decoupling the names in the code from the names in the database. For example, if you decide that placeholder should be renamed place_holder and moved to another module, you might end up with something like:

COMMAND_TABLE = {
    'place_holder': another_module.place_holder,
    'placeholder': another_module.place_holder,  # old name for compatibility with existing databases
    ...: ...,
}

CodePudding user response:

First: command= expects function's name without () and it when you select menu then it will use () to run this function.


It has to be real name, not string "placeholder" from database.

It may needs to use globals() to get real access to function placeholder

command = globals()["placeholder"]

Minimal working code:

def placeholder():
    print('test placeholder')
    
func = globals()["placeholder"]

func()

import tkinter as tk  # PEP8: `import *` is not preferred

# --- functions ---

def placeholder():
    print("This is an ouptut from a placeholder function")

# --- main ---

root = tk.Tk()
root_menubar = tk.Menu(root)    # PEP8: `lower_case_names`
root_file = tk.Menu(root_menubar, tearoff=0)
root_menubar.add_cascade(label="File", menu=root_file)
root_file.add_command(label='Placeholder', command=globals()["placeholder"])
root.config(menu=root_menubar)
root.mainloop()

EDIT:

As @acw1668 mentioned in comment: it is better to use .get("placeholder", None) instead of ["placeholder"] because it will not raise error when def placeholder doesn't exists

root_file.add_command(label='Placeholder', command=globals().get("placeholder", None))
  • Related