Hello every one, I am having a page in which user can add QnA with multiple questions and single answer. Currently how it works is I am having add and delete QnA button in the side bar from where we can add or delete it but only in last.
but with this we cannot delete a QnA in the mid somewhere which is a big problem as if there are 100 of these QnA and I want to delete 60th one I have to delete last 40 first. For this I want to add delete button after every container with which I can delete any QnA on any position. And the same problem goes with add or delete questions in each QnA. How can I achieve this?
Steps to reproduce
Code snippet:
import streamlit as st
from streamlit_javascript import st_javascript
import csv
import time
import ftplib
import urllib.request
import json
st.set_page_config(layout="wide")
st.title('Knowledge Base')
if "num_questions" not in st.session_state:
st.session_state.num_questions = 0
if "refresh" not in st.session_state:
st.session_state.refresh = 0
for k, v in st.session_state.items():
st.session_state[k] = v
st.session_state["changedcard"] = st_javascript(f"JSON.parse(sessionStorage.getItem('changedcard'));")
if st.session_state["changedcard"] == 0:
pass
else:
st_javascript(f"(sessionStorage.removeItem('changedcard'));")
st_javascript(f"(sessionStorage.removeItem('editcard'));")
testv = json.dumps(st.session_state["changedcard"])
st.session_state[st.session_state["editcard"]] = testv
with st.sidebar:
if st.button("Add QnA"):
st.session_state["num_questions"] += 1
else:
pass
if st.button("Delete QnA"):
st.session_state["num_questions"] -= 1
else:
pass
for i in range(st.session_state["num_questions"]):
if f"qa_num_{i}" not in st.session_state:
st.session_state[f"qa_num_{i}"] = 0
if f"ans_{i}" not in st.session_state:
st.session_state[f"ans_{i}"] = ""
con = st.container()
if con.button(f"{i}.Add Questions"):
st.session_state[f"qa_num_{i}"] += 1
else:
pass
if con.button(f"{i}.Delete Questions"):
st.session_state[f"qa_num_{i}"] -= 1
else:
pass
# qa_num = con.number_input(
# str(i + 1) + ". Add or remove Questions",
# min_value=0,
# step=1,
# key=f"qa_num_{i}",
# )
col1, col2 = con.columns([5, 5])
for j in range(st.session_state[f"qa_num_{i}"]):
if f"question_{i}_{j}" not in st.session_state:
st.session_state[f"question_{i}_{j}"] = ""
col1.text_input(
"Questions",
label_visibility="visible" if j == 0 else "collapsed",
key=f"question_{i}_{j}",
)
with col2:
if f"ans_{i}" not in st.session_state:
st.session_state[f"ans_{i}"] = ''
if st.session_state[f"ans_{i}"] == '':
if st.button(f"{i+1}.Create card"):
st.session_state.refresh=1
st.session_state[f"ans_{i}"] = '''{
"type": "AdaptiveCard",
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.6",
"body": []
}'''
else:
pass
else:
pass
try:
ans=json.loads(st.session_state[f"ans_{i}"])
k = "editcard"
adata = json.dumps(ans)
if st.button(f"{i+1}.Edit"):
st_javascript(f"sessionStorage.setItem('{k}', JSON.stringify({adata}));")
st.session_state["editcard"] = f"ans_{i}"
else:
pass
if st.button(f"{i+1}.Delete card"):
st.session_state.refresh=1
st.session_state[f"ans_{i}"] = ''
else:
pass
except:
st.text_area(
"Answer",
key=f"ans_{i}",
)
if st.session_state.refresh==1:
st.session_state.refresh = 0
time.sleep(3)
st.experimental_rerun()
Since you are saving your questions and answers to session state by number, that will create difficulty if you want to delete from the middle since your loop is just blindly working through 1, 2, 3, âŠ
Alternative 1: You can keep a list of question/answer indexes, and loop through that list rather than a complete range. The delete button would remove the index from the list of active questions (possibly deleting the associated data in session state, but not decrement num_questions as youâd want to keep creating new indexes for new questions if you wanted to avoid shifting around all your other data.
Alternative 2: You could use something like pandas to keep your list of data and iterate directly through your items. It could handle all the indexing for you and give you a simpler method or removing or sorting your questions.
@mathcatsand I tried this approach as well can this work? because it is not allowing me to delete containers.
import streamlit as st
from streamlit_javascript import st_javascript
import csv
import time
import ftplib
import urllib.request
import json
st.set_page_config(layout="wide")
# with open("designing.css") as source_des:
# st.markdown(f"<style>{source_des.read()}</style>",unsafe_allow_html=True)
st.title('Knowledge Base')
if "num_questions" not in st.session_state:
st.session_state.num_questions = 0
if "question" not in st.session_state:
st.session_state.question = []
if "answer" not in st.session_state:
st.session_state.answer = []
if "qindex" not in st.session_state:
st.session_state.qindex = []
if "refresh" not in st.session_state:
st.session_state.refresh = 0
for k, v in st.session_state.items():
st.session_state[k] = v
head1, head2 = st.columns([3,3])
if head1.button('Load'):
head1.write('File Loaded')
else:
pass
if head2.button('Save'):
head2.write('File Saved')
else:
pass
with st.sidebar:
if st.button("Add QnA"):
st.session_state["num_questions"] += 1
else:
pass
for i in range(st.session_state["num_questions"]):
if i >= len(st.session_state.question):
st.session_state.question.append([])
if i >= len(st.session_state.answer):
st.session_state.answer.append('')
if i >= len(st.session_state.qindex):
st.session_state.qindex.append(0)
con = st.container()
if con.button(f"{i}.Add Questions"):
st.session_state.qindex[i] += 1
else:
pass
if con.button(f"{i}.Delete Questions"):
st.session_state.qindex[i] -= 1
else:
pass
col1, col2 = con.columns([5, 5])
for j in range(st.session_state.qindex[i]):
if j >= len(st.session_state.question[i]):
st.session_state.question[i].append('')
st.session_state.question[i][j] = col1.text_input(
f"question_{i}_{j}",
label_visibility="visible" if j == 0 else "collapsed",
)
with col2:
st.session_state.answer[i] = st.text_area(f"ans_{i}")
if st.button(f"{i}.Delete QnA"):
st.session_state['qindex'].pop(i)
st.session_state['answer'].pop(i)
st.session_state['question'].pop(i)
st.session_state["num_questions"] -= 1
st.session_state.refresh==1
else:
pass
if st.session_state.refresh==1:
st.session_state.refresh = 0
time.sleep(3)
st.experimental_rerun()
print(st.session_state)
Yes, using lists instead of dataframes is a valid approach. What do you mean by ânot allowing me to delete containers?â Can you describe the problem?
You have your delete button inside of a loop and the action it takes modifies the length of that loop. Additionally, your widgets are going to remember their previous (deleted) value if you donât have some way to tell Streamlit that itâs a new widget with a new value. If Streamlit constructs a widgets with the same arguments, it will think itâs the same widget even if you intended it to create a new one.
You can force the value of your widgets:
st.session_state.question[i][j] = col1.text_input(
f"question_{i}_{j}",
label_visibility="visible" if j == 0 else "collapsed",
value = st.session_state.question[i][j]
)
Thank you @mathcatsand It worked. I have another problem if you can help with that the button names I want to be same but whenever I am adding a key in the button like
Since buttons arenât stateful, you canât dictate a button state. So if you assign a key to a button where the data associated to the key is pre-existing, it will error. You can only assign a key my_key to a button if you never assign a value to st.session_state.my_key manually.
At a minimum, you have to exclude all button keys from your session_state dictionary rewrite at the top of your script (or not use keys on buttons):
for k, v in st.session_state.items():
st.session_state[k] = v
@mathcatsand@Goyo I have removed this line from the code and it seems to be working fine now.
for k, v in st.session_state.items():
st.session_state[k] = v
Will removing it cause any problems later?
And @mathcatsand I am trying to follow the code you provided which is really great.
I also want to add the paging functionality just the way you added in your code but for containers and not the columns. Can you help me direct how can I achieve it in my current code?
That seems to an intermittent bug, so it might come back. In that case you would have to exclude the buttons keys from the loop, as mentioned in a former comment.
That workaround is needed to preserve data associated to a widget key, specifically. When the widget is no longer rendered, Streamlitâs cleanup process deletes the associated key from session state (and itâs currently a bug/feature request for Streamlit to connect to a widget of the same key on a different page as it currently doesnât).
If the data is not stored in a key associated to a widget, you wonât lose it by going to another page.
(Iâm on mobile now, so Iâll provide the columns to containers modification later today.)
Thanks for stopping by! We use cookies to help us understand how you interact with our website.
By clicking âAccept allâ, you consent to our use of cookies. For more information, please see our privacy policy.
Cookie settings
Strictly necessary cookies
These cookies are necessary for the website to function and cannot be switched off. They are usually only set in response to actions made by you which amount to a request for services, such as setting your privacy preferences, logging in or filling in forms.
Performance cookies
These cookies allow us to count visits and traffic sources so we can measure and improve the performance of our site. They help us understand how visitors move around the site and which pages are most frequently visited.
Functional cookies
These cookies are used to record your choices and settings, maintain your preferences over time and recognize you when you return to our website. These cookies help us to personalize our content for you and remember your preferences.
Targeting cookies
These cookies may be deployed to our site by our advertising partners to build a profile of your interest and provide you with content that is relevant to you, including showing you relevant ads on other websites.