2

I created a GUI based off this question while trying to teach myself how to use jsonstore. I don't have the reputation points to add a comment so I'm asking my question here. I think I have the basic idea down but for some reason I can't save the data to a json file. I get the following error:

AttributeError: 'NoneType' object has no attribute 'text'

I've tried following the documentation but I can't see anywhere where it would explain what I'm doing wrong.

main.py

from kivy.storage.jsonstore import JsonStore
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
from kivy.properties import BooleanProperty, ListProperty, StringProperty, ObjectProperty


Window.clearcolor = (0.02745098, 0.074509804, 0.121568627, 1)
Window.size = (2000, 900)

class TitleScreen(Screen):
    pass

class MainScreen(Screen):
    pass

class CreateProfile(Screen):
    First = ObjectProperty()
    Middle = ObjectProperty()
    Last = ObjectProperty()

    def __init__(self, **kwargs):
        super(CreateProfile, self).__init__(**kwargs)
        self.store = JsonStore("bco.json")
        self.load()

    def save(self):
        self.store.put('profile', first = self.label.text)
        self.store.put('profile', middle = self.label.text)
        self.store.put('profile', last = self.label.text)

    def load(self):
        try:
            self.Last.text = self.store.get('profile')['score']
        except KeyError:
            pass


class CreatePacket(Screen):
    pass

class ScreenManagement(ScreenManager):
    pass

presentation = Builder.load_file("customwidget.kv")

class CustomWidgetApp(App):
    def build(self):
        return presentation

if __name__ == "__main__":
    CustomWidgetApp().run()

customwidget.kv

#: import FadeTransition kivy.uix.screenmanager.FadeTransition
#: import hex kivy.utils.get_color_from_hex
#: import FocusBehaviors kivy.uix.behaviors.focus

ScreenManagement:
    transition: FadeTransition()
    TitleScreen:
    MainScreen:
    CreateProfile:
    CreatePacket:

<MainLabel@Label>:
    font_size:50
    bold: True
    size_hint_x: 1
    size_hint_y: 1.85
    color: 0.396078431, 0.803921569, 0.807843137, 1
    font_name: '/home/jarren/PycharmProjects/BCO_Form_Filler/practice/pirulen rg.ttf'

<SubLabel@Label>:
    font_size: 35
    bold: True
    halign: "center"
    size_hint_x: 1
    size_hint_y: 1.5
    color: 0.396078431, 0.803921569, 0.807843137, 1
    font_name: '/home/jarren/PycharmProjects/BCO_Form_Filler/practice/pirulen rg.ttf'

<OtherLabel@Label>:
    font_size: 12
    bold: True
    color: 0.396078431, 0.803921569, 0.807843137, 1
    font_name: '/home/jarren/PycharmProjects/BCO_Form_Filler/practice/pirulen rg.ttf'
    text_size: self.size

<Button@Button>:
    font_size: 20
    bold: True
    color: 0.396078431, 0.803921569, 0.807843137, 1
    font_name: '/home/jarren/PycharmProjects/BCO_Form_Filler/practice/pirulen rg.ttf'
    background_color: 0.02745098, 0.074509804, 0.121568627, .01
    canvas.before:
        Color:
            rgba: 0.396078431, 0.803921569, 0.807843137, 1
        Line:
            width: 2
            rectangle: self.x, self.y, self.width, self.height
    on_press: self.background_color = (0.396078431, 0.803921569, 0.807843137, 1)
    on_release: self.background_color = (0.02745098, 0.074509804, 0.121568627, .01)

# TODO: Create a focus behavior to "Tab" between widgets
<TextInput@TextInput>:
    font_size: 12
    color: 0.396078431, 0.803921569, 0.807843137, 1
    font_name: '/home/jarren/PycharmProjects/BCO_Form_Filler/practice/pirulen rg.ttf'
    padding_x: 10
    padding_y: 10
    focus_next: None
    focus_previous: None
    unfocus_on_touch: True
    background_color: 0.02745098, 0.074509804, 0.121568627, .01
    canvas.before:
        Color:
            rgba: 0.396078431, 0.803921569, 0.807843137, 1
        Line:
            width: 1
            rectangle: self.x, self.y, self.width, self.height

<TitleScreen>:
    id: "title"
    FloatLayout:
        MainLabel:
            text: "Easy Button"
            size_hint_x: 1
            size_hint_y: 1.25

        SubLabel:
            text: 'Test'
            size_hint_x: 1
            size_hint_y: 1
        Button:
            text: "Click Here To Continue"
            on_release: app.root.current = "main"
            size_hint: (.75, .15)
            pos_hint: {'x': .12, 'y': .2}

<MainScreen>:
    name: "main"
    MainLabel:
        text: "Home Page"
    BoxLayout:
        Button:
            on_release: app.root.current = "create_profile"
            text: "Create Profile"
            size_hint: (.5, .15)
        Button:
            on_release: app.root.current = "create_packet"
            text: "Create Packet"
            size_hint: (.5, .15)

