Giter VIP home page Giter VIP logo

graphene-django's Introduction

Graphene Logo Graphene-Django

build pypi Anaconda-Server Badge coveralls

Graphene-Django is an open-source library that provides seamless integration between Django, a high-level Python web framework, and Graphene, a library for building GraphQL APIs. The library allows developers to create GraphQL APIs in Django quickly and efficiently while maintaining a high level of performance.

Features

  • Seamless integration with Django models
  • Automatic generation of GraphQL schema
  • Integration with Django's authentication and permission system
  • Easy querying and filtering of data
  • Support for Django's pagination system
  • Compatible with Django's form and validation system
  • Extensive documentation and community support

Installation

To install Graphene-Django, run the following command:

pip install graphene-django

Configuration

After installation, add 'graphene_django' to your Django project's INSTALLED_APPS list and define the GraphQL schema in your project's settings:

INSTALLED_APPS = [
    # ...
    'graphene_django',
]

GRAPHENE = {
    'SCHEMA': 'myapp.schema.schema'
}

Usage

To use Graphene-Django, create a schema.py file in your Django app directory and define your GraphQL types and queries:

import graphene
from graphene_django import DjangoObjectType
from .models import MyModel

class MyModelType(DjangoObjectType):
    class Meta:
        model = MyModel

class Query(graphene.ObjectType):
    mymodels = graphene.List(MyModelType)

    def resolve_mymodels(self, info, **kwargs):
        return MyModel.objects.all()

schema = graphene.Schema(query=Query)

Then, expose the GraphQL API in your Django project's urls.py file:

from django.urls import path
from graphene_django.views import GraphQLView
from . import schema

urlpatterns = [
    # ...
    path('graphql/', GraphQLView.as_view(graphiql=True)), # Given that schema path is defined in GRAPHENE['SCHEMA'] in your settings.py
]

Testing

Graphene-Django provides support for testing GraphQL APIs using Django's test client. To create tests, create a tests.py file in your Django app directory and write your test cases:

from django.test import TestCase
from graphene_django.utils.testing import GraphQLTestCase
from . import schema

class MyModelAPITestCase(GraphQLTestCase):
    GRAPHENE_SCHEMA = schema.schema

    def test_query_all_mymodels(self):
        response = self.query(
            '''
            query {
                mymodels {
                    id
                    name
                }
            }
            '''
        )

        self.assertResponseNoErrors(response)
        self.assertEqual(len(response.data['mymodels']), MyModel.objects.count())

Contributing

Contributions to Graphene-Django are always welcome! To get started, check the repository's issue tracker and contribution guidelines.

License

Graphene-Django is released under the MIT License.

Resources

Tutorials and Examples

Related Projects

  • Graphene - A library for building GraphQL APIs in Python
  • Graphene-SQLAlchemy - Integration between Graphene and SQLAlchemy, an Object Relational Mapper (ORM) for Python
  • Graphene-File-Upload - A package providing an Upload scalar for handling file uploads in Graphene
  • Graphene-Subscriptions - A package for adding real-time subscriptions to Graphene-based GraphQL APIs

Support

If you encounter any issues or have questions regarding Graphene-Django, feel free to submit an issue on the official GitHub repository. You can also ask for help and share your experiences with the Graphene-Django community on ๐Ÿ’ฌ Discord

Release Notes

graphene-django's People

Contributors

artofhuman avatar bellini666 avatar bossgrand avatar changeling avatar chriscauley avatar danpalmer avatar danyx23 avatar dependabot[bot] avatar doctorjohn avatar dulmandakh avatar firaskafri avatar ganwell avatar jackton1 avatar jkimbo avatar khankuan avatar kiendang avatar mvanlonden avatar nikolaik avatar patrick91 avatar pcraciunoiu avatar phalt avatar pizzapanther avatar sjdemartini avatar stegben avatar syrusakbary avatar tangerilli avatar tcleonard avatar ulgens avatar wsantos avatar zbyte64 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

graphene-django's Issues

Mutations with Django User as SimpleLazyObject throw an error

Upgrading to graphene 1.0 and graphene-django, a mutation I have started throwing an error. Note, queries don't throw the error. None of the django examples incorporate a user object, one with a mutation would be great.

Received incompatible instance "[email protected]"."

https://github.com/graphql-python/graphene-django/blob/master/graphene_django/types.py#L105

I started debugging and found that Django is using the SimpleLazyObject to represent users since they may or may not be anonymous, which is causing DjangoObjectType.is_type_of to throw the error.

The call to is_valid_django_model doesn't pass since SimpeLazyObject isn't a class according to inspect.isclass(model) and the error is raised.

I thought about trying to reify the SimpleLazyObject to the User, talked about here, but decided to just override the is_type_of classmethod for my UserNode.

I'm not super happy with this, since I'm not sure what is_valid_django_model is trying to catch.

class UserNode(DjangoObjectType):
    """Graphene object for User."""

    class Meta:
        model = User
        only_fields = ('first_name', 'last_name', 'email', 'profile')
        filter_fields = ['first_name', 'last_name', 'email']
        filter_order_by = ['first_name']
        interfaces = (Node, )

    @classmethod
    def is_type_of(cls, root, context, info):
        """Override `is_type_of` to take care of django's SimpleLazyObject for the User model in mutations."""
        if isinstance(root, cls):
            return True
        model = root._meta.model
        return model == cls._meta.model

