HOME/Articles/

mysql example boardgame inventory scanner (snippet)

Article Outline

Python mysql example 'boardgame inventory scanner'

Functions in program:

  • def numcheckedout(bggid):
  • def checkout(bggid, playerid):
  • def checkin(bggid, playerid, checkingid):
  • def checking(c, upc):
  • def playercommands(upc):
  • def commands(upc):
  • def main_menu():
  • def inventory(upc):
  • def player_search(upc):
  • def gameoutput(result, col2):
  • def undo(bggid, name):
  • def alter_name(name):
  • def user_search(name):
  • def get_name(bggid):
  • def get_bggid_from_publisher(c, bggid):
  • def add_game(c, bggid, name, upc):
  • def get_bggid_from_name(name):
  • def scan_upcitemdb(upc):
  • def scan_upcindex(upc):
  • def upc_search(upc):

Modules used in program:

  • import urllib2, sys, re
  • import mysql.connector
  • import requests

python boardgame inventory scanner

Python mysql example: boardgame inventory scanner

import requests
import mysql.connector
from xml.dom import minidom
import urllib2, sys, re
from BeautifulSoup import BeautifulSoup as bs

menu_select = "1"

# to do: check to see if inventory table exists.  If not, create it.
# to do: check to see if replica bgg database exists.  If not, don't use it.
# to do: add help
# to do: add ability to some how read and/or manipulate database?
# to do: bgg database doesn't have collection table.  The conversion errored out with: ERROR 1064 (42000) at line 857795: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'current_date, owned INTEGER NOT NULL, rank INTEGER, weight REAL, trading INTEGER' at line 1
# to do: add menu 'above' this that lets the user select catalog or check in/out
# to do: add exit function to go back to the menu 'above' this
# to do: update the date when you increment a game on inventory


def upc_search(upc):
        # Checks to see if the UPC is already in the inventory database
        # If it is, it outputs the BGGID of the game associated with it

        query = "SELECT bggid FROM inventory WHERE upc = {upc}".format(upc = upc)
        c.execute(query)
        bggid = c.fetchone()

        return bggid

def scan_upcindex(upc):
        # Searches UPCIndex for the UPC
        # Returns the game names that it found, if any 

        url = 'http://www.upcindex.com/'
        url += str(upc)

        hdr = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11',
                   'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
                   'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.3',
                   'Accept-Encoding': 'none',
                   'Accept-Language': 'en-US,en;q=0.8',
                   'Connection': 'keep-alive'}

        req = urllib2.Request(url, headers=hdr)

        page = urllib2.urlopen(req)
        soup = bs(page)


        name = soup.find("ol", {"class":"no-slide"})
        title = []
        for li in name:
                title.append(li.text.encode("utf-8"))

        return title

def scan_upcitemdb(upc):
        # Searches UPCItemdb for the UPC and returns the game names, if any
        print("Unable to find in UPCindex.  Searching UPCitemdb.")
        url = 'https://api.upcitemdb.com/prod/trial/lookup?upc='
        #upc = 609456647229

        url += str(upc)

        response = requests.get(url)
        #       print(response.text)
        if response.status_code != 200:
                print("Item not found!")
                title = []
                return
        # print(response.json())


        offers= response.json()["items"][0]["offers"]
#       print(offers[0])
        title = []
        for index,item in enumerate(response.json()["items"][0]["offers"]):
                title.append(item["title"])
#               print(response.json()["items"][0]["offers"][index]["title"])

        return title

def get_bggid_from_name(name):
        # searches the database for the game name and returns the bggid, if it found it.
        # if multiple games were found with the same name, it goes to get_bggid_from_publisher function
        #    which will ultimately display the games to the user and allows them to select which one they want


        # to do: don't add % at the end or beginning of the name (it might lead to multiples)

        # Remove :, &, and leading spaces
        name = name.replace(':', '%')
        name = name.replace('&', '%')
        name = name.rstrip(' ')         # remove trailing spaces
        name = name.lstrip(' ')         # remove leading spaces
        name = name.replace('and', '%') # to do: consider making this better
        query = "SELECT bggid FROM games WHERE name LIKE '{nm}'".format(nm = name)


        c.execute(query)

        result = c.fetchall()
        if not result:
                return
        elif len(result) == 1:
                bggid = str(result[0][0])
        else: # more than one bggid for that game name 817054010226         
                bggid = []
                string = ""
                for game in result:
                        string += str(game[0]) + ", "
                        bggid.append(game[0])
                # string = string.rstrip(", ")
                # print("more than one game with that name: " + string)

                bggid = get_bggid_from_publisher(c, bggid)

        return bggid