<CreateProfile>:
    name: "create_profile"

    AnchorLayout:
        anchor_x: 'center'
        anchor_y: 'top'
        MainLabel:
            text: "Create Profile"
            size_hint: (1, .15)

    BoxLayout:
        size_hint: (.95, .2)
        pos_hint: {'x': 0, 'y': .85}
        spacing: 10
        padding: 10
        halign: "left"

        OtherLabel:
            text: "First"

        OtherLabel:
            text: "Middle"

        OtherLabel:
            text: "Last"

    BoxLayout:
        size_hint: (.95, .07)
        pos_hint: {'x': 0, 'y': .8}
        spacing: 20
        padding: 10
        halign: "right"
        text_size: self.size

        TextInput:
            id: 'First'


        TextInput:
            id: "Middle"


        TextInput:
            id: 'Last'    

    BoxLayout:

        Button:
            on_release: app.root.current = "main"
            text: "back Home"
            size_hint: (.5, .15)
        Button:
            on_release: root.save()
            text: "Save Profile"
            size_hint: (.5, .15)

<CreatePacket>:
    name: "create_packet"
    MainLabel:
        text: "Select Packet"
    FloatLayout:
        Button:
            on_release: app.root.current = "main"
            text: "back Home"
            size_hint: (1, .15)
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Jarren Poulsen
  • 141
  • 1
  • 13

2 Answers2

1

Your code has several problems but the main one is that you do not understand how to expose any .kv widget to .py, one of the simplest ways is to use an ObjectProperty as you try to do but that property is not linked to the widget, I prefer to do the creation in the .kv for its simplicity.

On the other hand I recommend you avoid the abuse of try-except since it hides errors, the best thing is to verify.

Another error is that you are overwriting the value of profile in the .json, the idea is to save everything in one.

Considering the above, the solution is:

*.py

from kivy.app import App
from kivy.storage.jsonstore import JsonStore
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.core.window import Window
from kivy.clock import Clock


Window.clearcolor = (0.02745098, 0.074509804, 0.121568627, 1)
Window.size = (2000, 900)

class TitleScreen(Screen):
    pass

class MainScreen(Screen):
    pass

class CreateProfile(Screen):
    def __init__(self, **kwargs):
        super(CreateProfile, self).__init__(**kwargs)
        self.store = JsonStore("bco.json")
        Clock.schedule_once(lambda *args: self.load())

    def save(self):
        self.store.put('profile', 
            first = self.first.text,
            middle = self.middle.text,
            last = self.last.text)

    def load(self):
        if self.store.exists('profile'):
            profile = self.store.get('profile')
            v = [("first", self.first), ("middle", self.middle), ("last", self.last)]
            for key, ti in v:
                val = profile.get(key)
                if val:
                    ti.text = val

class CreatePacket(Screen):
    pass

class ScreenManagement(ScreenManager):
    pass

presentation = Builder.load_file("customwidget.kv")

class CustomWidgetApp(App):
    def build(self):
        return presentation

if __name__ == "__main__":
    CustomWidgetApp().run()

*.kv

#: import FadeTransition kivy.uix.screenmanager.FadeTransition
#: import hex kivy.utils.get_color_from_hex
#: import FocusBehaviors kivy.uix.behaviors.focus

ScreenManagement:
    transition: FadeTransition()
    TitleScreen:
    MainScreen:
    CreateProfile:
    CreatePacket:

<MainLabel@Label>:
    font_size:50
    bold: True
    size_hint_x: 1
    size_hint_y: 1.85
    color: 0.396078431, 0.803921569, 0.807843137, 1
    font_name: '/home/jarren/PycharmProjects/BCO_Form_Filler/practice/pirulen rg.ttf'

<SubLabel@Label>:
    font_size: 35
    bold: True
    halign: "center"
    size_hint_x: 1
    size_hint_y: 1.5
    color: 0.396078431, 0.803921569, 0.807843137, 1
    font_name: '/home/jarren/PycharmProjects/BCO_Form_Filler/practice/pirulen rg.ttf'

<OtherLabel@Label>:
    font_size: 12
    bold: True
    color: 0.396078431, 0.803921569, 0.807843137, 1
    font_name: '/home/jarren/PycharmProjects/BCO_Form_Filler/practice/pirulen rg.ttf'
    text_size: self.size

<Button@Button>:
    font_size: 20
    bold: True
    color: 0.396078431, 0.803921569, 0.807843137, 1
    font_name: '/home/jarren/PycharmProjects/BCO_Form_Filler/practice/pirulen rg.ttf'
    background_color: 0.02745098, 0.074509804, 0.121568627, .01
    canvas.before:
        Color:
            rgba: 0.396078431, 0.803921569, 0.807843137, 1
        Line:
            width: 2
            rectangle: self.x, self.y, self.width, self.height
    on_press: self.background_color = (0.396078431, 0.803921569, 0.807843137, 1)
    on_release: self.background_color = (0.02745098, 0.074509804, 0.121568627, .01)