Here's the mutation (stripped down) that was throwing the error:

from graphql_relay.node.node import from_global_id
Rid = namedtuple('Rid', 'name id')

class IntroduceAirplane(relay.ClientIDMutation):
    """Mutation to add and edit airplanes"""

    class Input:
        name = graphene.String()
        id = graphene.String()

    airplane = graphene.Field(AirplaneNode)

    @classmethod
    def mutate_and_get_payload(cls, input, context, info):
        """Method to clean the input and create a rebate."""
        if not context.user.is_authenticated():
            raise Exception('User is not logged in.')

        pk = input.get('id')

        if pk:
            rid = Rid(*from_global_id(pk))
            input.pop('id', None)
            Airplane.objects.filter(pk=rid.id).update(**input)
            airplane = Airplane.objects.get(pk=rid.id)
        else:
            airplane = Airplane.objects.create(created_by=context.user, **input)

        return IntroduceAirplane(airplane=airplane)

Permission System

I would like to add a permission system but want to some feedback on the API before I implement.

You would have two options and I'm proposing to add both:

Option 1: Custom queryset method

This option would let you overwrite how a queryset is filtered.

class UserNode(DjangoObjectType):
  class Meta:
    model = User
    interfaces = (relay.Node,)
    only_fields = ('email', 'first_name', 'last_name')

  @classmethod
  def get_queryset (cls, queryset, args, request, info):
    return queryset.filter(owner=request.user)

Option 2: Permissions List

This option would setup a Meta API to use to define permissions

def auth_required(queryset, args, request, info):
  if request.user.is_authenticated():
    return queryset

  return queryset.none()

class UserNode(DjangoObjectType):
  class Meta:
    model = User
    interfaces = (relay.Node,)
    only_fields = ('email', 'first_name', 'last_name')
    permissions = [auth_required]

If these look like good APIs then I'll implement.

GenericRelations

I've been reading through the source code and the docs and I've been trying to get a GenericRelation to work, but I can't work it out. I feel like there's some cleverness to be done with a resolve method. Any tips?

# ignoring imports

class TaggedItem(models.Model):
    tag = models.SlugField()
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')  # does not resolve

class Bookmark(models.Model):
    url = models.URLField()
    tags = GenericRelation(TaggedItem)  # is not automatically available

class TaggedItemNode(graphene_django.types.DjangoObjectType):
    # parent = graphene.ID() ... ?

    # resolve_parent(...) ... ?

    class Meta:
        model = TaggedItem
        interfaces = (graphene.Node,)

class BookmarkNode(graphene_django.types.DjangoObjectType):
    # tags = graphene.ID()  ... ?

    # resolve_tags(...) ... ?

    class Meta:
        model = Bookmark
        interfaces = (graphene.Node,)

Any help much appreciated.

P

Allow for SimpleLazyObject

We use SimpleLazyObject to populate context.user. However this line errors out since the hacks SimpleLazyObject puts in are not enough to fool inspect.isclass.

I feel I should be able to make a pull, but my testing / python skills are still young, and i have some hard deadlines :(

My feelings is you may be able to simply do detection FOR if its a correctly created SimpleLazyObject (this being a django specific extension)... Docs here

`ManyToOneRel` fields are wrongly named, causing the field resolver to return every single record.

<This is on Django 1.8.16>

A foreign key on model Article to model Reporter will automatically create a reverse field called article_set on the Reporter model. However for some reason, the corresponding related field name does not contain the _set suffix:

>> Reporter.article_set.related.name
'article'

This causes the constructed field on the ReporterObjectType to be wrongly named article at https://github.com/graphql-python/graphene-django/blob/master/graphene_django/types.py#L25, which in turn causes the Reporter.article field to be unresolvable, as the Reporter instance lacks the article attribute.

That in turn causes https://github.com/graphql-python/graphene-django/blob/master/graphene_django/fields.py#L49 to default to the Article model's manager, which results in every Article instance being returned.

Issue with choices that are translations

I have a model that looks something like this:

class Organisation(models.Model):

    CATEGORY_CHOICES = (
        (1, _('Government administrative authorities')),
        (2, _('Authorities under parliament')),
        (3, _('State utilities')),
        (4, _('AP fonds')),
        (5, _('Courts of Sweden and courts administration')),
        (6, _('Swedish foreign authorities')),
    )
    category = models.IntegerField(_('category'), 
        choices=CATEGORY_CHOICES
    )

The import part is that category is a choice that has a translation (which is in Swedish btw).
When I query for this field I get this (as an example):
"category": "STATLIGA_F_RVALTNINGSMYNDIGHETER"

...which looks pretty weird to me.

The schema looks like this:

class OrganisationNode(DjangoObjectType):

    class Meta:
        model = Organisation
        interfaces = (graphene.Node,)

If I instead do this:

 class OrganisationNode(DjangoObjectType):

    category = graphene.String()

    class Meta:
        model = Organisation
        interfaces = (graphene.Node,)

    def resolve_category(self, args, context, info):
        return self.get_category_display()

...the result is as expected, e.g.:
"category": "Statliga fรถrvaltningsmyndigheter"

So, there is a way around it. I was just wondering if this is by design or...??

Sharing DjangoObjectType fields with Mutation

I have this Django Person model which create mutation looks like this:

mutation  {
    createPerson(first_name:"Peter", last_name:"Smith", age:27) {
        person {
            first_name, last_name, age
        }
    }
}
# Result
{
    "createPerson": {
        "person" : {
            "first_name": "Peter",
            "last_name": "Smith",
            "age": 27
        },
    }
}

Is there any way I could transform that to (using alias for createPerson):

mutation  {
    person:createPerson(first_name:"Peter", last_name:"Smith", age:27) {
       first_name,
       last_name,
       age
    }
}
# Result
{
        "person" : {
            "first_name": "Peter",
            "last_name": "Smith",
            "age": 27
        },
}

I know how can I do this using Graphene's AbstractType and ObjectType (Docs' example), but cannot make it work using DjangoObjectType.

