2021年4月1日星期四

Take user input from Kivy App and append to google sheets using Python

I'm trying to make a simple Python Kivy app (using a kv file for the layout) that I will use to keep inventory of my parts for work. I am very new to python and kivy, so I need a little bit of direction on this.

One of the basic functions will be to allow the user to input a new part on the "Add Part to Inventory" screen (one of four screens). The user input consists of four items (part name, serial number, on-hand quantity, and the minimum amount needed).

Once the user enters this info and presses the 'Submit' button, I would like that data to be appended to a new row in a google sheet that I'm using as a backend database using the Google Drive/Sheets API (which I was able to get working).

Most of the example apps that I've found online only have one screen, and are only dealing with one type of user input (usually text, whereas my input will be text and integers).

I'm confused as to where I need to put my ids in the kv file (I'm assuming under the AddPartWindow layout), how to call a function when a button is pressed that is not in the root class, how to store the user input into a list since I'm not sure what the ObjectProperty function is really doing, and finally, how to append that list to a new row in my google sheet.

I'm not looking for a cut and paste answer, just some direction would be nice as I have not been able to find a good reference for exactly what I'm trying to do. My main confusion lies in having multiple screens, and how to transfer data between the different screens and their respective classes. Should data only be funneled through a single root class? Or can each class be used to handle data coming from their respective screens (windows) in the app? What exactly is the ObjectProperty function doing? I find the kivy website to be a bit convoluted when describing the properties class.

Any help/direction woud be greatly appreciated.

Here is the main.py code:

import kivy  from kivy.app import App  from kivy.uix.gridlayout import GridLayout  from kivy.uix.screenmanager import ScreenManager, Screen  from kivy.core.window import Window  from kivy.uix.button import Button  from kivy.properties import ObjectProperty  import gspread  from oauth2client.service_account import ServiceAccountCredentials    scope = ['https://www.googleapis.com/auth/drive']  creds = ServiceAccountCredentials.from_json_keyfile_name('My_First_Project-3d753d98320e.json', scope)  client = gspread.authorize(creds)  iQue_sheet = client.open("InventoryBackend").sheet1    #root layout  class InventoryWindow(Screen):      pass    #Layout in question  class AddPartWindow(Screen):        #Is there anything else I need to do with these before saving into a list or dictionary?            part_name = ObjectProperty(None)      serial_number = ObjectProperty(None)      on_hand_cnt = ObjectProperty(None)      min_needed = ObjectProperty(None)        #This function should save the user input into a list, np, and then append to the google sheet iQue_sheet      #Wasn't sure if it should be a list or a dictionary.      #I'm assuming .text is type-casting each object to a string.  Can this be used for numerical inputs?            def new_part(self):          np = [self.part_name.text, self.serial_number.text, self.on_hand_cnt.text, self.min_needed.text]          iQue_sheet.append(np)    class OnOrderWindow(Screen):      pass    class OrderFormWindow(Screen):      pass    class WindowManager(ScreenManager):      pass    class InventoryApp(App):      def build(self):          #These are used to enable going back and forth between screens using buttons          sm = ScreenManager()          sm.add_widget(InventoryWindow(name='inv_window'))          sm.add_widget(OnOrderWindow(name='on_order_window'))          sm.add_widget(AddPartWindow(name='add_part_window'))          sm.add_widget(OrderFormWindow(name='order_form_window'))          return sm    if __name__ == "__main__":      InventoryApp().run()    

Here is the my .kv file for the layout, the Add Part window is the last layout towards the end:

