I am trying to create a simple open-sourced project for tagging Japanese Audiobooks with metadata from Audible Japan. I have a working script that grabs the title, author, and narrators, and I am trying to populate Tkinter's tree with that data. The problem arose when I tried doing it the OOP methodology, and broke everything up into different classes and functions.
import tkinter as tk
from tkinter import *
from tkinter import ttk
import requests
from bs4 import BeautifulSoup
class ResultFrame(ttk.Frame):
def __init__(self, container):
super().__init__(container)
# field options
options = {'padx': 10, 'pady': 10}
# result label frame
self.result_label = ttk.LabelFrame(self, text='Results')
self.result_label.grid(row=1, column=0, sticky=tk.W, **options)
# result tree
self.tree = ttk.Treeview(self.result_label)
self.tree['columns'] = ("Title", "Author", "Narrator", "Published")
self.tree.column("#0", width=0, stretch=NO) # hide the first autogenerated column
self.tree.column("Title", anchor=W)
self.tree.column("Author", anchor=W)
self.tree.column("Narrator", anchor=W)
self.tree.column("Published", anchor=W)
# result tree headings
self.tree.heading('#0', text='') # hide the first autogenerated column
self.tree.heading('Title', text='Title', anchor=W)
self.tree.heading('Author', text='Author', anchor=W)
self.tree.heading('Narrator', text='Narrator', anchor=W)
self.tree.heading('Published', text='Published', anchor=W)
self.tree.pack()
self.grid(padx=10, pady=10, sticky=tk.NSEW)
def populate_tree(self, data):
count = 0
for book in data.items():
self.tree.insert(
parent='', index='end', iid=count, text='',
values=(
book[0], book[1]['authors'], book[1]['narrators'], 'not implemented'
)
)
count = 1
class SearchFrame(ttk.Frame):
def __init__(self, container):
super().__init__(container)
self.audiobooks = {}
# field options
options = {'padx': 5, 'pady': 5}
# search label
self.search_label = ttk.Label(self, text='Search')
self.search_label.grid(row=0, column=0, sticky=tk.W, **options)
# search entry
self.search = tk.StringVar()
self.search_entry = ttk.Entry(self, textvariable=self.search, width=50)
self.search_entry.grid(row=0, column=1, **options)
self.search_entry.focus()
self.search_button = ttk.Button(self, text="search")
self.search_button['command'] = self.search_audible
self.search_button.grid(row=0, column=2, sticky=tk.W, **options)
# # results label
# self.result_label = ttk.Label(self)
# self.result_label.grid(row=1, columnspan=3, **options)
# add padding to the frame and show it
self.grid(padx=10, pady=10, sticky=tk.NSEW)
def search_audible(self):
"""
Search audible for the book
:param self:
:return:
"""
temp_authors = []
temp_narrators = []
# build the url
base_url = "https://www.audible.co.jp/search?ipRedirectOverride=true&overrideBaseCountry=true&keywords="
query = self.search_entry.get()
# request the page
page = requests.get(base_url query)
soup = BeautifulSoup(page.content, "html.parser")
# grab only the content that we need
search_results = soup.find(id="center-3")
# scrape information
for product_item in search_results.find_all('li', class_="bc-list-item productListItem"):
title = product_item['aria-label']
# get all of the authors from anchor tags
for link in soup.find("li", class_="bc-list-item authorLabel").find_all("a"):
if link.text not in temp_authors:
temp_authors.append(link.text)
# join the results with comma separation to prepare for MP3/M4B file tags
authors = ", ".join(temp_authors)
for link in soup.find("li", class_="bc-list-item narratorLabel").find_all("a"):
if link.text not in temp_narrators:
temp_narrators.append(link.text)
narrators = ", ".join(temp_narrators)
self.audiobooks[title] = {
"authors": authors,
"narrators": narrators
}
ResultFrame.populate_tree(self, data=self.audiobooks)
class App(tk.Tk):
def __init__(self):
super().__init__()
self.title('Audiobook Tagger')
if __name__ == '__main__':
app = App()
SearchFrame(app)
ResultFrame(app)
app.mainloop()
From my understanding reading this answer: https://stackoverflow.com/a/41513358, I would essentially have to make ResultFrame inherit from SearchFrame, so it would be ResultFrame(SearchFrame) instead of ResultFrame(ttk.Frame). But the issue is that both of my classes are inheriting from ttk.Frame, so I am unsure how to proceed.
To summarize my question, in my SearchFrame class, the function called search_audible scraped information from Audible. I would like to send that scrapped information to ResultFrame's populate_tree() so that it can show the results conveniently.
If possible, I would prefer to have the two Frames into separated classes, because it helps organize and manage the code in my opinion.
CodePudding user response:
To summarize my question, in my SearchFrame class, the function called search_audible scraped information from Audible. I would like to send that scrapped information to ResultFrame's populate_tree() so that it can show the results conveniently.
In order to do that SearchFrame needs to be aware of ResultFrame. You can do that by:
class SearchFrame(ttk.Frame):
def __init__(self, container,reciever):
super().__init__(container)
self.reciever = reciever
Through that reference you can target this object by:
self.reciever.populate_tree(self, data=self.audiobooks)
since you need a reference to that object you need to swap the order of construction to have a reference you can pass:
res = ResultFrame(app)
ser = SearchFrame(app,res)
Hint
class App(tk.Tk):
def __init__(self):
super().__init__()
self.res = ResultFrame(self)
self.ser = SearchFrame(self,self.res)
self.title('Audiobook Tagger')
if __name__ == '__main__':
app = App()
app.mainloop()
