예제 #1
0
def generate_sheet(path, expanded=False):
    # Generates a template to be filled and imported. Can accept either condensed or expanded
    temp_wb = Workbook()
    temp_sheet = temp_wb.active
    # Setup the pages to have the right headers. Grab lists of the col names and merge them
    db_export = DatabaseManager()
    col_keys = db_export.db_getcolnames('winedata')
    col_keys.extend(db_export.db_getcolnames('userinventory')[1:])

    if expanded:
        temp_sheet.title = 'Expanded Inventory'
        temp_sheet.append(col_keys)

        # Add a label to the filename so the parser knows which one we're looking at
        path += 'template_expanded.xlsx'

    else:
        temp_sheet.title = 'Condensed Inventory'

        # Replace the 'location' tag with 'qty' and add it to the condensed table and remove the dates
        qty_index = col_keys.index('location')
        col_keys[qty_index] = 'qty'
        date_index = col_keys.index('date_in')
        del col_keys[date_index:date_index+2]
        temp_sheet.append(col_keys)

        # Add a label to the filename so the parser knows which one we're looking at
        path += 'template_condensed.xlsx'
    
    # Save the file
    temp_wb.save(path)
예제 #2
0
def update_userinv_row(update_input, rowid):
    # Updates a userinventory row using the row id (so duplicates aren't
    # affected). This will mostly be used for moving and checking out bottles
    arg = 'UPDATE userinventory SET '
    for term in update_input:
        arg += term + ' = :' + term + ', '
    arg = arg.rstrip(', ')
    arg += ' WHERE rowid = ' + str(rowid)

    inv_update = DatabaseManager()
    inv_update.db_execute(arg, update_input)
예제 #3
0
def update_winedata_row(update_input):
    # updates a specific row in a specified table
    # this function takes a list of the following format:
    # entry_input = [wine_id, upc, winery, region,
    #                name, varietal, wtype,
    #                vintage, msrp, value]
    terms = cleanup_dbinput(update_input)
    arg = 'UPDATE winedata SET '
    for term in terms:
        arg += term + ' = :' + term + ', '
    arg = arg.rstrip(', ')
    arg += ' WHERE wine_id = :wine_id'

    db_update = DatabaseManager()
    db_update.db_execute(arg, terms)
예제 #4
0
def drop_row(wine_id, rowid=None, table='userinventory'):
    # drops a row from the database, if for example it is
    # entered by mistake
    # this function only takes a wine_id input, which forces
    # the wine to be positively identified before it is
    # deleted
    # This also allows the table to be selected. By default,
    # the table is the user inventory (which is less dangerous)
    terms = [wine_id]
    arg = 'DELETE FROM ' + table + ' WHERE wine_id = ?'
    if rowid:
        arg += ' AND rowid = ?'
        terms.append(rowid)

    terms = tuple(terms)
    db_drop = DatabaseManager()
    db_drop.db_execute(arg, terms)
예제 #5
0
def fetch_db(fetch_input, table='userinventory', in_cellar=True, sort_by=None):
    # fetches a row from the database
    # shoud be faster than searching since it doesn't have to guess
    terms = cleanup_dbinput(fetch_input)

    if table == 'both':
        arg = 'SELECT * FROM winedata JOIN userinventory USING (wine_id) WHERE '
    else:
        arg = 'SELECT * FROM ' + table + ' WHERE '
    for term in terms:
        arg += '{0}={1} AND '.format(term, ':' + term)
    arg = arg.rstrip(' AND ')
    if in_cellar == True and table != 'winedata':
        arg += ' AND date_out IS NULL'
    if sort_by != None:
        arg += ' ORDER BY ' + sort_by
    fetch = DatabaseManager()
    return fetch.db_fetch(arg, terms, 'all')
예제 #6
0
def get_rowid(entry_input, table='userinventory'):
    # Returns the row id of the desired input
    terms = cleanup_dbinput(entry_input)

    arg = 'SELECT rowid FROM ' + table + ' WHERE '
    for term in terms:
        arg += '{0} = {1} AND '.format(term, ':' + term)
    arg = arg.rstrip(' AND ')

    getid = DatabaseManager()
    return getid.db_fetch(arg, terms, 'one')[0]


####################################################################
############################ Test Code #############################
####################################################################

# userinv_dict = {"wine_id":input_list[0],
#                 "bottle_size":input_list[2],
#                 "location":input_list[3],
#                 "comments":input_list[4],
#                 "date_in":input_list[5],
#                 "date_out":input_list[6]}

# winedata_dict = {"wine_id":None,
#                  "upc":'081128011680',
#                  "winery":'Burnt Bridge Cellars',
#                  "region":'Walla Walla',
#                  "name":None,
#                  "varietal":'Cabernet Sauvignon',
#                  "wtype":'Table',
#                  "vintage":2012,
#                  "msrp":'$35',
#                  "value":'$35'}

# winedata_dict = {"winery":'ur'}

