Here is all of the code
#! /usr/bin/env python3.8
'''Docstring'''
###### Do the imports ######
import tkinter as tk
from tkinter import ttk, messagebox
import string
from functools import partial
import webbrowser
from modules import database as db, time_converter as tc
import settings
### Check if Recipe.db exists ###
db.Database().check_for_database()
def opensite():
'''Docstring'''
webbrowser.open_new('http://recipes.phpshelf.net')
###### Intiate the window ######
class RootWindow:
'''Docstring'''
def __init__(self, master):
self.master = master
self.master.columnconfigure(0, weight=1)
self.master.rowconfigure(0, weight=1)
###### Intiate all of our main containerframes ######
self.logo_frame()
self.letter_frame()
self.container_frame()
self.title_frame()
self.recipe_frame()
self.footer_frame()
###### Initate the widgets ######
Child(self.logoframe, self.footerframe)
Child2().lettermenu(self.letterframe, self.titleframe, self.recipeframe)
Child3().titlemenu(self.titleframe, self.recipeframe)
Child4().recipe(self.recipeframe)
###### This sets all the container frames ######
###### Need to create a class for generic ######
###### frames to eliminate repetitive code #####
def logo_frame(self):
'''logo frame'''
self.logoframe = ttk.Frame(self.master, border=5, relief='ridge')
self.logoframe.grid(column=0, row=0, sticky='new')
self.logoframe.grid_columnconfigure(0, weight=3)
def letter_frame(self):
'''letterframe'''
self.letterframe = ttk.Frame(self.master, border=5, relief='ridge')
self.letterframe.grid(column=0, row=1, sticky='new')
for i in range(26):
self.letterframe.grid_columnconfigure(i, weight=3)
def container_frame(self):
'''Container frame will hold two frames. Title frame and recipe frame'''
self.containerframe = ttk.Frame(self.master)
self.containerframe.grid(column=0, row=2, sticky='nw')
self.containerframe.grid_columnconfigure(0, weight=3)
def title_frame(self):
'''Title Frame'''
style = ttk.Style()
style.configure('Title.TLabel', background='gray86')
self.titleframe = ttk.Frame(self.containerframe, style='Title.TLabel', \
border=5, relief='ridge')
self.titleframe.grid(column=0, row=0, sticky='nw')
self.titleframe.grid_columnconfigure(0, weight=3)
def recipe_frame(self):
'''Recipe Frame'''
self.recipeframe = ttk.Frame(self.containerframe, border=5, relief='ridge')
self.recipeframe.grid(column=1, row=0, sticky='new')
self.recipeframe.grid_columnconfigure(0, weight=3)
def footer_frame(self):
'''footer'''
self.footerframe = ttk.Frame(self.master, border=5, relief='ridge')
self.footerframe.grid(column=0, row=3, sticky='new')
self.footerframe.grid_columnconfigure(0, weight=3)
###### This class defines and displays header and footer ######
class Child:
'''logo'''
def __init__(self, logoframe, footerframe):
self.my_logo(logoframe)
self.my_footer(footerframe)
def my_logo(self, logoframe):
'''doc'''
imgfile = tk.PhotoImage(file=settings.IMG)
self.logo = ttk.Label(logoframe, image=imgfile)
self.logo.image = imgfile
self.logo.grid(column=0, row=0)
def my_footer(self, footerframe):
'''footer'''
style = ttk.Style()
style.map('Footer.TLabel', \
foreground=[('pressed', 'firebrick'), ('active', 'red')], \
background=[('pressed', '!disabled', 'gray86'), ('active', 'gray86')] \
)
style.configure('Footer.TLabel', foreground='blue', \
font=('Times', 12, 'normal', 'underline'))
footer = ttk.Button(footerframe, text='Register for Johnny\'s CookBook', \
style='Footer.TLabel', cursor='hand2', command=partial(opensite))
footer.grid(column=0, row=0, ipady=3, padx=10, sticky='nw')
form = ttk.Button(footerframe, text='Add Recipe', style='Footer.TLabel', cursor='hand2', \
command=partial(AddRecipe().my_form))
form.grid(column=1, row=0, padx=10)
###### Class produces the letter menu ######
class Child2:
'''doc'''
def __init__(self):
pass
def lettermenu(self, letterframe, titleframe, recipeframe):
'''lettermenu'''
letters = string.ascii_uppercase
i = 0
style = ttk.Style()
style.configure('Btn.TButton', width=3)
for letter in letters:
button = ttk.Button(letterframe, text=letter, style='Btn.TButton', \
command=partial(Child3().titlemenu, titleframe, recipeframe, letter=letter))
button.grid(column=i, row=0, sticky='new')
i += 1
###### This class retrieves and displays recipe titles ######
class Child3:
'''doc'''
def __init__(self):
pass
def titlemenu(self, titleframe, recipeframe, letter='a'):
'''title menu'''
###### Query the database for recipe titles from our letter menu defaults to a ######
data = db.Database().title_query(letter=letter)
###### Create the canvas and scrollbar ######
canvas = tk.Canvas(titleframe, highlightcolor='gray87')
scrollbar = ttk.Scrollbar(titleframe, orient='vertical', command=canvas.yview)
scrollable_frame = ttk.Frame(canvas)
scrollable_frame.bind(
'<Configure>',
lambda e: canvas.configure(scrollregion=canvas.bbox('all'))
)
canvas.create_window((0, 0), window=scrollable_frame, anchor='nw')
canvas.configure(yscrollcommand=scrollbar.set)
###### If there are not any results returned, display message ######
if not data:
msg = messagebox.showerror(title='No listing', \
message='Sorry, there have not been any recipes added starting with ' + letter)
if msg == 'ok':
return True
else:
###### Setup our look for the recipe menu ######
style = ttk.Style()
style.map('L.TLabel', \
background=[('pressed', '!disabled', 'gray87'), ('active', '#ffffee')], \
foreground=[('pressed', 'red'), ('active', 'red')])
style.configure('L.TLabel', relief='flat', padding=2, foreground='blue')
###### Loop through and display the return results from the database ######
for recipe_id, title in data:
title = ttk.Button(scrollable_frame, text=title.title(), \
style='L.TLabel', cursor='hand2', \
command=partial(Child4().recipe, recipeframe, recipe_id=recipe_id))
title.grid(column=0, row=recipe_id, sticky='nw')
scrollbar.grid(column=0, row=0, sticky='nsw')
canvas.grid(column=1, row=0, sticky='nsw', padx=8)
class Child4:
'''Docstring'''
def __init__(self):
pass
def recipe(self, recipeframe, recipe_id=1):
'''Docstring'''
data = db.Database().id_query(recipe_id)
canvas = tk.Canvas(recipeframe, width=600)
canvas.configure(border=5, highlightcolor='gray87')
scrollbar = ttk.Scrollbar(recipeframe, orient='vertical', command=canvas.yview)
scrollable_frame = ttk.Frame(canvas)
scrollable_frame.bind(
'<Configure>',
lambda e: canvas.configure(scrollregion=canvas.bbox('all'))
)
canvas.create_window((0, 0), window=scrollable_frame, anchor='nw')
canvas.configure(yscrollcommand=scrollbar.set)
style = ttk.Style()
### Title
style.configure('Title.TLabel', \
font=('Sans', 12, 'bold', 'underline'), \
foreground='blue', padding=5, relief='raised')
title = ttk.Label(scrollable_frame, text=f'{data[1]}', style='Title.TLabel')
title.grid(columnspan=2, column=0, row=0, sticky='new')
style2 = ttk.Style()
style2.configure('Time.TLabel', \
foreground='blue', relief='raised', padding=5)
times = ttk.Label(scrollable_frame, \
text=f' Prep Time: {tc.time_converter(data[4])} | \
Cook Time: {tc.time_converter(data[5])} \
| Total Time: {tc.time_converter((data[4]+data[5]))}', style='Time.TLabel')
times.grid(columnspan=2, column=0, row=1, sticky='new')
### Mini Headers for ingredients and instructions
style3 = ttk.Style()
style3.configure('MiniHeader.TLabel', \
font=('Sans', 10, 'bold', 'underline', \
'italic'), foreground='blue', relief='raised', padding=5)
mini_header1 = ttk.Label(scrollable_frame, \
text=' Ingredients', style='MiniHeader.TLabel')
mini_header1.grid(column=0, row=2, sticky='new')
mini_header2 = ttk.Label(scrollable_frame, \
text=' Instructions', style='MiniHeader.TLabel')
mini_header2.grid(column=1, row=2, sticky='new')
style4 = ttk.Style()
style4.configure('Recipe.TLabel', \
padding=8, border=8, relief='sunken', wraplength=291)
ingredients = ttk.Label(scrollable_frame, \
text=data[2], style='Recipe.TLabel')
ingredients.grid(column=0, row=3, sticky='nw')
instructions = ttk.Label(scrollable_frame, \
text=data[3], style='Recipe.TLabel')
instructions.grid(column=1, row=3, sticky='nw')
scrollbar.grid(column=0, row=0, sticky='nsw')
canvas.grid(column=1, row=0, sticky='nsw')
class AddRecipe:
'''doc'''
def __init__(self):
pass
def my_form(self):
'''docstring'''
self.top = tk.Toplevel()
self.top.configure(border=8, relief='ridge')
self.top.resizable(width=False, height=False)
self.top.title('Add a Recipe')
frame = tk.Frame(self.top)
frame.grid(column=0, row=0, padx=25, pady=25, sticky='new')
header_style = ttk.Style()
header_style.configure('Header.TLabel', font=('Sans', 18, 'bold'), padding=0)
header = ttk.Label(frame, text='Add a Recipe', style='Header.TLabel')
header.grid(columnspan=2, column=0, row=0)
title_frame = ttk.Frame(frame, border=5, relief='ridge', padding=8)
title_frame.grid(columnspan=2, column=0, row=1, ipadx=2)
title = ttk.Label(title_frame, text='Title:')
title.grid(column=0, row=0, sticky='nw')
title_entry = tk.Entry(title_frame, width=100)
title_entry.grid(column=0, row=1)
ingredients_frame = ttk.Frame(frame, border=5, relief='ridge', padding=8)
ingredients_frame.grid(column=0, row=2, sticky='nw')
ingredients = ttk.Label(ingredients_frame, text='Ingredients:')
ingredients.grid(columnspan=2, column=0, row=0, sticky='nw')
ingredients_entry = tk.Text(ingredients_frame, width=46, height=20)
ingr_scrollbar = tk.Scrollbar(ingredients_frame)
ingr_scrollbar.configure(command=ingredients_entry.yview)
ingredients_entry.configure(yscrollcommand=ttk.Scrollbar.set)
ingr_scrollbar.grid(column=0, row=1, sticky='ns')
ingredients_entry.grid(column=1, row=1, sticky='nw')
instruction_frame = ttk.Frame(frame, border=5, relief='ridge', padding=8)
instruction_frame.grid(column=1, row=2, sticky='nw')
instructions = ttk.Label(instruction_frame, text='Instructions:')
instructions.grid(columnspan=2, column=0, row=0, sticky='nw')
instructions_entry = tk.Text(instruction_frame, width=47, height=20)
instr_scrollbar = tk.Scrollbar(instruction_frame)
instr_scrollbar.configure(command=instructions_entry.yview)
instructions_entry.configure(yscrollcommand=ttk.Scrollbar.set)
instr_scrollbar.grid(column=0, row=1, sticky='ns')
instructions_entry.grid(column=1, row=1, sticky='nw')
# ### Bottom frame ###
btm_frame_style = ttk.Style()
btm_frame_style.configure('BFrame.TFrame')
bottom_frame = ttk.Frame(frame, border=5, relief='ridge', padding=8, \
style='BFrame.TFrame')
bottom_frame.grid(columnspan=2, column=0, row=3, sticky='new')
prep = tk.Label(bottom_frame, text='Prep. Time in min.')
prep.grid(column=0, row=0, sticky='w', padx=10)
prep_time = tk.Spinbox(bottom_frame, from_=0, to=320, width=5)
prep_time.grid(column=1, row=0, sticky='w')
cook = ttk.Label(bottom_frame, text='Cook Time in min.')
cook.grid(column=2, row=0, padx=10)
cook_time = tk.Spinbox(bottom_frame, from_=0, to=320, width=5)
cook_time.grid(column=3, row=0)
spacer = ttk.Frame(bottom_frame, width=300)
spacer.grid(column=4, row=1)
btn_style = ttk.Style()
btn_style.configure('Submit.TButton')
submit_btn = ttk.Button(bottom_frame, text='Submit Recipe', style='Submit.TButton', \
command=partial(self.submit_values, title_entry, ingredients_entry, \
instructions_entry, prep_time, cook_time))
submit_btn.grid(column=5, row=0)
def submit_values(self, title, ingredients, instructions, prep_time, cook_time):
'''Docstring'''
if not title.get() or not ingredients.get('1.0', 'end-1c') or not \
instructions.get('1.0', 'end-1c'):
messagebox.showerror(title='Input Error', \
message='Required fields: Title, Ingredients, and Instructions')
self.top.destroy()
self.my_form()
print(title.get())
print(ingredients.get('1.0', 'end-1c'))
print(instructions.get('1.0', 'end-1c'))
print(prep_time.get())
print(cook_time.get())
msg = messagebox.askyesno(title='Add Recipe', message='Add another recipe?')
if msg is True:
self.top.destroy()
self.my_form()
if msg is False:
self.top.destroy()
def main():
'''Docstring'''
root = tk.Tk()
root.title('Johnny\'s CookBook')
img = tk.PhotoImage(file='/home/johnny/Desktop/CookBook/images/cookbook_logo.png')
root.configure(width=img.width())
root.resizable(width=False, height=False)
RootWindow(root)
root.mainloop()
if __name__ == '__main__':
main()
settings.py
'''Settings.py'''
IMG = 'images/cookbook_logo.png'
The imports from module folder
database.py
#! /usr/bin/env python3.8
'''Database connection, queries and timeconverter'''
import sqlite3
from sqlite3 import Error
class Database:
'''Docstring'''
def __init__(self):
pass
def check_for_database(self):
'''Docstring'''
try:
with open('Recipes.db'):
print('Files exists')
except FileNotFoundError:
try:
print('Creating database and tables')
conn = sqlite3.connect('Recipes.db')
cur = conn.cursor()
cur.execute('create table recipes( \
id integer primary key autoincrement, \
title varchar(100) not null, \
ingredients text not null, \
instructions text not null, \
prep integer not null default 0, \
cook integer not null default 0)')
conn.execute('insert into recipes (title, ingredients, instructions) values \
("a test recipe","test ingredient\nAnother ingredient", \
"test instructions instructions")')
conn.commit()
conn.close()
except Error:
print(Error)
except Error:
print(Error)
def title_query(self, letter):
'''Docstring'''
try:
conn = sqlite3.connect('Recipes.db')
cursor = conn.cursor()
cursor.execute(f'select id, title from recipes where title like "{letter}%"')
results = cursor.fetchall()
conn.commit()
conn.close()
return results
except Error:
print(f'Error: {Error}')
def id_query(self, recipe_id):
'''Dcostring'''
try:
conn = sqlite3.connect('Recipes.db')
cursor = conn.cursor()
cursor.execute(f'select * from recipes where id = {recipe_id}')
result = cursor.fetchone()
conn.commit()
conn.close()
return result
except Error:
print(f'Error: {Error}')
def enter_recipe(self, title, ingredients, instructions, prep, cook):
'''Docstring'''
try:
conn = sqlite3.connect('/home/johnny/Desktop/CookBook/Recipes.db')
cursor = conn.cursor()
cursor.execute(f'insert into recipes (title, ingredients, \
instructions, prep, cook) values ("{title}", \
"{ingredients}", "{instructions}", "{prep}", "{cook}")')
conn.commit()
conn.close()
except (Error, FileNotFoundError) as error:
print(error)
time_converter.py
#! /usr/bin/env python3.8
def time_converter(arg):
'''Convert minutes to hours if above 30'''
hours, minutes = divmod(arg, 60)
if arg > 60:
if hours > 1:
var = f'{hours} hrs. {minutes} min.'
else:
var = f'{hours} hr. {minutes} min.'
elif arg == 60:
var = f'{hours} hr.'
else:
var = f'{minutes} min.'
return var