Filling a `SelectField` with the list of configured IdPs

Hi,

we are developing an Indico plugin for automatic group assignmed for SSO logins based on this idea which should be generic, e.g. allow to configure the identity provider it is acting on and the domain / realm it is filtering for (so it can be re-used by others, and maybe also upstreamed). It’ll include full coverage by tests and optional automatic cleanup of old group assignments (e.g. in case users change their organisation and use a different EduGain IdP to log in).

The plugin is mostly ready, but I have a problem populating a SelectField with the multipass.identity_providers list. Here’s what I’m doing right now:

class SettingsForm(IndicoForm):
    identity_provider = SelectField(_('Provider'), [InputRequired()],
                                    description=_('The identity provider accounts need to be '
                                                  'associated with to be added to the group.'))

class SSOGroupMappingPlugin(IndicoPlugin):
    ...
    def init(self):
        identity_providers = multipass.identity_providers.values()
        if not identity_providers:
            del self.settings_form.identity_provider
        idp_choices = [(p.name, p.title) for p in sorted(identity_providers, key=attrgetter('title'))]
        self.settings_form.identity_provider.choices = idp_choices

        super().init()

(you can find the full code here)

I have a pytest which confirms the choices variable is filled with the expected tuple:

Running this in an actual installation and adding a print statement confirms that the list of tuples is populated correctly in init.

However, when actually installing the plugin in a real instance and then entering plugin configuration, the SelectField remains empty.

Am I missing something in terms of execution order? Is there a different way to dynamically populate a SelectField?

Thanks in advance and cheers,
Oliver

You need to set the choices in the SettingsForm’s __init__ method:

def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    self.identity_provider.choices = ...

del self.identity_provider will work there as well in order to remove the field.

PS: This is because of how WTForms works - it does some magic where the actual field instance is only created when the form is instantiated (so each form instance has its own field instance), until that point SomeForm.field is just a wrapper containing the field name and its args/kwargs.

1 Like

Many thanks! I gave it a test immediately, and can confirm that it works like a charm.
With your explanation, I now also understand why — this was not really obvious to me from my current knowledge of WTForms, but it makes a lot of sense.
Now there’s only a few real-life tests and some polishing needed and the plugin will be done, many thanks!