Basically, the class CreatePerson should have PersonNode's fields instead of this person field wrapping PersonNode's fields. Is there any way I can do this using PersonNode (DjangoObjectType)?

Please, find the code below:

# Model
class Person(models.Model):
    first_name = models.CharField()
    last_name = models.CharField()
    age = models.IntegerField()

# DjangoObjectType
class PersonNode(DjangoObjectType):
    class Meta:
        model = Person
        interfaces = (Node, )

# CreatePersonMutation
class CreatePerson(graphene.Mutation):
    class Input:
        first_name = graphene.String()
        last_name = graphene.Float()
        age = graphene.Integer()

    person = graphene.Field(PersonNode)

    def mutate(self, args, context, info):
        # do the model mutation...
        return CreatePerson(person=person)

# Creating Schema
class MyMutations(graphene.ObjectType):
    create_person = CreatePerson.Field()

schema = graphene.Schema(mutation=MyMutations)

`select_related` and `prefetch_related`

When I inspected the actual SQL queries being run against DB, I found that for foreignkey and manytomany relationships, we have the n+1 problem.

For example, suppose there are 5 teams, each with 11 members, this query:

query {
  teams {
    edges {
      node {
        members {
          edges {
            node {
              name
            }
          }
        }
      }
    }
  }
}

will result in 1 query for selecting teams, and 5 more queries for selecting members of each team. 6 queries in total.

Normally, we would do a query like this Team.objects.all().prefetch_related('members') to reduce to 2 queries.

I think it would be extremely beneficial if DjangoObjectType can detect special fields:

  • ForeignField
  • OneToOneField
  • OneToManyField
  • ManyToManyField
    and apply appropriate prefetch_related and/or select_related when such fields are present in graph queries.

Help text for arguments

Currently, when using custom filtersets to add additional arguments to a query there does not seem to be any way to change the automatic description. Instead, the description is always "Filter" for any arguments.

It would be good to offer a way to change this, ideally by picking up on the label argument of the Django filter, like this:

class OutcodeFilter(django_filters.FilterSet):
    class Meta:
        model = Outcode
        order_by = ('code', )

    max_time = django_filters.NumberFilter(method='filter_max_time', label="The maximum time")
    def filter_max_time(self, queryset, name, value):
        return queryset.max_time(value)

Access Django Model primary key

Hi,

I would like to do something like this:

allIngredients {
  edges {
    node {
      id
      pk
      name
    }
  }
}

where the result would be something like this:

{
  "id": "Q29tcGFueU5vZGU6MQ==",
  "pk": 42,
  "name": "carrots"
}

Is this possible? How could it be achieved?

Perhaps relatedly, how could I expose model properties?

Thanks!

Filtering fields ignored inside fragments

Hello guys,

I'm trying to run this query and it's completely ignore the 'employeeType'(or any other) filter:

{
 business(id: "") {
    ...F2
  }
}

fragment F2 on BusinessNode {
  employee(employeeType:":ceo") {
    edges {
      node {
        status
        employeeType
        id
      }
    }
  }
}

On the second hand, this query works perfectly fine:

employee(employeeType:":ceo") {
    edges {
     node {
       status
       employeeType
       id
      }
    }
  }

Both of the objects(BusinessNode and EmployeeNode) are DjangoObjectType.

Thanks,
Felix

convert_django_field_with_choices appears not to work

Hi. Given:

RATING_CHOICES = (
    (0, ''),   # or (0 'none')
    (1, '*'), 
    (2, '**'),
    (3, '***'), 
    (4, '****'), 
    (5, '*****')
)

class Note(models.Model):

    rating = models.IntegerField(choices=RATING_CHOICES, blank=True, default=0)

# ...
class NoteNode(DjangoObjectType):

    class Meta:
        model = Note
        interfaces = (relay.Node,)

# fails when loading the schema
from mattermind.schema import schema

I get a failure to auto-create:

choices [(0, 0, ''), (1, 1, '*'), (2, 2, '**'), (3, 3, '***'), (4, 4, '****'), (5, 5, '*****')]
named_choices [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]
named_choices_descriptions {0: '', 1: '*', 2: '**', 3: '***', 4: '****', 5: '*****'}
Traceback (most recent call last):
  File "try_schema.py", line 13, in <module>
    def convert_choice_name(name):
  File "/Users/crucial/miniconda3/envs/mattermind/lib/python3.5/site-packages/graphene/types/enum.py", line 41, in __call__
    return cls.from_enum(PyEnum(*args, **kwargs), description=description)
  File "/Users/crucial/miniconda3/envs/mattermind/lib/python3.5/enum.py", line 243, in __call__
    return cls._create_(value, names, module=module, qualname=qualname, type=type, start=start)
  File "/Users/crucial/miniconda3/envs/mattermind/lib/python3.5/enum.py", line 342, in _create_
    classdict[member_name] = member_value
  File "/Users/crucial/miniconda3/envs/mattermind/lib/python3.5/enum.py", line 60, in __setitem__
    if _is_sunder(key):
  File "/Users/crucial/miniconda3/envs/mattermind/lib/python3.5/enum.py", line 26, in _is_sunder
    return (name[0] == name[-1] == '_' and
TypeError: 'int' object is not subscriptable

The debug statements are posted for lines 45-47 from here:

https://github.com/graphql-python/graphene-django/blob/master/graphene_django/converter.py#L40

named_choices looks wrong.

graphene
graphene-django
django_graphiql
graphql-core
django==1.9
django-filter==0.11.0

python 3.5

Awesome project, everything else has been very simple so far, thanks !

Setting a max limit on connections

GitHub's graphQL implementation requires a limit on queries, with a max limit of 30. How would I go about enforcing a max limit on every connection field in graphene?

Bridge Django field choices and graphene's enum type

It would be awesome to see something like this:

class Foo(models.Model):
    TWITTER = 'twitter'
    WEBSITE = 'website'
    FULL_NAME = 'full_name'
    IDENTITY_CHOICES = (
        (TWITTER, 'Twitter'),
        (WEBSITE, 'Website'),
        (FULL_NAME, 'Full Name'),
    )

    main_identity = models.CharField(max_length=10, choices=IDENTITY_CHOICES, default=FULL_NAME)

turned into something like this:

class Identity(graphene.Enum):
    TWITTER = Foo.TWITTER
    WEBSITE = Foo.WEBSITE
    FULL_NAME = Foo.FULL_NAME


class FooType(DjangoObjectType):
    main_identity = graphene.NonNull(Identity)

    class Meta:
        model = Foo

automatically. I will work on this if I have time to implement it.

ArrayField converter broke in Django 1.8.*

Guys,

JSONField was added just in Django 1.9[1] and in graphene code try import that as version 1.8.*
This could cause problem to convert_postgres_array_to_list, because if I'm using django 1.8 graphene_django will try import JSONField causing a ImportError and broke all the others imports.

A possible solution below:

try:
    # Postgres fields are only available in Django 1.8+
    from django.contrib.postgres.fields import ArrayField, HStoreField, RangeField
except ImportError:
    ArrayField, HStoreField, RangeField = (MissingType, ) * 3


try:
    # Postgres fields are only available in Django 1.9+
    from django.contrib.postgres.fields import JSONField
except ImportError:
    JSONField = MissingType

[1] - https://docs.djangoproject.com/en/1.9/releases/1.9/
[2] - https://github.com/graphql-python/graphene-django/blob/master/graphene_django/compat.py#L23

Node's in schema are active when not included in Query

I don't know if this is a bug or intentional, but I'm assuming it's a bug because it seems like it could open up vulnerabilities.

https://github.com/chriscauley/graphene-demo

The above repo is me working through the "filtering" and "authentication" sections of the docs. If you pull that (currently at commit 42146fb44ca69b7af6b561c50781aa48e0f7c808), migrate, run python manage.py loaddata initial.json, and start the dev server you'll have a set of initial data including a super user with username "admin" and password "password" (no quotes). Then go to /graphql in a browser and enter in the following query:

query {
  allAnimals {
    edges{
      node {
        name,
        id,
        user {
          id,
          username,
          password,
          lastName,
          lastLogin
        }
      }
    }
  }
}

The result should be the error message in this screenshot: an error message because there is no node to query users.

screenshot 2016-11-03 at 17 10 26

Next, go to graphene_demo/schema.py and uncomment the CurrentUserNode class. Re-run the query and you get the following screenshot, which has returned the queried user data.

screenshot 2016-11-03 at 17 12 28

Even though I didn't change the query it still is exposing the User table. That seems like a bug to me because (I would assume) exposing a table should be explicit.

Custom resolver to fetch data from methods

Hi
I have a question. I cannot find any suitable example. Is there a possibility to add an extra field which will be returning data from particular method ? I mean I have a Node defined (node inherits from DjangoObjectType) and my model has some properties (method with @Property decorator). I would like to be able to add this data into graphql response. I've tried with resolve_{name} method but there is no access to particular instance inside this method. Maybe I missed something.

Thanks, great project !

Use choice keys, not descriptions, for enums

Currently, the automatically generated choices for Django fields correspond to the text labels passed in not the keys.

This is problematic because changing labels in the future would lead to all your enum values being updated. It's also contrary to what I would expect as a user.

Examples for bi-directional UUID support

It seems like UUID model fields get converted here:
https://github.com/graphql-python/graphene-django/blob/master/graphene_django/converter.py#L78-L81

which means they show up as base64 encoded SWRlbnRpdHlOb2RlOmMxYmZiNTFjLWUyYTItNGM3YS1hOWQ5LTNiMDA4ODA2YmFhOA== in query responses. However, when sending them back in a mutation, it complains badly formed hexadecimal UUID string in the error field. Sending the hex instead (e.g. c1bfb51c-e2a2-4c7a-a9d9-3b008806baa8 works but is annoying to not be able to turn it around.

At the moment I'm using a helper function to decode and strip the IdentityNode: from the front:

def uuid_hex_from_b64(encoded):
    decoded = base64.b64decode(encoded)
    return decoded.split(b":")[1]

and use it like:

new_club = {
    "name": input.get('name'),
    "treasurer_id": uuid_hex_from_b64(input.get('treasurer_id'))
}
club = Club.objects.create(**new_club)

This works but I'm not sure if this is a terrible idea or if it's sane, but some documentation of its usage as ok would save others like me hours of poking around! Thanks!

cookbook example doesn't work?

I am trying to run cookbook example and when I try to get item by ID, web client raises an error:

query {
ingredient(id: "SW5ncmVkaWVudE5vZGU6MQ==") { # actual id is takef rom the previous request
name
}
}
screen shot 2016-09-28 at 11 49 07

Examples on adding calculated fields

Exposing fields that are in the db/model works fine, but what about calculated fields? For example, if I want to expose a field called Author.book_count that will obviously not be queriable or editable, how can I do that?

Reusable apps/modules

One of the things which makes Django awesome is the ability to easily share apps across multiple projects. Django Rest Framework definitely leverages this by making it possible to include entire routes in your urls.py file (to reuse REST routes from other apps) or extending serializers/views if you want to customize another app.

I'd like to start a conversation on what the best approach is to reusable Graphene schemas with Django.

In particular, I think we need to have clear examples of:

  1. If I want to wholesale include a reusable schema (ex. a generic "users" schema) from another project, how do I add it to mine.
  2. If I want to extend another project's schema (ex. extending the users schema and changing the mutation to make names immutable), how do I do that.
  3. What are best practices for developing reusable graphql schemas with Graphene.

I'm happy to work on pull requests, examples, or documentation once we have a bit of a consensus.

Numeric in filter on custom filterset_class

I am trying to set up a in filter for a DjangoFilterConnectionField so that I can make a query looking for any of a list of Ids.

I am following the recommendataion on the django_filter docuemntation : https://django-filter.readthedocs.io/en/latest/ref/filters.html#baseinfilter

here is my factory function for creating these classes:

class NumberInFilter(django_filters.filters.BaseInFilter, django_filters.filters.NumberFilter):
    pass

def create_model_id_filter(model):
    model_name = model.__name__
    filter_class = type(
        '{model_name}IdFilter'.format(model_name=model_name),
        (django_filters.FilterSet,),
        dict(
            id__in=NumberInFilter(name='id', lookup_expr='in'),
            Meta=type('Meta', (object,), dict(model=model))
        )
    )
    return filter_class

but the type of the query is showing up as a Float type so won't let me pass in strings arguments to the id__In filter.
image

Is there a good way to accomplish this?

Bypass csrf

I have the use case of a mobile client app accessing graphql resource with Apollo Client. However, the client cannot access because of CSRF validation. In web I can get the token from cookie, but there is no cookie in mobile app. How can I solve this issue?

Mutation Error: "mutate() takes 4 positional arguments but 5 were given"

I have the following schema:

# app/schema.py
import graphene
from graphene import relay, AbstractType
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField

from .models import Person

class PersonNode(DjangoObjectType):

    class Meta:
        model = Person
        filter_fields = ["id", "first_name", "last_name"]
        interfaces = (relay.Node, )

class Query(AbstractType):
    all_person = DjangoFilterConnectionField(PersonNode)

class CreatePerson(graphene.Mutation):
    class Input:
        firstName = graphene.String()
        lastName = graphene.String()
    
    person = graphene.Field(PersonNode)
    ok = graphene.Boolean()

    @classmethod
    def mutate(cls, input, args, info):
        f_name = args.get('firstName')
        l_name = args.get('lastName')
        p = Person.objects.create(first_name=f_name, last_name=l_name)
        return CreatePerson(person=PersonNode(p), ok=True)

class MutationPerson(AbstractType):
    createPerson = CreatePerson.Field()

# .\schema.py
import graphene

import srv_engine.main.schema as main_schema


class Query(main_schema.Query, graphene.ObjectType):
    pass

class Mutation(main_schema.MutationPerson, graphene.ObjectType):
    pass

schema = graphene.Schema(query=Query, mutation=Mutation)

When i try to execute the mutation, with the following query:

mutation{
  createPerson(firstName: "Foo", lastName: "Bar" ){
    ok,
    person{
      firstName
    }
  }
}

I get the following result:

{
  "errors": [
    {
      "message": "mutate() takes 4 positional arguments but 5 were given",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ]
    }
  ],
  "data": {
    "createPerson": null
  }
}

Any help would be good.

DjangoFilterConnectionField and resolve support

I found that the following example from the docs doesn't work correctly.

The thing is that if there is a resolver for a DjangoFilterConnectionField like in the example, the filtering and ordering created by DjangoFilterConnectionField will be ignored. Details below.

class Query(ObjectType):
    my_posts = DjangoFilterConnectionField(CategoryNode)

    def resolve_my_posts(self, args, context, info):
        # context will reference to the Django request
        if not context.user.is_authenticated():
            return Post.objects.none()
        else:
            return Post.objects.filter(owner=context.user)

The issues seems to be that DjangoFilterConnectionField ignores what the resolver returns.

@staticmethod
def connection_resolver(resolver, connection, default_manager, filterset_class, filtering_args,
                        root, args, context, info):
    filter_kwargs = {k: v for k, v in args.items() if k in filtering_args}
    order = args.get('order_by', None)
    qs = default_manager.get_queryset()
    if order:
        qs = qs.order_by(order)
    qs = filterset_class(data=filter_kwargs, queryset=qs)

    return DjangoConnectionField.connection_resolver(resolver, connection, qs, root, args, context, info)

Here the QuerySet is built just fine but the resolver does not have an impact on the QuerySet (which is fine if no resolver is used anyway).

The problem arrises when the DjangoConnectionField resolves the query.

@staticmethod
def connection_resolver(resolver, connection, default_manager, root, args, context, info):
    iterable = resolver(root, args, context, info)
    if iterable is None:
        iterable = default_manager
    iterable = maybe_queryset(iterable)
    if isinstance(iterable, QuerySet):
        _len = iterable.count()
    else:
        _len = len(iterable)
    connection = connection_from_list_slice(
        iterable,
        args,
        slice_start=0,
        list_length=_len,
        list_slice_length=_len,
        connection_type=connection,
        edge_type=connection.Edge,
        pageinfo_type=PageInfo,
    )
    connection.iterable = iterable
    connection.length = _len
    return connection

Here we consider the default_manager (which is the QuerySet that was built by DjangoFilterConnectionField) only if there resolver is None.

So you either can have the DjangoFilterConnection field or a resolver. But it should be possible to use both (to limit an initial QuerySet and check of authorisation, etc.).

Solution

The solution I use now locally by changing DjangoFilterConnectionField is the following:

@staticmethod
def connection_resolver(resolver, connection, default_manager, filterset_class, filtering_args,
                        root, args, context, info):
    filter_kwargs = {k: v for k, v in args.items() if k in filtering_args}
    order = args.get('order_by', None)

    def new_resolver(root, args, context, info):
        qs = resolver(root, args, context, info)
        if qs is None:
            qs = default_manager.get_queryset()
        if order:
            qs = qs.order_by(order)
        qs = filterset_class(data=filter_kwargs, queryset=qs)
        return qs

    return DjangoConnectionField.connection_resolver(new_resolver, connection, None, root, args, context, info)

Basically it wraps the resolver and uses its QuerySet if available or create one with the default_manager. It also guarantees that the resolver returns something and therefore we can set the default_manager to None just to make sure it there is no other way to get to the QuerySet.

I would have created a PR but I couldn't get the tests of the repo running locally. :(

PS: I'd also really like to get the tests running so I can contribute to the code directly. Any pointers how to get it running are really appreciated as I didn't have luck by following the instructions in "Contributing".

Auto schema generation

So I started playing around with graphene-django last night. We have a complex data model so I don't want to write a shadow graphql schema for our models. We have a similar approach for generating DRF serializers using metaprogramming so I decided to try something with and currently have a query that generates a schema from our app:

from django.apps import apps
from graphene import relay, ObjectType, AbstractType, Schema, Node
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField


def build_query_objs():
    queries = {}
    models = apps.get_app_config('our_app_name').get_models()
    for model in models:
        model_name = model.__name__
        node = type('{model_name}Node'.format(model_name=model_name),
                    (DjangoObjectType,),
                    dict(
                        Meta=type('Meta',
                                  (object,),
                                  dict(model=model,
                                       interfaces=(relay.Node,))
                                  )
                    )
                    )
        queries.update({model_name: relay.Node.Field(node)})
        queries.update({'all_{model_name}'.format(model_name=model_name): DjangoFilterConnectionField(node)})
    return queries

Query = type('Query', (ObjectType,), build_query_objs())

schema = Schema(query=Query)

This worked well but noticed that I couldn't filter on any fields so I have more code (on dev machine atm) that generates filter_fields, and order_filter_field arguments for the generated Meta classes. With a single pass I can filter on 1 thing. I'd like to be able to filter on any nested filters eg customer__order__note_contains or something. Is there a good to get all these query-filter strings for each model?

It would be awesome if you left the filter_fields blank that all possible filters would be generated for the node Meta class.

Bundle external JavaScript sources for GraphiQL

Currently, the GraphiQL interface served with graphene-django cannot be used without an internet connection because it relies on javascript files served from a CDN. This can be problematic in some dev environments. I would suggest bundling these files instead to ensure graphiql can also be used offline.

Can't run cookbook example

When I run the cookbook example, and go to localhost:8000/graphql, I get

TypeError at /graphql

Invalid attributes: filter_order_by

Environment:


Request Method: GET
Request URL: http://localhost:8000/graphql

Django Version: 1.9
Python Version: 2.7.12
Installed Applications:
['django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'graphene_django',
 'django_filters',
 'cookbook.ingredients.apps.IngredientsConfig',
 'cookbook.recipes.apps.RecipesConfig']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware']



Traceback:

File "/home/tvogels/.local/lib/python2.7/site-packages/django/core/handlers/base.py" in get_response
  149.                     response = self.process_exception_by_middleware(e, request)

File "/home/tvogels/.local/lib/python2.7/site-packages/django/core/handlers/base.py" in get_response
  147.                     response = wrapped_callback(request, *callback_args, **callback_kwargs)

File "/home/tvogels/.local/lib/python2.7/site-packages/django/views/generic/base.py" in view
  62.             self = cls(**initkwargs)

File "/home/tvogels/.local/lib/python2.7/site-packages/graphene_django/views.py" in __init__
  70.             schema = graphene_settings.SCHEMA

File "/home/tvogels/.local/lib/python2.7/site-packages/graphene_django/settings.py" in __getattr__
  111.             val = perform_import(val, attr)

File "/home/tvogels/.local/lib/python2.7/site-packages/graphene_django/settings.py" in perform_import
  55.         return import_from_string(val, setting_name)

File "/home/tvogels/.local/lib/python2.7/site-packages/graphene_django/settings.py" in import_from_string
  69.         module = importlib.import_module(module_path)

File "/opt/bb/lib/python2.7/importlib/__init__.py" in import_module
  37.     __import__(name)

File "/bb/ssd/tvogels/graphene-django/examples/cookbook/cookbook/schema.py" in <module>
  2. import cookbook.recipes.schema

File "/bb/ssd/tvogels/graphene-django/examples/cookbook/cookbook/recipes/schema.py" in <module>
  6. class RecipeNode(DjangoObjectType):

File "/home/tvogels/.local/lib/python2.7/site-packages/graphene_django/types.py" in __new__
  72.             **defaults

File "/home/tvogels/.local/lib/python2.7/site-packages/graphene/types/options.py" in __init__
  29.                     ', '.join(sorted(meta_attrs.keys()))

Exception Type: TypeError at /graphql
Exception Value: Invalid attributes: filter_order_by

Choice fields automatically converted to enums

class User(DjangoObjectType):
    class Meta:
        model = get_user_model()
        only_fields = ['signup_site_version']

class Query(graphene.ObjectType):
    user = graphene.Field(User)

    def resolve_user(self, args, context, info):
        return user_from_info(info)  # return django user from context

User model:

@python_2_unicode_compatible
class User(AbstractUser):
    (...)
    signup_site_version_choices = [(1, u'site 1'), (2, u'site 2')]
    signup_site_version = models.IntegerField(choices=signup_site_version_choices)

Query

query { 
  user {
    signupSiteVersion
  }
}

Result

{
  "data": {
    "user": {
      "signupSiteVersion": "A_1"
    }
  }
}

A copy-paste from where the weird value is born:

def convert_choice_name(name):
    name = to_const(force_text(name))
    try:
        assert_valid_name(name)
    except AssertionError:
        name = "A_%s" % name
    return name

We've had similar problems in the old graphene (< 1.0): graphql-python/graphene#134
The output is "A_1", because the field converter assumes django choices are tuples (id, name) where name is a constant name (not containing spaces etc.) or is convertible to a valid constant name. For some reason it is not successful while doing that for "site 1", but that is not an issue. The problem is it assumes we would like to do that.
In our django application (I suppose it is the norm) we use the second tuple element as a human-readable label. It means that in most cases replacing space with an underscore will do the work, but
in the other names are still bound to be long and there is no character we can safely assume the name won't contain (including hyphens, all native character varieties etc.).
The Enum fields are certainly nice, as their values are schema-validated, to say the least, but I doubt automatic conversion should be the only possible behaviour.

Help requested: the attribute 'order_by' got removed from the models and requests.

Hey guys,

We've noticed that in the last updates, the attribute 'order_by' got removed from the models and requests.

  • Is there something in the works for replacing it?

  • Can you suggest an alternative for us to use?

  • Why was this option removed?

Thank you guys in advance. Best regards.

Reference links:

  • Commit where 'order_by' got removed: 8dfe7bb

  • Commit where 'order_by' got removed from the docs: 9d35b76

Recursive Node Relationships

Is something like this possible

class UserNode(DjangoObjectType):

     friends = graphene.List(UserNode)

     class Meta:
         interfaces = (Node, )
         model = UserModel

if there currently isn't a way to do this, I propose doing something similar to how django models resolve strings to reference models

Strange query result using get_node

I have a schema like this one :

class PropositionVoterNode(DjangoObjectType):
    class Meta:
        model = PropositionVoter
        interfaces = (relay.Node, )

    @classmethod
    def get_node(cls, id, context, info):
        print('b')
        return PropositionVoter(id=1)


class ArticleNode(DjangoObjectType):
    class Meta:
        model = PropositionVoter
        interfaces = (relay.Node, )

    @classmethod
    def get_node(cls, id, context, info):
        print('a')
        return PropositionVoter(id=1)


class Query(AbstractType):
    provotes = relay.Node.Field(PropositionVoterNode)

Now if I send a query like this one :

{
  provotes(id: "QXJ0aWNsZU5vZGU6MQ==") {
    id 
  }
}

In my shell I obtain :

System check identified no issues (0 silenced).
December 06, 2016 - 16:54:18
Django version 1.10, using settings 'vote_graphql.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
tota
[06/Dec/2016 16:54:20] "POST /graphql? HTTP/1.1" 200 63

The query result I obtain is :

{
  "data": {
    "provotes": {
      "id": "UHJvcG9zaXRpb25Wb3Rlck5vZGU6MQ=="
    }
  }
}

In my point of view, I should obtain something more like :

{
  "data": {
    "provotes": null
  }
}

The problem comes from that I use a key generated by ArticleNode to query provotes (defined by PropositionVoterNode). I know it's an incorrect way to use graphql and I'm sure that no one will ever do that again but I think this behavior stange.

Anyway close the issue if it is not :)

