Is there a “dirty form” value for the Integration UI?

When working with our integration, if any of the form elements have been changed, when I press the “submit” button the code does its thing behind the scenes, sets up files, calls services, etc.

If I press the “x” to close without saving, it does precisely that.

If I press “Submit” and nothing has changed, it is like I pressed “x”. However, I would like pressing “Submit” to always call the “code-behind” (or whatever it’s called). Now I have to believe there is some sort of “dirty form” variable for the form to know whether to close without saving form fields/calling code-behind.

TL;DR Is there a way to always execute code when submit is pressed?

Use user_input dictionary as your dirty bit. If it’s None, then nothing was populated.

    async def async_step_user(self, user_input=None):
        if user_input is not None:  #<-----------------

Is there a simple way of tricking the form into thinking something changed?

Now that’s a good question. Let me think on it.

Can you just post your code and intentions with it? I can’t think of anything, maybe there’s another approach?

It’s in the following:

When the user presses “submit” _generate_package is called which creates a bunch of yaml files in the packages directory. If the user makes any changes in the UI, the yaml needs to be updated so _generate_package needs to be called. But we are having some development issues where we need to (or want, I should say) to call that regardless if any UI fields are changed.

That’s only called when setup entry is invoked. If you want it to appear on any ‘submit’ it needs to be moved outside async_setup_entry. Then you can call it inside any of your async_steps. The async_steps are always executed prior to submitting and after. SO what happens is those steps build the form, then when you hit submit, the data comes through that flow again, but this time it’ll hit the 'if user_input is not None:" line. At that point you should be creating your config entity OR bringing up a new form. You can perform other logic to verify that you’re on the first execution by building a counter and attaching it to the logic to not execute the code, but that’s typically done though the user data (not using a counter).

If i’m not making sense… here’s what It would look like in code:


__init__.py

async def _generate_package(service):
    ...

async def async_setup_entry(hass, config_entry):
    ...
    etc
    ...
    hass.services.async_register(
        DOMAIN,
        SERVICE_GENERATE_PACKAGE,
        _generate_package,
        schema=vol.Schema({vol.Optional(ATTR_NAME): vol.Coerce(str)}),
    )

config_flow.py

    async def async_step_user(self, user_input={}):
        if not self.firsttime:
            hass.services.async_register(
                DOMAIN,
                SERVICE_GENERATE_PACKAGE,
                _generate_package,
                schema=vol.Schema({vol.Optional(ATTR_NAME): vol.Coerce(str)}),
            )

you’d have to fill in all that service data that your registering. But you get the general idea I hope. Unless I’m completely missing the mark on what you mean.

I think I get it, but since @firstof9 cobbled this together I’m going to have him take a look. Not sure how he is populating default values, which is what I think your “first time” setting is for.

This is specifically in regards to the options flow, since it’s supper “hacky” as some dev’s have put it. :stuck_out_tongue:
I’ve since created a bool confirmation dialog to handle this scenario, hopefully.

yah but it’s a custom component. If it works it works. So you already have this solved?

It’s more of a workaround, I’m not 100% sure it’ll work for the options flow just yet, but it seems to in the last few tests I’ve done. :crossed_fingers:

EDIT: It doesn’t work as well as I’d like so still going to work on it.

If I could find a way to delete an entry in the config_entry.data, example say I want to delete config_entry.data['foo'], then I could make my plan work properly. :frowning:

If data is a dictionary, data.pop(“foo”) should work

Ya I already attempted that: TypeError: 'mappingproxy' object does not support item deletion :frowning:

So I ended up having to pull the config data from config_entry.data entry by entry and dumping it into it’s own dict w/o the data I wanted to remove/exclude, then config_entry.data = config (config being the new dict) the data back into the ‘mappingproxy’.

:sweat_smile:

image

hopefully you did a quick and dirty

mydata = { k: v for k, v in config_entry.data.items() }
if 'foo' in mydata:
    mydata.pop('foo')

No just dirty dirty :stuck_out_tongue:

mydata = {
    CONF_SOMETHING: config_entry.data[CONF_SOMETHING],
...
}

:frowning:

well swap to what I wrote and you can condense your cod a bit. Should work right out of the box! And now you know how to create a quick dictionary

I’ll give it a shot :slight_smile:

I’m only kind of following, but using a 2nd UI seems like it should be unnecessary. Having a “don’t overwrite” checkbox on the main form makes more sense, but even that is questionable. Why would you change the settings, save them and not overwrite? There’s no purpose for doing so.

Just a little of my background thoughts on this.

The yaml for the integration should always use the configuration setting values. If you change those settings, they should be updated in the integration’s yaml. This is done by calling a service named generate. Pressing “Submit” (with a setting change) does just that. Without a setting change, the form seems to say “nothing to do here, bye”. The reason I want this case to update the yaml with no setting change (via call to generate) is for development reasons. It’s faster and convenient for me.

I’m not sure this is worth the time being spent on it, but now that it is in between the teeth it’s a shame to let it go. At least @firstof9 learned about dictionaries. Watch out! Teach that boy something and he goes out and uses it. I’m quite impressed with his git learning curve.