{"id":50277873,"url":"https://github.com/imsweb/ims.trashcan","last_synced_at":"2026-05-27T22:02:15.195Z","repository":{"id":62570799,"uuid":"269117170","full_name":"imsweb/ims.trashcan","owner":"imsweb","description":null,"archived":false,"fork":false,"pushed_at":"2025-04-04T16:51:48.000Z","size":90,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-11-27T18:24:07.343Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/imsweb.png","metadata":{"files":{"readme":"README.md","changelog":"changes/.gitkeep","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-06-03T14:56:46.000Z","updated_at":"2025-04-04T16:51:51.000Z","dependencies_parsed_at":"2025-04-03T21:21:35.254Z","dependency_job_id":"b37961f4-67d1-4db6-9567-d9c9aa9427dc","html_url":"https://github.com/imsweb/ims.trashcan","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/imsweb/ims.trashcan","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imsweb%2Fims.trashcan","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imsweb%2Fims.trashcan/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imsweb%2Fims.trashcan/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imsweb%2Fims.trashcan/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/imsweb","download_url":"https://codeload.github.com/imsweb/ims.trashcan/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imsweb%2Fims.trashcan/sbom","scorecard":{"id":486474,"data":{"date":"2025-08-11","repo":{"name":"github.com/imsweb/ims.trashcan","commit":"8c7356ce9d2536d0b65a353160c6ed70e0ff29b9"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Code-Review","score":0,"reason":"Found 0/29 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.md:0","Info: FSF or OSI recognized license: MIT License: LICENSE.md:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 2 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-19T17:57:03.393Z","repository_id":62570799,"created_at":"2025-08-19T17:57:03.393Z","updated_at":"2025-08-19T17:57:03.393Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33585203,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-27T02:00:06.184Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-27T22:02:14.188Z","updated_at":"2026-05-27T22:02:15.187Z","avatar_url":"https://github.com/imsweb.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"Introduction\n============\n\n\nims.trashcan provides a way to maintain a backup of recently deleted content without having to resort to \nbackups or rolling back commits that could include several entangled commits occurring after the item was\ndeleted.\n\n\nHow it Works\n============\n\nThe goal of this package is to store actual python objects without them being an actual\nobject in the database. A trashed item should not be able to be traversed to or have any of its methods\nor attributes accessed, but exist only as binary data until restored. To do this they are stored as\npython pickles. Pickles are serialized python objects that can be deserialized and in fact this is how\nthe ZODB operates. To store data as pickles, we actually create a ZODB Blob using the ZODB export\nprocess. A trashed item has the following attributes:\n\n - data: a Blob instance of the ZODB export\n - path: the traversal path from which it was deleted\n - id: a unique id in the trash can, consisting of the original item's id + a timestamp\n - title: title of the original item\n - _created: a DateTime instance of when the trash item was created\n \nTrashed items are restored using an inverse process, where ZODB import deserializes the Blob pickle.\n\n\nConfiguration and Cleanup\n============\n\nIn the ZMI in a site's portal_trash_can you can configure the number of days to keep content (default 7)\nbefore removing from the site (you will still need to pack your database after this point for them to be\nfully removed from it). This will be respected by this tool's `__call__` method which you can access\nprogrammatically:\n\n```python\n\u003e\u003e\u003e import plone.api\n\u003e\u003e\u003e can = plone.api.portal.get('portal_trash_can')\n\u003e\u003e\u003e can()\n```\n\nor via HTTP request `http://localhost:8080/Plone/portal_trash_can`\n\nIt is recommended that you set up a cron job to execute this periodically.\n\n\n# Trashable Items\n\nBy default, any object that provides the plone.dexterity.interfaces.IDexterityItem interface will be\ntrashable. Not that this does not include IDexterityContainer. It is possible to register other subscribers\nto trigger entering the trashcan using ZCML. This example shows how to register an imagined interface\n\"foo.bar.interfaces.IMyInterface\" in the event that it is deleted (IObjectRemovedEvent).\n\n```xml\n  \u003csubscriber\n     for=\"foo.bar.interfaces.IMyInterface\n          zope.lifecycleevent.interfaces.IObjectRemovedEvent\"\n     handler=\"ims.trashcan.events.trashEventHandler\" /\u003e\n```\n\nNote that this\nis completely unnecessary for items that are Dexterity non-container objects and in fact would result in\nduplicates\n\n## Deleted containers\n\nBy default, IDexterityContainer is not a registered trashable interface. This is because the IObjectRemovedEvent\nwill trigger twice for any contained objects if we try to trash it. This not only doubles the amount of\ndata entering the trash can, it also doubles the overhead time needed to delete the item. Generally, \ncontainers only have metadata and what we care about are the pages and files contained in it.\nIt is strongly recommended that you do not register a container as trashable unless you understand this\ncaveat and deem it absolutely necessary and worth the added cost.\n\n# Restoring Items\n\nTo restore an item, first find it via the site's ZMI: ${site_url}/portal_trash_can/manage_workspace. This\nlists all non-expired deleted items by \"id + timestamp\". Accessing a trashed item contains metadata\nincluding original title and deletion date/time. There are no options when restoring content, it will\nsimply restore it to the original location. If that original location no longer exists, it will\nrestore it to the portal root\n\n## Restoring an Entire Folder\n\nBarring custom delete subscribers for that content type, a folderish object will generally not itself\nbe included in the trash can. Consider the following folder structure given these ids.\n\n```\nportal/\n└── folderA/\n    ├── page1\n    └── page2\n```\nIf folderA is deleted, the trash can will contain two entries: page1 and page2. By default, since\nfolderA no longer exists, these would be restored to the portal root. You can however first create\na root folder folderA and then restore these pages, which will restore them to their original location.\n\n### Exceptions\n\nIn some cases it will not be possible to restore content without additional work:\n- The deleted page has been replaced by a new page with an identical id. You will have to rename the id\nof the new page before being able to restore\n- A deleted folder has been replaced by a non-container with the identical id. In the above example, this\nwould correlate to /portal/folderA being replaced by e.g. a Page with that path. You will not be able to restore\n/portal/folderA/page1 because the current /portal/folderA cannot contain items. You will need to rename\nthe new folderA and create an actual folder in its place\n- The python class is no longer included in the Zope client. In this case the pickle cannot be\ndeserialized until it is included in Zope. You will need to restore the client and/or roll back changes\nto make that class available.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fimsweb%2Fims.trashcan","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fimsweb%2Fims.trashcan","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fimsweb%2Fims.trashcan/lists"}