0

I am creating an app to help me learn python. This app consists of a tkinter gui that displays news data that is scraped from multiple sites which are selected in the gui. The user can also enter date, two text fields, and two drop downs to be passed to the backed.

My problem is that the updated values entered into the gui, e.g. description text are not registering to the actual variables (variable.get()) i.e. the variables in the dictionary on the 4th last line are NULL.

If I hard code values in the dictionary, the dictionary passes without an issue.

I know my code is not the best but I am learning. Any help whatsoever is greatly appreciated because I have been stuck on this for a day and cannot find a solution anywhere online.

The issue is in the add button at the very bottom of my code.



from tkinter import *
from tkcalendar import *
import pandas as pd
from functools import partial
import datetime
from tkinter import ttk


class FrontEnd(object):

    def __init__(self):

        self.window = Tk()
        self.window.geometry("1000x600")
        self.enter_data()
        self.buttons()

        self.window.mainloop()

    def enter_data(self):
        """Users will enter data here"""
        #Category
        self.cat_text = ttk.Label(self.window, width = 10, text = "Category")
        self.cat_text.grid(row=5,column=0,rowspan=1,columnspan=1,sticky=W)

        #list for category drop down
        self.OptionList = [
        "News"
        , "Alert"
        , "Recall"
        , "Discussion"
        ]
        self.variable = StringVar(self.window)
        self.variable.set(self.OptionList[0]) # default value

        self.w = ttk.OptionMenu(self.window, self.variable, *self.OptionList).grid(row=6,column=0,rowspan=1,sticky=W)

        #Date
        self.date_text = ttk.Label(self.window, width = 8, text = "Date")
        self.date_text.grid(row=5,column=1,rowspan=1,columnspan=1,sticky=W)

        #Enter date
        self.date_picker = DateEntry(self.window)
        self.date_picker.grid(row=6,column=1,rowspan=1,sticky=W)

        #Sources
        self.source_list = ["EC RASFF","IFSQN","FDA","FSAI","NZ FSA","UK FSA","USDA","USDA FSIS","IFS","FSSC","SF360","BRC","FSANZ","SQF","EFSA","UN FAO","CFIA","FDA FSMA","UN FAO","EC RAPID","WHO"]
        self.source_text = ttk.Label(self.window, width = 8, text = "Source")
        self.source_text.grid(row=5,column=2,rowspan=1,columnspan=1,sticky=W)
        self.source_variable = StringVar(self.window)

        self.source_variable.set(self.source_list[0]) # default value

        #Description
        self.desc_text = ttk.Label(self.window, width = 12, text = "Description:")
        self.desc_text.grid(row=8,column=0,rowspan=1,columnspan=1,sticky=W)

        self.desc_tb = Text(self.window,height = 1, width = 80)
        self.desc_tb.grid(row=8,column=1,rowspan=1,columnspan=6,sticky=W)

        #URL
        self.url_text = ttk.Label(self.window, width = 12, text = "URL:")
        self.url_text.grid(row=9,column=0,rowspan=1,columnspan=1,sticky=W)

        self.url_tb = Text(self.window,height = 1, width = 80)
        self.url_tb.grid(row=9,column=1,rowspan=1,columnspan=6,sticky=W)

        self.s = ttk.OptionMenu(self.window, self.source_variable, *self.source_list).grid(row=6,column=2,rowspan=1,sticky=W)

        self.outp = Listbox(self.window)
        self.outp.grid(row=12,column=0,rowspan=1,columnspan=30,sticky=N+E+W+S)

    def buttons(self):

        #Send the entered data from enter_data to the backend
        self.addtodf = ttk.Button(self.window, width = 12, text = "Add"
            , command = partial(self.output_window,
                ({"category": self.variable.get(), "description": self.desc_tb.get('1.0', END), "link": self.desc_tb.get('1.0', END), "date" : self.date_picker.get_date(), "site_type": self.source_variable.get()})
                )).grid(row=3,column=5,rowspan=1,columnspan=1,sticky=W)


    def output_window(self,dict):
        self.outp.insert(END, dict)
        self.outp.insert(END, "\n")

if __name__ == "__main__":
    app = FrontEnd()