WindowManager:      InventoryWindow:      OnOrderWindow:      OrderFormWindow:      AddPartWindow:    <ItemLabel@Label>      font_size: '15sp'      halign: 'left'      valign: 'middle'      text_size: self.size    <ItemButton@Button>      pos_hint: {'center_x':0.5, 'center_y':0.5}      size_hint: 0.65, 0.2      <ItemButton2@Button>      pos_hint: {'center_x':0.5, 'center_y':0.5}      size_hint: 0.65, 0.2      on_release:          app.root.current = "order_form_window"      <InventoryWindow>:      name: "inv_window"          add_probe: add_probe        FloatLayout:          Label:              text: 'Inventory'              font_size: '25sp'              size_hint: (1, 0.17)              pos_hint: {'x': 0, 'y': 0.87}        GridLayout:          cols: 4            padding:[10, 65, 10, 10]          spacing: 5            BoxLayout:              orientation: 'vertical'              ItemLabel:                  text:'iQue3: Probe/Tubing'            BoxLayout:              orientation: 'vertical'                ItemButton:                  text:'Add'                  on_release:                      root.new_part()            BoxLayout:              orientation: 'vertical'                ItemButton:                  text:'Sub'            BoxLayout:              orientation: 'vertical'              ItemButton2:                  text:'Order'            BoxLayout:              orientation: 'vertical'              ItemLabel:                  text:'Gen2: Probe/Tubing'            BoxLayout:              orientation: 'vertical'              ItemButton:                  text:'Add'            BoxLayout:              orientation: 'vertical'              ItemButton:                  text:'Sub'            BoxLayout:              orientation: 'vertical'              ItemButton2:                  text:'Order'            BoxLayout:              orientation: 'vertical'              ItemLabel:                  text:'Beads'            BoxLayout:              orientation: 'vertical'              ItemButton:                  text:'Add'            BoxLayout:              orientation: 'vertical'              ItemButton:                  text:'Sub'            BoxLayout:              orientation: 'vertical'              ItemButton2:                  text:'Order'            BoxLayout:              orientation: 'vertical'              ItemLabel:                  text:'iQue3 Fluid Maint. Kit'            BoxLayout:              orientation: 'vertical'              ItemButton:                  text:'Add'            BoxLayout:              orientation: 'vertical'              ItemButton:                  text:'Sub'            BoxLayout:              orientation: 'vertical'              ItemButton2:                  text:'Order'            BoxLayout:              orientation: 'vertical'              ItemLabel:                  text:'Screener Fluid Maint. Kit'            BoxLayout:              orientation: 'vertical'              ItemButton:                  text:'Add'            BoxLayout:              orientation: 'vertical'              ItemButton:                  text:'Sub'            BoxLayout:              orientation: 'vertical'              ItemButton2:                  text:'Order'            BoxLayout:              orientation: 'vertical'          GridLayout:          cols: 2          size_hint: 1, 0.15          padding: [10, 0, 10, 10]              BoxLayout:                Button:                  text: 'Add Part to Inventory'                  font_size: '18sp'                  size_hint: (1, 0.75)                  on_release:                      app.root.current = "add_part_window"                      root.manager.transition.direction = "right"                Button:                  text: 'On Order'                  font_size: '18sp'                  size_hint: (1, 0.75)                  on_release:                      app.root.current = "on_order_window"                      root.manager.transition.direction = "left"      <OnOrderWindow>:        name: "on_order_window"        FloatLayout:          Label:              text: 'On Order'              font_size: '25sp'              size_hint: (1, 0.17)              pos_hint: {'x': 0, 'y': 0.87}            GridLayout:              cols: 2              size_int: 1, .15              padding:[10, 510, 10, 10]                Button:                  text: "Inventory"                  font_size: '18sp'                  size_hint: (1, 0.6)                  on_release:                      app.root.current = "inv_window"                      root.manager.transition.direction = "right"              Button:                  text:"Add Part to Inventory"                  size_hint: (1, 0.6)                  font_size: '18sp'                  on_release:                      app.root.current = "add_part_window"                      root.manager.transition.direction = "left"      <OrderFormWindow>      name: "order_form_window"        FloatLayout:          Label:              text: 'Order Form'              font_size: '25sp'              size_hint: (1, 0.17)              pos_hint: {'x': 0, 'y': 0.87}            GridLayout:              cols:2              padding: [50, 50, 50, 120]                ItemLabel:                  text: "Part: "                ItemLabel:                  text: "Populate Part Here"                ItemLabel:                  text: "Serial Number: "                ItemLabel:                  text: "Populate SN Here"                ItemLabel:                  text: "On Hand: "                ItemLabel:                  text: "Populate On Hand Count Here"                ItemLabel:                  text: "Minimum Needed: "                ItemLabel:                  text: "Populate Min Needed Here"                ItemLabel:                  text: "Order Quantity"                TextInput:                  halign: 'left'                  valign: 'middle'                  input_type: 'number'                  input_filter: 'int'                  multiline:False            GridLayout:              cols:2              size_hint: 1, 0.15              padding: [10, 0, 10, 10]                Button:                  text:"Cancel"                  on_release: app.root.current = "inv_window"                Button:                  text:"Submit Order"                  on_release: app.root.current = "on_order_window"      #This is the add part screen layout I'm referring to  <AddPartWindow>      name: "add_part_window"            #These are the id's I was referring to:                    part_name: part_name          serial_number: serial_number          on_hand_cnt: on_hand_cnt          min_needed: min_needed        FloatLayout:          Label:              text: 'Add Part to Inventory'              font_size: '25sp'              size_hint: (1, 0.17)              pos_hint: {'x': 0, 'y': 0.86}            GridLayout:              cols:2              padding: [50, 100, 50, 120]              spacing: 5                ItemLabel:                  text: "Name of Part: "                TextInput:                  id: part_name                  halign: 'left'                  valign: 'middle'                  multinline:False                ItemLabel:                  text: "Serial Number: "                TextInput:                  id: serial_number                  halign: 'left'                  valign: 'middle'                  multiline:False                ItemLabel:                  text: "How Many On Hand?: "                TextInput:                  id: on_hand_cnt                  halign: 'left'                  valign: 'middle'                  multinline:False                ItemLabel:                  text: "Minimum Needed?: "                TextInput:                  id: min_needed                  halign: 'left'                  valign: 'middle'                  multiline:False            GridLayout:              cols:2              size_hint: 1, 0.15              padding: [10, 0, 10, 10]                Button:                  text:"Cancel"                  on_release:                      app.root.current = "inv_window"                      root.manager.transition.direction = "left"                #Here is the button I'm referring to, I realize that putting "root." in front of new_part() is not               #the correct thing to put, so it's a placeholder for now:                            Button:                  text:"Submit"                  on_release:                      root.new_part()                      app.root.current = "inv_window"                      root.manager.transition.direction = "left"    

