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]
Related
I have a little problem to solve, but I donĀ“t get an acceptable solution. I try to have a script that automaticcaly fills in FormFields in Word with entries of dictionaries. So far I used pywin32 for that task and somehow I managed to find a solution that works at all. At least as long as I update the word document.
The command that I use so far in a loop is "marks(i).Result = keyValue". Here "marks" is - as far as I understand - a formfields object, which I get by "marks = doc.FormFields".
As I said, all this works to some extent, but as soon as I update the document in Word, all entries disappear again.
Would be really great if someone could help me and knows how to make it that the entry remains permanent. Unfortunately I do not understand the documentation of pywin32 at all. Attached below is the whole function.
Thanks in advance!
def wordExport(window, excDict, docPath):
wordApp = win32com.client.Dispatch('Word.Application') #Word App Object
wordApp.Visible = False
doc = wordApp.Documents.Open(docPath) #Open .docx
marks = doc.FormFields # FormFields object
cMarks = marks.Count # FormField count
cMatches = 0 #Match counter
cOverwrt = 0 #Fields overwritten
for i in range(1, cMarks + 1):
fieldName = str(marks(i).Name) #String object of FormField
#Default Cases --------------------------------------------------------------------
if fieldName in excDict:
keyValue = excDict.get(fieldName) #Get value to key(FormField)
resultIsEmpty = not marks(i).Result #No text has been entered before
# No prior entry present
if keyValue != "None"
marks(i).Result = keyValue + " "
cMatches += 1
if not resultIsEmpty:
cOverwrt += 1
doc.Close()
wordApp.Visible = False
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()
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']}")
thanks to some help from yesterday from stackoverflow, I've made progress in my code. But I have a question, concerning my page. Here is my code
#!/usr/bin/python
print 'Content-type: text/html\n'
print
#May 17
import cgi, cgitb
cgitb.enable()
form=cgi.FieldStorage()
#May19
dataset1=open('Book1.csv','r')
dataset2=open('Race Demographic by state.txt','r')
sources='''<hr><h4>Sources!</h4>Race Demographics
SAT Scores
SAT Scores txt file
Race Demographics txt file</body></html>'''
def datasplitter(x):
data = x.read()
return data.split("\n")
def datdatamang(x):
data = datasplitter(x)
index = 0
WhileNumber = 0
while index < len(data):
WhileNumber = 0
string = data[index]
string.split(" ")
x=''
while WhileNumber < len(string):
if string[WhileNumber] == ',':
x=x+'</td><td>'
else:
x=x+string[WhileNumber]
WhileNumber+= 1
' '.join(string)
data[index]='<tr><td>'+x+'</td></tr>'
index+=1
result=' '.join(data)
result='''<table border='1'>'''+result+'</table>'
return result
#May 19
def getDescription():
page = ''
state = ''
#May20
if 'Drop' in form:
if form['Drop'].value =="Description":
page+= 'Ever since its conception, the SAT examinations have been widely controversial.Many individuals believe that their race is the most intellectually superior, and that their SAT scores reflect on their superiority. It is my job to find this correlation between scores and race and end the battle among the races!'
if form['Drop'].value=="High SAT Scores":
page+= datdatamang(dataset1)
if form['Drop'].value=="Race Demographics":
page+= datdatamang(dataset2)
if form['Drop'].value=="source":
page+= sources
else:
return page
#May 21
def getState():
table=''
if 'on' in form:
state+= form['on'].value
if state in dataset1:
state.header=dataset1.index(state)
for n in dataset1[state.header]:
table+='''<tr>'''+n+'''</tr>'''
return '''<td>'''+table+'''</td>'''
def getRacebyState():
if 'on' in form:
state+= form['on'].value
if state in dataset2:
state.header=dataset2.index(state)
for n in dataset2[state.header]:
table+='''<tr>'''+n+'''</tr>'''
return '''<td>'''+table+'''</td>'''
#May 20
print '''<html><title>SAT Analysis</title>'''
print '''<body>'''
print getDescription()
print '''</body></html>'''
#May 17 - Writing the analysisV2.html file and starting the code
#May 19 - Tables, and the like
#May 20 - Writing if statements for drop-down menu, building the page.
#May 21 - Working on text fields and getState
Essentially, my page works so that you have a drop-down menu to choose from (choosing one of these values: e.g. "High SAT Scores" and "Race Demographics" causes my python code to generate a page that with tables, or descriptions concerning the drop-down menu option), or a text field (which searches for a state in my CSV files, and returns a table-row with the data about that particular State). Using cgi.FieldStorage() Python collects the value that is submitted through the HTML form. However, how do I write the code so that I only send a value from the text-field only, through the HTML form.
I.e. if I do not want to use the drop-down menu, and instead only want to use the text-field to find a state in particular and not submit form data through the drop-down menu, how do I do that?
I am not sure whether I am missing something here but, assuming 'on' is the name of your text field, you can check to see whether the text field is empty like so:
if 'on' in form and form['on']:
doSomethingWithOn()
else:
doSomethingWithDrop()
I'm using the on page function and a page template to make headers for a subset of the pages in my document:
templates.append(PageTemplate(id='Overview', frames=frame, onPage=HeaderOverview))
The header function for this template:
################################
# Function HeaderOverview - header for overview page
def HeaderOverview(canvas,doc):
canvas.saveState()
headboxh = 15
headboxx = 20
headboxy = 730
headboxw = 570
canvas.rect(headboxx, headboxy, headboxw, headboxh, fill=1)
canvas.setFillColor(colors.black)
canvas.setFont("Helvetica", 14)
canvas.setFillColor(colors.white)
canvas.drawString(headboxx + 15,headboxy+.25*headboxh,"Mathematics")
textWidth = stringWidth("Mathematics", "Helvetica", 12)
canvas.setFont("Helvetica", 12)
canvas.drawString(headboxw - 15 - textWidth,headboxy+.25*headboxh,course)
canvas.restoreState()
This works great, except that the course variable that's passed (which changes with each page in the section) is the last one in the sequence, since this function's not really called until the final build (I think that's how it works). What I need is to do this so that the value is the value that's on the page. If I could draw it as I write the page itself, that'd be fine, too. Here's my attempt at that:
####################################################################################
# Function makeGradeOverview(course): makes Overview chart for grade
#
def makeGradeOverview(canvas, course):
report.append(NextPageTemplate("Overview"))
report.append(PageBreak())
headboxh = 50
headboxx = 20
headboxy = 600#730
headboxw = 540
canvas.saveState()
canvas.setFont("Helvetica", 12)
textWidth = stringWidth(course, "Helvetica", 12)
canvas.drawString(headboxw - 15 - textWidth,headboxy+.25*headboxh,course)
canvas.restoreState()
# put course name as title
if len(course)<=2:
headerrow = ''.join(['Grade ', course, ' Overview'])
else:
headerrow = ''.join([course, ' Overview'])
report.append(Paragraph(headerrow, styles["Overview Title"]))
report.append(Spacer(1, 16))
GridInfo = []
topics = topiclist(course)
for topic in topics:
report.append(Paragraph(topic, styles["Overview Sub"]))
report.append(Spacer(1, 8))
subtopics = subtopiclist(course, topic)
sublist = []
for subtopic in subtopics:
report.append(Paragraph(''.join([r'<bullet>&bull</bullet>',subtopic]), styles["Overview Table"]))
This doesn't throw an error or anything, but it doesn't seem to actually draw anything, either.
Thanks for the help!
Here's another idea...
Perhaps it would work to use specific flowables that can be identified to update the course. You can add custom attributes to flowables if necessary to help identify them (see this post).
For example, you might be able to do something like this:
...
report.append(some_content)
report.append(PageBreak())
report[-1].new_course = True # gives that PageBreak flowable a custom attribute
report.append(some_more_content)
...
And set up some variables:
course_list = [...]
course_iter = iter(course_list)
current_course = next(course_iter)
Then you can check each flowable after it is rendered to see if it has that attribute and update the current course if it does.
def afterFlowable(flowable):
global current_course
if hasattr(flowable, 'new_course'):
current_course = next(course_iter)
doc.afterFlowable = afterFlowable
HeaderOverview will be able to use the current_course variable to get the right course, since both HeaderOverview and afterFlowable are called at various points during the final build.