Extending the registration form model

Hi there

I’ve extended the event_registration.forms table so there are now two additional columns.

I have also used the add_form_fields signal to add two additional text inputs to the RegistrationFormForm:

@signals.add_form_fields.connect
def _add_sheep_registration_form_fields(form, **kwargs):
  if (form.__name__ == 'RegistrationFormForm'):
    yield ('sheep_booking_id', StringField('Sheep booking ID'))
    yield ('sheep_booking_type', StringField('Sheep booking type'))

But I now need to find a way to have this data stored in the forms table but I feel like I’ve exhausted my options. I naively hoped that processing the form in the validated signal would do the trick:

@signals.form_validated.connect
def _process_sheep_registration_fields(form, **kwargs):
    setattr(form, 'sheep_booking_id', form.ext__sheep_booking_id)
    setattr(form, 'sheep_booking_type', form.ext__sheep_booking_type)

But no dice here, so they I tried the registration_form_created signal but I don’t have access to those attributes on the RegistrationForm:

@signals.event.registration_form_created.connect
def _save_sheep_registration_fields(form, **kwargs):
  # nothing here :(
  return False

I’m now thinking that the only way to achieve this will be to override the blueprint routes somehow to use a custom controller but I hope there’s another way!

Any thoughts greatly appreciated!

from flask import g and store your data as attributes on that object (it’s a place to store request-scoped data). Then access it inside _save_sheep_registration_fields.

BTW, If this is in a plugin you shouldn’t use the decorator syntax for signals but rather self.connect() inside the plugin’s init() method.

Thanks for the quick response @ThiefMaster I’ll give it a go! And also for the tip on signals - I’ll refactor!

@ThiefMaster thanks so much for your help. The data is now being saved to the database via a custom model that extends RegistrationForm - next problem: is it possible to get the RegistrationForm object from the add_form_fields signal so I can set the default values?

No, and it wouldn’t help you anyway as the model isn’t even instantiated when showing the creation form. Try passing default='something' when instantiating the custom StringField objects.

Or do you mean when editing a registration? That’s actually a good point and so far we didn’t need it for any of the cases where we used those signals ourselves. But you can probably do some dirty hack and access it via g.rh.regform

Sorry, I don’t think I made myself clear. I want to be able to show the persisted values on the registration form edit form (RHRegistrationFormEdit)…

I’ve managed to load the RegistrationForm via the route parameter when I’m adding fields to the registration edit form but I can’t find a way to set the values of the fields:

@signals.add_form_fields.connect
def _add_sheep_registration_form_fields(form, **kwargs):
  sheep_booking_id = ''
  sheep_booking_type = ''
  if (form.__name__ == 'RegistrationFormForm'):
    if 'reg_form_id' in request.view_args :
      regform = (SheepRegistrationForm.query
                          .filter_by(id=request.view_args['reg_form_id'], is_deleted=False)
                          .options(defaultload('form_items').joinedload('children').joinedload('current_data'))
                          .one())
      if regform:
        sheep_booking_id = regform.sheep_booking_id
        sheep_booking_type = regform.sheep_booking_type

    sheep_booking_id_field = StringField('Sheep booking ID', default=sheep_booking_id)
    sheep_booking_id_field.data = sheep_booking_id
    sheep_booking_type_field = StringField('Sheep booking type', default=sheep_booking_type)
    yield ('sheep_booking_id', sheep_booking_id_field)
    yield ('sheep_booking_type', sheep_booking_type_field)

It seems setting a default value only works when creating a new registration form and setting the data attribute has no affect.

The wtforms docs does seem to indicate that this is the expected behaviour for default values:

https://wtforms.readthedocs.io/en/latest/faq.html#why-does-blank-input-not-go-back-to-the-default-value

Is there a way to work around this?

A possible workaround (untested) would be connecting a signal handler to signals.rh.process_args for the sender RHRegistrationFormEdit. In this signal, you fetch your :sheep: data and simply store it on rh.regform (using attributes resembling the names of the form fields, i.e. ext__sheep_booking_id etc.).

While this is pretty ugly, setting attributes that are otherwise unused on a model instance is harmless (SQLAlchemy simply ignores them).

@ThiefMaster

I’m trying to handle updates via the form_validated signal but within that context I can’t find any reference to the RegistrationForm, i.e. via the passed in form. Do you know if it’s possible?

You could grab the data in the signals.form_validated signal.

Also, instead of checking the string name of the sender, import the actual class and use .connect_via(RHRegistrationFormEdit) (or, when using the plugin connect() method, by specifying sender=RHRegistrationFormEdit in the call). This way the signal is only called when the sender matches, which is of course much more efficient than running it every time and manually checking if it’s for the correct class.

PS: Usually Python uses 4 spaces for indentation :wink:

@ThiefMaster

I’m trying to handle updates via the signals.form_validated signal but I can’t find any reference to the RegistrationForm from within that context, i.e. via the sender. Am I missing something or is it not possible?

https://docs.getindico.io/en/stable/plugins/signals/#indico.core.signals.form_validated

The sender is the form object, which you can use in an isinstance check to see if it’s the correct form class.

@ThiefMaster

That makes sense but how can I get the RegistrationForm from the RegistrationFormForm ?

g.rh.regform should work there as well

Yes it does! Wow, I think we’ve done it. Thanks so much @ThiefMaster I’d of never of been able to do this without your help!

1 Like

You’re welcome! Out of curiosity, what does your plugin do exactly? (I guess “registering sheep” was just a placeholder? :sheep:)

Well the plugin is doing a lot but this bit in particular is interfacing with a 3rd party CRM so we can track registrations that take place in Indico.

I actually think we’ve done a lot of interesting work with Indico on this project, largely due to the help of @bkolobara, it would be good to share our learning\successes with the community at some point…

Sounds like it would be pretty interesting to see this plugin released on GitHub at some point - even if it may not be that useful to people outside your organization (unless the CRM is widely used), it would certainly be an interesting example for plugins doing non-trivial things. (And you’d benefit by getting feedback from us if we spot anything nasty in the code!)