graphene-django-plus


Namegraphene-django-plus JSON
Version 5.1 PyPI version JSON
download
home_pagehttps://github.com/0soft/graphene-django-plus
SummaryTools to easily create permissioned CRUD endpoints in graphene.
upload_time2023-06-20 18:10:19
maintainer
docs_urlNone
authorThiago Bellini Ribeiro
requires_python>=3.8,<4.0
licenseMIT
keywords graphene django graphql crud permissions
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage
            # graphene-django-plus

[![build status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2F0soft%2Fgraphene-django-plus%2Fbadge%3Fref%3Dmaster&style=flat)](https://actions-badge.atrox.dev/0soft/graphene-django-plus/goto?ref=master)
[![docs status](https://img.shields.io/readthedocs/graphene-django-plus.svg)](https://graphene-django-plus.readthedocs.io)
[![coverage](https://img.shields.io/codecov/c/github/0soft/graphene-django-plus.svg)](https://codecov.io/gh/0soft/graphene-django-plus)
[![PyPI version](https://img.shields.io/pypi/v/graphene-django-plus.svg)](https://pypi.org/project/graphene-django-plus/)
![python version](https://img.shields.io/pypi/pyversions/graphene-django-plus.svg)
![django version](https://img.shields.io/pypi/djversions/graphene-django-plus.svg)

> ## DEPRECATION WARNING
>
> Graphene itself is abandoned and most users are migrating to other better alternatives, like
> [strawberry](https://github.com/strawberry-graphql/strawberry).
>
> For that reason this lib is being deprecated and new features will no longer be developed for it.
> Maintenance is still going to happen and PRs are still welcomed though.
>
> For anyone looking for alternatives, I created
> [strawberry-django-plus](https://github.com/blb-ventures/strawberry-django-plus) to use not
> only as a migration path to the projects I maintain, but also to add even more awesome features.
> Be sure to check it out!

Tools to easily create permissioned CRUD endpoints in [graphene-django](https://github.com/graphql-python/graphene-django).

## Install

```bash
pip install graphene-django-plus
```

To make use of everything this lib has to offer, it is recommended to install
both [graphene-django-optimizer](https://github.com/tfoxy/graphene-django-optimizer)
and [django-guardian](https://github.com/django-guardian/django-guardian).

```bash
pip install graphene-django-optimizer django-guardian
```

## What it does

- Provides some base types for Django Models to improve querying them with:
  - Unauthenticated user handling
  - Automatic optimization using [graphene-django-optimizer](https://github.com/tfoxy/graphene-django-optimizer)
  - Permission handling for queries using the default [django permission system](https://docs.djangoproject.com/en/2.2/topics/auth/default/#topic-authorization)
  - Object permission handling for queries using [django guardian](https://github.com/django-guardian/django-guardian)
  - Relay id conversion so querying can use the global id instead of the model's id
- Provides a set of complete and simple CRUD mutations with:
  - Unauthenticated user handling
  - Permission handling using the default [django permission system](https://docs.djangoproject.com/en/2.2/topics/auth/default/#topic-authorization)
  - Object permission handling using [django guardian](https://github.com/django-guardian/django-guardian)
  - Automatic input generation based on the model (no need to write your own input type or use `django forms` and `drf serializers`)
  - Automatic model validation based on the model's validators
- Very simple to create some quick CRUD endpoints for your models
- Easy to extend and override functionalities
- File upload handling

## What is included

Check the [docs](https://graphene-django-plus.readthedocs.io) for a complete
api documentation.

### Models

- `graphene_django_plus.models.GuardedModel`: A django model that can be used
  either directly or as a mixin. It will provide a `.has_perm` method and a
  `.objects.for_user` that will be used by `ModelType` described below to
  check for object permissions.

### Types and Queries

- `graphene_django_plus.types.ModelType`: This enchances
  `graphene_django_plus.DjangoModelType` by doing some automatic `prefetch`
  optimization on setup and also checking for objects permissions on queries
  when it inherits from `GuardedModel`.

- `graphene_django_plus.fields.CountableConnection`: This enchances
  `graphene.relay.Connection` to provide a `total_count` attribute.

Here is an example describing how to use those:

```py
import graphene
from graphene import relay
from graphene_django.fields import DjangoConnectionField

from graphene_django_plus.models import GuardedModel
from graphene_django_plus.types import ModelType
from graphene_django_plus.fields import CountableConnection


class MyModel(GuardedModel):
    class Meta:
        # guardian permissions for this model
        permissions = [
            ('can_read', "Can read the this object's info."),
        ]

    name = models.CharField(max_length=255)


class MyModelType(ModelType):
    class Meta:
        model = MyModel
        interfaces = [relay.Node]

        # Use our CountableConnection
        connection_class = CountableConnection

        # When adding this to a query, only objects with a `can_read`
        # permission to the request's user will be allowed to return to him
        # Note that `can_read` was defined in the model.
        # If the model doesn't inherid from `GuardedModel`, `guardian` is not
        # installed or this list is empty, any object will be allowed.
        # This is empty by default
        object_permissions = [
            'can_read',
        ]

        # If unauthenticated users should be allowed to retrieve any object
        # of this type. This is not dependent on `GuardedModel` and neither
        # `guardian` and is defined as `False` by default
        public = False

        # A list of Django model permissions to check. Different from
        # object_permissions, this uses the basic Django's permission system
        # and thus is not dependent on `GuardedModel` and neither `guardian`.
        # This is an empty list by default.
        permissions = []


class Query(graphene.ObjectType):
    my_models = DjangoConnectionField(MyModelType)
    my_model = relay.Node.Field(MyModelType)
```

This can be queried like:

```graphql
# All objects that the user has permission to see
query {
  myModels {
    totalCount
    edges {
      node {
        id
        name
      }
    }
  }
}

# Single object if the user has permission to see it
query {
  myModel(id: "<relay global ID>") {
    id
    name
  }
}
```

### Mutations

- `graphene_django_plus.mutations.BaseMutation`: Base mutation using `relay`
  and some basic permission checking. Just override its `.perform_mutation` to
  perform the mutation.

- `graphene_django_plus.mutations.ModelMutation`: Model mutation capable of
  both creating and updating a model based on the existence of an `id`
  attribute in the input. All the model's fields will be automatically read
  from Django, inserted in the input type and validated.

- `graphene_django_plus.mutations.ModelCreateMutation`: A `ModelMutation`
  enforcing a "create only" rule by excluding the `id` field from the input.

- `graphene_django_plus.mutations.ModelUpdateMutation`: A `ModelMutation`
  enforcing a "update only" rule by making the `id` field required in the
  input.

- `graphene_django_plus.mutations.ModelDeleteMutation`: A mutation that will
  receive only the model's id and will delete it (if given permission, of
  course).

Here is an example describing how to use those:

```py
import graphene
from graphene import relay

from graphene_django_plus.models import GuardedModel
from graphene_django_plus.types import ModelType
from graphene_django_plus.mutations import (
    ModelCreateMutation,
    ModelUpdateMutation,
    ModelDeleteMutation,
)


class MyModel(GuardedModel):
    class Meta:
        # guardian permissions for this model
        permissions = [
            ('can_write', "Can update this object's info."),
        ]

    name = models.CharField(max_length=255)


class MyModelType(ModelType):
    class Meta:
        model = MyModel
        interfaces = [relay.Node]


class MyModelUpdateMutation(ModelUpdateMutation):
    class Meta:
        model = MyModel

        # Make sure only users with the given permissions can modify the
        # object.
        # If the model doesn't inherid from `GuardedModel`, `guardian` is not
        # installed on this list is empty, any object will be allowed.
        # This is empty by default.
        object_permissions = [
            'can_write',
        ]

        # If unauthenticated users should be allowed to retrieve any object
        # of this type. This is not dependent on `GuardedModel` and neither
        # `guardian` and is defined as `False` by default
        public = False

        # A list of Django model permissions to check. Different from
        # object_permissions, this uses the basic Django's permission system
        # and thus is not dependent on `GuardedModel` and neither `guardian`.
        # This is an empty list by default.
        permissions = []


class MyModelDeleteMutation(ModelDeleteMutation):
    class Meta:
        model = MyModel
        object_permissions = [
            'can_write',
        ]


class MyModelCreateMutation(ModelCreateMutation):
    class Meta:
        model = MyModel

    @classmethod
    def after_save(cls, info, instance, cleaned_input=None):
        # If the user created the object, allow him to modify it
        assign_perm('can_write', info.context.user, instance)


class Mutation(graphene.ObjectType):
    my_model_create = MyModelCreateMutation.Field()
    my_model_update = MyModelUpdateMutation.Field()
    my_model_delete = MyModelDeleteMutation.Field()
```

This can be used to create/update/delete like:

```graphql
# Create mutation
mutation {
  myModelCreate(input: { name: "foobar" }) {
    myModel {
      name
    }
    errors {
      field
      message
    }
  }
}

# Update mutation
mutation {
  myModelUpdate(input: { id: "<relay global ID>", name: "foobar" }) {
    myModel {
      name
    }
    errors {
      field
      message
    }
  }
}

# Delete mutation
mutation {
  myModelDelete(input: { id: "<relay global ID>" }) {
    myModel {
      name
    }
    errors {
      field
      message
    }
  }
}
```

Any validation errors will be presented in the `errors` return value.

To turn off auto related relations addition to the mutation input - set global
`MUTATIONS_INCLUDE_REVERSE_RELATIONS` parameter to `False` in your
`settings.py`:

```
GRAPHENE_DJANGO_PLUS = {
    'MUTATIONS_INCLUDE_REVERSE_RELATIONS': False
}
```

Note: in case reverse relation does not have `related_name` attribute set -
mutation input will be generated as Django itself is generating by appending
`_set` to the lower cased model name - `modelname_set`

## License

This project is licensed under MIT licence (see `LICENSE` for more info)

## Contributing

Make sure to have [poetry](https://python-poetry.org/) installed.

Install dependencies with:

```bash
poetry install
```

Run the testsuite with:

```bash
poetry run pytest
```

Feel free to fork the project and send me pull requests with new features,
corrections and translations. We'll gladly merge them and release new versions
ASAP.

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/0soft/graphene-django-plus",
    "name": "graphene-django-plus",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.8,<4.0",
    "maintainer_email": "",
    "keywords": "graphene,django,graphql,crud,permissions",
    "author": "Thiago Bellini Ribeiro",
    "author_email": "thiago@bellini.dev",
    "download_url": "https://files.pythonhosted.org/packages/ef/76/4ecc6be72bf3dffe947ff930a13c9aa7a0dd29b2c2dfdb6bcafef72bc89c/graphene_django_plus-5.1.tar.gz",
    "platform": null,
    "description": "# graphene-django-plus\n\n[![build status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2F0soft%2Fgraphene-django-plus%2Fbadge%3Fref%3Dmaster&style=flat)](https://actions-badge.atrox.dev/0soft/graphene-django-plus/goto?ref=master)\n[![docs status](https://img.shields.io/readthedocs/graphene-django-plus.svg)](https://graphene-django-plus.readthedocs.io)\n[![coverage](https://img.shields.io/codecov/c/github/0soft/graphene-django-plus.svg)](https://codecov.io/gh/0soft/graphene-django-plus)\n[![PyPI version](https://img.shields.io/pypi/v/graphene-django-plus.svg)](https://pypi.org/project/graphene-django-plus/)\n![python version](https://img.shields.io/pypi/pyversions/graphene-django-plus.svg)\n![django version](https://img.shields.io/pypi/djversions/graphene-django-plus.svg)\n\n> ## DEPRECATION WARNING\n>\n> Graphene itself is abandoned and most users are migrating to other better alternatives, like\n> [strawberry](https://github.com/strawberry-graphql/strawberry).\n>\n> For that reason this lib is being deprecated and new features will no longer be developed for it.\n> Maintenance is still going to happen and PRs are still welcomed though.\n>\n> For anyone looking for alternatives, I created\n> [strawberry-django-plus](https://github.com/blb-ventures/strawberry-django-plus) to use not\n> only as a migration path to the projects I maintain, but also to add even more awesome features.\n> Be sure to check it out!\n\nTools to easily create permissioned CRUD endpoints in [graphene-django](https://github.com/graphql-python/graphene-django).\n\n## Install\n\n```bash\npip install graphene-django-plus\n```\n\nTo make use of everything this lib has to offer, it is recommended to install\nboth [graphene-django-optimizer](https://github.com/tfoxy/graphene-django-optimizer)\nand [django-guardian](https://github.com/django-guardian/django-guardian).\n\n```bash\npip install graphene-django-optimizer django-guardian\n```\n\n## What it does\n\n- Provides some base types for Django Models to improve querying them with:\n  - Unauthenticated user handling\n  - Automatic optimization using [graphene-django-optimizer](https://github.com/tfoxy/graphene-django-optimizer)\n  - Permission handling for queries using the default [django permission system](https://docs.djangoproject.com/en/2.2/topics/auth/default/#topic-authorization)\n  - Object permission handling for queries using [django guardian](https://github.com/django-guardian/django-guardian)\n  - Relay id conversion so querying can use the global id instead of the model's id\n- Provides a set of complete and simple CRUD mutations with:\n  - Unauthenticated user handling\n  - Permission handling using the default [django permission system](https://docs.djangoproject.com/en/2.2/topics/auth/default/#topic-authorization)\n  - Object permission handling using [django guardian](https://github.com/django-guardian/django-guardian)\n  - Automatic input generation based on the model (no need to write your own input type or use `django forms` and `drf serializers`)\n  - Automatic model validation based on the model's validators\n- Very simple to create some quick CRUD endpoints for your models\n- Easy to extend and override functionalities\n- File upload handling\n\n## What is included\n\nCheck the [docs](https://graphene-django-plus.readthedocs.io) for a complete\napi documentation.\n\n### Models\n\n- `graphene_django_plus.models.GuardedModel`: A django model that can be used\n  either directly or as a mixin. It will provide a `.has_perm` method and a\n  `.objects.for_user` that will be used by `ModelType` described below to\n  check for object permissions.\n\n### Types and Queries\n\n- `graphene_django_plus.types.ModelType`: This enchances\n  `graphene_django_plus.DjangoModelType` by doing some automatic `prefetch`\n  optimization on setup and also checking for objects permissions on queries\n  when it inherits from `GuardedModel`.\n\n- `graphene_django_plus.fields.CountableConnection`: This enchances\n  `graphene.relay.Connection` to provide a `total_count` attribute.\n\nHere is an example describing how to use those:\n\n```py\nimport graphene\nfrom graphene import relay\nfrom graphene_django.fields import DjangoConnectionField\n\nfrom graphene_django_plus.models import GuardedModel\nfrom graphene_django_plus.types import ModelType\nfrom graphene_django_plus.fields import CountableConnection\n\n\nclass MyModel(GuardedModel):\n    class Meta:\n        # guardian permissions for this model\n        permissions = [\n            ('can_read', \"Can read the this object's info.\"),\n        ]\n\n    name = models.CharField(max_length=255)\n\n\nclass MyModelType(ModelType):\n    class Meta:\n        model = MyModel\n        interfaces = [relay.Node]\n\n        # Use our CountableConnection\n        connection_class = CountableConnection\n\n        # When adding this to a query, only objects with a `can_read`\n        # permission to the request's user will be allowed to return to him\n        # Note that `can_read` was defined in the model.\n        # If the model doesn't inherid from `GuardedModel`, `guardian` is not\n        # installed or this list is empty, any object will be allowed.\n        # This is empty by default\n        object_permissions = [\n            'can_read',\n        ]\n\n        # If unauthenticated users should be allowed to retrieve any object\n        # of this type. This is not dependent on `GuardedModel` and neither\n        # `guardian` and is defined as `False` by default\n        public = False\n\n        # A list of Django model permissions to check. Different from\n        # object_permissions, this uses the basic Django's permission system\n        # and thus is not dependent on `GuardedModel` and neither `guardian`.\n        # This is an empty list by default.\n        permissions = []\n\n\nclass Query(graphene.ObjectType):\n    my_models = DjangoConnectionField(MyModelType)\n    my_model = relay.Node.Field(MyModelType)\n```\n\nThis can be queried like:\n\n```graphql\n# All objects that the user has permission to see\nquery {\n  myModels {\n    totalCount\n    edges {\n      node {\n        id\n        name\n      }\n    }\n  }\n}\n\n# Single object if the user has permission to see it\nquery {\n  myModel(id: \"<relay global ID>\") {\n    id\n    name\n  }\n}\n```\n\n### Mutations\n\n- `graphene_django_plus.mutations.BaseMutation`: Base mutation using `relay`\n  and some basic permission checking. Just override its `.perform_mutation` to\n  perform the mutation.\n\n- `graphene_django_plus.mutations.ModelMutation`: Model mutation capable of\n  both creating and updating a model based on the existence of an `id`\n  attribute in the input. All the model's fields will be automatically read\n  from Django, inserted in the input type and validated.\n\n- `graphene_django_plus.mutations.ModelCreateMutation`: A `ModelMutation`\n  enforcing a \"create only\" rule by excluding the `id` field from the input.\n\n- `graphene_django_plus.mutations.ModelUpdateMutation`: A `ModelMutation`\n  enforcing a \"update only\" rule by making the `id` field required in the\n  input.\n\n- `graphene_django_plus.mutations.ModelDeleteMutation`: A mutation that will\n  receive only the model's id and will delete it (if given permission, of\n  course).\n\nHere is an example describing how to use those:\n\n```py\nimport graphene\nfrom graphene import relay\n\nfrom graphene_django_plus.models import GuardedModel\nfrom graphene_django_plus.types import ModelType\nfrom graphene_django_plus.mutations import (\n    ModelCreateMutation,\n    ModelUpdateMutation,\n    ModelDeleteMutation,\n)\n\n\nclass MyModel(GuardedModel):\n    class Meta:\n        # guardian permissions for this model\n        permissions = [\n            ('can_write', \"Can update this object's info.\"),\n        ]\n\n    name = models.CharField(max_length=255)\n\n\nclass MyModelType(ModelType):\n    class Meta:\n        model = MyModel\n        interfaces = [relay.Node]\n\n\nclass MyModelUpdateMutation(ModelUpdateMutation):\n    class Meta:\n        model = MyModel\n\n        # Make sure only users with the given permissions can modify the\n        # object.\n        # If the model doesn't inherid from `GuardedModel`, `guardian` is not\n        # installed on this list is empty, any object will be allowed.\n        # This is empty by default.\n        object_permissions = [\n            'can_write',\n        ]\n\n        # If unauthenticated users should be allowed to retrieve any object\n        # of this type. This is not dependent on `GuardedModel` and neither\n        # `guardian` and is defined as `False` by default\n        public = False\n\n        # A list of Django model permissions to check. Different from\n        # object_permissions, this uses the basic Django's permission system\n        # and thus is not dependent on `GuardedModel` and neither `guardian`.\n        # This is an empty list by default.\n        permissions = []\n\n\nclass MyModelDeleteMutation(ModelDeleteMutation):\n    class Meta:\n        model = MyModel\n        object_permissions = [\n            'can_write',\n        ]\n\n\nclass MyModelCreateMutation(ModelCreateMutation):\n    class Meta:\n        model = MyModel\n\n    @classmethod\n    def after_save(cls, info, instance, cleaned_input=None):\n        # If the user created the object, allow him to modify it\n        assign_perm('can_write', info.context.user, instance)\n\n\nclass Mutation(graphene.ObjectType):\n    my_model_create = MyModelCreateMutation.Field()\n    my_model_update = MyModelUpdateMutation.Field()\n    my_model_delete = MyModelDeleteMutation.Field()\n```\n\nThis can be used to create/update/delete like:\n\n```graphql\n# Create mutation\nmutation {\n  myModelCreate(input: { name: \"foobar\" }) {\n    myModel {\n      name\n    }\n    errors {\n      field\n      message\n    }\n  }\n}\n\n# Update mutation\nmutation {\n  myModelUpdate(input: { id: \"<relay global ID>\", name: \"foobar\" }) {\n    myModel {\n      name\n    }\n    errors {\n      field\n      message\n    }\n  }\n}\n\n# Delete mutation\nmutation {\n  myModelDelete(input: { id: \"<relay global ID>\" }) {\n    myModel {\n      name\n    }\n    errors {\n      field\n      message\n    }\n  }\n}\n```\n\nAny validation errors will be presented in the `errors` return value.\n\nTo turn off auto related relations addition to the mutation input - set global\n`MUTATIONS_INCLUDE_REVERSE_RELATIONS` parameter to `False` in your\n`settings.py`:\n\n```\nGRAPHENE_DJANGO_PLUS = {\n    'MUTATIONS_INCLUDE_REVERSE_RELATIONS': False\n}\n```\n\nNote: in case reverse relation does not have `related_name` attribute set -\nmutation input will be generated as Django itself is generating by appending\n`_set` to the lower cased model name - `modelname_set`\n\n## License\n\nThis project is licensed under MIT licence (see `LICENSE` for more info)\n\n## Contributing\n\nMake sure to have [poetry](https://python-poetry.org/) installed.\n\nInstall dependencies with:\n\n```bash\npoetry install\n```\n\nRun the testsuite with:\n\n```bash\npoetry run pytest\n```\n\nFeel free to fork the project and send me pull requests with new features,\ncorrections and translations. We'll gladly merge them and release new versions\nASAP.\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Tools to easily create permissioned CRUD endpoints in graphene.",
    "version": "5.1",
    "project_urls": {
        "Documentation": "https://graphene-django-plus.readthedocs.io",
        "Homepage": "https://github.com/0soft/graphene-django-plus",
        "Repository": "https://github.com/0soft/graphene-django-plus"
    },
    "split_keywords": [
        "graphene",
        "django",
        "graphql",
        "crud",
        "permissions"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "f3f497d10886b6e6d8a1b0ba14d50572899c58494d6654bcd10c1929fffef4c6",
                "md5": "921140234d35f0ef8869b7e20057eb5f",
                "sha256": "428281136e281656a02f496b5ef5b56b9b4f92ebf0afd8a04e183ebc8880c2a4"
            },
            "downloads": -1,
            "filename": "graphene_django_plus-5.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "921140234d35f0ef8869b7e20057eb5f",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8,<4.0",
            "size": 26465,
            "upload_time": "2023-06-20T18:10:17",
            "upload_time_iso_8601": "2023-06-20T18:10:17.716971Z",
            "url": "https://files.pythonhosted.org/packages/f3/f4/97d10886b6e6d8a1b0ba14d50572899c58494d6654bcd10c1929fffef4c6/graphene_django_plus-5.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "ef764ecc6be72bf3dffe947ff930a13c9aa7a0dd29b2c2dfdb6bcafef72bc89c",
                "md5": "1093fcfbdedfc0a685169583e1bbef89",
                "sha256": "2923d196ab3f392aab17b06a8a19d836d6eb82df932e4f0aaec2212fb6827f2e"
            },
            "downloads": -1,
            "filename": "graphene_django_plus-5.1.tar.gz",
            "has_sig": false,
            "md5_digest": "1093fcfbdedfc0a685169583e1bbef89",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8,<4.0",
            "size": 25039,
            "upload_time": "2023-06-20T18:10:19",
            "upload_time_iso_8601": "2023-06-20T18:10:19.325302Z",
            "url": "https://files.pythonhosted.org/packages/ef/76/4ecc6be72bf3dffe947ff930a13c9aa7a0dd29b2c2dfdb6bcafef72bc89c/graphene_django_plus-5.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-06-20 18:10:19",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "0soft",
    "github_project": "graphene-django-plus",
    "travis_ci": false,
    "coveralls": true,
    "github_actions": true,
    "lcname": "graphene-django-plus"
}
        
Elapsed time: 0.08758s