Help saving data to database

I am deveoping a database system to record a library of ebooks in a Sqlite database. My code to create the form works well and the form displays correctly. However, when I click the Add button to dave the data, nothing happens. Here is my Add event code:

def on_btnAddClick(*args):
    vCat=("Category")
    vTit=("Title")
    vAuth=("Author")
    vYear=("Year_Purchased")
    vComments=("Comments")

    curs.execute("INSERT INTO Books(Category,Title,Author,Year_Purchased,\
                 Comments) VALUES(?, ?, ?,?,?)", (vCat,vTit,vAuth,vYear,\
                                                  vComments) );  
    conn.commit()

Any help would be much appreciated.
Neil

Just to run through the basics: The Books table exists, right? And curs and conn are both global variables that have been set up? Is there a way you could post the full program?

Another thing that stands out to me is that args gets ignored completely: if you passed in vCat='mystery' or something, the first line would override vCat and set it to the string "Category"' (the parentheses are a bit odd here, stylistically, but don’t have any negative effect.)

Can you show us the rest of the code? Specifically, show us the line that calls on_btnAddClick()

I wouldn’t worry about *args. It looks like it could be using tkinter, so that’s just the event object telling you which widget received the click, which you know already (it’s the Add button).

I notice that the code has curs.execute but conn.commit. Shouldn’t they both use curs or both use conn?

1 Like

Try adding print statements inside the function for debugging purposes:

def on_btnAddClick(*args):

    print('I have entered the on_btnAddClick function.')

    vCat=("Category")
    vTit=("Title")
    vAuth=("Author")
    vYear=("Year_Purchased")
    vComments=("Comments")

    curs.execute("INSERT INTO Books(Category,Title,Author,Year_Purchased,\
                 Comments) VALUES(?, ?, ?,?,?)", (vCat,vTit,vAuth,vYear,\
                                                  vComments) );  
    conn.commit()

    print('I am leaving the on_btnAddClick function.')

The first print statement is to verify that you are actually calling the function when you press the button. The exiting print statement is to verify that it is not being hung up inside.

If both print statements are executed, check the two nested functions: conn.execute and conn.commit. If you are not entering the function, check the set-up code where you associate the button to this function (i.e., the callback function).

I don’t think that you need the semicolon at the end of the curs.execute() function (might be a C relapse).

Thank you Al, Matthew and Paul for your quick reply. I am attaching the full code for BooksLibrary_Support.py.

import sys
import tkinter as tk
import tkinter.ttk as ttk
from tkinter.constants import *

import BooksLibrary

# import the sqlite3 module
import sqlite3

vCat=""
vTit=""
vAuth=""
vYear=int()
vComments=""

# Define connection to Book DB and cursor
conn = sqlite3.connect("Books.db")
curs = conn.cursor()

_debug = True # False to eliminate debug printing from callback functions.

def main(*args):
    '''Main entry point for the application.'''
    global root
    root = tk.Tk()
    root.protocol( 'WM_DELETE_WINDOW' , root.destroy)
    # Creates a toplevel widget.
    global _top1, _w1
    _top1 = root
    _w1 = BooksLibrary.BooksLibrary(_top1)
    root.mainloop()

def on_btnAddClick(*args):
    vCat=("Category")
    vTit=("Title")
    vAuth=("Author")
    vYear=("Year_Purchased")
    vComments=("Comments")

    curs.execute("INSERT INTO Books(Category,Title,Author,Year_Purchased,\
                 Comments) VALUES(?, ?, ?,?,?)", (vCat,vTit,vAuth,vYear,\
                                                  vComments) );  
    conn.commit()
   
    

def on_btnDeleteClick(*args):
    if _debug:
        print('BooksLibrary_support.on_btnDeleteClick')
        for arg in args:
            print ('    another arg:', arg)
        sys.stdout.flush()

def on_btnEditClick(*args):
    if _debug:
        print('BooksLibrary_support.on_btnEditClick')
        for arg in args:
            print ('    another arg:', arg)
        sys.stdout.flush()


def on_btnExitClick(*args):
    root.destroy()

def on_btnUpdateClick(*args):
    if _debug:
        print('BooksLibrary_support.on_btnUpdateClick')
        for arg in args:
            print ('    another arg:', arg)
        sys.stdout.flush()

