0

In trying to create a widget to mimic the functions of a multiple choice quiz, I have run into an error that I can't find any solution for. I read this post and didn't find anything that applied to my specific situation.

I am trying to load a group of settings from a .JSON file and use them as the main properties for my KV objects. For example, loading the "canvas_color" setting from the file and assigning that value to the canvas color of my widget.

When I try and do this I get the error:

AttributeError: 'MultipleChoiceT' object has no attribute 'settings'

My code on github

My .py file containing the "missing" attribute:

import kivy
import json
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.app import App

kivy.require("1.11.1")

Builder.load_file("MultipleChoice_text.kv")


class MultipleChoiceT(BoxLayout):

    # collects info from json file and assigns it to settings var
    with open('widget_settings.json', 'r') as file:
        settings = json.load(file)["MultipleChoice_settings"][0]

    print(settings)

    test = 'hello'

    def __init__(self, **kwargs):
        super(MultipleChoiceT, self).__init__(**kwargs)
        self.app = App.get_running_app()
        print(self.app)
        self.w_width = 0.5
        self.w_height = 1
        self.data = {'pressed_btn': 0, 'btn_text': None}

    # the function called for when the toggle button is on_state
    def pressed(self, instance):
        self.data['pressed_btn'] = self.get_id(instance)
        self.data['btn_text'] = instance.text
        print(self.data)

    # changes the bottom label when submit button is pressed
    def submit(self):
        self.bottom_label.text = f"ID: {self.data['pressed_btn']} TEXT: {self.data['btn_text']}"

    # returns the id of the given instance
    @staticmethod
    def get_id(instance):
        for id, widget in instance.parent.ids.items():
            if widget.__self__ == instance:
                return id

    # null method for now, not implemented
    def change_question(self, new_text):
        self.question_label.text = str(new_text)

    # prints text when the next question button is pressed
    def next_question(self):
        print('next question pressed')

    # returns the dimensions for the toggle buttons based on their text length and font size defined in settings json
    def get_dimension(self, instance):
        if len(instance.text) < 25:
            width = ((len(instance.text) / 2) * instance.font_size) * 1.1
        else:
            width = (len(instance.text) / 2) * instance.font_size

        height = instance.font_size * 1.35
        return width * self.settings["btn_scalar"], height * self.settings["btn_scalar"]


class Test(BoxLayout):
    pass


class MainApp(App):
    def build(self):
        return Test()


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

And my .kv file (yes, I am aware the indentation is off, that is only in this post):

<MultipleChoiceT@BoxLayout>:

orientation: 'vertical'
padding: 10
id: main_layout
bottom_label: bottom_label

canvas:

    Color:
        rgba: root.settings["canvas_color"]
    Rectangle:
        pos: self.x + 5, self.y + 5
        size: self.width - 10, self.height - 10

Label:
    id: question_label
    font_size: root.settings["font_size"]
    text: 'Sample question'
    text_size: self.size[0] + 5, self.size[1]
    valign: 'middle'
    height: self.font_size * 1.33
    size_hint: 1, None
    color: root.settings["label_text_color"]

Label:
    size_hint_y: None
    height: 10

ToggleButton:
    id: btn1
    background_color: root.settings["button_bg_color"]
    color: root.settings["button_text_color"]
    font_size: root.settings["font_size"]
    text: 'Option1'
    group: 'MultChoice'
    on_state: root.pressed(self)
    size: root.get_dimension(self)
    size_hint: None, None
    halign: 'center'
    valign: 'center'

Label:
    size_hint_y: 0.1

ToggleButton:
    id: btn2
    background_color: root.settings["button_bg_color"]
    color: root.settings["button_text_color"]
    font_size: root.settings["font_size"]
    text: '4785839409fjvojrvn eijofgj4389'
    group: 'MultChoice'
    on_state: root.pressed(self)
    size: root.get_dimension(self)
    size_hint: None, None
    halign: 'center'
    valign: 'center'