Registering additional types

Currently, only built in model field types are supported.

If you try to use alternative field types (such as currency fields provided by https://github.com/django-money/django-money), they cause this error:

web_1  |   File "/usr/local/lib/python3.5/site-packages/graphene_django/converter.py", line 58, in convert_django_field
web_1  |     (field, field.__class__))
web_1  | Exception: Don't know how to convert the Django field renting.RenterProfile.phone_number (<class 'phonenumber_field.modelfields.PhoneNumberField'>)

There should probably be some API exposed to register conversions for additional field types. (Or if this is already possible, explain it in the docs.)

Example of custom filter function for a Node Filterset

Awesome work guys really love this project!

I'm trying to do something that hasn't been documented yet and was hoping I could ask for some insight.

I have a simple node class that has a filterset for it with a custom filter function

class BuildingNode(DjangoObjectType):
    class Meta:
        interfaces = (Node, )
        model = Buildings

 class BuildingFilter(django_filters.FilterSet):
     full_address = django_filters.MethodFilter()

    def filter_full_address(self, queryset, value):
        if value:
               return querset.filter(#special_filter_case)
        return queryset

    class Meta:
         model = Buildings
         fields = {
            'address__route': ['exact', 'icontains'],
         } 

Everything filters fine on http://localhost:8000/graphiql

But on my frontend when I filter by my custom function I get warnings that look like this

Attempted to add noncontiguous index to GraphQLSegment: 12 to (0, 10)
Attempted to add noncontiguous index to GraphQLSegment: 13 to (0, 10)
Attempted to add an ID already in GraphQLSegment: client:client:-18995767902:QnVpbGRpbmdOb2RlOjMzNjkxMGFlLTY5YzQtNDU5My1iMjliLTZiNjQ5NjY0MjQyNg==

But when I filter by address__route these errors never appear.

I looked through django_graphene and couldn't find any clues on what I need to do to resolve these warnings. I'm guessing I need to wrap my queryset with something.

It would be awesome to see how to do this in the cookbook example!

Get real id field

I have noticed that if I base 64 decode the id field that I can get the :, is there some way that I could also just get the id as a number.

Also is there any way to get a Relay.Node by id rather than the unique id?

DjangoFilterConnectionField fetches the entire model before slicing

https://github.com/graphql-python/graphene-django/blob/master/graphene_django/fields.py#L51

This condition is wrong for DjangoFilterConnectionField; iterable is a FilterSet descendant, which does not inherit from QuerySet. This causes a very expensive len(iterable) call, which FilterSet relays to the underlying queryset, which calls self._fetch_all() instead of self.count(). This all happens before slicing is applied, so asking for pagination does not improve performance.

Looks like the condition should be isinstance(iterable, (QuerySet, FilterSet))? Filter sets do have a .count() method, so they can follow the same interface as query sets.

It doesn't seem possible to create circular connections in graphene-django

We'd like to take advantage of the networked nature of GraphQL queries to highly interconnect our data models, however, we've been unable to find a way to do this using graphene-django.

In the the JavaScript implementation, it seems to be possible for two nodes to define connections to each other (http://stackoverflow.com/questions/39256942/dynamically-creating-graphql-schema-with-circular-references#39290345), however, this doesn't seem possible in the Django implementation.

Here's a bit of sample code that demonstrates what we're trying to accomplish:

from django.db import models
from django_filters import FilterSet, OrderingFilter
from graphene import ObjectType, Schema, relay
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField


class Recipe(models.Model):
    name = models.CharField(max_length=50)
    ingredients = models.ManyToManyField('Ingredient', related_name='recipes')


class Ingredient(models.Model):
    name = models.CharField(max_length=50)


class RecipeFilter(FilterSet):

    order_by = OrderingFilter(fields=[('name', 'name')])

    class Meta:
        fields = {'name': ['icontains']}
        model = Recipe


class IngredientFilter(FilterSet):

    order_by = OrderingFilter(fields=[('name', 'name')])

    class Meta:
        fields = {'name': ['icontains']}
        model = Ingredient


class RecipeNode(DjangoObjectType):

    ingredients = DjangoFilterConnectionField(IngredientNode, filterset_class=IngredientFilter)

    class Meta:
        interfaces = [relay.Node]
        model = Recipe
        only_fields = ['name']


class IngredientNode(DjangoObjectType):

    recipes = DjangoFilterConnectionField(RecipeNode, filterset_class=RecipeFilter)

    class Meta:
        interfaces = [relay.Node]
        model = Ingredient
        only_fields = ['name']


class Queries(ObjectType):

    all_recipes = DjangoFilterConnectionField(RecipeNode, filterset_class=RecipeFilter)
    all_ingredients = DjangoFilterConnectionField(IngredientNode, filterset_class=IngredientFilter)


schema = Schema(query=Queries)

This implementation does not work since IngredientNode is not defined by the time it is referenced in RecipeNode.

Using a lambda to define the connection (recipes = DjangoFilterConnectionField(lambda: RecipeNode, filterset_class=RecipeFilter) ) results in an AttributeError: 'function' object has no attribute '_meta' error.

Attempting to assign the attribute after IngredientNode is defined (RecipeNode.ingredients = DjangoFilterConnectionField(IngredientNode, filterset_class=IngredientFilter)) raises no errors, but the ingredients attribute does not show up in the final schema.

A GraphQL query that demonstrates the type of questions we'd like to be able to answer:

{
  allRecipes(name_Icontains: "omelette") {
    edges {
      node {
        name
        ingredients(name_Icontains: "egg") {
          edges {
            node {
              name
            }
          }
        }
      }
    }
  }
  allIngredients(name_Icontains: "egg") {
    edges {
      node {
        name
        recipes(name_Icontains: "omelette") {
          edges {
            node {
              name
            }
          }
        }
      }
    }
  }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.