if __name__ == '__main__':
    BooksLibrary.start_up()

The other program BooksLibrary.py that creates the form was generated by using the Page GUI program and is also below;

#! /usr/bin/env python3
#  -*- coding: utf-8 -*-
#
# GUI module generated by PAGE version 8.0
#  in conjunction with Tcl version 8.6
#    Nov 24, 2024 11:50:48 AM GMT  platform: Darwin

import sys
import tkinter as tk
import tkinter.ttk as ttk
from tkinter.constants import *
import os.path

_location = os.path.dirname(__file__)

import BooksLibrary_support

_bgcolor = 'wheat'
_fgcolor = 'black'
_tabfg1 = 'black' 
_tabfg2 = 'white' 
_bgmode = 'light' 
_tabbg1 = '#d9d9d9' 
_tabbg2 = 'gray40' 

_style_code_ran = 0
def _style_code():
    global _style_code_ran
    if _style_code_ran: return        
    try: BooksLibrary_support.root.tk.call('source',
                os.path.join(_location, 'themes', 'new-wheat.tcl'))
    except: pass
    style = ttk.Style()
    style.theme_use('new-wheat')
    style.configure('.', font = "TkDefaultFont")
    _style_code_ran = 1

class BooksLibrary:
    def __init__(self, top=None):
        '''This class configures and populates the toplevel window.
           top is the toplevel containing window.'''

        top.geometry("360x390+572+464")
        top.minsize(72, 15)
        top.maxsize(1600, 847)
        top.resizable(1,  1)
        top.title("Books Library")
        top.configure(background="wheat")
        top.configure(highlightbackground="wheat")
        top.configure(highlightcolor="black")

        self.top = top
        self.combobox = tk.StringVar()

        _style_code()
        self.lblYearPub = ttk.Label(self.top)
        self.lblYearPub.place(relx=0.053, rely=0.441, height=21, width=94)
        self.lblYearPub.configure(font="-family {.AppleSystemUIFont} -size 13")
        self.lblYearPub.configure(relief="flat")
        self.lblYearPub.configure(text='''Year Published''')
        self.lblYearPub.configure(compound='left')

        self.eAuthor = ttk.Entry(self.top)
        self.eAuthor.place(relx=0.361, rely=0.359, relheight=0.059
                , relwidth=0.481)
        self.eAuthor.configure(font="-family {.AppleSystemUIFont} -size 13")
        self.eAuthor.configure(cursor="ibeam")

        self.eYearPub = ttk.Entry(self.top)
        self.eYearPub.place(relx=0.361, rely=0.441, relheight=0.062
                , relwidth=0.208)
        self.eYearPub.configure(font="-family {.AppleSystemUIFont} -size 13")
        self.eYearPub.configure(cursor="ibeam")

        self.eTitle = ttk.Entry(self.top)
        self.eTitle.place(relx=0.361, rely=0.274, relheight=0.062
                , relwidth=0.481)
        self.eTitle.configure(font="-family {.AppleSystemUIFont} -size 13")
        self.eTitle.configure(cursor="ibeam")

        self.lblTitle = ttk.Label(self.top)
        self.lblTitle.place(relx=0.053, rely=0.274, height=22, width=38)
        self.lblTitle.configure(font="-family {.AppleSystemUIFont} -size 13")
        self.lblTitle.configure(relief="flat")
        self.lblTitle.configure(text='''Title''')
        self.lblTitle.configure(compound='left')

        self.btnExit = ttk.Button(self.top)
        self.btnExit.place(relx=0.542, rely=0.826, height=26, width=90)
        self.btnExit.configure(command=BooksLibrary_support.on_btnExitClick)
        self.btnExit.configure(text='''Exit''')
        self.btnExit.configure(compound='left')

        self.btnDelete = ttk.Button(self.top)
        self.btnDelete.place(relx=0.694, rely=0.674, height=26, width=70)
        self.btnDelete.configure(command=BooksLibrary_support.on_btnDeleteClick)
        self.btnDelete.configure(text='''Delete''')
        self.btnDelete.configure(compound='left')

        self.lblCategory = ttk.Label(self.top)
        self.lblCategory.place(relx=0.053, rely=0.192, height=22, width=65)
        self.lblCategory.configure(font="-family {.AppleSystemUIFont} -size 13")
        self.lblCategory.configure(relief="flat")
        self.lblCategory.configure(text='''Category''')
        self.lblCategory.configure(compound='left')

        self.btnEdit = ttk.Button(self.top)
        self.btnEdit.place(relx=0.278, rely=0.674, height=26, width=65)
        self.btnEdit.configure(command=BooksLibrary_support.on_btnEditClick)
        self.btnEdit.configure(text='''Edit''')
        self.btnEdit.configure(compound='left')

        self.btnUpdate = ttk.Button(self.top)
        self.btnUpdate.place(relx=0.472, rely=0.674, height=26, width=75)
        self.btnUpdate.configure(command=BooksLibrary_support.on_btnUpdateClick)
        self.btnUpdate.configure(text='''Update''')
        self.btnUpdate.configure(compound='left')

        self.btnAdd = ttk.Button(self.top)
        self.btnAdd.place(relx=0.083, rely=0.674, height=26, width=65)
        self.btnAdd.configure(command=BooksLibrary_support.on_btnAddClick)
        self.btnAdd.configure(text='''Add''')
        self.btnAdd.configure(compound='left')

        self.lblComments = ttk.Label(self.top)
        self.lblComments.place(relx=0.053, rely=0.523, height=21, width=70)
        self.lblComments.configure(font="-family {.AppleSystemUIFont} -size 13")
        self.lblComments.configure(relief="flat")
        self.lblComments.configure(text='''Comments''')
        self.lblComments.configure(compound='left')

        self.cmbCategory = ttk.Combobox(self.top)
        self.cmbCategory.place(relx=0.361, rely=0.192, relheight=0.062
                , relwidth=0.517)
        self.value_list = ['Horror,','Historical,','Literary,','Mystery,','Romance,','Science','Fiction,','Thriller',]
        self.cmbCategory.configure(values=self.value_list)
        self.cmbCategory.configure(font="-family {.AppleSystemUIFont} -size 13")
        self.cmbCategory.configure(textvariable=self.combobox)

        self.eRecNo = ttk.Entry(self.top)
        self.eRecNo.place(relx=0.361, rely=0.11, relheight=0.062, relwidth=0.208)

        self.eRecNo.configure(font="-family {.AppleSystemUIFont} -size 13")
        self.eRecNo.configure(cursor="ibeam")

        self.lblAuthor = ttk.Label(self.top)
        self.lblAuthor.place(relx=0.053, rely=0.359, height=21, width=47)
        self.lblAuthor.configure(font="-family {.AppleSystemUIFont} -size 13")
        self.lblAuthor.configure(relief="flat")
        self.lblAuthor.configure(text='''Author''')
        self.lblAuthor.configure(compound='left')

        self.lblRecNo = ttk.Label(self.top)
        self.lblRecNo.place(relx=0.053, rely=0.11, height=21, width=57)
        self.lblRecNo.configure(font="-family {.AppleSystemUIFont} -size 13")
        self.lblRecNo.configure(relief="flat")
        self.lblRecNo.configure(text='''Record #''')
        self.lblRecNo.configure(compound='left')

        self.eComments = ttk.Entry(self.top)
        self.eComments.place(relx=0.361, rely=0.523, relheight=0.062
                , relwidth=0.481)
        self.eComments.configure(font="-family {.AppleSystemUIFont} -size 13")
        self.eComments.configure(cursor="ibeam")

