I have two input fields that shall allow user selection via
ID, in the number input
(sorted) name, in the selection box
Changing one input field should update the other to keep them in sync.
How do you implement that behaviour with streamlit?
What I tried so far
ID selected -> update name selection box:
users = [(1, 'Jim'), (2, 'Jim'), (3, 'Jane')]
users.sort(key=lambda user: user[1]) # sort by name
selected_id = st.sidebar.number_input('ID', value=1)
options = ['%s (%d)' % (name, id) for id, name in users]
index = [i for i, user in enumerate(users) if user[0] == selected_id][0]
selected_option = st.sidebar.selectbox('Name', options, index)
Name selected -> update ID number input (using st.empty()):
users = [(1, 'Jim'), (2, 'Jim'), (3, 'Jane')]
users.sort(key=lambda user: user[1]) # sort by name
id_input = st.sidebar.empty()
options = ['%s (%d)' % (name, id) for id, name in users]
selected_option = st.sidebar.selectbox('Name', options)
# e.g. get 2 from "Jim (2)"
id = int(re.match(r'\w+ \((\d+)\)', selected_option).group(1))
selected_id = id_input.number_input('ID', value=id)
To keep the widgets in sync, there are two issues that need to be addressed:
We need to be able to tell when either widget has caused the current selection to change; and
We need to update the state of both widgets at the end of the script so that the browser keeps the new values when the script is re-run for visual updates.
For (1), it looks like there's no way of doing it without introducing some kind of persistent state. Without a way to store the current selection between script runs, we can only compare the two widgets' values with each other and with the default value. This causes problems once the widgets have been changed: For example, if the default value is 1, the value of the number input is 2, and the value from the selectbox is 3, we cannot tell whether it is the number input or the selectbox that was most recently changed (and therefore which one to update to the latest value).
For (2), it's a simple matter of using placeholders and refreshing the widgets whenever the selection has changed. Importantly, the widgets should not be refreshed if the selection has not changed, or we will get DuplicateWidgetID errors (since the content of the widgets will not have changed either, and they will have the same generated keys).
Here's some code that shows one way of dealing with both issues and capturing the user's selection at the end. Note that using #st.cache in this way will persist a single global selection across multiple browser sessions, and will allow anyone to clear the selection via the Streamlit menu -> 'Clear cache', which could be a problem if multiple users are accessing the script at the same time.
import re
import streamlit as st
# Simple persistent state: The dictionary returned by `get_state()` will be
# persistent across browser sessions.
#st.cache(allow_output_mutation=True)
def get_state():
return {}
# The actual creation of the widgets is done in this function.
# Whenever the selection changes, this function is also used to refresh the input
# widgets so that they reflect their new state in the browser when the script is re-run
# to get visual updates.
def display_widgets():
users = [(1, "Jim"), (2, "Jim"), (3, "Jane")]
users.sort(key=lambda user: user[1]) # sort by name
options = ["%s (%d)" % (name, id) for id, name in users]
index = [i for i, user in enumerate(users) if user[0] == state["selection"]][0]
return (
number_placeholder.number_input(
"ID", value=state["selection"], min_value=1, max_value=3,
),
option_placeholder.selectbox("Name", options, index),
)
state = get_state()
# Set to the default selection
if "selection" not in state:
state["selection"] = 1
# Initial layout
number_placeholder = st.sidebar.empty()
option_placeholder = st.sidebar.empty()
# Grab input and detect changes
selected_number, selected_option = display_widgets()
input_changed = False
if selected_number != state["selection"] and not input_changed:
# Number changed
state["selection"] = selected_number
input_changed = True
display_widgets()
selected_option_id = int(re.match(r"\w+ \((\d+)\)", selected_option).group(1))
if selected_option_id != state["selection"] and not input_changed:
# Selectbox changed
state["selection"] = selected_option_id
input_changed = True
display_widgets()
st.write(f"The selected ID was: {state['selection']}")
Related
TL;DR: I'm basically trying to work out how to merge these two example codes from the PySimpleGui Github: https://github.com/PySimpleGUI/PySimpleGUI/blob/master/DemoPrograms/Demo_Layout_Extend.py and
https://github.com/PySimpleGUI/PySimpleGUI/blob/master/DemoPrograms/Demo_Multiline_Right_Click_Menu_Clipboard.py where the element key passed to the clipboard function has been dynamically created...
Using the docs for PySimpleGui and the plethora of info on both stackoverflow and the Github demos I have a 'simple' gui made that can add and remove Input fields and I have everything sorted for that side of things (ie adding elements to the GUI using layout_extend, removing elements using .hide_row() and keeping a list of removed values etc...
Like the layout_extend demo I am using a Frame, and adding GUI elements to it using an iterator to ensure unique 'keys' are created.
##CODE SNIPPET AS MOST OF IT IS PRETTY MUCH THE SAME AS THE EXAMPLE DEMO CODE WITH MINOR CHANGES
right_click_menu = ['', ['Copy', 'Paste', 'Select All', 'Cut']]
def do_clipboard_operation(event, window, element):
#Same as Demo Code Here No Point Copying!
...
...
if event in right_click_menu[1]:
do_clipboard_operation(event, program_window, element)
if event == '-ADDPATH-':
program_window.extend_layout(program_window['-FRAME1-'], [[sg.Text('PATH:'),sg.Input(key=f'-PATH{i}-', rick_click_menu=right_click_menu), sg.FolderBrowse(initial_folder="/base/path", key=f'-BROWSEPATH{i}-'), sg.Button('Remove Path', key=f'-REMOVEPATH{i}-')]])
i += 1
...
...
When a User is presented with an input field that they want to put a long string of text in (in my case folder paths) there is a desire to the 'right-click context menu' so I wanted to look at adding it.
My issue comes that the Multi-line Rick Click Menu Clipboard demo needs the triggering element key passed to the function (which is then used to add / remove text into), but because I have 'dynamically generated' Key names for the elements I need to work out which element key activated the right click menu before being able to pass it and I just don't know how to do that.....
The code I've written is basically identical to the Layout_extend demo.....
I appreciate that PySimpleGui, as awesome as it is, might not really be the best tool to handle dynamic GUIs but as I've always struggled with OO programming its pretty much the only way I've been able to build a functional Python GUI to date....
If this isn't possible at this stage, that's ok, but least I asked (which makes me feel better when I inevitably get someone saying 'its really annoying I can't right click' when they use it)!!
Thanks in advance as always!!
Owen.
EDIT::
Added Code (Had to remove some references to specific details - root paths, and other such info, placed '<' '>' around placeholders for where data existed - but the code is the same:
#!/bin/python3
import PySimpleGUI as sg
import os.path
from getpass import getuser
from string import ascii_letters, digits
from random import choice as rchoice
SPLASH_IMAGE_PATH=r'<AN IMAGE FILE LOCATION>'
OUT_PATH='./'
DISPLAY_SPLASH_MS = 1500
REMOVE_LIST=[]
VERSION_NUM='0.3.0'
splash_theme = {'BACKGROUND': '#ffcc00',
'TEXT': '#000000',
'INPUT': '#ffffff',
'TEXT_INPUT': '#ffffff',
'SCROLL': '#ffffff',
'BUTTON': ('white', '#000000'),
'PROGRESS': ('#ffffff', '#ffffff'),
'BORDER': 0,
'SLIDER_DEPTH': 0,
'PROGRESS_DEPTH': 0
}
right_click_menu = ['', ['Copy','Paste','Select All','Cut']]
def do_clipboard_operation(event, window, element):
print(event)
print(window)
print(element)
##Random string generator: https://www.stackoverflow.com/questions/2257441/random-sting-generation-with-upper-case-letters-and-digits (Ignacio Vazquez-Abrams answer)
def random_string_gen(size=8, chars=ascii_letters + digits):
return ''.join(rchoice(chars) for _ in range(size))
def main():
sg.theme_add_new('SplashScreen', splash_theme)
sg.theme('SplashScreen')
splash_window= sg.Window('Uploader', [[sg.Image(SPLASH_IMAGE_PATH)], [sg.Text('Uploader', font=("any",20))], [sg.Text(f'Version: {VERSION_NUM}', font=('any',12))]], element_justification='c', no_titlebar=True, keep_on_top=True).read(timeout=DISPLAY_SPLASH_MS, close=True)
sg.theme('DarkAmber')
layout = [ [sg.Text('Please Enter FULL Paths. Use The Browse Button To Navigate, Or Copy Path Into Field.')],
[sg.Text('Use The "Add Path" Button To Add More Rows, And The "Remove Path" Button To Remove A Row.')],
[sg.Text('When Finished, Press The "Upload" Button - This Will Take Time To Process.')],
[sg.Frame('Paths:', [[sg.Text('PATH:'), sg.Input(key='-PATH0-', right_click_menu=right_click_menu), sg.FolderBrowse(initial_folder="<ROOT PATH>", key='-BROWSEPATH0-' ), sg.Button('Remove Path', key='-REMOVEPATH0-')]], key='-FRAME1-')],
[sg.OK('Upload',key='-RUN-'), sg.Button('Add Additional Path', key='-ADDPATH-'), sg.Cancel('Exit', key='-EXIT-')]
]
program_window = sg.Window('Uploader', layout)
i = 1
num_rows=1
user=str(getuser())
while True:
event, values = program_window.read()
if event == sg.WIN_CLOSED or event == '-EXIT-':
break
if event in right_click_menu[1]:
print(values)
# do_clipboard_operation(event, program_window, element)
if event == '-ADDPATH-':
##Add new row to output window. uses f' ' string formatting to use the i iterator to create unique keys
program_window.extend_layout(program_window['-FRAME1-'], [[sg.Text('PATH:'), sg.Input(key=f'-PATH{i}-', right_click_menu=right_click_menu), sg.FolderBrowse(initial_folder="<ROOT PATH>", key=f'-BROWSEPATH{i}-' ), sg.Button('Remove Path', key=f'-REMOVEPATH{i}-')]])
i += 1
num_rows += 1
if event == '-RUN-':
##Iterate over values and remove duplicates (both the text field and the browse button can be populated with the same data so duplicates cannot be avoided
##Remove blank values (can occur when path is pasted into text field and browse button not used, and remove any paths that occur in the remove_list. generated when lines are removed
##Remove items that appear in REMOVE_LIST (created when removing lines)
##Output to a new out_list as items in value cannot be changed
##Validate User input to: ensure no <AVOID PATH> paths, no invalid paths, at least one path is submitted, and path submitted actually contains an <SPECIFIC FILES>... Show popup errors if any found and allow user to remove / alter offending paths.
##If all is OK generate a random string with the username appended and write all valid paths into a temp file with that random name. This is for back-end program to run.
out_list = []
error_list = []
avoid_path_error=False
invalid_path=False
for key, value in values.items():
if value not in out_list and not value == '' and not value in REMOVE_LIST:
if '<AVOID PATH>' in value:
avoid_path_error=True
sg.popup('One Or More Of Your Paths Is Located In The <AVOID PATH> And Cannot Be Used As An Input!\n\nPlease Remove The Offending Path.', title='<AVOID PATH> FOUND', keep_on_top=True, custom_text=('Sorry'), button_color=('white', 'red'))
break
elif not os.path.isdir(value):
invalid_path=True
sg.popup(f'The Following Invalid Path Has Been Entered And Does Not Seem To Exist / Is Unreadable:\n\n{value}', title='INVALID PATH ENTERED', keep_on_top=True, custom_text=('Oops!'), button_color=('white', 'red'))
break
else:
out_list.append(value)
if not avoid_path_error == True and not invalid_path == True:
if not out_list:
sg.popup('You Have Not Added Any Paths!!! Please Try Again', title='NO PATH ENTERED', keep_on_top=True, custom_text=('Oops!'), button_color=('white', 'red'))
else:
for path in out_list:
for files in os.listdir(path):
if '<SPECIFIC FILE>' in files.lower():
break
else:
error_list.append(path)
if error_list:
error_list_string = '\n'.join(error_list)
sg.popup(f'The Following Path(s) Do NOT Contain <SPECIFIC FILE>...\nPlease ONLY Add Full Paths:\n\n{error_list_string}', title='NO <SPECIFIC PATH> FOUND', keep_on_top=True, custom_text=('Sorry!'), button_color=('white', 'red'))
else:
print(random_string_gen() + '.' + user)
break
if '-REMOVEPATH' in event:
##Remove Line from window. values Dict cannot be altered, so need to generate a new list with the values of the data held in the value dict: -PATHx- and -BROWSEPATHx-
##Where x is the unique iterator number used to ensure unique keys. The Event also uses the same unique iterator key: -REMOVEPATHx- so replacing -REMOVEPATH with -PATH and -BROWSEPATH
##generates the keys we need. call the hide_row() function to remove row from window, and append both -PATHx- and -BROWSEPATHx-.
folder_path=str(event).replace("-REMOVEPATH", "-PATH")
browse_path=str(event).replace("-REMOVEPATH", "-BROWSEPATH")
num_rows -= 1
if num_rows == 0:
program_window[event].hide_row()
program_window.extend_layout(program_window['-FRAME1-'], [[sg.Text('PATH:'), sg.Input(key=f'-PATH{i}-', right_click_menu=right_click_menu), sg.FolderBrowse(initial_folder="<ROOT PATH>", key=f'-BROWSEPATH{i}-' ), sg.Button('Remove Path', key=f'-REMOVEPATH{i}-')]])
i += 1
num_rows = 1
else:
program_window[event].hide_row()
REMOVE_LIST.append(values[folder_path])
REMOVE_LIST.append(values[browse_path])
program_window.close()
if __name__ == "__main__":
main()
Using a qTableWidget to view my sqlite database entries.
The first column of the database is hidden since it's the ID that gets auto assigned to an entry.
Now I want the user to select the row he wants to delete and then be able to press the delete button.
I'm using the following to do that:
class StartScherm(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self):
super(StartScherm, self).__init__()
self.setupUi(self)
self.database_laden()
# -- Button presses with .connect -- #
self.tableSnelleKijk.doubleClicked.connect(self.weergave_scherm)
self.tableSnelleKijk.setSelectionBehavior(
QtWidgets.QTableView.SelectRows)
self.tableSnelleKijk.setColumnHidden(0, True)
Now I'm trying to make a function that once the users clicks on the delete button in the GUI, the function reads the ID so I can delete it from the sqlite database. I use this:
def delete(self):
index = self.tableSnelleKijk.selectedItems()
print(index[0].text())
unfortunately, this gives me the first visable column data as text instead of the hidden columnwith the ID.
How do I access the hidden data?
EDIT: changed column to row
The database is hidden on the tableview only, in sqlite it's simply there to have a rowID I can use to access a current row to delete/edit.
Database code:
c.execute("""CREATE TABLE Medailles (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
orde text,
periode text,
locatie text,
beschrijving text
)""")
The column is hidden in the QtableView with .setColumnHidden.
The database gets populated when the users decides to enter a new entry, this is the function that takes care of this:
def add(self):
orde = self.orde_comboBox.currentText()
periode = self.periode_input.text()
locatie = self.locatie_input.text()
beschrijving = self.beschrijving_input.toPlainText()
medaille = Medaille(orde, periode, locatie, beschrijving)
with conn:
c.execute("INSERT INTO Medailles (orde, periode, locatie, beschrijving) VALUES (:orde, :periode, :locatie, :beschrijving)", {
'orde': medaille.orde, 'periode': medaille.periode, 'locatie': medaille.locatie, 'beschrijving': medaille.beschrijving})
self.close()
If the data is in an invisible column, the items in that column cannot be selected. You can access those indexes (and their data) by using the sibling(row, column) function (or siblingAtColumn(column) for Qt>=5.11):
def delete(self):
rows = set()
for index in self.tableSnelleKijk.selectedItems():
id = index.sibling(index.row(), 0).text()
rows.add(id)
print('IDs to delete: {}'.format(sorted(rows)))
Do note that if you need to write data to the database or edit its layout you should consider using a basic QTableView with a QtSql.QSqlTableModel instead, otherwise you might face some inconsistencies, bugs or even data corruption if you're not really careful with your implementation.
To get the value of a hidden column in a QTableWidget (PyQt5):
row = self.tblWidget.currentRow()
col = position of hidden column - usually 0
ID = self.tblWidget.item(row,col).text()
I'm working on to compute the average of x records and I don't want to include the last one (the record where I trigger the action).I can trigger the action in existing record or in a new one (not yet in database).
Here is my code:
#api.one
#api.depends('stc')
def _compute_average_gross(self):
if self.stc:
base_seniority = 12
match_seniority = self.seniority.split()
total_seniority = int(match_seniority[0]) + int(match_seniority[2]) * 12
if total_seniority < 12:
base_seniority = total_seniority if total_seniority else 1 # avoid dividing by 0
# if the hr.payslip is already in db
if self._origin.id:
limit = 13
# could be self.env.cr.execute()
sum_sbr = sum(self.search([('employee_id', '=', self.employee_id.id)], order='create_date desc', limit=limit)[1:].mapped('line_ids').filtered(lambda x: x.code == 'SBR').mapped('amount'))
sum_average_gross = sum(self.search([('employee_id', '=', self.employee_id.id)], order='create_date desc', limit=limit)[1:].mapped('average_gross'))
else:
limit = 12
# could be self.env.cr.execute()
sum_sbr = sum(self.search([('employee_id', '=', self.employee_id.id)], order='create_date desc', limit=limit).mapped('line_ids').filtered(lambda x: x.code == 'SBR').mapped('amount'))
sum_average_gross = sum(self.search([('employee_id', '=', self.employee_id.id)], order='create_date desc', limit=limit).mapped('average_gross'))
self.average_gross = round((sum_sbr + sum_average_gross) / base_seniority, 2)
With that I got an error that self doesn't have _origin, I trier with origin but got the same error. I also tried with self.context['params'].get('id') but it doesn't work as expected.
Could you help me?
To check if record is not saved in database do this:
if isinstance(self.id, models.NewId):
# record is not saved in database.
# do your logic
# record is saved in databse
if not isinstance(self.id, models.NewId):
# ....
for all who are coming to this after the accepted answer:
the correct solution should be this
if isinstance(self.id, models.NewId) and not self._origin:
# record is not saved in database.
# do your logic
# record is saved in databse
if not isinstance(self.id, models.NewId) or self._origin:
# ....
I'm not sure if _origin already existed in Odoo 10, but needed the same in Odoo 13
I haven't tested in a single record but with res.partner and the partner contacts (field child_ids) and the problem is if you open an existing contact and change any field, odoo transfers the existing record in a new record and you get a false positive answer as the record.id is a new ID but the origin exists in DB
haven't tested with copy functionality but i'm sure oodo is correctly resetting the origin in the new record so my answer should be right
Hello community!
I have quite a problem with my python code, I have been trying to resolve it for days, but I can't make it work. I am working on an online shopping service for my school's assignment. I am trying to create a credit card validator. What my problem concerns is that when I have an entry for user to put his expiry date, I am trying to format it in this way MM/YY so for instance 01/20 or 11/23 . I am trying to make it automatically add the / slash. So that when user inputs 12 it will automatically add the /
So far I am tracking when there is a change in the entry box, so it knows when to add the slash. I have unfortunately problem with formatting. So when the input is 12/21 and the users deletes the first "2" char, the input becomes this:
While debbuing I sometimes can see these results:
Or such:
Here is the code I use. Do you see any mistake or is there an efficient way to do so?
def validateDate():
global entryDateCard,dateCard ##entryDateCard is just Entry ID, dateCard is the expiry date
print("bbb")
if (len(entryDateCard.get())==5):
print("ccc")
entryDateCard.insert(0,dateCard)
dateCard = entryDateCard.get()
if (2<=len(entryDateCard.get())<=4 and (entryDateCard.get().find("/")!=2)): ## does not work even when true
print("aaa")
dateCard.replace("/","")
entryDateCard.delete(0,"end")
entryDateCard.insert(0,dateCard)
entryDateCard.insert(2,"/")
if(len(entryDateCard.get())>5):
entryDateCard.delete(0,"end")
entryDateCard.insert(0,dateCard[:5])
dateCard = dateCard[:5]
EDIT1 : Tried this but still does not work (Posting an entire snippet of code)
import tkinter
w = 1280
h = 720
canvas = tkinter.Canvas(width=w,height=h,bg="#71CAE7")
canvas.pack()
entryDateCard=0
dateCard=""
cardDateSV=tkinter.StringVar()
cardDateSV.trace("w", lambda name, index, mode, sv=cardDateSV: validateDate())
entryDateCard = tkinter.Entry(width=15,font = "Helvetica 15 bold",textvariable=cardDateSV)
entryDateCard.pack()
entryDateCard.place(x=(w//2)-275,y=h-(0.61*h),height=30)
def validateDate():
global entryDateCard,dateCard
dateCard = entryDateCard.get()
if (len(dateCard.replace("/",""))==2):
entryDateCard.insert(2,"/")
print("two")
elif (len(dateCard.replace("/",""))==3):
entryDateCard.delete(0,"end")
entryDateCard.insert(0,dateCard[:2])
entryDateCard.insert(2,"/")
entryDateCard.insert(3,dateCard[3:4])
print("three")
elif (len(dateCard.replace("/",""))==4):
entryDateCard.delete(0,"end")
entryDateCard.insert(0,dateCard[:2])
entryDateCard.insert(2,"/")
entryDateCard.insert(3,dateCard[3:5])
print("four")
elif (len(dateCard.replace("/",""))>=5):
print("max")
entryDateCard.delete(0,"end")
entryDateCard.insert(0,dateCard[:5])
dateCard = dateCard[:5]
I am attempting to query all rows for a column called show_id. I would then like to compare each potential item to be added to the DB with the results. Now the simplest way I can think of doing that is by checking if each show is in the results. If so pass etc. However the results from the below snippet are returned as objects. So this check fails.
Is there a better way to create the query to achieve this?
shows_inDB = Show.query.filter(Show.show_id).all()
print(shows_inDB)
Results:
<app.models.user.Show object at 0x10c2c5fd0>,
<app.models.user.Show object at 0x10c2da080>,
<app.models.user.Show object at 0x10c2da0f0>
Code for the entire function:
def save_changes_show(show_details):
"""
Save the changes to the database
"""
try:
shows_inDB = Show.query.filter(Show.show_id).all()
print(shows_inDB)
for show in show_details:
#Check the show isnt already in the DB
if show['id'] in shows_inDB:
print(str(show['id']) + ' Already Present')
else:
#Add show to DB
tv_show = Show(
show_id = show['id'],
seriesName = str(show['seriesName']).encode(),
aliases = str(show['aliases']).encode(),
banner = str(show['banner']).encode(),
seriesId = str(show['seriesId']).encode(),
status = str(show['status']).encode(),
firstAired = str(show['firstAired']).encode(),
network = str(show['network']).encode(),
networkId = str(show['networkId']).encode(),
runtime = str(show['runtime']).encode(),
genre = str(show['genre']).encode(),
overview = str(show['overview']).encode(),
lastUpdated = str(show['lastUpdated']).encode(),
airsDayOfWeek = str(show['airsDayOfWeek']).encode(),
airsTime = str(show['airsTime']).encode(),
rating = str(show['rating']).encode(),
imdbId = str(show['imdbId']).encode(),
zap2itId = str(show['zap2itId']).encode(),
added = str(show['added']).encode(),
addedBy = str(show['addedBy']).encode(),
siteRating = str(show['siteRating']).encode(),
siteRatingCount = str(show['siteRatingCount']).encode(),
slug = str(show['slug']).encode()
)
db.session.add(tv_show)
db.session.commit()
except Exception:
print(traceback.print_exc())
I have decided to use the method above and extract the data I wanted into a list, comparing each show to the list.
show_compare = []
shows_inDB = Show.query.filter().all()
for item in shows_inDB:
show_compare.append(item.show_id)
for show in show_details:
#Check the show isnt already in the DB
if show['id'] in show_compare:
print(str(show['id']) + ' Already Present')
else:
#Add show to DB
For querying a specific column value, have a look at this question: Flask SQLAlchemy query, specify column names. This is the example code given in the top answer there:
result = SomeModel.query.with_entities(SomeModel.col1, SomeModel.col2)
The crux of your problem is that you want to create a new Show instance if that show doesn't already exist in the database.
Querying the database for all shows and looping through the result for each potential new show might become very inefficient if you end up with a lot of shows in the database, and finding an object by identity is what an RDBMS does best!
This function will check to see if an object exists, and create it if not. Inspired by this answer:
def add_if_not_exists(model, **kwargs):
if not model.query.filter_by(**kwargs).first():
instance = model(**kwargs)
db.session.add(instance)
So your example would look like:
def add_if_not_exists(model, **kwargs):
if not model.query.filter_by(**kwargs).first():
instance = model(**kwargs)
db.session.add(instance)
for show in show_details:
add_if_not_exists(Show, id=show['id'])
If you really want to query all shows upfront, instead of putting all of the id's into a list, you could use a set instead of a list which will speed up your inclusion test.
E.g:
show_compare = {item.show_id for item in Show.query.all()}
for show in show_details:
# ... same as your code