Label:
    size_hint_y: 0.1

ToggleButton:
    id: btn3
    background_color: root.settings["button_bg_color"]
    color: root.settings["button_text_color"]
    font_size: root.settings["font_size"]
    text: 'Option3'
    group: 'MultChoice'
    on_state: root.pressed(self)
    size: root.get_dimension(self)
    size_hint: None, None
    halign: 'center'
    valign: 'center'

Label:
    size_hint_y: 0.1

ToggleButton:
    id: btn4
    background_color: root.settings["button_bg_color"]
    font_size: root.settings["font_size"]
    color: root.settings["button_text_color"]
    text: 'A sample question type'
    group: 'MultChoice'
    on_state: root.pressed(self)
    size: root.get_dimension(self)
    size_hint: None, None
    halign: 'center'
    valign: 'center'

Label:
    size_hint: 1, 0.5

GridLayout:
    id: bottom_layout
    cols: 4

    size_hint_y: None

    canvas:
        Color:
            rgba: root.settings["second_canvas_color"]
        Rectangle:
            pos: self.x, self.y
            size: self.width, self.height


    Button:
        id: submit_btn
        text: 'Press to submit'
        on_press: root.submit()
        font_size: root.settings["font_size"]
        background_color: root.settings["second_btn_bg_color"]
        color: root.settings["second_btn_txt_color"]

        # for text wrapping
        size: root.get_dimension(self)
        text_size: self.size
        halign: 'center'
        valign: 'center'

    Label:
        id: bottom_label
        text: 'answer'
        font_size: root.settings["font_size"]
        color: root.settings["second_lbl_txt_color"]
        size_hint_x: main_layout.size_hint_x * 2

        # for text wrapping
        size: root.get_dimension(self)
        text_size: self.size
        halign: 'center'
        valign: 'center'

    Button:
        id: next_btn
        text: 'Next question'
        font_size: root.settings["font_size"]
        background_color: root.settings["second_btn_bg_color"]
        color: root.settings["second_btn_txt_color"]
        on_press: root.next_question()

        # for text wrapping
        size: self.texture_size
        text_size: self.size
        halign: 'center'
        valign: 'center'

<Test@BoxLayout>:
    orientation: 'horizontal'
    MultipleChoiceT:
    MultipleChoiceT:

Whenever the MultipleChoice_text.py file is run, I get an error saying on line 11 of MultipleChoice_text.kv that the MultipleChoiceT object has no attribute 'settings' Maybe it's just my understanding of how/what attributes are. But when I ran the file on its own, without trying to have multiple the MultipleChoiceT widgets in one layout, it ran perfectly.

My current idea is that the settings attribute is made on the instance level and is only created when the instance is created. And the instance is not created until after root.settings[whatever] is called. I'm not sure how I can fix this issue.

I have tried to move the creation of the settings attribute around and into different classes to see if it can be initialized before it is called, but I had no luck with that. I also tried importing the settings attribute into the kv file but that didn't do anything either. My closest attempt has been running the MultipleChoiceT widget on its own and having it be the only thing in the app. That works when the code is as follows:

class MainApp(App):
    def build(self):
        return MultipleChoiceT()

MainApp().run()

Any help or pointers would be appreciated as this is for a school project due in 2 weeks :) I can post the full error message if needed but the only thing I found useful in it was the line stating the missing attribute.

As a side note, Is PyQt5 a better option for GUI development in python? I looked into it and chose not to go that route because I did not find a way for it to support scalable applications. And, the forum posts were sparse

1 Answer 1

0
<MultipleChoiceT@BoxLayout>:

This is the wrong syntax, it creates a new widget class called MultipleChoiceT, in addition to the one you defined in Python. You want <MultipleChoiceT>.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks! It works perfectly. Is this true for any custom widget I want to create? Do I have to define it in python with inheritance to whatever kivy object I need and leave it as just the class name, no @ tag?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.