diff --git a/README.md b/README.md index 97878ef..94a525d 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,9 @@ This package add some extra functionalities to graphene-django to facilitate the graphql use without Relay: - 1. Allow pagination and filtering on Queries. - 2. Allow to define DjangoRestFramework serializers based Mutations. - 3. Add support to Subscription's requests and its integration with websockets using Channels package. :muscle: + 1. Allows pagination and filtering on Queries. + 2. Allows to define DjangoRestFramework serializers based Mutations. + 3. Adds support to Subscription's requests and its integration with websockets using Channels package. :muscle: ## Installation @@ -45,8 +45,8 @@ pip install graphene-django-extras ### Queries and Mutations examples: -This is a basic example of graphene-django-extras package use. You can configure some pagination -default values on settings.py like this: +This is a basic example of graphene-django-extras package use. You can configure global params +for DjangoListObjectType classes pagination definitions on settings.py like this: ```python GRAPHENE_DJANGO_EXTRAS = { @@ -85,7 +85,7 @@ class UserListType(DjangoListObjectType): pagination = LimitOffsetGraphqlPagination(page_size=20) ``` -#### 2- Input Types can be defined for use on mutations: +#### 2- You can to define InputTypes for use on mutations: ```python from graphene_django_extras import DjangoInputObjectType @@ -93,11 +93,11 @@ from graphene_django_extras import DjangoInputObjectType class UserInput(DjangoInputObjectType): class Meta: - description = " User InputType to use as input on Arguments classes on traditional Mutations " + description = " User InputType definition to use as input on an Arguments class on traditional Mutations " model = User ``` -#### 3- You can define traditional mutations that use InputTypes or Mutations based on DRF SerializerClass: +#### 3- You can define traditional mutations that use InputTypes or Mutations based on DRF serializers: ```python import graphene @@ -113,7 +113,7 @@ class UserSerializerMutation(DjangoSerializerMutation): DjangoSerializerMutation auto implement Create, Delete and Update functions """ class Meta: - description = " Serializer based Mutation for Users " + description = " DRF serializer based Mutation for Users " serializer_class = UserSerializer @@ -128,7 +128,7 @@ class UserMutation(graphene.Mutation): new_user = graphene.Argument(UserInput) class Meta: - description = " Traditional graphene mutation for Users " + description = " Graphene traditional mutation for Users " @classmethod def mutate(cls, root, info, *args, **kwargs): @@ -153,22 +153,22 @@ class Queries(graphene.ObjectType): all_users3 = DjangoListObjectField(UserListType, filterset_class=UserFilter, description=_('All Users query')) # Defining a query for a single user - # The DjangoObjectField have a ID input field, that allow filter by id and is't necessary resolve method + # The DjangoObjectField have a ID type input field, that allow filter by id and is't necessary to define resolve function user = DjangoObjectField(UserType, description=_('Single User query')) - # Another way to define a single user query - user1 = DjangoObjectField(UserListType.getOne(), description=_('User List with pagination and filtering')) + # Another way to define a query to single user + user1 = DjangoObjectField(UserListType.getOne(), description=_('User list with pagination and filtering')) class Mutations(graphene.ObjectType): - user_create = UserSerializerMutation.CreateField(deprecation_reason='Some deprecation message') + user_create = UserSerializerMutation.CreateField(deprecation_reason='Some one deprecation message') user_delete = UserSerializerMutation.DeleteField() user_update = UserSerializerMutation.UpdateField() traditional_user_mutation = UserMutation.Field() ``` -#### 5- Examples of queries: +#### 5- Queries's examples: ```js { allUsers(username_Icontains:"john"){ @@ -209,7 +209,7 @@ class Mutations(graphene.ObjectType): } ``` -#### 6- Examples of Mutations: +#### 6- Mutations's examples: ```js mutation{ @@ -251,7 +251,7 @@ mutation{ ### **Subscriptions:** -This first approach to support Graphql subscriptions with Channels in **graphene-django-extras**, use **channels-api** package. +This first approach to add Graphql subscriptions support with Channels in **graphene-django-extras**, use **channels-api** package. #### 1- Defining custom Subscriptions classes: @@ -268,18 +268,18 @@ class UserSubscription(Subscription): class Meta: mutation_class = UserMutation stream = 'users' - description = 'Subscription for users' + description = 'User Subscription' class GroupSubscription(Subscription): class Meta: mutation_class = GroupMutation stream = 'groups' - description = 'Subscriptions for groups' + description = 'Group Subscription' ``` -Add ours subscriptions definitions into our app schema: +Add the subscriptions definitions into your app's schema: ```python # app/graphql/schema.py @@ -292,7 +292,7 @@ class Subscriptions(graphene.ObjectType): GroupSubscription = PersonSubscription.Field() ``` -Add your app schema into your project root schema: +Add the app's schema into your project root schema: ```python # schema.py @@ -302,17 +302,17 @@ import custom.app.route.graphql.schema class RootQuery(custom.app.route.graphql.schema.Query, graphene.ObjectType): class Meta: - description = 'Root Queries for my Project' + description = 'The project root query definition' class RootSubscription(custom.app.route.graphql.schema.Mutation, graphene.ObjectType): class Meta: - description = 'Root Mutations for my Project' + description = 'The project root mutation definition' class RootSubscription(custom.app.route.graphql.schema.Subscriptions, graphene.ObjectType): class Meta: - description = 'Root Subscriptions for my Project' + description = 'The project root subscription definition' schema = graphene.Schema( @@ -324,7 +324,7 @@ schema = graphene.Schema( #### 2- Defining Channels settings and custom routing config ( *For more information see Channels documentation* ): -Defining our app routing, like custom apps urls: +We define app routing, as if they were app urls: ```python # app/routing.py @@ -345,7 +345,7 @@ app_routing = [ ] ``` -Defining our project routing, like custom root project urls: +We define project routing, as if they were project urls: ```python # project/routing.py @@ -358,7 +358,7 @@ project_routing = [ ``` -You should add **channels** and **channels_api** modules into your INSTALLED_APPS setting and you must defining your routing project definition into the CHANNEL_LAYERS setting: +You should put into your INSTALLED_APPS the **channels** and **channels_api** modules and you must to add your project's routing definition into the CHANNEL_LAYERS setting: ```python # settings.py @@ -384,17 +384,18 @@ CHANNEL_LAYERS = { ... ``` -#### 3- Examples of Subscriptions: +#### 3- Subscription's examples: -In your client you must define websocket connection to: *'ws://host:port/custom_websocket_path'*. -When the connection is established, the server return a websocket message like this: +In your WEB client you must define websocket connection to: *'ws://host:port/custom_websocket_path'*. +When the connection is established, the server return a websocket's message like this: *{"channel_id": "GthKdsYVrK!WxRCdJQMPi", "connect": "success"}*, where you must store the **channel_id** value to later use in your graphql subscriptions request for subscribe or unsubscribe operations. -The Subscription accept five possible parameters: + +The graphql's subscription request accept five possible parameters: 1. **operation**: Operation to perform: subscribe or unsubscribe. (*required*) - 2. **action**: Action you wish to subscribe: create, update, delete or all_actions. (*required*) - 3. **channelId**: Websocket connection identification. (*required*) - 4. **id**: ID field value of model object that you wish to subscribe to. (*optional*) - 5. **data**: List of desired model fields that you want in subscription's notification. (*optional*) + 2. **action**: Action to which you wish to subscribe: create, update, delete or all_actions. (*required*) + 3. **channelId**: Identification of the connection by websocket. (*required*) + 4. **id**: Object's ID field value that you wish to subscribe to. (*optional*) + 5. **data**: Model's fields that you want to appear in the subscription notifications. (*optional*) ```js subscription{ @@ -412,7 +413,9 @@ subscription{ } ``` -In this case, the subscription request sanded return a websocket message to client like this: *{"action": "update", "operation": "subscribe", "ok": true, "stream": "users", "error": null}* and each time than the user with id=5 get modified, you will receive a message through websocket's connection with the following format: +In this case, the subscription request sent return a websocket message to client like this: +*{"action": "update", "operation": "subscribe", "ok": true, "stream": "users", "error": null}* +and from that moment each time than the user with id=5 get modified, you will receive a message through websocket's connection with the following format: ```js { @@ -432,7 +435,7 @@ In this case, the subscription request sanded return a websocket message to clie } ``` -For unsubscribe you must send a graphql subscription request like this: +For unsubscribe you must send a graphql request like this: ```js subscription{ @@ -449,14 +452,17 @@ subscription{ } ``` -**NOTE:** Each time than the Graphql server restart, you must to reestablish the websocket's connection and resend the subscription graphql request with a new websocket connection id. +**NOTE:** Each time than the graphql's server restart, you must to reestablish the websocket connection and resend the graphql's subscription request with the new websocket connection id. ## Change Log: #### v0.1.0-alpha12: - 1. Added MAX_PAGE_SIZE configuration field to global dict GRAPHENE_DJANGO_EXTRAS - configuration for better customize paginations. + 1. Added new settings param: MAX_PAGE_SIZE, to use on GRAPHENE_DJANGO_EXTRAS + configuration dict for better customize DjangoListObjectType's pagination. + 2. Added support to Django's field: GenericRel. + 3. Improve model's fields calculation for to add all possible related and reverse fields. + 4. Improved documentation translation. #### v0.1.0-alpha11: 1. Improved ordering for showed fields on graphqli's IDE. @@ -482,7 +488,8 @@ subscription{ 1. Add queryset options to DjangoListObjectType Meta class for specify wanted model queryset. 2. Add AuthenticatedGraphQLView on graphene_django_extras.views for use 'permission', 'authorization' and 'throttle' classes based on the DRF settings. Special thanks to - [@jacobh](https://github.com/jacobh) for this [comment](https://github.com/graphql-python/graphene/issues/249#issuecomment-300068390) + [@jacobh](https://github.com/jacobh) for this + [comment](https://github.com/graphql-python/graphene/issues/249#issuecomment-300068390) #### v0.1.0-alpha3: 1. Fixed bug on subscriptions when not specified any field in "data" parameter to bean return on notification @@ -512,8 +519,10 @@ subscription{ #### v0.0.1: 1. Fixed bug on DjangoInputObjectType class that refer to unused interface attribute. - 2. Added support to create nested objects like in [DRF](http://www.django-rest-framework.org/api-guide/serializers/#writable-nested-representations), - it's valid to SerializerMutation and DjangoInputObjectType, only is necessary to specify nested_fields=True on its Meta class definition. + 2. Added support to create nested objects like in + [DRF](http://www.django-rest-framework.org/api-guide/serializers/#writable-nested-representations), + it's valid to SerializerMutation and DjangoInputObjectType, only is necessary to specify nested_fields=True + on its Meta class definition. 3. Added support to show, only in mutations types to create objects and with debug=True on settings, inputs autocomplete ordered by required fields first. 4. Fixed others minors bugs. diff --git a/README.rst b/README.rst index 35e84a2..c8571ac 100644 --- a/README.rst +++ b/README.rst @@ -3,9 +3,9 @@ Graphene-Django-Extras ====================== This package add some extra functionalities to **graphene-django** to facilitate the graphql use without Relay: - 1. Allow pagination and filtering on Queries. - 2. Allow to define DjangoRestFramework serializers based Mutations. - 3. Add support to Subscription's requests and its integration with websockets using **Channels** package. + 1. Allows pagination and filtering on Queries. + 2. Allows to define DjangoRestFramework serializers based Mutations. + 3. Adds support to Subscription's requests and its integration with websockets using **Channels** package. Installation: ------------- @@ -47,8 +47,8 @@ Extra functionalities: Queries and Mutations examples: ------------------------------- -This is a basic example of graphene-django-extras package use. You can configure some pagination -default values on settings.py like this: +This is a basic example of graphene-django-extras package use. You can configure global params for +DjangoListObjectType classes pagination definitions on settings.py like this: .. code:: python @@ -91,9 +91,9 @@ default values on settings.py like this: pagination = LimitOffsetGraphqlPagination() -*************************************************** -2- InputTypes can be defined for use on mutations: -*************************************************** +***************************************************** +2- You can to define InputTypes for use on mutations: +***************************************************** .. code:: python @@ -102,7 +102,7 @@ default values on settings.py like this: class UserInput(DjangoInputObjectType): class Meta: - description = " User Input Type for used as input on Arguments classes on traditional Mutations " + description = " User InputType definition to use as input on an Arguments class on traditional Mutations " model = User @@ -110,7 +110,7 @@ default values on settings.py like this: 3- Defining Mutations: ********************** -You can define traditional mutations that use Input Types or Mutations based on DRF SerializerClass: +You can define traditional mutations that use InputTypes or Mutations based on DRF serializers: .. code:: python @@ -127,7 +127,7 @@ You can define traditional mutations that use Input Types or Mutations based on DjangoSerializerMutation auto implement Create, Delete and Update functions """ class Meta: - description = " Serializer based Mutation for Users " + description = " DRF serializer based Mutation for Users " serializer_class = UserSerializer @@ -142,7 +142,7 @@ You can define traditional mutations that use Input Types or Mutations based on new_user = graphene.Argument(UserInput) class Meta: - description = " Traditional graphene mutation for Users " + description = " Graphene traditional mutation for Users " @classmethod def mutate(cls, root, info, *args, **kwargs): @@ -169,24 +169,24 @@ You can define traditional mutations that use Input Types or Mutations based on all_users3 = DjangoListObjectField(UserListType, filterset_class=UserFilter, description=_('All Users query')) # Defining a query for a single user - # The DjangoObjectField have a ID input field, that allow filter by id and is't necessary resolve method definition + # The DjangoObjectField have a ID type input field, that allow filter by id and is't necessary to define resolve function user = DjangoObjectField(UserType, description=_('Single User query')) - # Another way to define a single user query + # Another way to define a query to single user user1 = DjangoObjectField(UserListType.getOne(), description=_('User List with pagination and filtering')) class Mutations(graphene.ObjectType): - user_create = UserSerializerMutation.CreateField(deprecation_reason='Some deprecation message') + user_create = UserSerializerMutation.CreateField(deprecation_reason='Some one deprecation message') user_delete = UserSerializerMutation.DeleteField() user_update = UserSerializerMutation.UpdateField() traditional_user_mutation = UserMutation.Field() -*********************** -5- Examples of queries: -*********************** +********************** +5- Queries's examples: +********************** .. code:: python @@ -229,9 +229,9 @@ You can define traditional mutations that use Input Types or Mutations based on } -************************* -6- Examples of Mutations: -************************* +************************ +6- Mutations's examples: +************************ .. code:: python @@ -274,7 +274,7 @@ You can define traditional mutations that use Input Types or Mutations based on Subscriptions: -------------- -This first approach to support Graphql subscriptions with Channels in graphene-django-extras, use channels-api package. +This first approach to add Graphql subscriptions support with Channels in graphene-django-extras, use channels-api package. ***************************************** 1- Defining custom Subscriptions classes: @@ -294,17 +294,17 @@ You must to have defined a DjangoSerializerMutation class for each model that yo class Meta: mutation_class = UserMutation stream = 'users' - description = 'Subscription for users' + description = 'User Subscription' class GroupSubscription(Subscription): class Meta: mutation_class = GroupMutation stream = 'groups' - description = 'Subscriptions for groups' + description = 'Group Subscription' -Add ours subscriptions definitions into our app schema: +Add the subscriptions definitions into your app's schema: .. code:: python @@ -318,7 +318,7 @@ Add ours subscriptions definitions into our app schema: GroupSubscription = PersonSubscription.Field() -Add your app schema into your project root schema: +Add the app's schema into your project root schema: .. code:: python @@ -329,17 +329,17 @@ Add your app schema into your project root schema: class RootQuery(custom.app.route.graphql.schema.Query, graphene.ObjectType): class Meta: - description = 'Root Queries for my Project' + description = 'The project root query definition' class RootSubscription(custom.app.route.graphql.schema.Mutation, graphene.ObjectType): class Meta: - description = 'Root Mutations for my Project' + description = 'The project root mutation definition' class RootSubscription(custom.app.route.graphql.schema.Subscriptions, graphene.ObjectType): class Meta: - description = 'Root Subscriptions for my Project' + description = 'The project root subscription definition' schema = graphene.Schema( @@ -356,6 +356,7 @@ Add your app schema into your project root schema: You must to have defined a DjangoSerializerMutation class for each model that you want to define a Subscription class: +We define app routing, as if they were app urls: .. code:: python @@ -389,7 +390,7 @@ Defining our project routing, like custom root project urls: ] -You should add channels and channels_api modules into your INSTALLED_APPS setting and you must defining your routing project definition into the CHANNEL_LAYERS setting: +You should put into your INSTALLED_APPS the channels and channels_api modules and you must to add your project's routing definition into the CHANNEL_LAYERS setting: .. code:: python @@ -416,20 +417,20 @@ You should add channels and channels_api modules into your INSTALLED_APPS settin ... -***************************** -3- Examples of Subscriptions: -***************************** +*************************** +3- Subscription's examples: +*************************** -In your client you must define websocket connection to: 'ws://host:port/custom_websocket_path'. -When the connection is established, the server return a websocket message like this: +In your WEB client you must define websocket connection to: 'ws://host:port/custom_websocket_path'. +When the connection is established, the server return a websocket's message like this: {"channel_id": "GthKdsYVrK!WxRCdJQMPi", "connect": "success"}, where you must store the channel_id value to later use in your graphql subscriptions request for subscribe or unsubscribe operations. -The Subscription accept five possible parameters: +The graphql's subscription request accept five possible parameters: 1. **operation**: Operation to perform: subscribe or unsubscribe. (required) -2. **action**: Action you wish to subscribe: create, update, delete or all_actions. (required) -3. **channelId**: Websocket connection identification. (required) -4. **id**: ID field value of model object that you wish to subscribe to. (optional) -5. **data**: List of desired model fields that you want in subscription's notification. (optional) +2. **action**: Action to which you wish to subscribe: create, update, delete or all_actions. (required) +3. **channelId**: Identification of the connection by websocket. (required) +4. **id**: Object's ID field value that you wish to subscribe to. (optional) +5. **data**: Model's fields that you want to appear in the subscription notifications. (optional) .. code:: python @@ -448,7 +449,7 @@ The Subscription accept five possible parameters: } -In this case, the subscription request sanded return a websocket message to client like this: *{"action": "update", "operation": "subscribe", "ok": true, "stream": "users", "error": null}* and each time than the user with id=5 get modified, you will receive a message through websocket's connection with the following format: +In this case, the subscription request sent return a websocket message to client like this: *{"action": "update", "operation": "subscribe", "ok": true, "stream": "users", "error": null}* and from that moment each time than the user with id=5 get modified, you will receive a message through websocket's connection with the following format: .. code:: python @@ -469,7 +470,7 @@ In this case, the subscription request sanded return a websocket message to clie } -For unsubscribe you must send a graphql subscription request like this: +For unsubscribe you must send a graphql request like this: .. code:: python @@ -487,29 +488,30 @@ For unsubscribe you must send a graphql subscription request like this: } -*NOTE*: Each time than the Graphql server restart, you must to reestablish the websocket's connection and resend the subscription graphql request with a new websocket connection id. +*NOTE*: Each time than the graphql's server restart, you must to reestablish the websocket connection and resend the graphql's subscription request with the new websocket connection id. Change Log: ----------- -************** +*************** v0.1.0-alpha12: -************** -1. Added MAX_PAGE_SIZE configuration field to global dict GRAPHENE_DJANGO_EXTRAS -configuration for better customize paginations. +*************** +1. Added new settings param: MAX_PAGE_SIZE, to use on GRAPHENE_DJANGO_EXTRAS configuration dict for better customize DjangoListObjectType's pagination. +2. Added support to Django's field: GenericRel. +3. Improve model's fields calculation for to add all possible related and reverse fields. +4. Improved documentation translation. -************** +*************** v0.1.0-alpha11: -************** +*************** 1. Improved ordering for showed fields on graphqli's IDE. 2. Added better descriptions for auto generated fields. -************** +*************** v0.1.0-alpha10: -************** -1. Improve converter.py file to avoid create field for auto generate OneToOneField -product of an inheritance. +*************** +1. Improve converter.py file to avoid create field for auto generate OneToOneField product of an inheritance. 2. Fixed bug in Emun generation for fields with choices of model inheritance child. ************** @@ -537,8 +539,7 @@ v0.1.0-alpha4: ************** v0.1.0-alpha3: ************** -1. Fixed bug on subscriptions when not specified any field in "data" parameter to bean return on -notification message. +1. Fixed bug on subscriptions when not specified any field in "data" parameter to bean return on notification message. ************** v0.1.0-alpha2: diff --git a/graphene_django_extras/converter.py b/graphene_django_extras/converter.py index ed12778..d710f22 100644 --- a/graphene_django_extras/converter.py +++ b/graphene_django_extras/converter.py @@ -2,7 +2,7 @@ from collections import OrderedDict from django.conf import settings -from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation +from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation, GenericRel from django.db import models from django.utils.encoding import force_text from graphene import (Field, ID, Boolean, Dynamic, Enum, Float, Int, List, NonNull, String, UUID) @@ -239,6 +239,7 @@ def dynamic_type(): return Dynamic(dynamic_type) +@convert_django_field.register(GenericRel) @convert_django_field.register(models.ManyToManyRel) @convert_django_field.register(models.ManyToOneRel) def convert_many_rel_to_djangomodel(field, registry=None, input_flag=None, nested_fields=False): @@ -282,31 +283,6 @@ def dynamic_type(): return Dynamic(dynamic_type) -@convert_django_field.register(ArrayField) -def convert_postgres_array_to_list(field, registry=None, input_flag=None, nested_fields=False): - base_type = convert_django_field(field.base_field) - if not isinstance(base_type, (List, NonNull)): - base_type = type(base_type) - return List(base_type, description=field.help_text or field.verbose_name, - required=is_required(field) and input_flag == 'create') - - -@convert_django_field.register(HStoreField) -@convert_django_field.register(JSONField) -def convert_postgres_field_to_string(field, registry=None, input_flag=None, nested_fields=False): - return JSONString(description=field.help_text or field.verbose_name, - required=is_required(field) and input_flag == 'create') - - -@convert_django_field.register(RangeField) -def convert_postgres_range_to_string(field, registry=None, input_flag=None, nested_fields=False): - inner_type = convert_django_field(field.base_field) - if not isinstance(inner_type, (List, NonNull)): - inner_type = type(inner_type) - return List(inner_type, description=field.help_text or field.verbose_name, - required=is_required(field) and input_flag == 'create') - - @convert_django_field.register(GenericForeignKey) def convert_generic_foreign_key_to_object(field, registry=None, input_flag=None, nested_fields=False): @@ -361,3 +337,28 @@ def dynamic_type(): return DjangoListField(_type) return Dynamic(dynamic_type) + + +@convert_django_field.register(ArrayField) +def convert_postgres_array_to_list(field, registry=None, input_flag=None, nested_fields=False): + base_type = convert_django_field(field.base_field) + if not isinstance(base_type, (List, NonNull)): + base_type = type(base_type) + return List(base_type, description=field.help_text or field.verbose_name, + required=is_required(field) and input_flag == 'create') + + +@convert_django_field.register(HStoreField) +@convert_django_field.register(JSONField) +def convert_postgres_field_to_string(field, registry=None, input_flag=None, nested_fields=False): + return JSONString(description=field.help_text or field.verbose_name, + required=is_required(field) and input_flag == 'create') + + +@convert_django_field.register(RangeField) +def convert_postgres_range_to_string(field, registry=None, input_flag=None, nested_fields=False): + inner_type = convert_django_field(field.base_field) + if not isinstance(inner_type, (List, NonNull)): + inner_type = type(inner_type) + return List(inner_type, description=field.help_text or field.verbose_name, + required=is_required(field) and input_flag == 'create') diff --git a/graphene_django_extras/paginations/fields.py b/graphene_django_extras/paginations/fields.py index 3db0896..a2b6efc 100644 --- a/graphene_django_extras/paginations/fields.py +++ b/graphene_django_extras/paginations/fields.py @@ -86,9 +86,9 @@ def __init__(self, _type, page_size=graphql_api_settings.DEFAULT_PAGE_SIZE, page self.page_size_query_description = 'Number of results to return per page. Actual \'page_size\': {}'.format( self.page_size) - self.page_query_description = 'A page number within the paginated result set. Default: 1' - kwargs[self.page_query_param] = Int(default_value=1, description=self.page_query_description) + kwargs[self.page_query_param] = Int(default_value=1, + description='A page number within the result paginated set. Default: 1') if self.page_size_query_param: if not page_size: kwargs[self.page_size_query_param] = NonNull(Int, description=self.page_size_query_description) @@ -111,7 +111,7 @@ def list_resolver(self, manager, root, info, **kwargs): page_size = self.page_size assert page != 0, ValueError('Page value for PageGraphqlPagination must be ' - 'greater than or later than that zero') + 'greater than or smaller than that zero, not a zero value') assert page_size > 0, ValueError('Page_size value for PageGraphqlPagination must be a non-null value, you must' ' set global DEFAULT_PAGE_SIZE on GRAPHENE_DJANGO_EXTRAS dict on your' @@ -132,7 +132,7 @@ def __init__(self, _type, ordering='-created', cursor_query_param='cursor', *arg self.page_size_query_param = 'page_size' if not self.page_size else None self.cursor_query_param = cursor_query_param self.ordering = ordering - self.cursor_query_description = 'The paginations cursor value.' + self.cursor_query_description = 'The pagination cursor value.' self.page_size_query_description = 'Number of results to return per page.' kwargs[self.cursor_query_param] = NonNull(String, description=self.cursor_query_description) diff --git a/graphene_django_extras/paginations/pagination.py b/graphene_django_extras/paginations/pagination.py index 0b5caea..3c2bad6 100644 --- a/graphene_django_extras/paginations/pagination.py +++ b/graphene_django_extras/paginations/pagination.py @@ -121,8 +121,6 @@ def __init__(self, page_size=graphql_api_settings.DEFAULT_PAGE_SIZE, page_size_q self.page_size_query_description = 'Number of results to return per page. Default \'page_size\': {}'\ .format(self.page_size) - self.page_query_description = 'A page number within the paginated result set. Default: 1' - def to_dict(self): return { 'page_size': self.page_size, @@ -133,7 +131,8 @@ def to_dict(self): def to_graphql_fields(self): paginator_dict = { - self.page_query_param: Int(default_value=1, description=self.page_query_description), + self.page_query_param: Int(default_value=1, + description='A page number within the result paginated set. Default: 1'), } if self.page_size_query_param: @@ -172,7 +171,7 @@ def paginate_queryset(self, qs, **kwargs): class CursorGraphqlPagination(BaseDjangoGraphqlPagination): __name__ = 'CursorPaginator' - cursor_query_description = 'The paginations cursor value.' + cursor_query_description = 'The pagination cursor value.' page_size = graphql_api_settings.DEFAULT_PAGE_SIZE def __init__(self, ordering='-created', cursor_query_param='cursor'): diff --git a/graphene_django_extras/subscriptions/consumers.py b/graphene_django_extras/subscriptions/consumers.py index c2bd055..3a1f8b1 100644 --- a/graphene_django_extras/subscriptions/consumers.py +++ b/graphene_django_extras/subscriptions/consumers.py @@ -8,7 +8,7 @@ class GraphqlAPIDemultiplexer(WebsocketDemultiplexer): def connect(self, message, **kwargs): import json - """Forward connection to all consumers.""" + """ Forward connection to all consumers.""" resp = json.dumps({ "channel_id": self.message.reply_channel.name.split('.')[-1], "connect": 'success' diff --git a/graphene_django_extras/subscriptions/subscription.py b/graphene_django_extras/subscriptions/subscription.py index b0a72db..1e0721f 100644 --- a/graphene_django_extras/subscriptions/subscription.py +++ b/graphene_django_extras/subscriptions/subscription.py @@ -56,13 +56,13 @@ def __init_subclass_with_meta__(cls, mutation_class=None, stream=None, queryset= .format(cls.__name__, mutation_class) assert isinstance(stream, string_types), \ - 'You need to pass a valid string stream in {}.Meta, received "{}"'.format( + 'You need to pass a valid string stream name in {}.Meta, received "{}"'.format( cls.__name__, stream) if queryset: assert mutation_class._meta.model == queryset.model, \ - 'The queryset model must correspond with mutation_class model passed on it Meta class, received "{}",' \ - ' expected "{}"'.format(queryset.model.__name__, mutation_class._meta.model.__name__) + 'The queryset model must correspond with the mutation_class model passed on Meta class, received ' \ + '"{}", expected "{}"'.format(queryset.model.__name__, mutation_class._meta.model.__name__) description = description or 'Subscription Type for {} model'.format(mutation_class._meta.model.__name__) diff --git a/graphene_django_extras/utils.py b/graphene_django_extras/utils.py index 90a2bc2..f7746c2 100644 --- a/graphene_django_extras/utils.py +++ b/graphene_django_extras/utils.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from django import VERSION as DJANGO_VERSION from django.apps import apps -from django.contrib.contenttypes.fields import GenericForeignKey +from django.contrib.contenttypes.fields import GenericForeignKey, GenericRel from django.core.exceptions import ValidationError from django.db.models import NOT_PROVIDED from django.db.models import QuerySet, Manager @@ -21,10 +21,15 @@ def get_model_fields(model): else: private_fields = model._meta.virtual_fields + all_fields_list = list(model._meta.fields) + \ + list(model._meta.local_many_to_many) + \ + list(private_fields) + \ + list(model._meta.fields_map.values()) + local_fields = [ (field.name, field) for field - in list(model._meta.fields) + list(model._meta.local_many_to_many) + list(private_fields) + in all_fields_list ] # Make sure we don't duplicate local fields with "reverse" version @@ -177,7 +182,7 @@ def get_related_fields(model): return { field.name: field for field in model._meta.get_fields() - if field.is_relation and not isinstance(field, GenericForeignKey) + if field.is_relation and not isinstance(field, (GenericForeignKey, GenericRel)) }