Here is a screen shot of the Add Part Screen

The current error I'm getting when I enter input into the four fields and click on the Submit button is this:

File "C:\Users\edr27\kivy_venv\lib\site-packages\kivy\uix\behaviors\button.py", line 179, in on_touch_up       self.dispatch('on_release')     File "kivy\_event.pyx", line 705, in kivy._event.EventDispatcher.dispatch     File "kivy\_event.pyx", line 1248, in kivy._event.EventObservers.dispatch     File "kivy\_event.pyx", line 1132, in kivy._event.EventObservers._dispatch     File "C:\Users\edr27\kivy_venv\lib\site-packages\kivy\lang\builder.py", line 57, in custom_callback       exec(__kvlang__.co_value, idmap)     File "C:\Users\edr27\PycharmProjects\pythonProject\inventory.kv", line 345, in <module>       root.new_part()     File "C:\Users\edr27\PycharmProjects\pythonProject\main.py", line 35, in new_part       np = [self.part_name.text, self.serial_number.text, self.on_hand_cnt.text, self.min_needed.text]   AttributeError: 'NoneType' object has no attribute 'text'  

Edit:

Here is how I implemented Oussama's answer in order to get this to work, all I had to do was update the AddPartWindow class:

class AddPartWindow(Screen):      def __init__(self, **kwargs):          super().__init__(**kwargs)          self.np = []        def submit(self):          self.part_name = self.ids.part_name.text          self.serial_number = self.ids.serial_number.text          self.on_hand_cnt = self.ids.on_hand_cnt.text          self.min_needed = self.ids.min_needed.text            self.np = [self.part_name, self.serial_number, self.on_hand_cnt, self.min_needed]          iQue_sheet.append_row(self.np)  

I also made this change in order to append to sheet 3 of the workbook instead of sheet 1:

iQue_sheet = client.open("InventoryBackend").get_worksheet(2)    
https://stackoverflow.com/questions/66897185/take-user-input-from-kivy-app-and-append-to-google-sheets-using-python April 01, 2021 at 09:06AM

没有评论:

发表评论