def start_up():
    BooksLibrary_support.main()

if __name__ == '__main__':
    BooksLibrary_support.main()

The only button I have tried to program is the Add button, so the others are more place holders…

Finally, I am not a programmer - just an 89 year old hobbyist who has loved computers since the advent of the Apple in 1978 - and now needs to keep his mind active.
So thank you all very much.

1 Like

BooksLibrary_Support.py imports BooksLibrary.py and BooksLibrary.py imports BooksLibrary_Support.py. You should avoid circular imports.

I had to tweak the code a little because I don’t have a Mac, but it did save to the database.

2 Likes

Hello,

I was reviewing your code a little bit closer. Now, this is just a recommendation since as you stated it is already working.

Since your program is class based, it would perhaps make more sense to also include the methods that you currently have as call-back methods in their own class versus free standing in another module to promote encapsulation. You can then get access to them via inheritance.

Here is a very simple example highlighting this principle:

import tkinter as tk

class YourMethods:

    def calcValues(self):

        result = self.x * self.y
        print('x * y is: %d' % result)

    def print_greeting(self):
        print('Hello, there!')

    def division(self):
        print(f'{self.x} / {self.y} = {self.x / self.y}')

class App(tk.Tk, YourMethods):

    def __init__(self, x, y):

        self.x = x
        self.y = y

        super().__init__()

        self.btn1 = tk.Button(self, text="Hello, Tkinter!",
                              command=lambda: print("Hello, Tkinter!"))
        self.btn1.pack(padx=100, pady=20)

        self.btn2 = tk.Button(self, text="Click me!",
                              command=lambda: print("Pythoning is all!"))
        self.btn2.pack(padx=100, pady=20)

        self.btn3 = tk.Button(self, text="Calculate x * y!",
                              command=self.calcValues)
        self.btn3.pack(padx=100, pady=20)

        self.btn4 = tk.Button(self, text="Hello",
                              command=self.print_greeting)
        self.btn4.pack(padx=100, pady=20)

        self.btn5 = tk.Button(self, text="Division",
                              command=self.division)
        self.btn5.pack(padx=100, pady=20)


