Skip to content

Ensure action is always set when records are saved#12058

Open
jimchamp wants to merge 4 commits intointernetarchive:masterfrom
jimchamp:actions
Open

Ensure action is always set when records are saved#12058
jimchamp wants to merge 4 commits intointernetarchive:masterfrom
jimchamp:actions

Conversation

@jimchamp
Copy link
Collaborator

@jimchamp jimchamp commented Mar 9, 2026

Addresses #11955

Updates all save, save_many, and Thing._save calls to include a valid and accurate action.

We may also want to update the default save and save_many values in Infogami. This PR does that. When merged, we can check the /recentchanges views for the default types to determine if any transaction needs an action.

Technical

Testing

Screenshot

Stakeholders

Copilot AI review requested due to automatic review settings March 9, 2026 19:00
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR standardizes write operations across the codebase by ensuring action is supplied to save, save_many, and Thing._save calls, improving changeset classification/auditing.

Changes:

  • Added explicit action= values to many save/save_many/_save call sites (scripts + plugins).
  • Updated User.set_data to require an action and forward it to _save.
  • Refined list and import-related actions to be more specific (though some mappings/consumers may need updating).

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
scripts/update_stale_ocaid_references.py Adds an explicit bulk redaction action to save_many.
scripts/copydocs.py Adds an action to batch transfers (but currently breaks non-OL dest implementations).
openlibrary/plugins/upstream/covers.py Adds actions for updating covers/photos via _save.
openlibrary/plugins/upstream/code.py Adds an explicit action for revert saves.
openlibrary/plugins/upstream/addtag.py Adds explicit tag delete/update actions.
openlibrary/plugins/upstream/addbook.py Adds actions to several author-related saves; introduces a placeholder delete action needing finalization.
openlibrary/plugins/openlibrary/lists.py Replaces generic lists action with more specific list/series actions.
openlibrary/plugins/openlibrary/code.py Adds action for sampleload, add-author, and YAML updates (one formatting issue present).
openlibrary/plugins/openlibrary/bulk_tag.py Adds an explicit bulk tagging action.
openlibrary/plugins/openlibrary/api.py Adds actions for bulk delete and edition updates.
openlibrary/plugins/admin/code.py Adds actions for spam reverts / permission updates; leaves one _save() without action in stats POST.
openlibrary/olbase/events.py Adds a clarifying comment around subevent triggering.
openlibrary/core/models.py Requires action for User.set_data and adds actions for usergroup member changes + tag creation.
openlibrary/catalog/add_book/init.py Changes import save_many action selection (currently acknowledged as inaccurate and may affect changeset classification).
openlibrary/accounts/model.py Updates anonymization to pass an action into set_data.
Comments suppressed due to low confidence (1)

openlibrary/plugins/openlibrary/lists.py:742

  • action="update-list-items" is a new changeset kind for list edits; currently ListChangeset is only registered for kinds lists and series. If you keep this more granular action naming, you’ll likely want to register update-list-items (and delete-list, etc.) to ListChangeset so list change rendering/logic continues to work as before.
        return lst._save(comment="Updated list.", action="update-list-items", data=changeset_data)


class list_seed_yaml(list_seeds):
    encoding = "yml"

for group in web.group(docs, 50):
try:
print(dest.save_many(group, comment=comment))
print(dest.save_many(group, comment=comment, action="copydocs-transfer"))
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

copy() supports writing to a Disk destination (and tests use FakeServer), but those save_many implementations only accept (docs, comment=...). Passing action= here will raise TypeError. Consider either updating Disk.save_many/test doubles to accept an optional action (or **kwargs) and ignore it, or only passing action when dest is an OpenLibrary instance.

Copilot uses AI. Check for mistakes.
Comment on lines 712 to 715
doc = web.ctx.site.new(key, {"key": key, "type": {"key": "/type/delete"}})
doc._save(comment=comment)
# TODO : pull type from key, then compose `delete-{type}` action string
doc._save(comment=comment, action="delete-?")

Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

action="delete-?" looks like a placeholder and will leak into production changesets/recentchanges. Please replace with a real, stable action value (e.g. based on the doc type/key) before merging.

Copilot uses AI. Check for mistakes.
Comment on lines +749 to +753
# This is still wrong -- the `edits` list can contain works, authors,
# and editions. Some records may be new, and some may be existing.
# XXX : bulk-*
action = 'edit-edition' if existing_edition else 'add-edition'
web.ctx.site.save_many(edits, comment=comment, action=action)
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This batch save can include works/authors/editions and both creates and updates, but the new action is derived only from existing_edition and is acknowledged as “still wrong” in the comment. Also, this changes the changeset kind away from the existing add-book value which is registered to AddBookChangeset (used for rendering/behavior). Consider keeping action="add-book" (or introducing a new bulk/import action and registering it) so changesets remain correctly classified.

Copilot uses AI. Check for mistakes.
Comment on lines +399 to 404
action = "add-series" if list_type_plural == "series" else "add-list"
web.ctx.site.save_many(
records_to_save,
action="lists",
action=action,
comment=web.input(_comment="")._comment or None,
)
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These new list/series action values (add-list/add-series) won’t match the currently registered changeset kinds for lists (lists and series in openlibrary/core/lists/model.py). That likely means list-related changesets will no longer be parsed/rendered as ListChangeset. Either keep the existing action values, or update the changeset registration to include the new action strings.

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unclear to me if this will affect anything, aside from the /recentchanges/lists page(which will not load for me).

