Dealing with concurrent users¶
Why worry about concurrent users?¶
If you’re not careful, one user can easily overwrite the data persisted by another user working on the same page at the same time.
- Consider this sequence of events:
user A opens page X
user B opens page X
user A changes input on a form on page X, and clicks on a
ButtonInputthat submits it
the database is changed as a result of user A’s changes in such a way that page X would not render the same anymore
user B still has the old page X open, and now makes similar changes on that page and clicks on a
ButtonInputthat submits the info
Without intervention in the above scenario user B’s changes might overwrite those of user A or the application could break - depending on how the code was written.
Reahl prevents changes to outdated forms¶
When a user clicks on a
ButtonInput, Reahl checks whether the
Input values have
been changed since it was rendered for this user. If a change is detected, an error
is shown (with an option to refresh the
Customising the up-to-date-check¶
You don’t have do any coding to enable this check. You may want to customise what should be included in this up-to-date check, and what not.
You should also ensure (as with all
Forms) that your form displays any errors
that may occur as is done with
def __init__(self, view): super().__init__(view, 'simple_form') self.use_layout(FormLayout()) if self.exception: self.layout.add_alert_for_domain_exception(self.exception) domain_object = self.get_or_create_domain_object() link = self.add_child(A(view, Url('/'), description='Open another tab...')) link.set_attribute('target', '_blank') self.add_child(P(view, text='...and increment the value there. Come back here and submit the value. A Concurrency error will be shown')) #Your own widget that tracks changes self.add_child(MyConcurrencyWidget(view, domain_object)) self.layout.add_input(TextInput(self, domain_object.fields.some_field_value)) self.define_event_handler(domain_object.events.submit) self.add_child(Button(self, domain_object.events.submit)) self.define_event_handler(domain_object.events.increment) self.add_child(Button(self, domain_object.events.increment))
Form is submitted, the new user input is not immediately applied. First, the values of
all of its
PrimitiveInputs are computed as they would render at this point and compared with
what they were when the
Form was rendered initially.
If any differences are detected here it means that someone must have changed relevant data in
the database since the
Form was rendered.
To prevent a specific
PrimitiveInput from participating in this check, pass ignore_concurrent_change=True
when constructing the
If you have a
Widget that is not a
PrimitiveInput, but that represents some data in the database
which you want to partipate in this check, override
get_concurrency_hash_strings() on your
class MyConcurrencyWidget(Widget): def __init__(self, view, domain_object): super().__init__(view) self.domain_object = domain_object self.add_child(P(view, text='Counter: %s' % domain_object.counter)) def get_concurrency_hash_strings(self): yield '%s' % self.domain_object.counter
get_concurrency_hash_strings() yields one or more strings that represent the database state that
should influence the up-to-date check.