# enter_db(winedata_dict, 'winedata')
# drop_row(winedata_dict['wine_id'], 'winedata')
# print(search_db(winedata_dict, 'both'))
# find_bottle = input('Enter a bottle ID: ')
# print(lookup_db(find_bottle, 'winedata'))
예제 #7
0
def import_db(path):
    # This function imports an excel file (from a specified format),
    # checks for duplicates, and enters the non-duplicates into the
    # database. (Note: duplicates refer to the winedata table, not
    # the inventory. That will allow any)
    
    # First, grab the col names
    db_import = DatabaseManager()
    wine_keys = db_import.db_getcolnames('winedata')
    bottle_keys = db_import.db_getcolnames('userinventory')

    # Create an instance of the bottle object
    bottle = wine_bottle.Bottle({}, {})

    import_wb = load_workbook(path)
    import_ws = import_wb.active

    # Grab all rows from the spreadsheet and make a dictionary with
    # them. This will be used to add the entries to the db
    import_rows = tuple(import_ws.rows)
    input_dict = {}
    for key in import_rows[0]:
        input_dict[key.value] = None

    for i in range(len(import_rows)-1):
        for j, key in enumerate(input_dict):
            input_dict[key] = import_rows[i+1][j].value

        for wine_key in wine_keys:
            bottle.wine_info[wine_key] = str(input_dict[wine_key]) if wine_key in input_dict and input_dict[wine_key] != None else None
            
        for bottle_key in bottle_keys:
            bottle.bottle_info[bottle_key] = str(input_dict[bottle_key]) if bottle_key in input_dict and input_dict[bottle_key] != None else None
        
        if 'expanded' in path[-23:-5]:
            bottle.add_new()
            bottle.clear_bottle()
        elif 'condensed' in path[-23:-5]:
            for k in range(input_dict['qty']):
                bottle.add_new()
            bottle.clear_bottle()
예제 #8
0
def enter_db(entry_input, table='userinventory', ret_id=False):
    # enters a wine into the database
    # this function takes a list of the following format:
    # entry_input = [wine_id, upc, winery, region,
    #                name, varietal, wtype,
    #                vintage, msrp, value]
    # it is critical that the list is complete, even if some
    # of the values are null
    db_enter = DatabaseManager()
    terms = cleanup_dbinput(entry_input)

    #arg = 'INSERT INTO ' + table + ' (upc, winery, region, name, varietal, wtype, vintage, msrp, value) VALUES (?,?,?,?,?,?,?,?,?)'
    arg = 'INSERT INTO ' + table + ' ('
    values = '('
    for term in terms:
        arg += term + ', '
        values += ':' + term + ', '
    values = values.rstrip(', ') + ')'
    arg = arg.rstrip(', ') + ') VALUES ' + values

    return db_enter.db_execute(arg, terms, ret_id)
예제 #9
0
def lookup_db(lookup_number,
              table='userinventory',
              in_cellar=True,
              sort_by=None):
    # This is a function to quickly lookup based on either a upc or wine_id
    # This is really similar to the fetch_db function (and may end up
    # replacing it).
    lookup = DatabaseManager()
    placeholders = [lookup_number]
    if table == 'both':
        arg = 'SELECT * FROM winedata JOIN userinventory USING (wine_id) WHERE '
    else:
        arg = 'SELECT * FROM ' + table + ' WHERE '
    arg += '(wine_id=?'
    if table != 'userinventory':
        arg += ' OR upc=?)'
        placeholders.append(lookup_number)
    else:
        arg += ')'
    if in_cellar == True and table != 'winedata':
        arg += ' AND date_out IS NULL'
    if sort_by != None:
        arg += ' ORDER BY ' + sort_by
    return lookup.db_fetch(arg, tuple(placeholders), 'all')
예제 #10
0
def search_db(search_input,
              table='userinventory',
              in_cellar=True,
              sort_by=None):
    # searches for a wine in the database that resembles the input
    # first, assign the inputs to a list to be cleaned up
    # this function takes a list of the following format:
    # search_input = [wine_id, upc, winery, region,
    #                 name, varietal, wtype,
    #                 vintage, msrp, value]
    # it is critical that the list is complete, even if some
    # of the values are null

    terms = cleanup_dbinput(search_input)

    # craft the sql query string
    if table == 'both':
        arg = 'SELECT * FROM winedata JOIN userinventory USING (wine_id) WHERE '
    else:
        arg = 'SELECT * FROM ' + table + ' WHERE '
    for term in terms:
        arg += '{0} LIKE {1} AND '.format(term, ':' + term)
        terms[term] = '%' + terms[term] + '%'
    arg = arg.rstrip(' AND ')
    if in_cellar == True and table != 'winedata':
        arg += ' AND date_out IS NULL'
    if sort_by != None:
        arg += ' ORDER BY ' + sort_by

    # finally, call the search function from the db_man object
    db_search = DatabaseManager()
    result = db_search.db_fetch(arg, terms, 'all')
    if result:
        return result
    else:
        return None
