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()