I have a window that prints the news content in a textbox. You must first click on the "View Titles" button, then select the title, and finally click on the "View Content" button. It works correctly, without any problems.
In this question, I have placed the window code in a class of an external file (page1.py)
. The file will be opened in the main file (main.py)
. If I click on the "View Titles" button it works fine, but if I click on the "View Content" button I get an error in the function def content
:
item = df.iloc[selection[-1]]
AttributeError: 'NoneType' object has no attribute 'iloc'
I would like that when I click on the "View content" button, the content of the news is printed in the textobox. As I said above, the code when it was in a simple file with only one window ... it worked perfectly, while now if put in an external class I have problems.
I am new to Python. Can you show me the code in the answer? Otherwise I may not understand, thanks and sorry
Main.py
import tkinter as tk
from tkinter import ttk
from PIL import ImageTk
from page1 import Page1
root = tk.Tk()
root.geometry('480x320')
topbar = tk.Frame(root, bg='#e10a0a', height=43)
topbar.pack(fill='x')
style = ttk.Style()
style.theme_use('default') # select a theme that allows configuration of ttk.Notebook
# put the tabs at the left with white background
style.configure('TNotebook', tabposition='wn', background='white', tabmargins=0)
# configure tab with white background initially, yellow background when selected
style.configure('TNotebook.Tab', background='white', width=10, focuscolor='yellow', borderwidth=0)
style.map('TNotebook.Tab', background=[('selected', 'yellow')])
nb = ttk.Notebook(root)
nb.pack(fill='both', expand=1)
page1 = Page1(nb)
nb.add(page1, text='aaaaa', compound='left')
root.mainloop()
page1.py
import tkinter as tk
from tkinter import ttk
from tkinter import *
from tkinter import ttk
import tkinter as tk
import tkinter.font as tkFont
from tkinter import ttk
import tkinter as tk # PEP8: `import *` is not preferred
from tkinter import ttk
from tkinter.scrolledtext import ScrolledText
import requests
import requests_cache
from bs4 import BeautifulSoup
import pandas as pd
import re
import json
from dateutil import parser
import datetime
import locale
class Page1(tk.Frame):
def __init__(self, master, **kw):
super().__init__(master, **kw)
def get_data_for(place):
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36'
}
results = []
response = requests.get(f'https://www.tuttomercatoweb.com/{place}/', headers=headers)
print('url:', response.url)
print('status:', response.status_code)
#print('html:', response.text[:1000])
soup = BeautifulSoup(response.content, 'html.parser')
#Cover
cover_news = soup.find('div', {'class':'box pp'})
link = cover_news.find('a', href=True)['href']
coverNewsResponse = requests.get(link, headers=headers)
coverSoup = BeautifulSoup(coverNewsResponse.content, 'html.parser')
jsonStr = str(coverSoup.find('script'))
jsonStr = re.search('({.*})', jsonStr).group(1)
jsonData = json.loads(jsonStr)
timePublished = parser.parse(jsonData['datePublished']).strftime("%H:%M")
datePublished = parser.parse(jsonData['datePublished']).strftime("%Y-%m-%d")
title = jsonData['headline']
news = f" {timePublished} {place.upper()}, {title} (TMW)"
results.append( [datePublished, timePublished, place, title, news, link] )
# Side panel
side_news = soup.find_all('div', {'class':'box small'})
for each in side_news:
link = each.find('a', href=True)['href']
sideNewsResponse = requests.get(link, headers=headers)
sideSoup = BeautifulSoup(sideNewsResponse.content, 'html.parser')
jsonStr = str(sideSoup.find('script'))
jsonStr = re.search('({.*})', jsonStr).group(1)
jsonData = json.loads(jsonStr)
timePublished = parser.parse(jsonData['datePublished']).strftime("%H:%M")
datePublished = parser.parse(jsonData['datePublished']).strftime("%Y-%m-%d")
title = jsonData['headline']
news = f" {timePublished} {place.upper()}, {title} (TMW)"
results.append( [datePublished, timePublished, place, title, news, link] )
news = soup.find_all('div', attrs={"class": "tcc-list-news"})
for each in news:
for div in each.find_all("div"):
timePublished = div.find('span', attrs={'class': 'hh serif'}).text
datePublished = div.find_previous('div', {'class':'tcc-border upper date'})
if datePublished:
if datePublished.text in ['Ieri']:
yesterday = datetime.datetime.today() - datetime.timedelta(days = 1)
datePublished = yesterday.strftime("%Y-%m-%d")
else:
locale.setlocale(locale.LC_ALL, '') #locale.setlocale(locale.LC_ALL, 'it')
currentYear = datetime.datetime.today().strftime("%Y")
dateStr = datePublished.text
dateStr = datetime.datetime.strptime(dateStr ' ' currentYear, '%A %d %B %Y')
datePublished = dateStr.strftime("%Y-%m-%d")
else:
datePublished = datetime.datetime.today().strftime("%Y-%m-%d")
title = " ".join(span.text for span in div.select("a > span"))
news = f" {timePublished} {place.upper()}, {title} (TMW)"
link = div.find('a')['href']
results.append( [datePublished, timePublished, place, title, news, link] )
return results
def all_titles():
global df
allnews = [] # local variable
for place in ['atalanta', 'bologna']:
print('search:', place)
results = get_data_for(place)
print('found:', len(results))
allnews = results
text_download.insert('end', f"search: {place}\nfound: {len(results)}\n")
df = pd.DataFrame(allnews, columns=['date', 'time', 'place', 'title', 'news', 'link'])
df = df.sort_values(by=['date', 'time', 'place', 'title'], ascending=[False, False, True, True])
df = df.drop_duplicates(subset=['date', 'time', 'place', 'title'])
df = df.reset_index(drop=True)
listbox_title.delete('0', 'end')
for index, row in df.iterrows():
listbox_title.insert('end', row['news'])
def content(event=None): # `command=` executes without `event`, but `bind` executes with `event` - so it needs default value
# tuple
selection = listbox_title.curselection()
print('selection:', selection)
if selection:
item = df.iloc[selection[-1]]
#print('item:', item)
url = item['link']
#print('url:', url)
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36'
}
# keep page in database `SQLite`
# https://github.com/reclosedev/requests-cache
# https://sqlite.org/index.html
session = requests_cache.CachedSession('titles')
response = session.get(url, headers=headers)
#response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.content, 'html.parser')
content_download = "\n".join(item.get_text() for item in soup.select("div.text.mbottom"))
text_download.delete('1.0', 'end') # remove previous content)
text_download.insert('end', content_download)
# --- main ---
df = None
# window = tk.Tk()
# window.geometry("800x800")
frame_title = tk.Frame(self)
frame_title.pack(fill='both', expand=True, pady=5, padx=5)
listbox_title = tk.Listbox(frame_title, selectbackground="#960000", selectforeground="white", bg="white")
listbox_title.pack(side='left', fill='both', expand=True)
scrollbar_title = tk.Scrollbar(frame_title)
scrollbar_title.pack(side='left', fill='y')
scrollbar_title['command'] = listbox_title.yview
listbox_title.config(yscrollcommand=scrollbar_title.set)
listbox_title.bind('<Double-Button-1>', content) # it executes `content(event)`
# ----
text_download = ScrolledText(self, bg="white")
text_download.pack(fill='both', expand=True, pady=0, padx=5)
# ----
buttons_frame = tk.Frame(self)
buttons_frame.pack(fill='x')
button1 = tk.Button(buttons_frame, text="View Titles", command=all_titles) # don't use `[]` to execute functions
button1.pack(side='left', pady=5, padx=5)
button2 = tk.Button(buttons_frame, text="View Content", command=content) # don't use `[]` to execute functions
button2.pack(side='left', pady=5, padx=(0,5))
CodePudding user response:
The simple way to fix the issue is to change global df
to nonlocal df
inside all_titles()
function.
However, I would suggest to use instance variable instead by changing all df
to self.df
:
class Page1:
def __init__(self, master, **kw):
...
def all_titles():
...
# changed df to self.df
self.df = pd.DataFrame(allnews, columns=['date', 'time', 'place', 'title', 'news', 'link'])
self.df = self.df.sort_values(by=['date', 'time', 'place', 'title'], ascending=[False, False, True, True])
self.df = self.df.drop_duplicates(subset=['date', 'time', 'place', 'title'])
self.df = self.df.reset_index(drop=True)
listbox_title.delete('0', 'end')
for index, row in self.df.iterrows():
listbox_title.insert('end', row['news'])
def content(event=None):
...
if selection:
item = self.df.iloc[selection[-1]]
...
...
self.df = None
...
CodePudding user response:
I think you've declared df
to be a global variable in one of the functions nested under your __init__
method...then later on in your main section, which actually runs in the __init__
, you set df
to None
. You'll want to be much more careful about making variables global and think a bit more about function scope; do those functions you're defining need to be within the scope of __init__
, or can they have a different scope?