def add_game(c, bggid, name, upc):
        # adds game to the inventory table of the database (or increments its ownership value if it is already there)
        query = "SELECT owned FROM inventory WHERE bggid = {bggid}".format(bggid = bggid)
        c.execute(query)
        owned = c.fetchone()

        if owned: # This game is already in the inventory, see if we want to increment it
                owned = owned[0]

                response = str(raw_input( "There are {num} copies of {gm} in your inventory.  Do you want to add another? y/N ".format(num=owned, gm = name))) or "n"
                if response == 'y' or response == 'Y':
                        query = "UPDATE inventory SET owned = {num} WHERE bggid = {bggid}".format(num=owned+1, bggid=bggid)
                        c.execute(query)
                        print("Updated " + name + " in your collection.")
                elif response == 'n' or response == "N":
                        return
                else:
                        print("Invalid response.  Please scan again.")
        else:                    
                query = "INSERT INTO inventory (bggid, upc) VALUES ({bggid}, {upc})".format(bggid = bggid, upc = upc)
                c.execute(query)
                print("Added " + name + " to your collection.")

        conn.commit()

def get_bggid_from_publisher(c, bggid):
        # multiple BGGIDs found, this function will sort them by publisher and the user can pick from that
        pub_query = """ SELECT publishers.name
                FROM publishers
                INNER JOIN gamepub ON gamepub.pubid = publishers.pubid
                WHERE gamepub.bggid = {bggid}"""

        year_query = """SELECT games.year
        FROM games
        WHERE games.bggid = {bggid}"""

        name_query = "SELECT games.name FROM games WHERE games.bggid = {bggid}"

        i = 1
        print("Multiple games with that name were found:")
        choices = []

        name_string = ""
        for game in bggid:
                choices.append(game)
                query = name_query.format(bggid=game)
                c.execute(query)
                name = c.fetchall()[0][0].encode('utf-8')

                query = year_query.format(bggid=game)
                c.execute(query)
                year = c.fetchall()[0][0]

                print(str(i) + " : " + "("+str(year)+") "+ name)
                i += 1
         # TO DO: add the more info capability 
        more_info = i
        print(str(more_info) + " : More Info")
        print(str(more_info+1) + " : Exit")

        user_choice = input("Input number of the game you would like to select: ")




        i = 1
        if user_choice == more_info: # more info was selected
                for game in bggid:
                        pub_string = ""
                        query = pub_query.format(bggid=game)
                        c.execute(query)
                        result = c.fetchall()
                        for pub in result:
                                pub_string += pub[0].encode('utf-8') + ", "
                        pub_string = pub_string.rstrip(", ")

                        print(str(i) + " : " + pub_string)
                        i += 1

                print(str(more_info) + " : Input BGGID")
                print(str(more_info+1) + " : Exit")
                user_choice = input("Input number of the game you would like to select: ")
        elif user_choice == more_info+1: # user wants to exit
                return
        else: # game chosen
                bggid = choices[user_choice-1]



        if user_choice == more_info: # user inputs BGGID
                bggid = input("Input BGGID: ")
                query = name_query.format(bggid=bggid)
                c.execute(query)
                name = c.fetchall()[0][0].encode('utf-8')
                user_input = input("Add {nm}? Y/n: ".format(nm = name)) or "Y"
                if user_input != 'y' or user_input != "Y":
                        return # otherwise, bggid stays as user input
                else:
                        bggid = choices[user_choice-1]
        elif user_choice == more_info + 1: # user wants to exit
                return
        else:
                bggid = choices[user_choice-1]



        return bggid

def get_name(bggid):
        # gets the name from the db, so that we don't enter a new game name in
        query = """SELECT name FROM games WHERE bggid = {bggid}""".format(bggid = bggid)
        c.execute(query)
        return c.fetchall()[0][0]

