0

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
1
  • first: command= expects function's name without (). Second: it has to be real name, not string "placeholder" from database. So keeping function's name in database is not so good idea. It may needs command = globals()["placeholder"] to get real access to function placeholder. Commented Dec 17, 2021 at 4:41

2 Answers 2

1

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
    ...: ...,
}
Sign up to request clarification or add additional context in comments.

Comments

0

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))

3 Comments

It is better to use globals().get('placeholder',None) to avoid raising KeyError when placeholder() does not exist.
@acw1668 good point - I add this to answer.
Sounds like this could lead to the Unsafe Reflection security weakness?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.