예제 #11
0
def export_db(path):
    # Exports the database to an excel file at the specified
    # file path
    # First, create a workbook. Condensed uses Qty instead of location
    # Expanded preserves location data
    exp_wb = Workbook()
    exp_condensed = exp_wb.active
    exp_condensed.title = 'Condensed Inventory'
    exp_full = exp_wb.create_sheet('Expanded Inventory')

    # Setup the pages to have the right headers. Grab lists of the col names and merge them
    db_export = DatabaseManager()
    col_keys = db_export.db_getcolnames('winedata')
    col_keys.extend(db_export.db_getcolnames('userinventory')[1:])
    exp_full.append(col_keys)

    # Replace the 'location' tag with 'qty' and add it to the condensed table and remove the dates
    qty_index = col_keys.index('location')
    col_keys[qty_index] = 'qty'
    date_index = col_keys.index('date_in')
    del col_keys[date_index:date_index+2]
    exp_condensed.append(col_keys)

    # now, grab everything from each table and store it in a list of lists
    inv_rows = db_export.db_fetch('SELECT * FROM userinventory WHERE date_out IS NULL ORDER BY wine_id', rows='all')


    # Add everything to the excel file sequentially
    # First, initialize the variables that may not exist 
    # until later in the loop
    old_id = None
    qty = 0
    old_row = []

    # Iterate through each of the entries in the inventory For each
    # entry, look it up in the winedata table and add create one 
    # long list which the relevant data. This is a little slower,
    # but more memory efficient. Write that to the extended page. 
    for i in range(len(inv_rows)):
        write_row = list(db_export.db_fetch('SELECT * FROM winedata WHERE wine_id=?', (inv_rows[i][0],)))
        write_row.extend(inv_rows[i][1:])
        exp_full.append(write_row)
        # Remove the date in/out entries
        del write_row[date_index:date_index+2]
        # The condensed row will run one iteration behind in order
        # to check for another of the same bottle. It is initialized
        # as none to avoid a definition error in the first loop
        if old_row != []:
            # if the wine id is the same, increment the counter
            if old_id == inv_rows[i][0]:
                qty += 1
            # if it's not, incriment the counter to account for the
            # last bottle, write it, and reset qty counter
            else:
                qty += 1
                old_row[qty_index] = qty
                exp_condensed.append(old_row)
                qty = 0
        # set old row and old id so we can check it against the 
        # next index on the next cycle
        old_row = write_row.copy()
        old_id = inv_rows[i][0]
    # Because the condensed table runs one cycle behind, we have to
    # run the write command one more time to finish the last entry
    qty += 1
    old_row[qty_index] = qty
    exp_condensed.append(old_row)


    # Save the file
    exp_wb.save(path)
예제 #12
0
    def __init__(self):
        super().__init__()
        self.setupUi(Vinny)

        # Connect the buttons to their respective functions
        self.actionBottle.triggered.connect(self.delete_bottle)
        self.actionWine.triggered.connect(self.delete_wine)
        self.actionExport.triggered.connect(self.export_to_excel)
        self.actionImport.triggered.connect(self.import_from_excel)
        self.actionGenerate_Barcode.triggered.connect(self.generate_barcode)
        self.actionBackup_Database.triggered.connect(self.backup_database)
        self.actionPreferences.triggered.connect(self.edit_preferences)

        self.InventorySearch.clicked.connect(self.quick_search)
        self.InventoryCheckOut.clicked.connect(self.inv_check_out)
        self.InventoryMoveBottle.clicked.connect(self.inv_move_bottle)
        self.InventoryAddCopy.clicked.connect(self.inv_add_copy)
        self.InventoryEditBottle.clicked.connect(self.inv_edit_bottle)
        self.InventoryTable.cellClicked.connect(self.inv_get_bottle)

        self.AddBottleSearch.clicked.connect(self.ab_deep_search)
        self.AddBottleAdd.clicked.connect(self.ab_add_to_cellar)
        self.AddBottleTable.doubleClicked.connect(self.ab_get_wine)
        self.AddBottleClearFields.clicked.connect(self.ab_clear_fields)
        self.AddBottleUpdate.clicked.connect(self.ab_update_wine)
        self.AddBottleUPC.returnPressed.connect(self.ab_upc_fill)
        self.AddBottleGenerateBarcode.clicked.connect(self.generate_barcode)

        self.HistoryTable.cellClicked.connect(self.hist_get_bottle)

        # Connect all fields in the wines tab to a function to detect modifications
        self.ab_modified_flag = True
        self.AddBottleUPC.textChanged.connect(self.ab_modified)
        self.AddBottleWinery.textChanged.connect(self.ab_modified)
        self.AddBottleAVA.textChanged.connect(self.ab_modified)
        self.AddBottleBlendName.textChanged.connect(self.ab_modified)
        self.AddBottleVarietal.textChanged.connect(self.ab_modified)
        self.AddBottleType.currentIndexChanged.connect(self.ab_modified)
        self.AddBottleVintage.textChanged.connect(self.ab_modified)
        self.AddBottleMSRP.textChanged.connect(self.ab_modified)
        self.AddBottleCurrentValue.textChanged.connect(self.ab_modified)
        self.AddBottleComments.textChanged.connect(self.ab_modified)
        self.AddBottleRating.textChanged.connect(self.ab_modified)

        # Get the names of the collumns at the beginning so we don't have to do that a million times
        # Start by verifying that the database actually exists
        self.db_manager = DatabaseManager()
        self.db_manager.verify_db()
        self.wine_col_names = self.db_manager.db_getcolnames('winedata')
        self.inv_col_names = self.db_manager.db_getcolnames('userinventory')
        self.combined_col_names = self.wine_col_names.copy()
        self.combined_col_names.extend(self.inv_col_names[1:])
        self.location_index = self.combined_col_names.index('location')

        # Initialize the table sizes so they don't have to be queried every single time
        self.InventoryTable.setColumnCount(len(self.combined_col_names))
        self.InventoryTable.setHorizontalHeaderLabels(
            self.translate_col_names(self.combined_col_names))

        self.AddBottleTable.setColumnCount(len(self.wine_col_names))
        self.AddBottleTable.setHorizontalHeaderLabels(
            self.translate_col_names(self.wine_col_names))

        self.HistoryTable.setColumnCount(len(self.combined_col_names))
        self.HistoryTable.setHorizontalHeaderLabels(
            self.translate_col_names(self.combined_col_names))

        # Create a new bottle object to be manipulated by the user
        self.bottle = Bottle({}, {})

        # Populate the inventory table so it's ready to go at the start
        self.inv_table_pop(None, None)

        # Disable the 'Generate Barcode' button until it's ready to be used
        self.AddBottleGenerateBarcode.setEnabled(False)