Full project is here: https://github.com/seangibs/FoodNewsWebScraper

Tobias Funke
  • 1,614
  • 3
  • 13
  • 23
  • 1
    Too much code, We couldn't run your code.There are many things you didn't provide in your post(Like what does `ButtonFunc` do?).Try to show us a minimal example. – jizhihaoSAMA May 24 '20 at 14:25
  • Hey, updated there to only include applicable code. ButtonFunc at a very high level just creates and returns a dataframe. The problem is that the variables added to the dictionary at the very end are NULL – Tobias Funke May 24 '20 at 14:33
  • @jizhihaoSAMA also added the github – Tobias Funke May 24 '20 at 14:37
  • Are all the values in the dictionary `None`? – jizhihaoSAMA May 24 '20 at 14:59
  • 1
    Does this answer your question? [AttributeError: NoneType object has no attribute](https://stackoverflow.com/a/1101765/7414759) – stovfl May 24 '20 at 15:09
  • 1
    after downloading your code from the github, I was wondering if the code works in you? because when launching app.py, the first error is the name 'FrontEnd' is not defined, which is a consequence of circular import, this can be understood by replacing the line in buttonfunctions.py "from frontend import *" with "from frontend import FrontEnd" (tip : do not use *, it clutters the namespace and makes the code less clear) – 0dminnimda May 24 '20 at 15:21
  • @jizhihaoSAMA yeah all variables are None – Tobias Funke May 24 '20 at 16:04
  • @Александр I was running this application from frontend.py while debugging so accidentally introduce that issue. Thanks for the tip! – Tobias Funke May 24 '20 at 16:05
  • I generally recommend you read [pep8](https://www.python.org/dev/peps/pep-0008/), there are also tools like autopep8, and an online check like http://pep8online.com – 0dminnimda May 24 '20 at 16:32
  • because your code is difficult to read, but if you post the code, please be kind to leave comments in it, because it is difficult to understand it – 0dminnimda May 24 '20 at 16:42

2 Answers2

3

A careless problem.

In your __init__ function:

self.buttons()

When you create the app instance,it will execute the function button(the output_window will be evaluate).At this time,All the variable are the values you run this code firstly.(The argument won't be change even if you select or type in other words.)

My suggestions:

  1. Don't use function button(),just put it in the function enter_data().
  2. dict is a struct python has defined.Use other variable name.
  3. Use .get("0.0","end-1c") could avoid \n.

Followed by your minimal example,all the code could be:

from tkinter import *
from tkcalendar import *
# import pandas as pd
from functools import partial
import datetime
from tkinter import ttk


class FrontEnd(object):

    def __init__(self):

        self.window = Tk()
        self.window.geometry("1000x600")
        self.enter_data()

        self.window.mainloop()

    def enter_data(self):
        """Users will enter data here"""
        #Category
        self.cat_text = ttk.Label(self.window, width = 10, text = "Category")
        self.cat_text.grid(row=5,column=0,rowspan=1,columnspan=1,sticky=W)

        #list for category drop down
        self.OptionList = [
        "News"
        , "Alert"
        , "Recall"
        , "Discussion"
        ]
        self.variable = StringVar(self.window)
        self.variable.set(self.OptionList[0]) # default value

        self.w = ttk.OptionMenu(self.window, self.variable, *self.OptionList).grid(row=6,column=0,rowspan=1,sticky=W)

        #Date
        self.date_text = ttk.Label(self.window, width = 8, text = "Date")
        self.date_text.grid(row=5,column=1,rowspan=1,columnspan=1,sticky=W)

        #Enter date
        self.date_picker = DateEntry(self.window)
        self.date_picker.grid(row=6,column=1,rowspan=1,sticky=W)

        #Sources
        self.source_list = ["EC RASFF","IFSQN","FDA","FSAI","NZ FSA","UK FSA","USDA","USDA FSIS","IFS","FSSC","SF360","BRC","FSANZ","SQF","EFSA","UN FAO","CFIA","FDA FSMA","UN FAO","EC RAPID","WHO"]
        self.source_text = ttk.Label(self.window, width = 8, text = "Source")
        self.source_text.grid(row=5,column=2,rowspan=1,columnspan=1,sticky=W)
        self.source_variable = StringVar(self.window)

        self.source_variable.set(self.source_list[0]) # default value

        #Description
        self.desc_text = ttk.Label(self.window, width = 12, text = "Description:")
        self.desc_text.grid(row=8,column=0,rowspan=1,columnspan=1,sticky=W)

        self.desc_tb = Text(self.window,height = 1, width = 80)
        self.desc_tb.grid(row=8,column=1,rowspan=1,columnspan=6,sticky=W)

        #URL
        self.url_text = ttk.Label(self.window, width = 12, text = "URL:")
        self.url_text.grid(row=9,column=0,rowspan=1,columnspan=1,sticky=W)

        self.url_tb = Text(self.window,height = 1, width = 80)
        self.url_tb.grid(row=9,column=1,rowspan=1,columnspan=6,sticky=W)

        self.s = ttk.OptionMenu(self.window, self.source_variable, *self.source_list).grid(row=6,column=2,rowspan=1,sticky=W)

        self.outp = Listbox(self.window)
        self.outp.grid(row=12,column=0,rowspan=1,columnspan=30,sticky=N+E+W+S)

        self.addtodf = ttk.Button(self.window, width=12, text="Add", command=self.output_window).grid(row=3, column=5, rowspan=1, columnspan=1, sticky=W)

    def output_window(self):
        self.outp.insert(END, {"category": self.variable.get(),
                               "description": self.desc_tb.get('0.0', "end-1c"),
                               "link": self.desc_tb.get('0.0', "end-1c"),
                               "date": self.date_picker.get_date(),
                               "site_type": self.source_variable.get()}) # don't get it by passing argument.Just get it in the function.
        self.outp.insert(END, "\n")

if __name__ == "__main__":
    app = FrontEnd()

jizhihaoSAMA
  • 12,336
  • 9
  • 27
  • 49
  • 1
    This worked, thanks! I am still taking online courses so am pretty new to this and have never come across this issue before. Once again, thanks! – Tobias Funke May 24 '20 at 17:02
2

OK, so I see multiple problems there:

  • The function self.last_date appears out of nowhere. What does this function do?
  • self.buttons gets called but not self.enter_data so the OptionMenus and stuff does not exist so variables can't be changed. Furthermore don't even exist, which should lead to errors.
  • self.source_variable gets created but isn't assigned to any widget so can't by changed in the GUI itself.

All in all I'm wondering how this code could even run. It's very confusing. I guess you either didn't gave us all the needed context (probably on the background of @jizhihaoSAMA telling you not to post the full code which is correct) or you messed up something there.

Please try to make an simple, cut down to the problem, program that reproduces your problem, so we can help you.

PS: Lines like self.s = ttk.OptionMenu(self.window, self.source_variable, *self.source_list).grid(row=6,column=2,rowspan=1,sticky=W) aren't useful for you since grid returns None not the widget itself. You'd either need to create it and grid it in two steps like you did above or you can just get rid of the self.s =-part since there is no use for that then.

martineau
  • 119,623
  • 25
  • 170
  • 301
Nummer_42O
  • 334
  • 2
  • 16
  • Hi, self.last_date is a function that I plan to use in the future so is NA for now. It is just a date parameter that I will use to restrict some data in the backend. self.enter_data is being called, I was reducing my code for another commenter to make it clearer and accidentally removed that line. I have fixed this now and also reduced the code to make the problem clearer. Let me know if it is not clear enough though! – Tobias Funke May 24 '20 at 16:19
  • 1
    After further inspection and reading jizihihaoSAMAs answer it struct me like lightning. The problem is the dictionary you give as argument to partial. With that you evaluate all the tkinter variables the moment you call `buttons` which leads to the cenario that outputwindow will allways get called with exactly that evaluation. To avoid that you can do it how jizhihaoSAMA recommendet or use `lambda` instead. Then the command option for the `Button` would be as follows: ```command=lambda: self.otput_window({...your evaluation (too long for this comment)...})``` – Nummer_42O May 24 '20 at 17:05
  • Ok, I understand. I am going to merge the two classes because there is no need to have both only for having less methods in both (formatting). Thanks again – Tobias Funke May 24 '20 at 18:12