Home > front end >  Function to generate random string with nested function is giving me trouble
Function to generate random string with nested function is giving me trouble

Time:11-12

I'm gonna preface this by saying that im quite new to this, dont judge haha. The script im writing is to make a random password from different strings and randomize them. That I have already succeeded in completing, but know that I want to build a GUI for that script, the implementation with functions and tkinter is giving me trouble. I have two functions: One (add2all)that is supposed to check wether a variable is true, and if it is true add it to the string "all", that im later going to randomize with my second function. Im sure there are problems with how I get the value from the Checkbuttons (used to get the user input what kind of characters they want in the password), and then how they are added to the string. My second function (pwdgen) is supposed to get the input length from an Entry box were the user types an int, and then calls the add2all function to see what characters the user wants. add2all should generate a string with all the charracters that have been defined as true, so that pwdgen can randomize them with the given length. I also have a Button that is supposed to start the process. It calls on pwdgen, which then ccalls add2all and at the end, the variable password should have a randomized string which I can display to an entry box.

TL;DR: The function and tkinter implementation of a very simple script I previously wrote isn't working at all.

import random
from tkinter import *
from tkinter import ttk

root = Tk()

upper, lower, nums, syms = False, False, False, False
uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
lowercase = "abcdefghijklmnopqrstuvwxyz"
digits = "1234567890"
symbols = "!§$%&?* #~-;()/"

all = StringVar()

CheckBool1 = IntVar()
CheckBool2 = IntVar()
CheckBool3 = IntVar()
CheckBool4 = IntVar()
pwlength = IntVar()

#function that checks if a CheckButton input (which characters are wanted in password) 
#is true, if true adds the corresponding character string to the all string
def add2all():
    if CheckBool1:
        upper = True
    if CheckBool2:
        lower = True
    if CheckBool3:
        nums = True
    if CheckBool4:
        syms = True
    if upper:
        all  = uppercase
    if lower:
        all  = lowercase
    if nums:
        all  = digits
    if syms: 
        all  = symbols
#function that gets password length from user, then calls add2all, then gives var password
#the string of all randomized with specified length and then puts it into entry box pwdoupt
def pwdgen():
    pwdoutpt.delete(0, END)
    v = amt.get()
    v = pwlength
    add2all()
    password = "".join(random.choices(all, k=pwlength))
    pwdoutpt.insert(0, password)

greet = Label(root, text="Welcome to the interactive password generator!", height=3)

pwdoutpt = Entry(root, width=30)
generate = Button(root, text="Generate", command = lambda: pwdgen())

#inits all the labels and checkbuttons for my gui
chck_1 = Checkbutton(root, width = 5, var = CheckBool1)
chck_2 = Checkbutton(root, width = 5, var = CheckBool2)
chck_3 = Checkbutton(root, width = 5, var = CheckBool3)
chck_4 = Checkbutton(root, width = 5, var = CheckBool4)
lbl_1 = Label(root, text="Capital Letters")
lbl_2 = Label(root, text="Lowercase Letters")
lbl_3 = Label(root, text="Digits")
lbl_4 = Label(root, text="Symbols")
amt = Entry(root, width = 5)
amtlbl = Label(root, text="Num of characters in pwd.")
sep = ttk.Separator(root, orient="horizontal")

greet.grid(column=0, row=0, columnspan=4, pady=3)
pwdoutpt.grid(column=0, row=7, columnspan=4, padx=30, sticky=W)
amt.grid(column=0, row=1, sticky=W)
amtlbl.grid(column=1, row=1, columnspan=1, sticky=E)

chck_1.grid(column=0, row=2, sticky=E)
chck_2.grid(column=0, row=3, sticky=E)
chck_3.grid(column=0, row=4, sticky=E)
chck_4.grid(column=0, row=5, sticky=E)

lbl_1.grid(column=1, row=2, columnspan=1, sticky=W)
lbl_2.grid(column=1, row=3, columnspan=1, sticky=W)
lbl_3.grid(column=1, row=4, columnspan=1, sticky=W)
lbl_4.grid(column=1, row=5, columnspan=1, sticky=W)
sep.grid(column=0, columnspan=10, row=6, sticky=EW)
generate.grid(column=2, row=7)


root.mainloop()

