-
Notifications
You must be signed in to change notification settings - Fork 32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC 99: Preliminary work to support auto-save functionality #99
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,44 @@ | ||||||||||||||||||||||||||
# RFC 099: Preliminary work to support auto-save functionality | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
* RFC: 099 | ||||||||||||||||||||||||||
* Author: Matthew Westcott | ||||||||||||||||||||||||||
* Created: 2024-06-20 | ||||||||||||||||||||||||||
* Last Modified: 2024-06-20 | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
## Abstract | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
A much-requested feature for Wagtail is the ability to automatically save changes in the background while editing a page or snippet. Achieving this to an acceptable standard of user experience will involve completing various milestones that are best handled as separate development efforts. This RFC outlines various items of work that are pre-requisites for implementing auto-save functionality. | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
## Concurrent editing notifications | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
When multiple users have the editing interface for a page or snippet open at the same time, the last user to hit save will generally overwrite any changes made by the other users. This problem is compounded when auto-saving is active, since this overwriting will happen without the user's explicit action. To address this, we will implement a notification system that warns users when another user is editing the same item, and disables auto-saving when potentially conflicting changes are detected. This feature is covered by RFC 95. | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
## Deferring validation until publish | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
Currently, a revision can only be saved if it passes validation. This is undesirable because a user may spend an extended amount of time editing a page while it is in a state that would not pass validation - for example, a blog post might have a required 'category' field at the bottom of the form, and a user leaves this unset while they are writing the body of the post. In this situation, auto-save will not be able to save the user's work in progress. | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
To address this, we will relax the validation behaviour so that as standard, validation rules such as `blank=False` are not enforced when saving drafts. Instead, full validation will be enforced at the point that a page is published, scheduled, or submitted to a workflow. | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I’m worried that we’ll break developers’ expectations by making the nuance between DB-level and ORM-level validation more apparent without it being something that devs knowingly opt into. Could we consider alternatives? Perhaps adding an explicit way to distinguish between "valid" and "invalid" drafts? |
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
Since we need to create an initial database record before we can save revisions, any data that is invalid at the database level (such as a nonsensical date in a date field, or non-numeric data in an integer field) will still produce an immediate validation error. Developers will also be provided with a way to override this behaviour and enforce immediate validation for specific fields where appropriate - this could perhaps be done via an argument on `FieldPanel`. Wagtail itself will make use of this mechanism to require the title and slug fields to be filled in before a page can be saved. | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
(Implementation note: Django does not enforce validation rules in a model's `save` method, so we are not inherently prevented from saving data that breaks those rules. Rather, it happens in the `full_clean` method, which is part of the `Page` model's `save` logic by default but can be skipped. We will also need to prevent validation from being applied at the form level; this could possibly be done by stripping flags such as `required=True` from the derived form fields.) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
Deferring validation in this way will also improve the user experience of the live preview panel, which currently pauses updated while the page is in an invalid state. This does however mean that template authors will need to code more "defensively" to ensure that their templates are preview-friendly, since they will potentially be passed invalid or blank data that would ordinarily be prevented by the validation rules. | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Re my above comment about developers’ expectations, I’d like to see alternatives explored, and going into more depth on the types / commonness of preview errors that this change would cause. From personal experience, it seems pretty common already that devs implement functionality without being aware of compatibility issues with previews. |
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
## Dynamic insertion of validation error messages | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
Even with the above change, there will still be situations where a validation error is triggered on saving a draft, such as a blank title or a slug that is already in use. For a conventional form submission, the entire form is re-rendered with error messages inserted next to the relevant fields. When auto-saving is introduced, we will need to communicate these errors to the user without performing a page reload. The placement of these errors varies according to the panel and field; for example, errors within a StreamField are attached to the block that caused the error, and `TabbedInterface` additionally shows the count of errors as a badge on the tab. Errors also need to be indicated on the minimap. | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
Currently, the [Telepath](https://wagtail.github.io/telepath/) library is used within StreamField to provide a client-side API for block objects that are defined in Python and would conventionally only support server-side rendering, and the same approach can be used across the whole form. We will implement Telepath adapters for panel objects, which will provide a method to obtain a client-side panel object when given a DOM fragment. This object will then provide a method for inserting error messages into the panel. | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It’d be nice to also cover the criteria when those error messages would be displayed – assuming we’d fields / Telepath to keep track of whether fields are "touched" to decide whether to display the error messages or not. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would there be any way for us to instead treat autosaves as a different kind of revision? Revisions, because they kind of act as a proxy, could be treated differently to actual model saving. Bypassing some validation for the sake of convenience. From here, we would need to solve the UX problem of 'your work is saved but not fully saved'. Confluence does a good job of this, it's pretty intuitive, treating saves similar to what we treat publish as. When you see the page listed it says 'unpublished changes'. You edit and then click save or publish to apply your 'work in progress '. So our autosave is less about saving an actual version but allowing for a work in progress, per user even, that allows you to come back later and actually save your work. Confluence won't trigger any workflows or other 'proper' validation until you've actually saved things. Confluence also won't show your 'temporary' work in the proper revision listing. I really like it's approach, it let's me work on something but not share with my team until I'm ready. I also think the same 'temporary' revision mechanism is used for their pseudo-concurrent editing. Just an idea. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. note: I put a similar question/idea on the newer RFC https://github.com/wagtail/rfcs/pull/104/files#r1865306971 |
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
## Further applications for Telepath panel adapters (optional) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
Having Telepath adapters for panels would open up the possibility of further enhancements which, while not strictly necessary for auto-save, may improve the consistency of the codebase. | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
Firstly, the client-side panel object could provide a method for retrieving the current value of any fields within that panel. Calling this on the top-level panel would return a dictionary of all field values, which could then be submitted to a JSON API endpoint to save the draft. This would avoid the need to construct a Django Form object to process the submission, which may provide a way to bypass form-level validation. | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
Secondly, the adapter could provide support for dynamically inserting panels, much as StreamField does; this would allow sharing more of the implementation between StreamField and InlinePanel, and provide functionality such as duplication and inserting at arbitrary positions, which is currently missing from InlinePanel. | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In light of other discussions at the core team meeting, just flagging that this proposed approach will have larger impacts on our planned Stimulus migration path. With the underlying impacts to removal of inline scripts and jQuery. I really think that we should ensure we've fully validated a DOM / FormData based approach before going down this path. Having Telepath baked in, for a core concern potentially, for any other field and custom admin DOM implementation is a risk we need to think about. We'll also need to deal with JS data structures that may be at odds with the DOM/FormData. Finally, in terms of error patching into the DOM. I really think if we POSTed a submit, changes the request header to accept JSON, our save method could return a errors structure mostly out of the box using Django clean methods. From here we have an object of named (field names associated in our DOM) errors, as strings or html. This can be patched into the DOM using Then we can use the existing system of JS form /field constraint validation for as much enhanced UI around message presentation as needed. https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation#the_constraint_validation_api This also means the 'ownership' of what to do when there's a field error can be split out to a different controller. Or at the PanelController level. It's worth a POC if possible. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @gasman @thibaudcolas @laymonage - here's a very basic POC branch that builds our existing Stimulus approaches to bake in a simple (ignorant) auto-save behaviour.
POC implementation
What we get
What's not covered
It's worth calling out that the more we build on our own constructs for message, DOM representation and other things, the more it forces us away from building on the browser and it's capabilities. Sometimes it's a bit of a fight to get the most out of the browser, but we do get so much 'for free' and we also get a pretty functional out of the box behaviour from 'just a Django widget' that I think is a worthy goal when it comes to developers using and building on Wagtail. I like to think that we should give the power of the complexity/functionality of StreamField when we/devs need it, but without having to force all our HTML/JS to follow that paradigm up the whole DOM when it's probably not required. We want our JS/DOM to 'wear the right hat' if possible. Rant over, I hope this is helpful and is a good discussion starter. |
||||||||||||||||||||||||||
## Open Questions | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn’t have to be open questions, just things I’d want the RFC to cover.
Suggested change
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
// Include any questions until Status is ‘Accepted’ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rewording to make it clear what was covered in RFC 95. I’m concerned about disabling auto-saving, but need to think more about its alternatives.