def user_search(name):
        # searches the database for a game name given by the user
        # first searches exactly what the user typed.  Then, it adds wildcards to the beginning and end
        bggid = get_bggid_from_name(name)

        if not bggid: # no game found.  Add wild cards
                print("No game with name '{nm}' found.  Adding wildcards".format(nm = name))
                bggid = get_bggid_from_name("%" + name + "%")
                # name = get_name(bggid)
                # add_game(c, bggid, name, upc)

        return bggid

def alter_name(name):
        # alters the game name to remove things like 'deluxe edition', which may come up in a UPC search
        name = re.sub('deluxe edition', '', name, flags=re.IGNORECASE)

        name = re.sub('(brand new)', '', name, flags=re.IGNORECASE)
        name = name.lstrip('Kosmos - ')
        name = re.sub('board game', '', name, flags = re.IGNORECASE)
        name = re.sub('Brand', '', name, flags = re.IGNORECASE)
        name = re.sub(': N/a', '', name, flags = re.IGNORECASE)
        name = re.sub('game', '', name, flags = re.IGNORECASE)
        name = name.replace(''', '')
        name = name.rstrip(' ')
        name = name.rstrip('-')
        name = name.replace('()', '')

        return name

def undo(bggid, name):
        # removes the previous game that was added to the database (or reduces the ownership value if > 1)
        query = "SELECT owned FROM inventory WHERE bggid = {bggid}".format(bggid = bggid)
        c.execute(query)
        owned = c.fetchone()[0]
        if owned == 1:
                # need to remove the game from the database
                user_choice = raw_input("Remove '{gm}' from inventory? y/N: ".format(gm = name)) or "N"
                if user_choice == 'y' or user_choice == 'Y':
                        query = "DELETE FROM inventory WHERE bggid = {bggid}".format(bggid = bggid)
                        c.execute(query)
                else:
                        return
        elif owned > 1:
                user_choice = raw_input("Remove 1 copy of '{gm}' from inventory (there are current {x} copies in the inventory)? y/N: ".format(gm = name, x = owned)) or "n"
                if user_choice == 'y' or user_choice == 'Y':
                        query = "UPDATE inventory SET owned = {num} WHERE bggid = {bggid}".format(num=owned-1, bggid=bggid)
                        c.execute(query)
                        print("Updated " + name + " in your collection.")
                else:
                        return


        conn.commit() 

def gameoutput(result, col2):
        # prints game and number in inventory in a tabular form
        # result needs to be the result of a query with SELECT games.name, inventory.owned
        print("{0:<75s} {1}".format('Game', col2))
        print("{0:<75s} {1}".format('----', '----'))
        for game, owned in result:
                print("{0:<75s} {1}".format(game.encode('utf-8'), owned))
        print("{0:<75s} {1}".format('----', '----'))
        if isinstance(result[0][1], int):
                print("{0:<75s} {1}".format('Total', sum([pair[1] for pair in result])))

def player_search(upc):
        query = "SElECT playerid FROM players WHERE upc = {upc}".format(upc = upc)
        c.execute(query)
        playerid = c.fetchone()[0]

        return playerid

def inventory(upc):
        global menu_select
        if upc == "": # allows the user to press enter without any command or UPC
                True

        elif upc[0] == '/': # signifies user is going to use a function
                commands(upc)


        else:

                bggid = []
                name_upcindex = [] 
                name_upcitemdb = []

                bggid = upc_search(upc) # see if UPC is already in the db
                if bggid:               # if it is, get the name of the game and go to add_game function
                        bggid = bggid[0]
                        name = get_name(bggid)
                        add_game(c, bggid, name, upc)
                else:                   # if the UPC isn't in the database, search online for the UPC



                        # # Search upcindex
                        # try:
                        #         name_upcindex = scan_upcindex(upc)
                        #         
                        #         for game in name_upcindex:
                        #                 bggid = get_bggid_from_name(game)
                        #                 print(game)
                        #                 print(bggid)
                        #                 if bggid:
                        #                         bgname = game
                        #                         break
                        # except TypeError:
                        #         print("Game not found on UPCindex")
                        #         name_upcindex = []
                        # except urllib2.HTTPError:
                        #         print("UPCIndex HTTP Error 403: Forbidden")
                        #         name_upcindex = []

                        # If doesn't find it on upcindex, search UPCitemdb
                        try:
                                name_upcitemdb = scan_upcitemdb(upc)
                                if not bggid and name_upcitemdb:
                                        for game in name_upcitemdb:
                                                bggid = get_bggid_from_name(game)
                                                print(game)
                                                print(bggid)
                                                if bggid:
                                                        bgname = game
                                                        break
                        except IndexError:
                                print("Does not exist in UPCitemdb")
                                name_upcitemdb = []
                        except urllib2.HTTPError:
                                print("UPCitemdb HTTP Error")
                                name_upcitemdb = []

                        # to do time n space doesnt work

                        ## If still doesn't find it, start removing:

                        if not bggid and (name_upcindex or name_upcitemdb):
                                print("Unable to find in UPCIndex and UPCitemdb.  Altering Name.")
                                for name in name_upcindex + name_upcitemdb:
                                        alter_name(name)
                                        bggid = get_bggid_from_name(name)

                                        if bggid: # found game
                                                bgname = name
                                                break

                        if not bggid:
                                user_input = str(raw_input( "Unable to find this game.  Please type name: "))
                                bggid = user_search(user_input)



                        print("bggid: " + str(bggid))

                        if bggid:
                                bgname = get_name(bggid)
                                add_game(c, bggid, bgname, upc)

def main_menu():
        global menu_select
        print("Menu Options: ")
        i = 1
        for items in menu:
                print(str(i) + " : " + items)
                i += 1

        menu_select = raw_input("Select The Number of The Menu Option You Want [Default: 1]: ") or "1"
        return menu_select

def commands(upc):
        # to do: add command to add player
        if upc == '/undo':
                if bggid:
                        undo(bggid, name)
                        bggid = []
                        name = ""
                else:
                        print("No game to undo. Note: You can only undo the previous (1) game.")

        elif upc == '/showall':
                query = """SELECT games.name, inventory.owned
                                FROM games
                                INNER JOIN inventory ON games.bggid = inventory.bggid
                                ORDER BY games.name"""
                c.execute(query)
                result = c.fetchall()

                gameoutput(result, "Number Owned")

        elif upc[0:7] == '/search':
                upc = upc.lstrip('/search ')

                query = """SELECT games.name, inventory.owned
                                FROM games
                                INNER JOIN inventory ON games.bggid = inventory.bggid
                                WHERE games.name LIKE '%{nm}%'
                                ORDER BY games.name""".format(nm = upc)
                c.execute(query)
                result = c.fetchall()
                if result:
                        gameoutput(result, "Number Onwed")
                else:
                        print("'{gm}' was not found in your inventory.".format(gm = upc))

        elif upc[0:5] == '/last':
                upc = upc.lstrip('/last ')
                query = """SELECT games.name, inventory.date
                                FROM games
                                INNER JOIN inventory ON games.bggid = inventory.bggid
                                ORDER BY inventory.date desc LIMIT {num}""".format(num=upc)
                c.execute(query)
                result = c.fetchall()
                if result:
                        gameoutput(result, "Date Updated")
                else:
                        print("Wrong syntax.  To see the last X games added, use: /last 5")
        elif upc[0:5] == "/exit":
                main_menu()

        elif upc[0:10] == "/addplayer":
                upc = raw_input("Enter UPC: ")
                firstname = raw_input("First Name: ")
                lastname = raw_input("Last Name: ")

                query = "INSERT INTO players (firstname, lastname, upc) VALUES ('{fn}', '{ln}', '{upc}')".format(fn = firstname, ln = lastname, upc = upc)
                c.execute(query)

        else:
                print("Invalid syntax.  Type '/help'.")

def playercommands(upc):
        if upc[0:7] == "/search":
                upc = upc.lstrip("/search ")
                query = "SELECT games.name FROM games INNER JOIN checking ON games.bggid = checking.bggid INNER JOIN players ON players.playerid = checking.playerid WHERE checking.checkin = 0 AND players.upc = {upc}".format(upc = upc)
                c.execute(query)
                result = c.fetchall()

                query = "SELECT firstname FROM players WHERE upc = {upc}".format(upc = upc)
                c.execute(query)
                name = c.fetchone()[0]

                print("{name} has the following checked out: ".format(name = name))

                for game in result:
                        print(game[0].encode('utf-8'))
        else:
                print("Invalid function.")


def checking(c, upc):
        # to do: add undo function

        if upc == "":
                True
        if upc[0] == '/':
                playercommands(upc)
        else:
                # search UPC in inventory table.
                bggid = upc_search(upc)
                if bggid: # game is in inventory.
                        bggid = bggid[0]

                        upc = raw_input("Scan Player Badge: ")
                        playerid = player_search(upc)
                        if playerid: # player is in the player table
                                # check to see if the player has that game checked out
                                print(playerid)
                                print(bggid)
                                query = "SELECT checkingid FROM checking WHERE checkin = 0 AND playerid = {pi} AND bggid = {bi}".format(pi = playerid, bi = bggid)
                                print(query)
                                c.execute(query)

                                checkingid = c.fetchone()
                                if checkingid: # the user has that game checked out
                                        checkin(bggid, playerid, checkingid[0])
                                else: # that user doesn't have that game checked out
                                        checkout(bggid, playerid)
                else:
                        playerid = player_search(upc)
                        if playerid:
                                upc = raw_input('Scan Board Game: ')
                                bggid = upc_search(upc)
                                if bggid:
                                        bggid = bggid[0]
                                        query = "SELECT checkingid FROM checking WHERE checkin = 0 AND playerid = {pi} AND bggid = {bi}".format(pi = playerid, bi = bggid)
                                        c.execute(query)
                                        checkingid = c.fetchone()
                                        if checkingid: # the user has that game checked out
                                                checkingid = checkingid[0]
                                                checkin(bggid, playerid, checkingid)
                                        else: # that user doesn't have that game checked out
                                                checkout(bggid, playerid)



                        # to do: check to make sure user doesn't already have the game checked out before checking in








        conn.commit()

def checkin(bggid, playerid, checkingid):
        # to do: make sure the game is checked out before checking it in
        num = numcheckedout(bggid)



        query = "UPDATE checking SET checkin = now() WHERE checkingid = {ci}".format(ci = checkingid)
        c.execute(query)


        query = "UPDATE inventory SET numcheckedout = {num} WHERE bggid = {bggid}".format(num = int(num)-1, bggid = bggid)
        c.execute(query)

        # conn.commit()

        gamename = get_name(bggid)

        print("Checked in {gamename}".format(gamename = gamename))

def checkout(bggid, playerid):
        # to do: make sure player doesn'thave a game checked out already (select checkingid where checkin = 0 and playerid =)
        # to do: add override command to allow a player to have multiple games checked out

        num = numcheckedout(bggid)

        query = "UPDATE inventory SET numcheckedout = {num} WHERE bggid = {bggid}".format(num = int(num)+1, bggid = bggid)
        c.execute(query)

        query = "INSERT INTO checking (checkout, playerid, bggid) VALUES (NOW(), {pi}, {bi})".format(pi = playerid, bi = bggid)
        c.execute(query)

        gamename = get_name(bggid)

        print("Checked out {name} to player".format(name = gamename))

        # conn.commit()

def numcheckedout(bggid):
        query = "SELECT numcheckedout FROM inventory WHERE bggid = {bi}".format(bi = bggid)
        c.execute(query)
        numcheckedout = c.fetchone()[0]

        return numcheckedout

# Connect to the database        
conn = mysql.connector.connect(user='<user>', password='<password>', host='<ipaddress>', database='<databasename>')
c = conn.cursor()
bggid = []
name = ""

menu = ["Inventory", "Check In/Out"] # Inventory = 1, Check In = 2

main_menu()
while True:
        try:
                upc = raw_input('[{menu}] Scan UPC or enter command: '.format(menu = menu[int(menu_select)-1]))
                upc = upc.lstrip('0')

        except SyntaxError:
                print("Null UPC")

        if menu_select == "1":
                inventory(upc)
        elif menu_select == "2":
                checking(c,upc)





        conn.commit()






conn.close()


# TO DO some games might have more than one UPC.  Update database and code to be able to handle this.
# to do: when listing games, after more info add exit
''' To do: Games that don't work
ascension chronlic of the godslayer (lots come up, not sure which)




'''