{"id":48664174,"url":"https://github.com/appsembler/gestore","last_synced_at":"2026-04-10T10:34:19.358Z","repository":{"id":46088016,"uuid":"377000021","full_name":"appsembler/gestore","owner":"appsembler","description":"Django object management","archived":false,"fork":false,"pushed_at":"2021-11-15T12:39:48.000Z","size":119,"stargazers_count":2,"open_issues_count":0,"forks_count":2,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-11-06T19:01:39.788Z","etag":null,"topics":["openedx","utility-development"],"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/appsembler.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null}},"created_at":"2021-06-15T01:21:20.000Z","updated_at":"2025-06-23T15:19:40.000Z","dependencies_parsed_at":"2022-09-26T18:30:31.180Z","dependency_job_id":null,"html_url":"https://github.com/appsembler/gestore","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/appsembler/gestore","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appsembler%2Fgestore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appsembler%2Fgestore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appsembler%2Fgestore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appsembler%2Fgestore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/appsembler","download_url":"https://codeload.github.com/appsembler/gestore/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appsembler%2Fgestore/sbom","scorecard":{"id":204105,"data":{"date":"2025-08-11","repo":{"name":"github.com/appsembler/gestore","commit":"e40c4137b041601d9d33969b35af19b8a5b0701a"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.7,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","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":"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":"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":"Code-Review","score":8,"reason":"Found 7/8 approved changesets -- score normalized to 8","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":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/python-publish.yml:1","Warn: no topLevel permission defined: .github/workflows/tests.yml:1","Info: no jobLevel write permissions found"],"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":"Pinned-Dependencies","score":1,"reason":"dependency not pinned by hash detected -- score normalized to 1","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/python-publish.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/appsembler/gestore/python-publish.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/python-publish.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/appsembler/gestore/python-publish.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tests.yml:29: update your workflow using https://app.stepsecurity.io/secureworkflow/appsembler/gestore/tests.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tests.yml:31: update your workflow using https://app.stepsecurity.io/secureworkflow/appsembler/gestore/tests.yml/master?enable=pin","Warn: pipCommand not pinned by hash: .github/workflows/python-publish.yml:23","Warn: pipCommand not pinned by hash: .github/workflows/python-publish.yml:24","Warn: pipCommand not pinned by hash: .github/workflows/tests.yml:37","Warn: pipCommand not pinned by hash: .github/workflows/tests.yml:38","Info:   0 out of   4 GitHub-owned GitHubAction dependencies pinned","Info:   1 out of   1 third-party GitHubAction dependencies pinned","Info:   0 out of   4 pipCommand dependencies pinned"],"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":"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":"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":"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":"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":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE: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":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"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":"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":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 20 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-16T23:20:09.190Z","repository_id":46088016,"created_at":"2025-08-16T23:20:09.190Z","updated_at":"2025-08-16T23:20:09.190Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31638649,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-10T07:40:12.752Z","status":"ssl_error","status_checked_at":"2026-04-10T07:40:11.664Z","response_time":98,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["openedx","utility-development"],"created_at":"2026-04-10T10:34:17.427Z","updated_at":"2026-04-10T10:34:19.347Z","avatar_url":"https://github.com/appsembler.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://www.appsembler.com/\"\u003e\n    \u003cimg width=\"500\" alt=\"Gestore Django object manager\" src=\"https://user-images.githubusercontent.com/11036472/123709664-47cc6f80-d822-11eb-9a97-4f87ba3ca64d.png\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n  \u003cbr\u003e\n  \u003cbr\u003e\n  \"gestore\" means \"manager\" in Italian.\n  \u003cbr\u003e\n  \u003cbr\u003e\n  \u003ca href=\"https://github.com/appsembler/gestore/issues\"\u003eReport bug\u003c/a\u003e\n  ·\n  \u003ca href=\"mailto:security@appsembler.com\"\u003eReport security issues\u003c/a\u003e\n  ·\n  \u003ca href=\"https://www.appsembler.com/\"\u003eAppsembler\u003c/a\u003e\n  ·\n  \u003ca href=\"https://www.appsembler.com/blog/\"\u003eBlog\u003c/a\u003e\n\u003c/p\u003e\n\n\n## Gestore\n\nA set of tools that will help you to \n- Export individual objects from DB. \n- Import exported objects back.\n- Delete objects from the database and all other objects related to it.\n\n## Table of Contents\n1. [Why using this tool](#why-using-this-tool)\n    1. [Gestore vs Django dumpdata and loaddata](#gestore-vs-django-dumpdata-and-loaddata)\n1. [Get started](#get-started)\n1. [How does it work](#how-does-it-work)\n    1. [Export functionality](#export-functionality)\n    1. [Import functionality](#import-functionality)\n    1. [Delete functionality](#delete-functionality)\n    1. [Demo app](#demo-app)\n1. [Releasing](#releasing)\n1. [Challenges](#challenges)\n\n\u003e **Note**\n\u003e \n\u003e Object Import and Delete are not production-ready yet. Use with caution\n\n## Why using this tool\nThis idea came out of Appsembler Multi-Cluster UI/UX Workflows. This tool is handy for supporting multiple clusters. \nOther reasons why having robust Export/Import/Delete functionality on your app would be highly beneficial:\n- Frees your site from lots of data you are not using. It's a great idea to export such data to a file system so you can import it later.\n- Decreases the overhead for data tools.\n- Removes old data to keep your costs down and improve performance of aggregation functions (e.g., data pipelines)\n- Deletes obsolete objects as customers churn.\n- Data export is beneficial for GDPR reasons\n- Some customers want their data now for DR (disaster recovery) reasons, not because they're churning.\n- If you are strongly motivated to create a separate cluster for data that already exists on a current one.\n- Lowers the risk of objects (e.g., trial users) being able to crack your site isolation and access data from paying customers.\n\n### Gestore vs Django dumpdata and loaddata\nWhile the functionality might seem the same, these Gestore commands are entirely different from Django commands.\n\nYou can use Django's `dumpdata` commands to back up (export) your models or whole database. And `loaddata` command helps to import these objects back.\n\nOn the other hand, Gestore's `exportobjects` command will help you export all data across the database that's only related to a given Object. This functionality will make sure that you can import these exported objects successfully later.\n\n### Example\nLet's assume you want to export a specific `Books` object:\n\n\u003cimg width=\"965\" alt=\"Screen Shot 2021-06-28 at 11 16 15 AM\" src=\"https://user-images.githubusercontent.com/11036472/123684405-43905a00-d802-11eb-862b-abc0392b4bf6.png\"\u003e\n\nIn Django, you have to export all Books objects `dumpdata` and load it back later using `loaddata`. This method is not practical in two situations:\n- You only want to export one object, not the whole table.\n- Importing that object back might cause some problems as `Authors` object does not exist the exports file.\n\nGestore helps you overcome these issues altogether. When you provide it with your object ID, Gestore will scan all objects related to it, so when you import it back, you get it to work as expected.\n\n## Get started\n\nStart by installing the package from pip\n```shell\npip install gestore\n```\n\nTo be able to access the management commands, add `gestore` to your installed apps:\n\n```python\nINSTALLED_APPS = [\n    ...\n    'gestore',\n]\n```\n\nNow your project should be ready to use gestore to manage objects.\n\n## How does it work\nThis tool uses BFS to explore all objects in the DB from the given one. In the following GIF, let's assume you want to export object number 3; gestore will fetch its data and process all the objects it's connected to\n![Breadth first search animation](https://media.giphy.com/media/v6P6CSXDAthrRA4ZHi/giphy.gif)\n\n### Export functionality\nThis command will help you export all object-related data once triggered. For every model being processed: we get its data, including linked objects' keys (Foreign, ManyToMany, OneToOne) until we hit a base model that's not connected to any other model (leaf node).\n\nWe use a BFS technique to scrape data element by element from the database until we reach a node without any relations. For each processed object, we store its data and its children's data.\n\n\u003e The output of `exportobjects` can be used as input for `importobjects`.\n\n#### Command Usage\n\n```shell\npython manage.py exportobjects [-d] [-o OUTPUT] [-r [ROOT ...]] objects\n```\n`objects` is a list of objects to be exported. Each of these arguments must match the following syntax: `\u003capp_id\u003e.\u003cModel\u003e.\u003cobject_id\u003e`\n\n##### Example\n```shell\npython manage.py exportobjects auth.User.10 demoapp.Book.4 -o /path/to/exp.json\n```\n\n#### Command Arguments\n\n- `objects` The main argument of the `exportobjects`. Its representation is described above.\n- `--debug` flag is optional. Use it to prevent any file writing. It is helpful to see the JSON output of the data before writing it on your system.\n- `--output` is an optional argument that takes a path string of the location in which you want to store the data exports file.\n- `--root` is an optional argument you can use to skip processing certain models. Check the `generate_objects` for more info.\n- `--bucket` If provided, we will export the objects a GCP bucket in the path provided above (or the auto generated one). This needs settings configurations.\n  \n\n### Import functionality\n\nImporting objects is developed in a way that leverages Django's \n`django.core.serializers.python.Deserializer` functionality. In Django, if you are loading a JSON-formatted object into a Model, Django will check the desired table for that object ID and then determines whether to perform either an update or an insert action on that table.\n\n#### Command Usage\n\n```shell\npython manage.py importobjects [-d] [-o] path\n```\n\n##### Example\n```shell\npython manage.py importobjects /path/to/exp.json\n```\n\n#### Command Arguments\n\n- `path`. The main argument of the `importobjects`. It should point to an export file on your local system.\n- `--debug` performs a dry run. Will not commit or save any changes to the DB.\n- `--override` DANGEROUS. In case of a conflict, this will override objects in the DB with the ones being imported.\n- `--bucket` If provided, we will import the objects from the given path in a GCP bucket. This needs settings configurations.\n\n#### Main issues here\nLet's say I have two objects with the same ID. Both of these objects might have the same schema or might be completely different. How can we perform a safe import without sacrificing the current data and without duplicating all objects?\nIn other words, we have primary key collisions on import and need a strategy to prevent these collisions.\n\nAs this app is still under development, we now route for two ways to solve this:\n- **Manual editing**: We'll collect all conflicts before committing changes, then we notify the developer about them. The developer will go to the export file, check these objects, compare them with the ones in the database, and modify the import file with the desired values. Once satisfied, they can use the import command again.\n- **Force replacement**: Using the `--override` flag allows the command to replace all conflicting objects in the DB with the ones being imported. This is a very DANGEROUS approach and should never be considered in a production environment.\n\n\n#### Ways we are looking into:\n- Using **UUID**s in our system: It's the industry-standard solution making database IDs unique in distributed systems.\n- **Changing conflicting objects IDs**: This is a good solution to avoid all conflicts. We set an offset value (or auto increment) and add it to the new object being inserted in the database. Instead of `ID=1` we end up with `ID=9001`. This approach is nice in case conflicts have been resolved, but might cause data duplicates in case not.\n\n\n### Delete functionality\n#### Not implemented yet.\n\n### Demo app\nThis app is created for the sole purpose of testing and visualizing the manager commands in action. No other functionality is expected out of this app.\n\n## Releasing\nWe publish new releases using GitHub Actions. The following steps must\nbe followed to post a new release:\n- Create a PR to bump the version and get it merged. Version is being stored in [gestore/__init__.py](https://github.com/appsembler/gestore/blob/master/gestore/__init__.py) file which both the commands and the [setup.py](https://github.com/appsembler/gestore/blob/master/setup.py) file read its value from.\n- Once the PR is merged, go and make a new release out of master using the [Draft New Release button](https://docs.github.com/en/github/administering-a-repository/releasing-projects-on-github/managing-releases-in-a-repository):\n  - Mark dev releases as Pre-release so it's clear on GitHub and PyPI\n  - In a minute or so, the release will be published into [PyPI](https://pypi.org/project/gestore/).\n\n### Debugging failed releases\n- Go to the GitHub actions tab and select [Build and upload Python package](https://github.com/appsembler/gestore/actions/workflows/python-publish.yml). \n- Click on it to see build logs. \n\n### Dev releases\nUntil we feel this is production-ready, we will continue only to push releases that contain `dev` in them.\n\n## Challenges\n- **Platform state**: When exporting data from your project, it's assumed that importing it back will take place in the same project with the same data structures. If you upgrade a library that you're using its models, and these models were changed (fields removed, added, type changed), you will face some problems.\n- **Object conflicts** \n  - Some data like _usernames_ are unique cluster-wide; if we're importing such data from another cluster, some could be duplicated or overridden.\n  - Some exported objects might have a similar ID to a different object in the database. This tool will flag these objects for you so you know what to change and what to override.\n- **Using Buckets**: At the moment, we are only supporting GCP Cloud Storage, not only that, but we are using `gsutil` to perform this operation for us. I know this sounds stupid, but it was our only way to do so since `google-cloud-storage` doesn't have support for Python 3.5, which is something we have to support at the moment.\n## Reporting Security Issues\nPlease do not report security issues in public. Please email us \non security@appsembler.com.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fappsembler%2Fgestore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fappsembler%2Fgestore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fappsembler%2Fgestore/lists"}