if __name__ == "__main__":

    app = App(10, 5)
    app.title("My Tkinter app")
    app.mainloop()

This way, if you wish to add additional methods, you may do so in the inherited class.

Hello Matthew

I don’t know what you tweaked, but on my Mac my program definitely doesn’t save the contents of the form inputs. I think that the offending lines are connected to the vCat, vTit etc variables which I thought would capture the contents of the fields in the form. However, when I run a print statement just before curs.execute, I find that the contents of the variables are the words in quotes, which I should have expected. What I need is the contents on the form of those fields. Somehow I need to get these into the variables. So I need help on this please. I am obviously missing an important method/command???

Thank you for your input on the use of the class to promote encapsulation. I will need to study this further.

I forgot to ask. Do your other buttons work? Have you verified that you’re entering the methods when those buttons are pressed?

My other buttons except Exit are just tokens at the moment because I felt it necessary to get my form fields to be saved in the database first. I don’t seem to have the right method to do this as my vartiables are not saving the content of the form field to the database, and I don’t know how to do that. I am trying to read up the answer but I don’t know whether I will find it in Python Help, or TCL help or sqlite3 help.

I put a print statement to give values for all the variables after filling them with form values (so I thought) after the line vComments=(“Comments”) and the answers were all what was in between the quotes. vComments for example showed as Comments in the print statement printout. This is where I am going wrong. I need what is shown on the Comments field on the form to be captured by the variable.
Can you please help me here?

Sorry that post above is only part of my problem. Although there are no error messages, the variable contents (albeit incorrect) are still not saving to the database, which is why this thread started. Sorry for the confusion. It seems that my books.db is not receiving any changes, and, if it did, the content would be wrong as well.

Here is a modified version where the application is class based. I have gone ahead and created a separate class that contains the button methods as discussed in a previous post. For now, I have only included a print statement to verify that when a button is pressed, the respective method is actually being called as expected. I will leave it to you to build (populate) each method as per your requirements. But there is enough here where you can start building out.

I recommend adding one functionality at a time, test it fully, and verify that it works as expected. Once verified, then add the next functionality and so on. But do not wait until you have added all of the functions until the end to test. Otherwise, it will be much more complex to debug (i.e., where to start).

import tkinter as tk
import tkinter.ttk as ttk

# This class contains all of the button callback methods.
class ButtonCallBacks:

    def on_btnAddClick(*args):
        print('Entered on_btnAddClick')

    def on_btnDeleteClick(*args):
        print('Entered on_btnDeleteClick')

    def on_btnEditClick(*args):
        print('Entered on_btnEditClick')

    def on_btnExitClick(*args):
        print('Entered on_btnExitClick')

    def on_btnUpdateClick(*args):
        print('Entered on_btnUpdateClick')