When this change is applied, one will need to visit /recentchanges/add-list to see newly added lists.

Comment on lines 515 to 522
def POST(self, today):
"""Update stats for today."""
doc = self.get_stats(today)
# This seems like it would save a Thing
# ...but, I don't know if this endpoint is in use today.
# I suspect not....
doc._save()
raise web.seeother(web.ctx.path)
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This endpoint still calls doc._save() without an action, which conflicts with the PR goal of ensuring saves always provide an action for audit/recentchanges classification. Please pass an explicit action here (even if the endpoint is rarely used) or remove the save if it’s truly obsolete.

Copilot uses AI. Check for mistakes.
Comment on lines +185 to +186
# Must check `book.key` for record type, if needed
book._save('Update covers', action="update-cover")
Copy link
Collaborator Author

@jimchamp jimchamp Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Must check `book.key` for record type, if needed
book._save('Update covers', action="update-cover")
book._save('Update covers', action="add-cover")

This one is tricky, as (I believe) covers can be added and removed by this handler.

How would these render in /recentchanges/add-cover if a cover were removed? Would it break the page?

raise web.redirect(web.ctx.path)
edition.set_identifiers(data)
saveutil.save(edition)
# `action` : edit-book-isbn?
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# `action` : edit-book-isbn?

saveutil.save(edition)

comment = utils.get_message("comment_add_book")
# XXX : creates both work and edition
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# XXX : creates both work and edition

Using book as the record type is probably fine for cases where either a work or edition record is being updated.

Comment on lines +140 to +141
# `action` is always being passed to `commit` today, but maybe not tomorrow?
# Should `action` be a positional argument for this method?
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# `action` is always being passed to `commit` today, but maybe not tomorrow?
# Should `action` be a positional argument for this method?

Reviewers: Should action be a required positional parameter for this commit method?

self.docs = []

def save(self, doc) -> None:
def save(self, doc) -> None: # total misnomer -- nothing is saved in the method
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def save(self, doc) -> None: # total misnomer -- nothing is saved in the method
def save(self, doc) -> None:

Out-of-scope for this PR.


web.ctx.site.save_many(docs_to_update, comment="Bulk tagging works")
web.ctx.site.save_many(
docs_to_update, comment="Bulk tagging works", action="bulk-update-work-tags"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe bulk-update-tags is better here?

if not data["source_records"]:
del data["source_records"]
web.ctx.site.save(data, "Remove OCAID: Item no longer available to borrow.")
web.ctx.site.save(data, "Remove OCAID: Item no longer available to borrow.", action="update-edition")
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
web.ctx.site.save(data, "Remove OCAID: Item no longer available to borrow.", action="update-edition")
web.ctx.site.save(data, "Remove OCAID: Item no longer available to borrow.", action="update-book")

Generalize Edition and Work records as book.


web.ctx.site.save_many(delete_payload, comment)
# this deletes editions and works
web.ctx.site.save_many(delete_payload, comment, action="bulk-delete-book-records")
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
web.ctx.site.save_many(delete_payload, comment, action="bulk-delete-book-records")
web.ctx.site.save_many(delete_payload, comment, action="bulk-delete-books")

#
# I suspect that we'd want to filter these out from the
# `/recentchanges` page.
page._save("updated blocked IPs", action="block-ips")
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe update-blocked-ips is better here? Either way, this probably shouldn't be publicly accessible via /recentchanges, which will require other changes.

Comment on lines +749 to +751
# This is still wrong -- the `edits` list can contain works, authors,
# and editions. Some records may be new, and some may be existing.
# XXX : bulk-*
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# This is still wrong -- the `edits` list can contain works, authors,
# and editions. Some records may be new, and some may be existing.
# XXX : bulk-*

Reviewers: what should be done for this case?

# Clear patron's profile page:
data = {'key': patron.key, 'type': '/type/delete'}
patron.set_data(data)
patron.set_data(data, "delete-profile")
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll likely want to filter these from /recentchanges.

web.ctx.ip = web.ctx.ip or '127.0.0.1'
web.ctx.site.save_many(updated_eds, comment="Redacting ocaids")
web.ctx.site.save_many(
updated_eds, comment="Redacting ocaids", action="bulk-redact-ocaid"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
updated_eds, comment="Redacting ocaids", action="bulk-redact-ocaid"
updated_eds, comment="Redacting ocaids", action="bulk-edit-books"

I don't know if a bulk-redact-ocaid action is helpful at all. If it is, we'll probably want to exclude these from /recentchanges views.

else:
prev._save("revert to revision %d" % prev.revision)
prev._save(
"revert to revision %d" % prev.revision, action="revert-version"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to exclude these changes from /recentchanges views?

edition.set_identifiers(data)
saveutil.save(edition)
# `action` : edit-book-isbn?
saveutil.commit(comment="Added an %s identifier." % typ, action="edit-book")
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe update-book is more memorable?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...we shouldn't change this, though...

Should we conform to an edit-{record} pattern when naming actions (e.g. edit-tag or edit-author)? I've been prefixing update- to other such actions, but edit- has precedence.

Comment on lines +713 to +714
# TODO : pull type from key, then compose `delete-{type}` action string
doc._save(comment=comment, action="delete-?")
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewers: What should be done for this case? Do we want the actual type that's being deleted, or would delete-record suffice?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants