diff --git a/graphene_django/fields.py b/graphene_django/fields.py index 41132e2..ae18e98 100644 --- a/graphene_django/fields.py +++ b/graphene_django/fields.py @@ -164,4 +164,4 @@ class DjangoPermissionField(Field): def get_resolver(self, parent_resolver): """Intercept resolver to analyse permissions""" - return partial(auth_resolver, self.resolver or parent_resolver, self.permissions, True) + return partial(auth_resolver, self.resolver or parent_resolver, self.permissions, None, None, True) diff --git a/graphene_django/tests/test_types.py b/graphene_django/tests/test_types.py index 8a8643b..8ad1465 100644 --- a/graphene_django/tests/test_types.py +++ b/graphene_django/tests/test_types.py @@ -1,10 +1,10 @@ from mock import patch -from graphene import Interface, ObjectType, Schema, Connection, String +from graphene import Interface, ObjectType, Schema, Connection, String, Field from graphene.relay import Node from .. import registry -from ..types import DjangoObjectType, DjangoObjectTypeOptions +from ..types import DjangoObjectType, DjangoObjectTypeOptions, DjangoPermissionObjectType from .models import Article as ArticleModel from .models import Reporter as ReporterModel @@ -224,3 +224,101 @@ def test_django_objecttype_exclude_fields(): fields = list(Reporter._meta.fields.keys()) assert "email" not in fields + + +def extra_field_resolver(root, info, **kwargs): + return 'extra field' + + +class PermissionArticle(DjangoPermissionObjectType): + """Basic Type to test""" + + class Meta(object): + """Meta Class""" + field_to_permission = { + 'headline': ('content_type.permission1',), + 'pub_date': ('content_type.permission2',) + } + permission_to_field = { + 'content_type.permission3': ('headline', 'reporter', 'extra_field',) + } + model = ArticleModel + + extra_field = Field(String, resolver=extra_field_resolver) + + def resolve_headline(self, info, **kwargs): + return 'headline' + + +def test_django_permissions(): + expected = { + 'headline': ('content_type.permission1', 'content_type.permission3'), + 'pub_date': ('content_type.permission2',), + 'reporter': ('content_type.permission3',), + 'extra_field': ('content_type.permission3',), + } + assert PermissionArticle._field_permissions == expected + + +def test_permission_resolver(): + MyType = object() + + class Viewer(object): + def has_perm(self, perm): + return perm == 'content_type.permission3' + + class Info(object): + class Context(object): + user = Viewer() + context = Context() + + resolved = PermissionArticle.resolve_headline(MyType, Info()) + assert resolved == 'headline' + + +def test_resolver_without_permission(): + MyType = object() + + class Viewer(object): + def has_perm(self, perm): + return False + + class Info(object): + class Context(object): + user = Viewer() + context = Context() + + resolved = PermissionArticle.resolve_headline(MyType, Info()) + assert resolved is None + + +def test_permission_resolver_to_field(): + MyType = object() + + class Viewer(object): + def has_perm(self, perm): + return perm == 'content_type.permission3' + + class Info(object): + class Context(object): + user = Viewer() + context = Context() + + resolved = PermissionArticle.resolve_extra_field(MyType, Info()) + assert resolved == 'extra field' + + +def test_resolver_to_field_without_permission(): + MyType = object() + + class Viewer(object): + def has_perm(self, perm): + return perm != 'content_type.permission3' + + class Info(object): + class Context(object): + user = Viewer() + context = Context() + + resolved = PermissionArticle.resolve_extra_field(MyType, Info()) + assert resolved is None diff --git a/graphene_django/types.py b/graphene_django/types.py index b78c7ea..3699308 100644 --- a/graphene_django/types.py +++ b/graphene_django/types.py @@ -9,7 +9,7 @@ from graphene.types.utils import yank_fields_from_attrs from .converter import convert_django_field_with_choices from .registry import Registry, get_global_registry -from .utils import DJANGO_FILTER_INSTALLED, get_model_fields, is_valid_django_model +from .utils import DJANGO_FILTER_INSTALLED, get_model_fields, is_valid_django_model, auth_resolver def construct_fields(model, registry, only_fields, exclude_fields): @@ -170,7 +170,7 @@ class DjangoPermissionObjectType(DjangoObjectType): for field_name, field_permissions in cls._field_permissions.items(): attr = 'resolve_{}'.format(field_name) - resolver = getattr(cls, attr, None) + resolver = getattr(cls._meta.fields[field_name], 'resolver', None) or getattr(cls, attr, None) if not hasattr(field_permissions, '__iter__'): field_permissions = tuple(field_permissions) @@ -188,7 +188,7 @@ class DjangoPermissionObjectType(DjangoObjectType): _as=Field, ) for name, field in django_fields.items(): - if isinstance(field._type, NonNull): + if hasattr(field, '_type') and isinstance(field._type, NonNull): field._type = field._type._of_type setattr(cls, name, field) @@ -211,7 +211,7 @@ class DjangoPermissionObjectType(DjangoObjectType): cls._field_permissions[field] = cls._field_permissions.get(field, tuple()) + permissions @classmethod - def set_auth_resolver(cls, name, permissions, field, resolver=None): + def set_auth_resolver(cls, name, permissions, resolver=None): """ Set middleware resolver to handle field permissions :param name: Field name @@ -220,4 +220,4 @@ class DjangoPermissionObjectType(DjangoObjectType): :param resolver: Field resolver :return: Middleware resolver to check permissions """ - field.resolver = partial(auth_resolver, field.resolver or resolver, name, permissions, None, False) + return partial(auth_resolver, resolver, permissions, name, None, False) diff --git a/graphene_django/utils.py b/graphene_django/utils.py index 55581c4..bbc984b 100644 --- a/graphene_django/utils.py +++ b/graphene_django/utils.py @@ -6,6 +6,7 @@ from django.db.models.manager import Manager # from graphene.utils import LazyList +from graphene.types.resolver import get_default_resolver from graphene.utils.get_unbound_function import get_unbound_function @@ -110,11 +111,27 @@ def resolve_bound_resolver(resolver, root, info, **args): return resolver(root, info, **args) -def auth_resolver(parent_resolver, permissions, raise_exception, root, info, **args): +def resolve_default_resolver(attname, default_value, root, info, **args): + """ + Resolve field with default resolver + :param attname: Field name + :param default_value: Field default value + :param root: Schema root + :param info: Schema info + :param args: Schema args + :return: Resolved field + """ + resolver = get_default_resolver() + return resolver(attname, default_value, root, info, **args) + + +def auth_resolver(parent_resolver, permissions, attname, default_value, raise_exception, root, info, **args): """ Middleware resolver to check viewer's permissions :param parent_resolver: Field resolver :param permissions: Field permissions + :param attname: Field name + :param default_value: Default value to field if no resolver is provided :param raise_exception: If True a PermissionDenied is raised :param root: Schema root :param info: Schema info @@ -127,6 +144,8 @@ def auth_resolver(parent_resolver, permissions, raise_exception, root, info, **a if parent_resolver: # A resolver is provided in the class return resolve_bound_resolver(parent_resolver, root, info, **args) + # Get default resolver + return resolve_default_resolver(attname, default_value, root, info, **args) elif raise_exception: raise PermissionDenied() return None