class BooksLibraryApp(tk.Tk, ButtonCallBacks):

    def __init__(self, top=None):

        super().__init__()

        self.geometry("400x390+572+464")
        self.minsize(72, 15)
        self.maxsize(1600, 847)
        self.resizable(1,  1)
        self.title("Books Library")
        self.configure(background="wheat")
        self.configure(highlightbackground="wheat")
        self.configure(highlightcolor="black")

        # Change widget icon (you will have to edit actual path of image)
        ico = Image.open('C:/Desktop/book_image.jpg')
        photo = ImageTk.PhotoImage(ico)
        self.wm_iconphoto(False, photo)

        self.top = top
        self.combobox = tk.StringVar()

        # _style_code()
        self.lblYearPub = ttk.Label(self.top)
        self.lblYearPub.place(relx=0.053, rely=0.441, height=21, width=115)
        self.lblYearPub.configure(font="-family {.AppleSystemUIFont} -size 13")
        self.lblYearPub.configure(relief="flat")
        self.lblYearPub.configure(text='''Year Published''')
        self.lblYearPub.configure(compound='left')

        self.eAuthor = ttk.Entry(self.top)
        self.eAuthor.place(relx=0.361, rely=0.359, relheight=0.059
                , relwidth=0.481)
        self.eAuthor.configure(font="-family {.AppleSystemUIFont} -size 13")
        self.eAuthor.configure(cursor="ibeam")

        self.eYearPub = ttk.Entry(self.top)
        self.eYearPub.place(relx=0.361, rely=0.441, relheight=0.062
                , relwidth=0.208)
        self.eYearPub.configure(font="-family {.AppleSystemUIFont} -size 13")
        self.eYearPub.configure(cursor="ibeam")

        self.eTitle = ttk.Entry(self.top)
        self.eTitle.place(relx=0.361, rely=0.274, relheight=0.062
                , relwidth=0.481)
        self.eTitle.configure(font="-family {.AppleSystemUIFont} -size 13")
        self.eTitle.configure(cursor="ibeam")

        self.lblTitle = ttk.Label(self.top)
        self.lblTitle.place(relx=0.053, rely=0.274, height=22, width=38)
        self.lblTitle.configure(font="-family {.AppleSystemUIFont} -size 13")
        self.lblTitle.configure(relief="flat")
        self.lblTitle.configure(text='''Title''')
        self.lblTitle.configure(compound='left')

        self.btnExit = ttk.Button(self.top)
        self.btnExit.place(relx=0.542, rely=0.826, height=26, width=90)
        self.btnExit.configure(command=self.on_btnExitClick)
        self.btnExit.configure(text='''Exit''')
        self.btnExit.configure(compound='left')

        self.btnDelete = ttk.Button(self.top)
        self.btnDelete.place(relx=0.694, rely=0.674, height=26, width=70)
        self.btnDelete.configure(command=self.on_btnDeleteClick)
        self.btnDelete.configure(text='''Delete''')
        self.btnDelete.configure(compound='left')

        self.lblCategory = ttk.Label(self.top)
        self.lblCategory.place(relx=0.053, rely=0.192, height=22, width=75)
        self.lblCategory.configure(font="-family {.AppleSystemUIFont} -size 13")
        self.lblCategory.configure(relief="flat")
        self.lblCategory.configure(text='''Category''')
        self.lblCategory.configure(compound='left')

        self.btnEdit = ttk.Button(self.top)
        self.btnEdit.place(relx=0.278, rely=0.674, height=26, width=65)
        self.btnEdit.configure(command=self.on_btnEditClick)
        self.btnEdit.configure(text='''Edit''')
        self.btnEdit.configure(compound='left')

        self.btnUpdate = ttk.Button(self.top)
        self.btnUpdate.place(relx=0.472, rely=0.674, height=26, width=75)
        self.btnUpdate.configure(command=self.on_btnUpdateClick)
        self.btnUpdate.configure(text='''Update''')
        self.btnUpdate.configure(compound='left')

        self.btnAdd = ttk.Button(self.top)
        self.btnAdd.place(relx=0.083, rely=0.674, height=26, width=65)
        self.btnAdd.configure(command=self.on_btnAddClick)
        self.btnAdd.configure(text='''Add''')
        self.btnAdd.configure(compound='left')

        self.lblComments = ttk.Label(self.top)
        self.lblComments.place(relx=0.053, rely=0.523, height=21, width=80)
        self.lblComments.configure(font="-family {.AppleSystemUIFont} -size 13")
        self.lblComments.configure(relief="flat")
        self.lblComments.configure(text='''Comments''')
        self.lblComments.configure(compound='left')

        self.cmbCategory = ttk.Combobox(self.top)
        self.cmbCategory.place(relx=0.361, rely=0.192, relheight=0.062
                , relwidth=0.517)
        self.value_list = ['Horror,','Historical,','Literary,','Mystery,','Romance,','Science','Fiction,','Thriller',]
        self.cmbCategory.configure(values=self.value_list)
        self.cmbCategory.configure(font="-family {.AppleSystemUIFont} -size 13")
        self.cmbCategory.configure(textvariable=self.combobox)

        self.eRecNo = ttk.Entry(self.top)
        self.eRecNo.place(relx=0.361, rely=0.11, relheight=0.062, relwidth=0.208)

        self.eRecNo.configure(font="-family {.AppleSystemUIFont} -size 13")
        self.eRecNo.configure(cursor="ibeam")

        self.lblAuthor = ttk.Label(self.top)
        self.lblAuthor.place(relx=0.053, rely=0.359, height=21, width=47)
        self.lblAuthor.configure(font="-family {.AppleSystemUIFont} -size 13")
        self.lblAuthor.configure(relief="flat")
        self.lblAuthor.configure(text='''Author''')
        self.lblAuthor.configure(compound='left')

        self.lblRecNo = ttk.Label(self.top)
        self.lblRecNo.place(relx=0.053, rely=0.11, height=21, width=57)
        self.lblRecNo.configure(font="-family {.AppleSystemUIFont} -size 13")
        self.lblRecNo.configure(relief="flat")
        self.lblRecNo.configure(text='''Record #''')
        self.lblRecNo.configure(compound='left')

        self.eComments = ttk.Entry(self.top)
        self.eComments.place(relx=0.361, rely=0.523, relheight=0.062
                , relwidth=0.481)
        self.eComments.configure(font="-family {.AppleSystemUIFont} -size 13")
        self.eComments.configure(cursor="ibeam")