예제 #13
0
class MainInterface(QtWidgets.QMainWindow, Ui_Vinny):
    def __init__(self):
        super().__init__()
        self.setupUi(Vinny)

        # Connect the buttons to their respective functions
        self.actionBottle.triggered.connect(self.delete_bottle)
        self.actionWine.triggered.connect(self.delete_wine)
        self.actionExport.triggered.connect(self.export_to_excel)
        self.actionImport.triggered.connect(self.import_from_excel)
        self.actionGenerate_Barcode.triggered.connect(self.generate_barcode)
        self.actionBackup_Database.triggered.connect(self.backup_database)
        self.actionPreferences.triggered.connect(self.edit_preferences)

        self.InventorySearch.clicked.connect(self.quick_search)
        self.InventoryCheckOut.clicked.connect(self.inv_check_out)
        self.InventoryMoveBottle.clicked.connect(self.inv_move_bottle)
        self.InventoryAddCopy.clicked.connect(self.inv_add_copy)
        self.InventoryEditBottle.clicked.connect(self.inv_edit_bottle)
        self.InventoryTable.cellClicked.connect(self.inv_get_bottle)

        self.AddBottleSearch.clicked.connect(self.ab_deep_search)
        self.AddBottleAdd.clicked.connect(self.ab_add_to_cellar)
        self.AddBottleTable.doubleClicked.connect(self.ab_get_wine)
        self.AddBottleClearFields.clicked.connect(self.ab_clear_fields)
        self.AddBottleUpdate.clicked.connect(self.ab_update_wine)
        self.AddBottleUPC.returnPressed.connect(self.ab_upc_fill)
        self.AddBottleGenerateBarcode.clicked.connect(self.generate_barcode)

        self.HistoryTable.cellClicked.connect(self.hist_get_bottle)

        # Connect all fields in the wines tab to a function to detect modifications
        self.ab_modified_flag = True
        self.AddBottleUPC.textChanged.connect(self.ab_modified)
        self.AddBottleWinery.textChanged.connect(self.ab_modified)
        self.AddBottleAVA.textChanged.connect(self.ab_modified)
        self.AddBottleBlendName.textChanged.connect(self.ab_modified)
        self.AddBottleVarietal.textChanged.connect(self.ab_modified)
        self.AddBottleType.currentIndexChanged.connect(self.ab_modified)
        self.AddBottleVintage.textChanged.connect(self.ab_modified)
        self.AddBottleMSRP.textChanged.connect(self.ab_modified)
        self.AddBottleCurrentValue.textChanged.connect(self.ab_modified)
        self.AddBottleComments.textChanged.connect(self.ab_modified)
        self.AddBottleRating.textChanged.connect(self.ab_modified)

        # Get the names of the collumns at the beginning so we don't have to do that a million times
        # Start by verifying that the database actually exists
        self.db_manager = DatabaseManager()
        self.db_manager.verify_db()
        self.wine_col_names = self.db_manager.db_getcolnames('winedata')
        self.inv_col_names = self.db_manager.db_getcolnames('userinventory')
        self.combined_col_names = self.wine_col_names.copy()
        self.combined_col_names.extend(self.inv_col_names[1:])
        self.location_index = self.combined_col_names.index('location')

        # Initialize the table sizes so they don't have to be queried every single time
        self.InventoryTable.setColumnCount(len(self.combined_col_names))
        self.InventoryTable.setHorizontalHeaderLabels(
            self.translate_col_names(self.combined_col_names))

        self.AddBottleTable.setColumnCount(len(self.wine_col_names))
        self.AddBottleTable.setHorizontalHeaderLabels(
            self.translate_col_names(self.wine_col_names))

        self.HistoryTable.setColumnCount(len(self.combined_col_names))
        self.HistoryTable.setHorizontalHeaderLabels(
            self.translate_col_names(self.combined_col_names))

        # Create a new bottle object to be manipulated by the user
        self.bottle = Bottle({}, {})

        # Populate the inventory table so it's ready to go at the start
        self.inv_table_pop(None, None)

        # Disable the 'Generate Barcode' button until it's ready to be used
        self.AddBottleGenerateBarcode.setEnabled(False)

    def translate_col_names(self, input_list):
        # Translates code names to pretty names and back. It does this
        # using a dict with both directions in it, since all entries are
        # unique. Hacky? Yes. Does it work? Also yes. Does it really take
        # up that much space? Nope.
        #
        # Takes a list, outputs another list.
        translate_dict = {
            'wine_id': 'Wine ID',
            'upc': 'UPC',
            'winery': 'Winery',
            'region': 'Region',
            'name': 'Blend Name',
            'varietal': 'Varietal',
            'vintage': 'Vintage',
            'wtype': 'Type',
            'msrp': 'MSRP',
            'value': 'Value',
            'comments': 'Comments',
            'rating': 'Rating',
            'bottle_size': 'Bottle Size',
            'location': 'Location',
            'date_in': 'Date In',
            'date_out': 'Date Out',
            'Wine ID': 'wine_id',
            'UPC': 'upc',
            'Winery': 'winery',
            'Region': 'region',
            'Blend Name': 'name',
            'Varietal': 'varietal',
            'Vintage': 'vintage',
            'Type': 'wtype',
            'MSRP': 'msrp',
            'Value': 'value',
            'Comments': 'comments',
            'Rating': 'rating',
            'Bottle Size': 'bottle_size',
            'Location': 'location',
            'Date In': 'date_in',
            'Date Out': 'date_out'
        }

        output_list = []
        for name in input_list:
            output_list.append(translate_dict[name])
        return output_list

    def inv_table_pop(self, wine_id=None, location=None):
        # Populates the inventory table. Called any time there is a possible change
        # Organizes the table based on expected length of the returned entries
        sort_term = self.translate_col_names(
            [self.InventorySortBy.currentText()])[0]

        if wine_id or location:
            self.bottle.clear_bottle()
            # If there's a search term, it'll just use the integrated method in wine-bottle
            if wine_id:
                inv_rows = lookup_db(wine_id, table='both')
            else:
                self.bottle.bottle_info['location'] = location
                bottles = self.bottle.search_bottle()
                inv_rows = []
                for i, bottle in enumerate(bottles):
                    inv_rows.append(
                        list(lookup_db(bottle[0], table='winedata')[0]))
                    inv_rows[i].extend(bottle[1:])

        # Craft the SQL search query. I suspect having SQL doing the sorting is a tad faster.
        # If no terms specified, return all entries
        else:
            arg = 'SELECT * FROM winedata JOIN userinventory USING (wine_id) WHERE '
            arg += 'date_out IS NULL ORDER BY ' + sort_term
            if self.InventorySortAsc.isChecked():
                arg += ' ASC'
            else:
                arg += ' DESC'
            inv_rows = list(self.db_manager.db_fetch(arg, rows='all'))

        # Iteratively fills the table
        self.InventoryTable.setRowCount(0)
        if inv_rows:
            for row_num, row in enumerate(inv_rows):
                self.InventoryTable.insertRow(row_num)
                for col_num, col_entry in enumerate(row):
                    self.InventoryTable.setItem(
                        row_num, col_num,
                        QtWidgets.QTableWidgetItem(str(col_entry)))
            # self.InventoryTable.selectRow(0)
        else:
            self.bottle.clear_bottle()

        # Populates the history table as well
        self.history_table_pop()

    def history_table_pop(self):
        # Populates the history table as needed. Functions in mostly the same
        # way as the inv_table_pop method
        arg = 'SELECT * FROM winedata JOIN userinventory USING (wine_id) WHERE date_out IS NOT NULL ORDER BY date_out'
        hist_rows = list(self.db_manager.db_fetch(arg, rows='all'))

        self.HistoryTable.setRowCount(0)
        for row_num, row in enumerate(hist_rows):
            self.HistoryTable.insertRow(row_num)
            for col_num, col_entry in enumerate(row):
                self.HistoryTable.setItem(
                    row_num, col_num,
                    QtWidgets.QTableWidgetItem(str(col_entry)))

    def ab_fill_fields(self, wine_info):
        # Takes an input dictionary and filles the add bottle fields
        for term in wine_info:
            wine_info[term] = str(wine_info[term])

        self.AddBottleUPC.setText(wine_info['upc'])
        self.AddBottleWinery.setText(wine_info['winery'])
        self.AddBottleAVA.setText(wine_info['region'])
        self.AddBottleBlendName.setText(wine_info['name'])
        self.AddBottleVarietal.setText(wine_info['varietal'])
        self.AddBottleType.setCurrentText(wine_info['wtype'])
        self.AddBottleVintage.setText(wine_info['vintage'])
        self.AddBottleMSRP.setText(wine_info['msrp'])
        self.AddBottleCurrentValue.setText(wine_info['value'])
        self.AddBottleComments.setText(wine_info['comments'])
        self.AddBottleRating.setText(wine_info['rating'])

        # Set modified flag to false so it doesn't make duplicates
        self.ab_modified_flag = False

    def get_other_size(self):
        new_size, ok_pressed = QInputDialog.getText(self, 'Other Size',
                                                    'Enter Custom Size:',
                                                    QLineEdit.Normal, '')
        if ok_pressed:
            return new_size

    @QtCore.Slot()
    def hist_get_bottle(self):
        # Activated when a table item is selected. Grabs all info about a specific bottle and stores
        # it in the bottle object.

        # First, ensure that the dictionary is empty to prevent extra data being added
        self.bottle.clear_bottle()

        # Get the current row that has been selected.
        selection_row = self.HistoryTable.currentRow()
        # Highlight the whole row to be more visible (quality of life thing)
        self.HistoryTable.selectRow(selection_row)

        # Assign all items to the dictionary based on the col names
        for i, term in enumerate(self.wine_col_names):
            self.bottle.wine_info[term] = self.HistoryTable.item(
                selection_row, i).text()
        # Bottle info needs to be offset since its at the end
        for i, term in enumerate(self.inv_col_names):
            self.bottle.bottle_info[term] = self.HistoryTable.item(
                selection_row, i + len(self.wine_col_names) - 1).text()
        # wine_id only appears at the beginning, make sure it's editted properly
        self.bottle.bottle_info['wine_id'] = self.bottle.wine_info['wine_id']

    @QtCore.Slot()
    def inv_get_bottle(self):
        # Activated when a table item is selected. Grabs all info about a specific bottle and stores
        # it in the bottle object.

        # First, ensure that the dictionary is empty to prevent extra data being added
        self.bottle.clear_bottle()

        # Get the current row that has been selected.
        selection_row = self.InventoryTable.currentRow()
        # Highlight the whole row to be more visible (quality of life thing)
        self.InventoryTable.selectRow(selection_row)

        # Assign all items to the dictionary based on the col names
        for i, term in enumerate(self.wine_col_names):
            self.bottle.wine_info[term] = self.InventoryTable.item(
                selection_row, i).text()
        # Bottle info needs to be offset since its at the end
        for i, term in enumerate(self.inv_col_names):
            self.bottle.bottle_info[term] = self.InventoryTable.item(
                selection_row, i + len(self.wine_col_names) - 1).text()
        # wine_id only appears at the beginning, make sure it's editted properly
        self.bottle.bottle_info['wine_id'] = self.bottle.wine_info['wine_id']

    @QtCore.Slot()
    def quick_search(self):
        # Checks if there are search terms and calls the inv_table_pop method
        wine_id = None
        location = None
        if self.InventoryWineID.text():
            wine_id = self.InventoryWineID.text()
        if self.InventoryLocation.text():
            location = self.InventoryLocation.text()
        self.inv_table_pop(wine_id, location)

    @QtCore.Slot()
    def inv_check_out(self):
        # Checks out the selected bottle with the integrated method
        self.bottle.check_out()
        self.quick_search()

    @QtCore.Slot()
    def inv_add_copy(self, new_loc=None, new_size=None):
        # Adds a copy of the selected bottle by asking for a new size and location
        bottle_sizes = [
            self.AddBottleBottleSize.itemText(i)
            for i in range(self.AddBottleBottleSize.count())
        ]
        if not new_size:
            new_size, ok_pressed = QInputDialog.getItem(
                self, 'New Size', 'Select New Bottle Size:', bottle_sizes, 2,
                False)
            if ok_pressed == True:
                pass
            if new_size == 'Other...':
                new_size = self.get_other_size()
        self.bottle.bottle_info['bottle_size'] = new_size
        if not new_loc:
            new_loc, ok_pressed = QInputDialog.getText(self, 'New Location',
                                                       'Enter new location:',
                                                       QLineEdit.Normal, '')
            if ok_pressed == True:
                pass
        self.bottle.bottle_info['location'] = new_loc
        self.bottle.add_new()
        self.quick_search()

    @QtCore.Slot()
    def inv_edit_bottle(self):
        # Switches to the Wines tab and fills the fields with the selected
        # bottle info. Only activates if a wine has been selected
        if 'wine_id' in self.bottle.bottle_info:
            self.main_tab.setCurrentIndex(1)
            self.ab_fill_fields(self.bottle.wine_info)

    @QtCore.Slot()
    def inv_move_bottle(self):
        # Moves a bottle by querying the user for a new location and updating
        # the row
        new_location, ok_pressed = QInputDialog.getText(
            self, 'Move Bottle', 'Enter new location:', QLineEdit.Normal, '')
        if ok_pressed == True:
            self.bottle.update_bottle(new_info={'location': new_location})
        self.quick_search()

    @QtCore.Slot()
    def ab_deep_search(self):
        # Grabs the text from each box and searches for wines matching the
        # criteria. First creates a dictionary from the inputs
        wine_info = {
            "upc": self.AddBottleUPC.text(),
            "winery": self.AddBottleWinery.text(),
            "region": self.AddBottleAVA.text(),
            "name": self.AddBottleBlendName.text(),
            "varietal": self.AddBottleVarietal.text(),
            "wtype": self.AddBottleType.currentText(),
            "vintage": self.AddBottleVintage.text(),
            "msrp": self.AddBottleMSRP.text(),
            "value": self.AddBottleCurrentValue.text(),
            "rating": self.AddBottleRating.text(),
            "comments": self.AddBottleComments.toPlainText()
        }

        # Filters out empty text boxes
        for term in wine_info:
            if not wine_info[term]:
                wine_info[term] = None

        # For simplicity, bypasses the object since it isn't really needed here
        table_rows = search_db(wine_info, 'winedata', in_cellar=False)

        # Iteratively populates the table
        self.AddBottleTable.setRowCount(0)
        if table_rows:
            for row_num, row in enumerate(table_rows):
                self.AddBottleTable.insertRow(row_num)
                for col_num, col in enumerate(row):
                    self.AddBottleTable.setItem(
                        row_num, col_num, QtWidgets.QTableWidgetItem(str(col)))

    @QtCore.Slot()
    def ab_get_wine(self):
        # Activated when a row is double clicked on the wine table
        # Autofills all the fields in the area to be modified

        # Clear the bottle so that important stuff can be stored
        self.bottle.clear_bottle()

        # Get current row and highlight the whole row
        selection_row = self.AddBottleTable.currentRow()
        self.AddBottleTable.selectRow(selection_row)

        # Assign all items to the fields by iterating through
        # and assigning to a dictionary
        wine_info = {}
        for i, term in enumerate(self.wine_col_names):
            if self.AddBottleTable.item(selection_row, i).text() != 'None':
                wine_info[term] = self.AddBottleTable.item(selection_row,
                                                           i).text()
            else:
                wine_info[term] = ''

        # Assign the wine_id to the current bottle object so we know it's
        # a duplicate
        self.bottle.bottle_info['wine_id'] = self.AddBottleTable.item(
            selection_row, 0).text()
        self.bottle.wine_info['wine_id'] = self.AddBottleTable.item(
            selection_row, 0).text()

        self.ab_fill_fields(wine_info)

    @QtCore.Slot()
    def ab_modified(self):
        self.ab_modified_flag = True
        # self.bottle.clear_bottle()
        self.AddBottleGenerateBarcode.setEnabled(False)

    @QtCore.Slot()
    def ab_update_wine(self):
        if 'wine_id' in self.bottle.wine_info and self.ab_modified_flag == True:
            wine_info = {
                "upc": self.AddBottleUPC.text(),
                "winery": self.AddBottleWinery.text(),
                "region": self.AddBottleAVA.text(),
                "name": self.AddBottleBlendName.text(),
                "varietal": self.AddBottleVarietal.text(),
                "wtype": self.AddBottleType.currentText(),
                "vintage": self.AddBottleVintage.text(),
                "msrp": self.AddBottleMSRP.text(),
                "value": self.AddBottleCurrentValue.text(),
                "rating": self.AddBottleRating.text(),
                "comments": self.AddBottleComments.toPlainText()
            }
            for term in wine_info:
                if term not in self.bottle.wine_info or wine_info[
                        term] != self.bottle.wine_info[term]:
                    self.bottle.wine_info[term] = wine_info[term]
            self.bottle.update_wine()
            self.inv_table_pop()

    @QtCore.Slot()
    def ab_clear_fields(self):
        # Clear all fields, including the current bottle object
        self.AddBottleUPC.clear()
        self.AddBottleWinery.clear()
        self.AddBottleAVA.clear()
        self.AddBottleBlendName.clear()
        self.AddBottleVarietal.clear()
        self.AddBottleType.setCurrentIndex(0)
        self.AddBottleVintage.clear()
        self.AddBottleMSRP.clear()
        self.AddBottleCurrentValue.clear()
        self.AddBottleComments.clear()
        self.AddBottleRating.clear()
        self.AddBottleLocation.clear()
        self.AddBottleQty.clear()

        self.bottle.clear_bottle()

        # Disable the barcode button
        self.AddBottleGenerateBarcode.setEnabled(False)

        self.ab_deep_search()

    @QtCore.Slot()
    def ab_add_to_cellar(self):
        # Adds a new bottle to the cellar. If the wine_id exists, it's a copy
        # and can be added as such. Otherwise a new bottle is added.
        if self.ab_modified_flag == True:
            self.bottle.clear_bottle()
        new_size = self.AddBottleBottleSize.currentText()
        if new_size == 'Other...':
            new_size = self.get_other_size()
        if 'wine_id' in self.bottle.bottle_info or 'wine_id' in self.bottle.wine_info:
            if self.AddBottleSelLocation.isChecked():
                new_loc = self.AddBottleLocation.text()
                self.inv_add_copy(new_size=new_size, new_loc=new_loc)

            else:
                for i in range(int(self.AddBottleQty.text())):
                    self.inv_add_copy(new_size=new_size)
        else:
            wine_info = {
                "upc": self.AddBottleUPC.text(),
                "winery": self.AddBottleWinery.text(),
                "region": self.AddBottleAVA.text(),
                "name": self.AddBottleBlendName.text(),
                "varietal": self.AddBottleVarietal.text(),
                "wtype": self.AddBottleType.currentText(),
                "vintage": self.AddBottleVintage.text(),
                "msrp": self.AddBottleMSRP.text(),
                "value": self.AddBottleCurrentValue.text(),
                "comments": self.AddBottleComments.toPlainText()
            }

            bottle_info = {"bottle_size": new_size}

            if wine_info['wtype'] == 'Other...':
                new_type, ok_pressed = QInputDialog.getText(
                    self, 'Other Type', 'Enter Custom Type:', QLineEdit.Normal,
                    '')
                if ok_pressed:
                    wine_info['wtype'] = new_type

            for term in wine_info:
                if wine_info[term] == '':
                    wine_info[term] = None
            for term in bottle_info:
                if bottle_info[term] == '':
                    bottle_info[term] = None

            self.bottle.clear_bottle()
            if self.AddBottleSelLocation.isChecked() == True:
                bottle_info["location"] = self.AddBottleLocation.text()
                self.bottle.wine_info = wine_info
                self.bottle.bottle_info = bottle_info
                wine_id = self.bottle.add_new()
            else:
                for i in range(int(self.AddBottleQty.text())):
                    next_location, ok_pressed = QInputDialog.getText(
                        self, "Bottle {0}".format(i + 1),
                        "Enter location for Bottle {0}:".format(i + 1),
                        QLineEdit.Normal, "")
                    if ok_pressed == True:
                        bottle_info['location'] = next_location
                        self.bottle.wine_info = wine_info
                        self.bottle.bottle_info = bottle_info
                        wine_id = self.bottle.add_new()
                        # self.bottle.add_new()
        self.bottle.clear_bottle()
        self.quick_search()
        self.bottle.wine_info['wine_id'] = wine_id

        # Enable the barcode button now that there's a wine id
        self.AddBottleGenerateBarcode.setEnabled(True)

    @QtCore.Slot()
    def ab_upc_fill(self):
        upc = self.AddBottleUPC.text()
        self.bottle.clear_bottle()
        self.bottle.wine_info['upc'] = upc
        self.bottle.search_wine()
        self.ab_fill_fields(wine_info=self.bottle.wine_info)

    @QtCore.Slot()
    def delete_bottle(self):
        # Deletes bottle from the database entirely. This is different
        # from checking out a bottle because the entire thing is removed.
        # This is permenant and dangerous, so it uses a message box to
        # confirm.
        if 'wine_id' in self.bottle.bottle_info and self.bottle.bottle_info[
                'wine_id'] != None:
            msg_box = QMessageBox()
            msg_box.setText(
                "WARNING: You are about to delete this bottle from the database. You cannot undo this. Continue?"
            )
            msg_box.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
            msg_box.setDefaultButton(QMessageBox.Cancel)
            msg_box.setIcon(QMessageBox.Warning)
            ret = msg_box.exec_()

            if ret == QMessageBox.Ok:
                self.bottle.delete_bottle()
            else:
                return

            self.inv_table_pop()

    @QtCore.Slot()
    def delete_wine(self):
        # Deletes wine from the database entirely. This is permenant and
        # dangerous, so it uses a message box to confirm.
        if 'wine_id' in self.bottle.wine_info and self.bottle.wine_info[
                'wine_id'] != None:
            msg_box = QMessageBox()
            msg_box.setText(
                "WARNING: You are about to delete this wine from the database, along with all bottles of it. You cannot undo this. Continue?"
            )
            msg_box.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
            msg_box.setDefaultButton(QMessageBox.Cancel)
            msg_box.setIcon(QMessageBox.Warning)
            ret = msg_box.exec_()

            if ret == QMessageBox.Ok:
                self.bottle.delete_wine()
            else:
                return

            self.inv_table_pop()

    @QtCore.Slot()
    def export_to_excel(self):
        path, _ = QFileDialog.getSaveFileName(self, "Export...", "",
                                              "Excel Files (*.xlsx)")
        import_export.export_db(path)

    @QtCore.Slot()
    def import_from_excel(self):
        msg_box = QMessageBox()
        msg_box.setText(
            "Use one of the available templates to import your collection from an Excel spreadsheet."
        )
        msg_box.setInformativeText(
            "Note: do not change the filename of the template.")
        msg_box.setStandardButtons(QMessageBox.Open | QMessageBox.Cancel)
        expanded = msg_box.addButton('Create Expanded...',
                                     QMessageBox.ApplyRole)
        condensed = msg_box.addButton('Create Condensed...',
                                      QMessageBox.ApplyRole)
        msg_box.setDefaultButton(QMessageBox.Cancel)
        ret = msg_box.exec_()

        if msg_box.clickedButton() == expanded:
            path = QFileDialog.getExistingDirectory(
                self, "Select Directory for Template...") + '/'
            import_export.generate_sheet(path, expanded=True)
        elif msg_box.clickedButton() == condensed:
            path = QFileDialog.getExistingDirectory(
                self, "Select Directory for Template...") + '/'
            import_export.generate_sheet(path, expanded=False)
        elif ret == QMessageBox.Open:
            path = QFileDialog.getOpenFileName(
                self, "Select Filled-Out Template...")
            import_export.import_db(path[0])

    @QtCore.Slot()
    def backup_database(self):
        path = QFileDialog.getSaveFileName(self, "Backup...", '',
                                           'Database Files (*.db)')
        shutil.copyfile(os.getcwd() + '/wineinv_data.db', path[0])

    @QtCore.Slot()
    def generate_barcode(self):
        barcode_img = self.bottle.generate_label()
        msg_box = QMessageBox()
        msg_box.setText(
            "Lable has been created. Select one of the options below to continue:"
        )
        msg_box.setStandardButtons(QMessageBox.Save | QMessageBox.Cancel)
        pr_button = msg_box.addButton('Print', QMessageBox.ApplyRole)
        msg_box.setDefaultButton(pr_button)
        ret = msg_box.exec_()

        if ret != QMessageBox.Cancel:
            if msg_box.clickedButton() == pr_button:
                self.bottle.print_label()
            elif ret == QMessageBox.Save:
                path = QFileDialog.getSaveFileName(self, "Save As...", '',
                                                   'Picture Files (*.png)')[0]
                shutil.copyfile(barcode_img + '.png', path)

    @QtCore.Slot()
    def edit_preferences(self):
        msg_box = QMessageBox()
        msg_box.setText('Coming soon!')
        msg_box.setStandardButtons(QMessageBox.Ok)
        msg_box.exec_()