# TODO: Create a focus behavior to "Tab" between widgets
<TextInput@TextInput>:
    font_size: 12
    color: 0.396078431, 0.803921569, 0.807843137, 1
    font_name: '/home/jarren/PycharmProjects/BCO_Form_Filler/practice/pirulen rg.ttf'
    padding_x: 10
    padding_y: 10
    focus_next: None
    focus_previous: None
    unfocus_on_touch: True
    background_color: 0.02745098, 0.074509804, 0.121568627, .01
    canvas.before:
        Color:
            rgba: 0.396078431, 0.803921569, 0.807843137, 1
        Line:
            width: 1
            rectangle: self.x, self.y, self.width, self.height

<TitleScreen>:
    id: "title"
    FloatLayout:
        MainLabel:
            text: "Easy Button"
            size_hint_x: 1
            size_hint_y: 1.25

        SubLabel:
            text: 'Test'
            size_hint_x: 1
            size_hint_y: 1
        Button:
            text: "Click Here To Continue"
            on_release: app.root.current = "main"
            size_hint: (.75, .15)
            pos_hint: {'x': .12, 'y': .2}

<MainScreen>:
    name: "main"
    MainLabel:
        text: "Home Page"
    BoxLayout:
        Button:
            on_release: app.root.current = "create_profile"
            text: "Create Profile"
            size_hint: (.5, .15)
        Button:
            on_release: app.root.current = "create_packet"
            text: "Create Packet"
            size_hint: (.5, .15)

<CreateProfile>:
    name: "create_profile"

    first: first
    middle: middle
    last: last

    AnchorLayout:
        anchor_x: 'center'
        anchor_y: 'top'
        MainLabel:
            text: "Create Profile"
            size_hint: (1, .15)

    BoxLayout:
        size_hint: (.95, .2)
        pos_hint: {'x': 0, 'y': .85}
        spacing: 10
        padding: 10
        halign: "left"

        OtherLabel:
            text: "First"

        OtherLabel:
            text: "Middle"

        OtherLabel:
            text: "Last"
    BoxLayout:
        size_hint: (.95, .07)
        pos_hint: {'x': 0, 'y': .8}
        spacing: 20
        padding: 10
        halign: "right"
        text_size: self.size
        TextInput:
            id: first
        TextInput:
            id: middle
        TextInput:
            id: last    

    BoxLayout:
        Button:
            on_release: app.root.current = "main"
            text: "back Home"
            size_hint: (.5, .15)
        Button:
            on_release: root.save()
            text: "Save Profile"
            size_hint: (.5, .15)

<CreatePacket>:
    name: "create_packet"
    MainLabel:
        text: "Select Packet"
    FloatLayout:
        Button:
            on_release: app.root.current = "main"
            text: "back Home"
            size_hint: (1, .15)
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • That works beautifully! Thank you! Could you help me figure out how to append to the JSON file rather than write? – Jarren Poulsen Jan 10 '19 at 16:48
  • 1
    @JarrenPoulsen The concept of append is to obtain what there is, add the new data and save it again, so with get and put you can get that behavior: `d = self.store.get('profile') if not d: d = dict() d["first"] = "some value" self.store.put('profile', **d)` – eyllanesc Jan 10 '19 at 16:56
  • Excuse my ignorance...so the solution you gave me to append to a json file isn't working for me. I've spent the last few hours looking for a solution but your way seems to be unique. It's probably because I don't understand what to put in for "some value". I've tried hard coding it (that wrote to json, but didn't append). I've tried 'self.first.text' with no change. – Jarren Poulsen Jan 10 '19 at 19:42
0

I found a work around that still allows me to append information into a JSON by using TinyDB. Here is the updated code:

from kivy.app import App
from kivy.storage.jsonstore import JsonStore
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.core.window import Window
from tinydb import TinyDB, Query
from kivy.uix.listview import ListItemButton


Window.clearcolor = (0.02745098, 0.074509804, 0.121568627, 1)
Window.size = (2000, 900)

db = TinyDB('bcodb.json')

class ProfileListButton(ListItemButton):
    pass

class TitleScreen(Screen):
    pass

class MainScreen(Screen):
    pass

class CreateProfile(Screen):
    def __init__(self, **kwargs):
        super(CreateProfile, self).__init__(**kwargs)
        self.store = JsonStore("bcodb.json")

    def save(self):
        db.insert({'first': self.first.text, 'middle': self.middle.text, 'last': self.last.text})

    def update(self):
        db.update({'first': self.first.text, 'middle': self.middle.text, 'last': self.last.text})

class CreatePacket(Screen):
    pass

class ScreenManagement(ScreenManager):
    pass


presentation = Builder.load_file("customwidget2.kv")

class CustomWidgetApp(App):
    def build(self):
        return presentation

if __name__ == "__main__":
    CustomWidgetApp().run()

It's pretty easy to figure out with a simple database-like program I'm trying to develop. The .kv file is the same.

Jarren Poulsen
  • 141
  • 1
  • 13