if __name__ == "__main__":

    app = BooksLibraryApp()
    app.title("Book Library App")
    app.mainloop()

Note, you can also test a functionality offline in isolation (open IDLE and test it there in a different module, for example). Once you have proven that it works as expected, then add it to this application. Should make the development much easier.

Thank you very much for that Paul.
I can confirm that all buttons pressed were in the print output, so that all works perfectly.
Just one thing I don’t understand, and that is the widget icon image. I don’t know what I am looking for or where to find it. For the time being I have made them into comment lines.

The image can be anything you want it to be. I just took an image that was online that was a picture of a book. I pressed prt sc (I am using Windows so use the equivalent button on a Mac), opened the Paint app, and saved it as a .jpeg file. So, instead of it being the default feather, an image of a book was used (since you’re developing a book database). Note that the file type was `.jpeg’. Save it to your Desktop for now, since that is the path that I have included but you can save it anywhere you want but make sure the path of the image is reflected in the script.

Before saving it to the database, first verify that you are able to read the information correctly from the fields. Have you verified this? You can do this with print statements. Print the variables that are temporarily being assigned this information when you click to save the fields to the database. You will have to add these temporary development print statements.

By the way, I have updated the width values in the code snippet above so that the labels are not cut off as well as increasing the value of the geometry value.

No! I don’t seem to be able to read the information correctly from the form input widgets into the database or the print statement.

For now, let’s not think about the database - comment this code out. I am only asking if the values printed match the field values that you have entered. You have to verify one step at a time.

Sorry Paul. I misunderstood you. How do I show the form value in the print statement? That has been one of my problems. I seem to be wasting an awful lot of your time and I am so sorry if I seem to be a bit stupid.

My tweaks:

  1. “Books.db” doesn’t exist, so I have to create it. I did that by adding after the sqlite3.connect line:

    conn.execute("CREATE TABLE IF NOT EXISTS Books(Category,Title,Author,Year_Purchased, Comments)")
    
  2. I don’t have the “new-wheat” theme, so I commented that out.

After doing that, it saves to the database.

It doesn’t save the contents of the entry fields, of course, it just saves “Category” to the Category field, etc, because that’s how the code is written.

This:

vCat=("Category")

sets vCat to the string “Category”.

If you want to set it to the contents of the Category widget, you need:

vCat = _w1.cmbCategory.get()
1 Like