Make a ReportLab PDF interactive - Unable to reference widgets on PDF - python

I am currently building a ReportLab PDF Form which I want to make interactive. I want that the options shown on the second choice widget, depend on the selection of the first choice widget. (For example: if the selection of the first choice is 'Italy', show as food options on the second widget 'Pizza' and 'Spaghetti').
Currently I am trying to control such condition with an if, however I have been unable to reference the first widget correctly. (Since I called it name='choice1', I figured this would be the way of calling such widget). I get an error:
if choice1 == 'Italy':
NameError: name 'choice1' is not defined
Is there a correct way to do the desired action? I am looking at the Reportlab documentation, however I have not found an answer.
My current Python code is the following:
from reportlab.pdfgen.canvas import Canvas
from datetime import datetime, timedelta
from reportlab.platypus import Table, TableStyle
from reportlab.lib import colors
from reportlab.pdfbase import pdfform
import win32com.client as win32
import pathlib
file_name = 'Example.pdf'
document_title = 'Example'
title = 'Example File'
instrucciones_1 = 'INSTRUCCIONES: Llenar toda la solicitud con la información que se pide.'
instrucciones_2 = 'Al finalizar, este PDF se enviará automáticamente al encargado de su área para aprobación.'
instrucciones_3 = 'La aprobación queda sujeta al criterio de dicho encargado. '
canvas = Canvas(file_name)
canvas.setTitle(document_title)
canvas.setFont("Helvetica-Bold", 24)
canvas.drawCentredString(385, 795, title)
now = datetime.now()
full_date = f'{now.day}/{now.month}/{now.year}'
canvas.setFont('Helvetica', 16)
canvas.drawCentredString(350, 790-20, 'Fecha solicitud: ')
canvas.setFont('Helvetica-Bold', 16)
canvas.drawCentredString(450, 790-20, full_date)
canvas.line(35, 755, 565, 755)
canvas.setFont("Helvetica", 12)
canvas.drawString(40, 740, instrucciones_1)
canvas.drawString(40, 740-15, instrucciones_2)
canvas.drawString(40, 740-30, instrucciones_3)
canvas.line(35, 740 - 35, 565, 740 - 35)
form = canvas.acroForm
canvas.setFont('Helvetica', 14)
canvas.drawString(70, 675, 'Origin of Food: ')
options = ['Select origin', 'Italy', 'Mexico', 'US']
form.choice(name='choice1', tooltip='Select origin of food',
value='Select origin',
x=165, y=670, width=200, height=25,
borderColor=colors.blue, fillColor=colors.lightblue,
textColor=colors.black, forceBorder=True, options=options)
origin = ''
food_italy = ['Select food', 'Spaghetti', 'Pizza']
food_mexico = ['Select food', 'Chiles en Nogada', 'Tacos]
food_us = ['Select food', 'Burgers', 'Buffalo Wings']
if choice1.value == 'Italy':
sucursal = food_italy
elif choice1.value == 'Mexico':
sucursal = food_mexico
elif choice1.value == 'US':
sucursal = food_us
canvas.drawString(70, 645, 'Food: ')
form.choice(name='choice2', tooltip='Select food',
value='Select food',
x=165, y=670, width=200, height=25,
borderColor=colors.blue, fillColor=colors.lightblue,
textColor=colors.black, forceBorder=True, options=sucursal)
canvas.save()
Any suggestions?
Thanks a lot in advance!
EDIT
If PDF and Reportlab is not the way, is there some way I can do an executable file/form which can perform such tasks? Thanks a lot.

This cannot be done in the way you approach this, i.e. in the Python code. The reason for this is that the selection of e.g. 'Italy' happens only when the user opens the PDF file and selects it. So you would need to create a (deprecated) XFA form for this.

Related

How to restrict entry to only available options in AutocompleteCombobox while being able to type in it?

I am using AutocompleteCombobox from ttkwidgets.autocomplete for my selection widget. While all the features are good (like being able to filter the list while typing), I want to be able to type in it but only from the available options ie., I should not be able to type in custom values.
I tried using state=readonly but it doesn't allow me to type in the combobox.
Any solutions would be greatly appreciated.
Since You didn't provide example code and tkinter does not provide default autocomplete combobx, I assumed You are using AutocompleteCombobox from ttkwidgets.autocomplete.
To get only valid entries (no custom one), You have to re-implement autocomplete method of AutocompleteCombobox class.
Logic is simple: check if current user input is in autocomplete list. If not, remove last character and show again last autocomplete suggestion.
I used example code from this source as a base for my answer.
Here is code snippet implementing custom MatchOnlyAutocompleteCombobox:
from tkinter import *
from ttkwidgets.autocomplete import AutocompleteCombobox
countries = [
'Antigua and Barbuda', 'Bahamas', 'Barbados', 'Belize', 'Canada',
'Costa Rica ', 'Cuba', 'Dominica', 'Dominican Republic', 'El Salvador ',
'Grenada', 'Guatemala ', 'Haiti', 'Honduras ', 'Jamaica', 'Mexico',
'Nicaragua', 'Saint Kitts and Nevis', 'Panama ', 'Saint Lucia',
'Saint Vincent and the Grenadines', 'Trinidad and Tobago', 'United States of America'
]
ws = Tk()
ws.title('PythonGuides')
ws.geometry('400x300')
ws.config(bg='#8DBF5A')
class MatchOnlyAutocompleteCombobox(AutocompleteCombobox):
def autocomplete(self, delta=0):
"""
Autocomplete the Combobox.
:param delta: 0, 1 or -1: how to cycle through possible hits
:type delta: int
"""
if delta: # need to delete selection otherwise we would fix the current position
self.delete(self.position, END)
else: # set position to end so selection starts where textentry ended
self.position = len(self.get())
# collect hits
_hits = []
for element in self._completion_list:
if element.lower().startswith(self.get().lower()): # Match case insensitively
_hits.append(element)
if not _hits:
# No hits with current user text input
self.position -= 1 # delete one character
self.delete(self.position, END)
# Display again last matched autocomplete
self.autocomplete(delta)
return
# if we have a new hit list, keep this in mind
if _hits != self._hits:
self._hit_index = 0
self._hits = _hits
# only allow cycling if we are in a known hit list
if _hits == self._hits and self._hits:
self._hit_index = (self._hit_index + delta) % len(self._hits)
# now finally perform the auto completion
if self._hits:
self.delete(0, END)
self.insert(0, self._hits[self._hit_index])
self.select_range(self.position, END)
frame = Frame(ws, bg='#8DBF5A')
frame.pack(expand=True)
Label(
frame,
bg='#8DBF5A',
font=('Times', 21),
text='Countries in North America '
).pack()
entry = MatchOnlyAutocompleteCombobox(
frame,
width=30,
font=('Times', 18),
completevalues=countries
)
entry.pack()
ws.mainloop()

Python Loop not finishing

I made a script for organizing torrents. I download tv shows and then move files to another disk to a folder like /series name/season xx/
It was working with some issues until I've added a couple of ifs. I saw the problem of case sensitivity. For example, If i download mr robot.mkv and the folder Mr Robot already existed it created a different folder as mr robot. I did the same with season not to double move Season if season already exists.
Apparently my script is trying to create the folder continuosly and I don't see why.
#!/usr/bin/env python3
import sys, glob, re, os, shutil
from termcolor import colored
#enconding: utf-8
dir_series = "/home/user/series/series/"
buscar = "*[sS][0-9][0-9]*"
series = [s for s in glob.glob(buscar) if s.endswith(('.mp4', '.srt', '.avi', '.mkv'))]
if series:
arch_encontrados = len(series)
print(colored("\nArchivos encontrados:",'red', attrs=['bold'] ), colored(arch_encontrados, 'red', attrs=['bold'] ),'\n')
print(*series, sep = "\n")
for serie in series:
#Extraer el nombre de la serie
nombre = re.findall(r'.*[\. ][sS]\d', serie)[0]
nombre_final = re.sub(r'[\. ][sS]\d','',nombre).replace('.',' ')
#Extraer el número de la temporada
season = re.findall(r'[\. ][sS]\d\d', serie)[0]
season_final_numero = re.sub(r'[\. ][sS]','',season)
season_final = ('Season ' + season_final_numero)
#Armar el directorio final
for series_path in os.listdir(dir_series): #lista el contenido de /home/user/series/series/
if nombre_final.lower() == series_path.lower(): #compara el listado con la salida del nombre de la serie sin importar mayúsculas y minúsculas
for season_path in os.listdir(dir_series + series_path):
if season_final == season_path: #compara el listado de seasons contra season_final que tiene mayuscula
path = os.path.join(dir_series, series_path, season_final)
print(path)
else:
path = os.path.join(dir_series, series_path, 'season ', season_final_numero)
else:
print(colored("\n\n*****************************************",'cyan', attrs=['bold']))
print(colored("** Directorio no encontrado, creándolo **",'cyan', attrs=['bold']))
print(colored("*****************************************\n",'cyan', attrs=['bold']))
path = os.path.join(dir_series, nombre_final, season_final)
print(path)
os.makedirs(path)
#Mover el archivo
print(colored('\nCopiando','green'), serie, colored('a', 'green'), path + '/' + serie)
shutil.move(serie,path)
else:
print(colored('\nNo hay archivos para organizar.','green', attrs=['bold']))
input(colored("\n\nPresione Enter para continuar ...", attrs=['blink', 'bold']))
I'm not seeing an infinite loop, but I think I do see a bug that is causing the same directory to get made many times.
You are calling os.makedirs in the else of your inner for loop, which means that you will make the same directory once for each file in os.listdir(dir_series) that does NOT match nombre_final.lower().
I think the issue might just be that you (or your IDE) accidentally indented the os.makedirs(path) call two levels too deep when you added the if/else. I think it needs to be outside of the inner loop entirely.
You probably also need to add a break in the case where it does match, and maybe also a guard to stop it from making a new directory in the case where a match was found?

Cant get my functions (Munt2 and Munt1) to subtract my global variable (aantal_munten)

I have a problem with my code (trying to make the NIM game with a Tkinter GUI). Anyhow i cant get my functions (Munt1, Munt2) to subtract to my global (aantal_munten). And i dont know how to fix it. Can someone point me in the right direction? Also the TKinter gui is still a bit new for me so if you have any tips or tricks for me they are well appreciated!
import tkinter
import random
def toonStartscherm():
keuzescherm.pack_forget()
spelerscherm.pack_forget()
startscherm.pack()
def toonKeuzescherm():
keuzescherm.pack()
startscherm.pack_forget()
spelerscherm.pack_forget()
def spelOptie1():
keuzescherm.pack_forget()
startscherm.pack_forget()
spelerscherm.pack()
def randomSpeler():
beginnende_speler = random.choice(['1', '2'])
speler = beginnende_speler
if speler == '1':
speler = '2'
else:
speler = '1'
return str(speler)
def Munt1():
eenMunt = 1
aantal_munten -= eenMunt
def Munt2():
tweeMunt = 2
aantal_munten -= tweeMunt
nim_spel = tkinter.Tk()
global aantal_munten
aantal_munten = 7
startscherm = tkinter.Frame(master=nim_spel)
startscherm.pack()
start_welkom = tkinter.Label(startscherm, text= 'Welkom bij het NIM spel! \nHieronder volgende de spelregels.')
start_welkom.pack()
start_uitleg = tkinter.Label(startscherm, text= 'Het spel NIM start met 7 munten, elke speler mag één of twee munten pakken. \n De speler die de laatste munt pakt verliest!')
start_uitleg.pack() # zet uitleg automatisch in venster
doorgaan_knop = tkinter.Button(startscherm, text = 'Ik snap de regels!', fg = 'green', command = toonKeuzescherm) # maakt knop en laat stoppen
doorgaan_knop.pack(side = 'bottom')
keuzescherm = tkinter.Frame(master=nim_spel)
keuzescherm.pack()
keuze_opties = tkinter.Label(keuzescherm, text='Het NIM spel kan op twee manieren gespeeld worden. \n Optie 1: Tegen elkaar \n Optie 2: Tegen de computer')
keuze_opties.pack() # zet opties automatisch in venster
keuze_vraag = tkinter.Label(keuzescherm, text='Voor welke optie kies je?')
keuze_vraag.pack()
optie_1 = tkinter.Button(keuzescherm, text = 'Optie 1', fg = 'green', command = spelOptie1) # maakt knop en laat stoppen
optie_1.pack(side = 'left')
optie_2 = tkinter.Button(keuzescherm, text = 'Optie 2', fg = 'red', command = keuzescherm.quit) # maakt knop en laat stoppen
optie_2.pack(side = 'right')
spelerscherm = tkinter.Frame(master=nim_spel)
spelerscherm.pack()
beurt_speler = tkinter.Label(spelerscherm, text='Speler ' + (randomSpeler()) + ' is aan de beurt!')
beurt_speler.pack()
munten_over = tkinter.Label(spelerscherm, text='Er zijn nog ' + (str(aantal_munten)) + ' aantal munten over, hoeveel pak je er?')
munten_over.pack()
pak_1_munt = tkinter.Button(spelerscherm, text = '1 munt', fg = 'blue', command = Munt1)
pak_1_munt.pack(side = 'left')
pak_2_munt = tkinter.Button(spelerscherm, text = '2 munten', fg = 'blue', command = Munt2)
pak_2_munt.pack(side = 'right')
toonStartscherm()
nim_spel.mainloop()
def Munt1():
global aantal_munten
eenMunt = 1
aantal_munten -= eenMunt
def Munt2():
global aantal_munten
tweeMunt = 2
aantal_munten -= tweeMunt
I just added the global aantal_munten line and checked it, and it's working perfectly now.

Python, tkinter listbox

I have a problem with the listbox because it does not display names like in the first listbox under 1T, only on one line, I want to display the names one under the other. I have no idea how to do this.
Thank you for every advice and attention to the code below
import random
import tkinter, sys
from tkinter import *
import tkinter.messagebox as messagebox
los = []
list = ['1. Kamil Winnicki', '#2. Wiktor Jasiński', '3. Adam Turowski', '#4. Arek Major', '5. Dominik Piechotka', '#6. Jakub Laskowski', '7. Jakub Materak', '8. Kacper Kołodziejski',
'#9. Kamil Stankiewicz', '10. Konrad Nosek', '11. Krzysiek Wawszczak', '12. Andrzej Oleksiak', '13. Miłosz Tarucin', '14. Paweł Pawłowski', '#15. Mateusz Lebioda']
lines = list
for line in lines:
if line [0] != '#':
los.append(line)
main = tkinter.Tk()
def koniec():
main.destroy()
def losowanie():
messagebox.showinfo(message=random.sample(los ,1))
#nagłowek
te = tkinter.Label(main, text = 'Lista 1T:')
te.pack()
#Wyswietla liste 1T
listbox = Listbox(main, width=21, height=15)
listbox.insert(1, '1. Mateusz Lebioda', '2. Jakub Laskowski', '3. Kamil Winnicki', '4. Wiktor Jasiński', '5. Adam Turowski', '6. Arek Major', '7. Dominik Piechotka', '8. Jakub Materak', '9. Kacper Kołodziejski', '10. Kamil Stankiewicz', '11. Konrad Nosek', '12. Krzysiek Wawszczak', '13. Andrzej Oleksiak', '14. Miłosz Tarucin', '15. Paweł Pawłowski')
listbox.pack()
#Obecne osoby
obecne1 = tkinter.Label(main, text = 'Obecne osoby:')
obecne1.pack()
obecne = Listbox(main)
obecne.insert(1, los)
obecne.pack()
#losuje
y = tkinter.Button(main, text = 'losuj', command = losowanie)
y.pack()
#wyjscie z aplikacji
x = tkinter.Button(main, text = 'Zakoncz', command = koniec)
x.pack()
main.mainloop()
You need to unpack your list when inserting.
Changing insertion line would be enough.
obecne.insert("end", *los)
#^ notice this star here. That one makes the unpacking
or you can just iterate over your list items using for loop.
obecne = Listbox(main)
for item in los:
obecne.insert("end", item)
obecne.pack()

How to display a value in the window using tkinter?

What can I do to get the result in the interface instead of the terminal in the given code? I want the random.choice result to appear in the table.
import random
import tkinter, sys
from tkinter import *
lista = ['Kamil Winnicki', 'Wiktor Jasiński', 'Adam Turowski', 'Arek Major',
'Dominik Piechotka', 'Jakub Laskowski', 'Jakub Materak', 'Kacper Kołodziejski',
'Kamil Stankiewicz', 'Konrad Nosek', 'Krzysiek Wawszczak', 'Andrzej Oplebsiak',
'Miłosz Tarucin', 'Paweł Pawłowski', 'Mateusz Lebioda']
def koniec():
sys.exit()
def losowanie():
print(random.choice(lista))
main = tkinter.Tk()
#nagłowek
te = tkinter.Label(main, text = 'Lista 1T:')
te.pack()
#Wyswietla liste 1T
listbox = Listbox(main)
listbox.insert(1, '1. Mateusz Lebioda', '2. Jakub Laskowski', '3. Kamil Winnicki',
'4. Wiktor Jasiński', '5. Adam Turowski', '6. Arek Major',
'7. Dominik Piechotka', '8. Jakub Materak', '9. Kacper Kołodziejski',
'10. Kamil Stankiewicz', '11. Konrad Nosek', '12. Krzysiek Wawszczak',
'13. Andrzej Oplebsiak', '14. Miłosz Tarucin', '15. Paweł Pawłowski')
listbox.pack()
#losuje
y = tkinter.Button(main, text = 'losuj', command = losowanie)
y.pack()
#wyjscie z aplikacji
x = tkinter.Button(main, text = 'Zakoncz', command = koniec)
x.pack()
main.mainloop()
One way to do it would be to show a dialog with the choice.
import tkinter.messagebox as messagebox
def losowanie():
messagebox.showinfo(message=random.choice(lista))
If I understand the question correctly you need to append the result of random.choice(lista) to the listbox element, this can be achieved by the following code:
def losowanie():
listbox.insert(END, str(listbox.size() + 1) + ". " + random.choice(lista))
Like Dan-Dev suggested you could do that but you can also get rid of the function
This will do what you intend to, but without the function losowaine:
y = tkinter.Button(main, text = 'losuj', command = lambda :
listbox.insert(END, str(listbox.size() + 1) + ". " + random.choice(lista)))
This below code will just insert the random choice at the end of the table (with function losowanie)
def losowanie():
listbox.insert(END, (random.choice(lista)))

Categories