
This tutorial extends our Treeview insert() example by connecting the form to a MySQL database. The important difference is that the row is added to Treeview only after MySQL confirms that the database record was inserted successfully.
The workflow is:
Validate input
|
v
INSERT into MySQL
|
v
Get generated ID
|
v
Add confirmed row to Treeview
This example displays five columns: ID, Name, Class, Mark and Gender.
import tkinter as tk
from tkinter import ttk
root=tk.Tk()
root.geometry('560x500')
root.title('MySQL Insert and Treeview - plus2net')
tree=ttk.Treeview(root, columns=('id','name','class','mark','gender'), show='headings', selectmode='browse', height=8)
Configure the columns:
tree.column('id', width=50, anchor='center')
tree.column('name', width=120, anchor='w')
tree.column('class', width=90, anchor='center')
tree.column('mark', width=70, anchor='center')
tree.column('gender', width=90, anchor='center')
Add the headings:
tree.heading('id', text='ID')
tree.heading('name', text='Name')
tree.heading('class', text='Class')
tree.heading('mark', text='Mark')
tree.heading('gender', text='Gender')
The Treeview starts empty. We do not add a sample row that has not been saved in MySQL.
Name and Mark are single-line values, so Entry widgets are sufficient.
name_var=tk.StringVar()
class_var=tk.StringVar()
mark_var=tk.StringVar()
gender_var=tk.StringVar()
tk.Entry(root, textvariable=name_var, width=18)
class_menu=tk.OptionMenu(root, class_var, 'Three', 'Four', 'Five')
See Tkinter OptionMenu for more examples.
tk.Radiobutton(root, text='Male', variable=gender_var, value='Male')
tk.Radiobutton(root, text='Female', variable=gender_var, value='Female')
See Tkinter Radiobutton.
from sqlalchemy import create_engine, text
from sqlalchemy.exc import SQLAlchemyError
engine=create_engine('mysql+mysqldb://id:pw@localhost/my_db')
Replace the database username, password, host and database name with the values used by your MySQL installation.
We create the Engine once. A database connection is acquired only when the INSERT is executed.
Read the values and remove leading or trailing spaces.
my_name=name_var.get().strip()
my_class=class_var.get().strip()
my_mark=mark_var.get().strip()
my_gender=gender_var.get().strip()
Check the required text fields:
if len(my_name)<2:
show_status('Enter a valid name.', 'red')
return
if not my_class:
show_status('Select a class.', 'red')
return
if not my_gender:
show_status('Select a gender.', 'red')
return
try:
mark_value=int(my_mark)
except ValueError:
show_status('Mark must be an integer.', 'red')
return
This is clearer than using a generic validation flag and a bare except:.
Use named placeholders rather than building SQL from user-entered strings.
query=text('''INSERT INTO student
(name,class,mark,gender)
VALUES (:name,:class,:mark,:gender)''')
data={
'name':my_name,
'class':my_class,
'mark':mark_value,
'gender':my_gender
}
Execute the INSERT inside a transaction:
with engine.begin() as conn:
result=conn.execute(query,data)
new_id=result.lastrowid
If the student table uses an auto-increment primary key, MySQL assigns the new ID during the INSERT.
new_id=result.lastrowid
For example:
42
This value represents the actual database record, so it is useful both as the displayed ID and as the Treeview item's iid.
new_id comes from the MySQL primary key and is intentionally unique. Using iid=str(new_id) makes later update and delete operations easier.Only after the database transaction succeeds do we add the row:
tree.insert('', tk.END, iid=str(new_id), values=(new_id,my_name,my_class,mark_value,my_gender))
The Treeview row and the MySQL row now share the same ID.
The INSERT is wrapped in:
with engine.begin() as conn:
...
If the INSERT succeeds, the transaction is committed when the block completes. If a database exception occurs, the transaction is rolled back.
This also avoids keeping one database connection open for the full life of the Tkinter application.
import tkinter as tk
from tkinter import ttk
from sqlalchemy import create_engine, text
from sqlalchemy.exc import SQLAlchemyError
engine=create_engine('mysql+mysqldb://id:pw@localhost/my_db')
root=tk.Tk()
root.geometry('560x500')
root.title('MySQL Insert and Treeview - plus2net')
name_var=tk.StringVar()
class_var=tk.StringVar()
mark_var=tk.StringVar()
gender_var=tk.StringVar()
status_var=tk.StringVar()
def show_status(message,color):
status_var.set(message)
status_label.config(fg=color)
root.after(3000,lambda:status_var.set(''))
def clear_form():
name_var.set('')
class_var.set('')
mark_var.set('')
gender_var.set('')
name_entry.focus_set()
def add_data():
my_name=name_var.get().strip()
my_class=class_var.get().strip()
my_mark=mark_var.get().strip()
my_gender=gender_var.get().strip()
if len(my_name)<2:
show_status('Enter a valid name.','red')
return
if not my_class:
show_status('Select a class.','red')
return
if not my_gender:
show_status('Select a gender.','red')
return
try:
mark_value=int(my_mark)
except ValueError:
show_status('Mark must be an integer.','red')
return
query=text('''INSERT INTO student
(name,class,mark,gender)
VALUES (:name,:class,:mark,:gender)''')
data={'name':my_name,'class':my_class,'mark':mark_value,'gender':my_gender}
try:
with engine.begin() as conn:
result=conn.execute(query,data)
new_id=result.lastrowid
except SQLAlchemyError as e:
show_status(f'Database error: {e}','red')
return
if new_id is None:
show_status('Record was not confirmed by the database.','red')
return
tree.insert('',tk.END,iid=str(new_id),values=(new_id,my_name,my_class,mark_value,my_gender))
clear_form()
show_status(f'Record added. ID: {new_id}','green')
tree=ttk.Treeview(root,columns=('id','name','class','mark','gender'),show='headings',selectmode='browse',height=8)
tree.grid(row=0,column=0,columnspan=4,padx=20,pady=20)
tree.column('id',width=50,anchor='center')
tree.column('name',width=120,anchor='w')
tree.column('class',width=90,anchor='center')
tree.column('mark',width=70,anchor='center')
tree.column('gender',width=90,anchor='center')
tree.heading('id',text='ID')
tree.heading('name',text='Name')
tree.heading('class',text='Class')
tree.heading('mark',text='Mark')
tree.heading('gender',text='Gender')
tk.Label(root,text='Add Student',font=('Helvetica',16)).grid(row=1,column=0,columnspan=4,pady=8)
tk.Label(root,text='Name:').grid(row=2,column=0,sticky='e',padx=5,pady=5)
name_entry=tk.Entry(root,textvariable=name_var,width=18)
name_entry.grid(row=2,column=1,sticky='w')
tk.Label(root,text='Class:').grid(row=2,column=2,sticky='e',padx=5)
class_menu=tk.OptionMenu(root,class_var,'Three','Four','Five')
class_menu.grid(row=2,column=3,sticky='w')
tk.Label(root,text='Mark:').grid(row=3,column=0,sticky='e',padx=5,pady=5)
tk.Entry(root,textvariable=mark_var,width=8).grid(row=3,column=1,sticky='w')
gender_frame=tk.Frame(root)
gender_frame.grid(row=3,column=2,columnspan=2,sticky='w')
tk.Radiobutton(gender_frame,text='Male',variable=gender_var,value='Male').pack(side=tk.LEFT)
tk.Radiobutton(gender_frame,text='Female',variable=gender_var,value='Female').pack(side=tk.LEFT)
tk.Button(root,text='Add Record',command=add_data).grid(row=4,column=1,pady=12)
status_label=tk.Label(root,textvariable=status_var)
status_label.grid(row=5,column=0,columnspan=4,pady=5)
name_entry.focus_set()
root.mainloop()
A database failure would leave the GUI showing a record that was never saved.
Correct order:
INSERT into MySQL
get new_id
tree.insert(...)
Using:
with engine.begin() as conn:
provides a transaction that commits when the block completes successfully.
Create the Engine once and acquire a connection when database work is required.
The older code used:
t1.get('1.0',END)
which can include a trailing newline. Single-line Entry widgets and .strip() avoid this issue.
Use the expected exception:
except ValueError:
when checking integer conversion.
Only clear the form after MySQL returns successfully. If an error occurs, the user's values remain available for correction or retry.
If the purpose of the page is to demonstrate rows confirmed by MySQL, don't display records that were not retrieved from or inserted into the database.
Use bound parameters:
VALUES (:name,:class,:mark,:gender)
and pass the values separately.
MySQL is the persistent data source. Adding the Treeview row only after a successful database insert keeps the GUI synchronized with saved data.
lastrowid returns the identifier generated for an inserted row when the database and driver support it, such as an auto-increment MySQL primary key.
Yes. A database primary key is intended to be unique, so using its string value as the Treeview iid creates a useful link between the GUI item and database record.
It opens a database transaction and commits it when the block succeeds. If an exception occurs, the operation is rolled back.
The SQL statement and user-entered values remain separate. This is safer and more reliable than concatenating form values into the SQL string.
The exception is handled, an error is shown, the Treeview remains unchanged and the form values are retained.
text().engine.begin() for the INSERT transaction.lastrowid.iid.Author & Instructor at plus2net
I write and maintain practical tutorials on Python, PHP, SQL, JavaScript, HTML, jQuery, and web development at plus2net. The tutorials focus on clear explanations, working examples, and code that readers can test and adapt while learning.