CodePudding user response:

Firstly, don't use all as the variable name for your StringVar; it's a built-in python function. Don't use wildcard imports (from <module> import *), it's a bad habit and doesn't conform to PEP8. Instead, use import tkinter as tk.

You need to read how to use a StringVar; it is NOT a string and you can't add to it like one. A good application of using a StringVar in this code would be to display the generated password. If you bind the StringVar to your pwdoutpt Entry, updating the StringVar will automatically display the updated string.

You say you already had your password generator working correctly. Why not slightly adapt your password generator function so it takes the length and character types as inputs, and returns a randomly generated password? You are currently trying to integrate the password generating function into the GUI code, which is a bad idea is it can get messy super fast.

Taking all of the above into consideration, here is a working modified version of your code:

import random
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox

# Your password generating function
def pwdgen(password_length, chars_allowed):
    chars = [
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
        "abcdefghijklmnopqrstuvwxyz",
        "1234567890",
        "!§$%&?* #~-;()/"
    ]

    valid_chars = [x for i, x in enumerate(chars) if chars_allowed[i] == True]
    valid_chars = "".join(valid_chars)
    password = "".join(random.choices(valid_chars, k=password_length))
    return password

# Set up tkinter window
root = tk.Tk()
root.title("Password generator")

# Vars for checkbuttons and password entry
CheckBool1 = tk.IntVar()
CheckBool2 = tk.IntVar()
CheckBool3 = tk.IntVar()
CheckBool4 = tk.IntVar()
password = tk.StringVar()

# Main widgets
greet = tk.Label(root, text="Welcome to the interactive password generator!", height=3)
chck_1 = tk.Checkbutton(root, width = 5, var = CheckBool1)
chck_2 = tk.Checkbutton(root, width = 5, var = CheckBool2)
chck_3 = tk.Checkbutton(root, width = 5, var = CheckBool3)
chck_4 = tk.Checkbutton(root, width = 5, var = CheckBool4)
lbl_1 = tk.Label(root, text="Capital Letters")
lbl_2 = tk.Label(root, text="Lowercase Letters")
lbl_3 = tk.Label(root, text="Digits")
lbl_4 = tk.Label(root, text="Symbols")
amt = tk.Entry(root, width = 5)
amtlbl = tk.Label(root, text="Num of characters in pwd.")
sep = ttk.Separator(root, orient="horizontal")

# Password output
pwdoutpt = tk.Entry(root, width=30, textvariable=password, state="readonly")

# Generate password function and button
def insert_password():
    try:
        password_length = int(amt.get())
        chars_allowed = [CheckBool1.get(), CheckBool2.get(), CheckBool3.get(), CheckBool4.get()]
        password.set(pwdgen(password_length, chars_allowed))        
    except ValueError:
        messagebox.showerror("Error", "Invalid password length")
    except IndexError:
        messagebox.showerror("Error", "No character type selected")      
generate = tk.Button(root, text="Generate", command = insert_password)

# Gridding widgets
greet.grid(column=0, row=0, columnspan=4, pady=3)
pwdoutpt.grid(column=0, row=7, columnspan=4, padx=30, sticky=tk.W)
amt.grid(column=0, row=1, sticky=tk.W)
amtlbl.grid(column=1, row=1, columnspan=1, sticky=tk.E)

chck_1.grid(column=0, row=2, sticky=tk.E)
chck_2.grid(column=0, row=3, sticky=tk.E)
chck_3.grid(column=0, row=4, sticky=tk.E)
chck_4.grid(column=0, row=5, sticky=tk.E)

lbl_1.grid(column=1, row=2, columnspan=1, sticky=tk.W)
lbl_2.grid(column=1, row=3, columnspan=1, sticky=tk.W)
lbl_3.grid(column=1, row=4, columnspan=1, sticky=tk.W)
lbl_4.grid(column=1, row=5, columnspan=1, sticky=tk.W)
sep.grid(column=0, columnspan=10, row=6, sticky=tk.EW)
generate.grid(column=2, row=7)

root.mainloop()

Some further notes on the code for you:

  • The function insert_password uses a try except to test if the inputs are correct before generating the password.
  • pwdoutpt has state="readonly" so that the user can't modify the generated password but can still copy it
  • Related