{"id":34035579,"url":"https://github.com/topletal/django-model-mutations","last_synced_at":"2026-04-07T06:31:15.184Z","repository":{"id":52698290,"uuid":"219967268","full_name":"topletal/django-model-mutations","owner":"topletal","description":"Graphene Django mutations for Django models made easier","archived":false,"fork":false,"pushed_at":"2023-04-03T08:28:24.000Z","size":36,"stargazers_count":24,"open_issues_count":3,"forks_count":6,"subscribers_count":2,"default_branch":"master","last_synced_at":"2026-01-26T22:09:22.842Z","etag":null,"topics":["django","django-models","graphene","graphene-django","graphql","python"],"latest_commit_sha":null,"homepage":"","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/topletal.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-11-06T10:08:11.000Z","updated_at":"2025-04-09T10:10:08.000Z","dependencies_parsed_at":"2022-09-26T21:40:16.732Z","dependency_job_id":null,"html_url":"https://github.com/topletal/django-model-mutations","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/topletal/django-model-mutations","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/topletal%2Fdjango-model-mutations","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/topletal%2Fdjango-model-mutations/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/topletal%2Fdjango-model-mutations/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/topletal%2Fdjango-model-mutations/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/topletal","download_url":"https://codeload.github.com/topletal/django-model-mutations/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/topletal%2Fdjango-model-mutations/sbom","scorecard":{"id":894631,"data":{"date":"2025-08-11","repo":{"name":"github.com/topletal/django-model-mutations","commit":"ea55bfc495fa6058669f1f5c66e16833d185a4ca"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.7,"checks":[{"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":"Code-Review","score":0,"reason":"Found 1/13 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":"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":"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":"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":"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":"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":"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":"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":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.txt:0","Info: FSF or OSI recognized license: MIT License: LICENSE.txt: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"}},{"name":"Vulnerabilities","score":0,"reason":"32 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: PYSEC-2022-190 / GHSA-2gwj-7jmv-h26r","Warn: Project is vulnerable to: PYSEC-2020-32 / GHSA-2m34-jcjv-45xf","Warn: Project is vulnerable to: PYSEC-2020-36 / GHSA-3gh2-xw74-jmcw","Warn: Project is vulnerable to: PYSEC-2022-1 / GHSA-53qw-q765-4fww","Warn: Project is vulnerable to: PYSEC-2021-98 / GHSA-68w8-qjq3-2gfm","Warn: Project is vulnerable to: PYSEC-2022-20 / GHSA-6cw3-g6wv-c2xv","Warn: Project is vulnerable to: GHSA-7xr5-9hcq-chf9","Warn: Project is vulnerable to: PYSEC-2022-2 / GHSA-8c5j-9r9f-c6w8","Warn: Project is vulnerable to: GHSA-8x94-hmjh-97hq","Warn: Project is vulnerable to: PYSEC-2022-19 / GHSA-95rw-fx8r-36v6","Warn: Project is vulnerable to: PYSEC-2020-34 / GHSA-fr28-569j-53c4","Warn: Project is vulnerable to: PYSEC-2021-9 / GHSA-fvgf-6h6h-3322","Warn: Project is vulnerable to: PYSEC-2020-35 / GHSA-hmr4-m2h5-33qx","Warn: Project is vulnerable to: PYSEC-2019-15 / GHSA-hvmf-r92r-27hr","Warn: Project is vulnerable to: PYSEC-2022-3 / GHSA-jrh2-hc4r-7jwx","Warn: Project is vulnerable to: PYSEC-2020-33 / GHSA-m6gj-h9gm-gw44","Warn: Project is vulnerable to: PYSEC-2021-99 / GHSA-p99v-5w3c-jqq9","Warn: Project is vulnerable to: PYSEC-2021-8 / GHSA-qm57-vhq3-3fwf","Warn: Project is vulnerable to: GHSA-rrqc-c2jx-6jgv","Warn: Project is vulnerable to: PYSEC-2021-7 / GHSA-rxjp-mfm9-w4wr","Warn: Project is vulnerable to: PYSEC-2021-439 / GHSA-v6rh-hp5x-86rv","Warn: Project is vulnerable to: PYSEC-2019-16 / GHSA-vfq6-hq5r-27r6","Warn: Project is vulnerable to: PYSEC-2022-191 / GHSA-w24h-v9qh-8gxj","Warn: Project is vulnerable to: PYSEC-2020-31 / GHSA-wpjr-j57x-wxfw","Warn: Project is vulnerable to: PYSEC-2021-6 / GHSA-xgxc-v2qg-chmh","Warn: Project is vulnerable to: GHSA-xqcf-hj92-967m","Warn: Project is vulnerable to: PYSEC-2020-263 / GHSA-fx83-3ph3-9j2q","Warn: Project is vulnerable to: GHSA-gw84-84pc-xp82","Warn: Project is vulnerable to: PYSEC-2020-92 / GHSA-hj5v-574p-mj7c","Warn: Project is vulnerable to: PYSEC-2022-42969","Warn: Project is vulnerable to: GHSA-2m57-hf25-phgg","Warn: Project is vulnerable to: PYSEC-2023-87 / GHSA-rrm6-wvj7-cwh2"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-24T13:13:56.980Z","repository_id":52698290,"created_at":"2025-08-24T13:13:56.980Z","updated_at":"2025-08-24T13:13:56.980Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31503380,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-07T03:10:19.677Z","status":"ssl_error","status_checked_at":"2026-04-07T03:10:13.982Z","response_time":105,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: 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":["django","django-models","graphene","graphene-django","graphql","python"],"created_at":"2025-12-13T20:02:42.151Z","updated_at":"2026-04-07T06:31:15.180Z","avatar_url":"https://github.com/topletal.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Django Model Mutations\n\n[![build status](\n  http://img.shields.io/travis/topletal/django-model-mutations/master.svg?style=flat)](https://travis-ci.org/topletal/django-model-mutations)\n[![PyPI version](https://badge.fury.io/py/django-model-mutations.svg)](https://badge.fury.io/py/django-model-mutations)\n\nThis package adds Mutation classes that make creating graphene mutations with django models easier using Django Rest Framework serializers. It extends graphene Mutation class in a similar way to Django Rest Framework views or original Django views.\n\nIt also provides easy way to add permissions checks or ensure logged-in user, as well as easy way to override or add funcionality similar to django forms or rest framework views - such as ```get_queryset()``` or ```save()``` functions.\n\nThere is also advanced error reporting from rest framework, that returns non-valid fields and error messages.\n\nInspired by [Saleor](https://github.com/mirumee/saleor), [graphene-django-extras](https://github.com/eamigo86/graphene-django-extras/tree/master/graphene_django_extras) and [django-rest-framework](https://github.com/encode/django-rest-framework)\n\n## Installation\n```\npip install django-model-mutations\n```\n\n\n## Basic Usage\nMain classes that this package provides:\n\n| mutations | mixins |\n| ------ | ------ |\n|CreateModelMutation|LoginRequiredMutationMixin|\n|CreateBulkModelMutation|\n|UpdateModelMutation|\n|UpdateBulkModelMutation|\n|DeleteModelMutation|\n|DeleteBulkModelMutation|\n\n\n#### Django usage\nInput type (Arguments) is generated from serializer fields  \nReturn type is retrieved by model from global graphene registry, you just have to import it as in example\n```python\nfrom django_model_mutations import mutations, mixins\nfrom your_app.types import UserType  # important to import types to register in global registry\nfrom your_app.serializers import UserSerializer\n\n\n# Create Mutations\n# use mixins.LoginRequiredMutationMixin to ensure only logged-in user can perform this mutation\n# MAKE SURE this mixin is FIRST in inheritance order\nclass UserCreateMutation(mixins.LoginRequiredMutationMixin, mutations.CreateModelMutation):\n    class Meta:\n        serializer_class = UserSerializer\n        # OPTIONAL META FIELDS:\n        permissions = ('your_app.user_permission',) # OPTIONAL: specify user permissions\n        lookup_field = 'publicId'  # OPTIONAL: specify database lookup column, default is 'id' or 'ids'\n        return_field_name = 'myUser' # OPTIONAL: specify return field name, default is model name\n        input_field_name = 'myUser' # OPTIONAL: specify input field name, defauls is 'input'\n        \n\nclass UserBulkCreateMutation(mutations.CreateBulkModelMutation):\n    class Meta:\n        serializer_class = UserSerializer\n\n\n# Update Mutations\nclass UserUpdateMutation(mutations.UpdateModelMutation):\n    class Meta:\n        serializer_class = UserSerializer\n\n# WARNING: Bulk update DOES NOT USE serializer, due to limitations of rest framework serializer. \n# Instead specify model and argument fields by yourself.\nclass UserBulkUpdateMutation(mutations.UpdateBulkModelMutation):\n    class Arguments:\n        is_active = graphene.Boolean()\n\n    class Meta:\n        model = User\n\n# Delete Mutations\n# delete mutations doesn't use serializers, as there is no need\nclass UserDeleteMutation(mutations.DeleteModelMutation):\n    class Meta:\n        model = User\n\nclass UserBulkDeleteMutation(mutations.DeleteBulkModelMutation):\n    class Meta:\n        model = User\n\n\n# Add to graphene schema as usual\nclass Mutation(graphene.ObjectType):\n    user_create = UserCreateMutation.Field()\n    ....\n\nschema = graphene.Schema(mutation=Mutation)\n```\n\n\n#### GraphQl usage\nThe generated GraphQl schema can be modified with ```Meta``` fields as described above in ```UserCreateMutation```.\n\nBy default all mutations have ```errors``` field with ```field``` and ```messages``` that contain validation errors from rest-framework serializer or lookup errors. For now permission denied and other exceptions will not use this error reporting, but a default one, for usage see tests.\n```graphql\n# default argument name is input\n# default return field name is model name\nmutation userCreate (input: {username: \"myUsername\"}) {\n    user {\n        id\n        username\n    }\n    errors {\n        field\n        messages\n    }\n}\n\n\n# Bulk operations return 'count' and errors\nmutation userBulkCreate (input: [{username: \"myUsername\"}, {username:\"myusername2\"}]) {\n    count\n    errors {\n        field\n        messages\n    }\n}\n\n# update mutations\n# update and delete mutations by default specify lookup field 'id' or 'ids' for bulk mutations\nmutation {\n    userUpdate (id: 2, input: {username:\"newUsername\"} ) {\n        user {\n            id\n            username\n        }  \n        errors {\n            field\n            messages\n        }\n    } \n}   \n\n\nmutation {\n    userBulkUpdate (ids: [2, 3], isActive: false ) {\n        count\n        errors {\n           field\n           messages\n        }\n    }\n}  \n\n\n# delete mutations\nmutation {\n    userDelete (id: 1) {\n        user {\n            id\n        }\n        errors {\n           field\n           messages\n        }\n    }\n}  \n\n\nmutation {\n    userBulkDelete (ids: [1, 2, 3]) {\n        count\n        errors {\n           field\n           messages\n        }\n    }\n}  \n```\n\n### Adding funcionality\nAll classes are derived from ```graphene.Mutation```. When you want to override some major functionality, the best place probabably is ```perform_mutate```, which is called after permission checks from graphene ```mutate```.  \n\nIn general probably the main functions that you want to override are: ```save()``` and ```get_object()``` for single object mutations or ```get_queryset()``` for bulk mutations.  \n```get_object``` or ```get_queryset``` you should override to add more filters for fetching the object.   \n```save``` performs final save/update/delete to database and you can add additional fields there.\n\nExamples:\n```python\n# lets only update users that are inactive and add some random field\nclass UserUpdateInactiveMutation(mutations.UpdateModelMutation):\n    class Meta:\n        model = User\n\n    @classmethod\n    def get_object(cls, object_id, info, **input):\n    # can get the object first and then check\n        obj = super(UserUpdateInactiveMutation, cls).get_object(object_id, info, **input)\n        if obj.is_active:\n            return None\n        return obj\n\n    @classmethod\n    def save(cls, serializer, root, info, **input):\n        saved_object = serializer.save(updated_by=info.context.user)\n        return cls.return_success(saved_object)\n\n\n# same but for bulk mutation we have to override get_queryset\nclass UserBulkUpdateInactiveMutation(mutations.UpdateBulkModelMutation):\n    class Meta:\n        model = User\n\n    @classmethod\n    def get_queryset(cls, object_ids, info, **input):\n        qs = super(UserBulkUpdateInactiveMutation, cls).get_queryset(object_ids, info, **input)\n        qs.filter(is_active=False)\n        return qs\n```\n\nFor the whole function flow, please check the Base models in ```django_model_mutations\\mutations.py```.\nIt was inspired by rest framework, so you can find functions like ```get_serializer_kwargs```, ```get_serializer```, ```validate_instance``` (for example here you can override default ```ValidationError``` exception and return None if you don't want exception of non existing id lookup etc.)\n\n## Contributing\nPull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.\n\nPlease make sure to update tests as appropriate.\n\n## License\n[MIT](https://choosealicense.com/licenses/mit/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftopletal%2Fdjango-model-mutations","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftopletal%2Fdjango-model-mutations","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftopletal%2Fdjango-model-mutations/lists"}