mirror of
https://github.com/graphql-python/graphene-django.git
synced 2025-04-26 12:03:47 +03:00
Merge remote-tracking branch 'origin/master' into convert_form_choice#7
This commit is contained in:
commit
e99d22510f
|
@ -6,10 +6,11 @@ from promise import Promise
|
||||||
|
|
||||||
from graphene.types import Field, List
|
from graphene.types import Field, List
|
||||||
from graphene.relay import ConnectionField, PageInfo
|
from graphene.relay import ConnectionField, PageInfo
|
||||||
|
from graphene.utils.get_unbound_function import get_unbound_function
|
||||||
from graphql_relay.connection.arrayconnection import connection_from_list_slice
|
from graphql_relay.connection.arrayconnection import connection_from_list_slice
|
||||||
|
|
||||||
from .settings import graphene_settings
|
from .settings import graphene_settings
|
||||||
from .utils import maybe_queryset
|
from .utils import maybe_queryset, auth_resolver
|
||||||
|
|
||||||
|
|
||||||
class DjangoListField(Field):
|
class DjangoListField(Field):
|
||||||
|
@ -151,3 +152,20 @@ class DjangoConnectionField(ConnectionField):
|
||||||
self.max_limit,
|
self.max_limit,
|
||||||
self.enforce_first_or_last,
|
self.enforce_first_or_last,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PermissionField(Field):
|
||||||
|
"""Class to manage permission for fields"""
|
||||||
|
|
||||||
|
def __init__(self, type, permissions=(), permissions_resolver=auth_resolver, *args, **kwargs):
|
||||||
|
"""Get permissions to access a field"""
|
||||||
|
super(PermissionField, self).__init__(type, *args, **kwargs)
|
||||||
|
self.permissions = permissions
|
||||||
|
self.permissions_resolver = permissions_resolver
|
||||||
|
|
||||||
|
def get_resolver(self, parent_resolver):
|
||||||
|
"""Intercept resolver to analyse permissions"""
|
||||||
|
parent_resolver = super(PermissionField, self).get_resolver(parent_resolver)
|
||||||
|
if self.permissions:
|
||||||
|
return partial(get_unbound_function(self.permissions_resolver), parent_resolver, self.permissions, True)
|
||||||
|
return parent_resolver
|
||||||
|
|
|
@ -107,7 +107,7 @@ class DjangoFormMutation(BaseDjangoFormMutation):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def __init_subclass_with_meta__(
|
def __init_subclass_with_meta__(
|
||||||
cls, form_class=None, only_fields=(), exclude_fields=(), **options
|
cls, form_class=None, mirror_input=False, only_fields=(), exclude_fields=(), **options
|
||||||
):
|
):
|
||||||
|
|
||||||
if not form_class:
|
if not form_class:
|
||||||
|
@ -115,7 +115,10 @@ class DjangoFormMutation(BaseDjangoFormMutation):
|
||||||
|
|
||||||
form = form_class()
|
form = form_class()
|
||||||
input_fields = fields_for_form(form, only_fields, exclude_fields)
|
input_fields = fields_for_form(form, only_fields, exclude_fields)
|
||||||
output_fields = fields_for_form(form, only_fields, exclude_fields)
|
if mirror_input:
|
||||||
|
output_fields = fields_for_form(form, only_fields, exclude_fields)
|
||||||
|
else:
|
||||||
|
output_fields = {}
|
||||||
|
|
||||||
_meta = DjangoFormMutationOptions(cls)
|
_meta = DjangoFormMutationOptions(cls)
|
||||||
_meta.form_class = form_class
|
_meta.form_class = form_class
|
||||||
|
|
|
@ -139,3 +139,36 @@ class ModelFormMutationTests(TestCase):
|
||||||
self.assertEqual(result.errors[0].messages, ["This field is required."])
|
self.assertEqual(result.errors[0].messages, ["This field is required."])
|
||||||
self.assertIn("age", fields_w_error)
|
self.assertIn("age", fields_w_error)
|
||||||
self.assertEqual(result.errors[1].messages, ["This field is required."])
|
self.assertEqual(result.errors[1].messages, ["This field is required."])
|
||||||
|
|
||||||
|
|
||||||
|
class FormMutationTests(TestCase):
|
||||||
|
def test_default_meta_fields(self):
|
||||||
|
class MyMutation(DjangoFormMutation):
|
||||||
|
class Meta:
|
||||||
|
form_class = MyForm
|
||||||
|
self.assertNotIn("text", MyMutation._meta.fields)
|
||||||
|
|
||||||
|
def test_mirror_meta_fields(self):
|
||||||
|
class MyMutation(DjangoFormMutation):
|
||||||
|
class Meta:
|
||||||
|
form_class = MyForm
|
||||||
|
mirror_input = True
|
||||||
|
|
||||||
|
self.assertIn("text", MyMutation._meta.fields)
|
||||||
|
|
||||||
|
def test_default_input_meta_fields(self):
|
||||||
|
class MyMutation(DjangoFormMutation):
|
||||||
|
class Meta:
|
||||||
|
form_class = MyForm
|
||||||
|
|
||||||
|
self.assertIn("client_mutation_id", MyMutation.Input._meta.fields)
|
||||||
|
self.assertIn("text", MyMutation.Input._meta.fields)
|
||||||
|
|
||||||
|
def test_exclude_fields_input_meta_fields(self):
|
||||||
|
class MyMutation(DjangoFormMutation):
|
||||||
|
class Meta:
|
||||||
|
form_class = MyForm
|
||||||
|
exclude_fields = ['text']
|
||||||
|
|
||||||
|
self.assertNotIn("text", MyMutation.Input._meta.fields)
|
||||||
|
self.assertIn("client_mutation_id", MyMutation.Input._meta.fields)
|
||||||
|
|
46
graphene_django/tests/test_fields.py
Normal file
46
graphene_django/tests/test_fields.py
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
from unittest import TestCase
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from graphene_django.fields import PermissionField
|
||||||
|
|
||||||
|
|
||||||
|
class MyInstance(object):
|
||||||
|
value = "value"
|
||||||
|
|
||||||
|
def resolver(self):
|
||||||
|
return "resolver method"
|
||||||
|
|
||||||
|
|
||||||
|
class PermissionFieldTests(TestCase):
|
||||||
|
|
||||||
|
def test_permission_field(self):
|
||||||
|
MyType = object()
|
||||||
|
field = PermissionField(MyType, permissions=['perm1', 'perm2'], source='resolver')
|
||||||
|
resolver = field.get_resolver(None)
|
||||||
|
|
||||||
|
class Viewer(object):
|
||||||
|
def has_perm(self, perm):
|
||||||
|
return perm == 'perm2'
|
||||||
|
|
||||||
|
class Info(object):
|
||||||
|
class Context(object):
|
||||||
|
user = Viewer()
|
||||||
|
context = Context()
|
||||||
|
|
||||||
|
self.assertEqual(resolver(MyInstance(), Info()), MyInstance().resolver())
|
||||||
|
|
||||||
|
def test_permission_field_without_permission(self):
|
||||||
|
MyType = object()
|
||||||
|
field = PermissionField(MyType, permissions=['perm1', 'perm2'], source='resolver')
|
||||||
|
resolver = field.get_resolver(field.resolver)
|
||||||
|
|
||||||
|
class Viewer(object):
|
||||||
|
def has_perm(self, perm):
|
||||||
|
return False
|
||||||
|
|
||||||
|
class Info(object):
|
||||||
|
class Context(object):
|
||||||
|
user = Viewer()
|
||||||
|
context = Context()
|
||||||
|
|
||||||
|
with self.assertRaises(PermissionDenied):
|
||||||
|
resolver(MyInstance(), Info())
|
|
@ -1,4 +1,4 @@
|
||||||
from ..utils import get_model_fields
|
from ..utils import get_model_fields, has_permissions
|
||||||
from .models import Film, Reporter
|
from .models import Film, Reporter
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,3 +10,23 @@ def test_get_model_fields_no_duplication():
|
||||||
film_fields = get_model_fields(Film)
|
film_fields = get_model_fields(Film)
|
||||||
film_name_set = set([field[0] for field in film_fields])
|
film_name_set = set([field[0] for field in film_fields])
|
||||||
assert len(film_fields) == len(film_name_set)
|
assert len(film_fields) == len(film_name_set)
|
||||||
|
|
||||||
|
|
||||||
|
def test_has_permissions():
|
||||||
|
class Viewer(object):
|
||||||
|
@staticmethod
|
||||||
|
def has_perm(permission):
|
||||||
|
return permission
|
||||||
|
|
||||||
|
viewer_as_perm = has_permissions(Viewer(), [False, True, False])
|
||||||
|
assert viewer_as_perm
|
||||||
|
|
||||||
|
|
||||||
|
def test_viewer_without_permissions():
|
||||||
|
class Viewer(object):
|
||||||
|
@staticmethod
|
||||||
|
def has_perm(permission):
|
||||||
|
return permission
|
||||||
|
|
||||||
|
viewer_as_perm = has_permissions(Viewer(), [False, False, False])
|
||||||
|
assert not viewer_as_perm
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import inspect
|
import inspect
|
||||||
|
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.manager import Manager
|
from django.db.models.manager import Manager
|
||||||
|
|
||||||
|
|
||||||
# from graphene.utils import LazyList
|
# from graphene.utils import LazyList
|
||||||
|
from graphene.utils.get_unbound_function import get_unbound_function
|
||||||
|
|
||||||
|
|
||||||
class LazyList(object):
|
class LazyList(object):
|
||||||
|
@ -81,3 +83,53 @@ def import_single_dispatch():
|
||||||
)
|
)
|
||||||
|
|
||||||
return singledispatch
|
return singledispatch
|
||||||
|
|
||||||
|
|
||||||
|
def has_permissions(viewer, permissions):
|
||||||
|
"""
|
||||||
|
Verify that at least one permission is accomplished
|
||||||
|
:param viewer: Field's viewer
|
||||||
|
:param permissions: Field permissions
|
||||||
|
:return: True if viewer has permission. False otherwise.
|
||||||
|
"""
|
||||||
|
if not permissions:
|
||||||
|
return True
|
||||||
|
return any([viewer.has_perm(perm) for perm in permissions])
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_bound_resolver(resolver, root, info, **args):
|
||||||
|
"""
|
||||||
|
Resolve provided resolver
|
||||||
|
:param resolver: Explicit field resolver
|
||||||
|
:param root: Schema root
|
||||||
|
:param info: Schema info
|
||||||
|
:param args: Schema args
|
||||||
|
:return: Resolved field
|
||||||
|
"""
|
||||||
|
resolver = get_unbound_function(resolver)
|
||||||
|
return resolver(root, info, **args)
|
||||||
|
|
||||||
|
|
||||||
|
def auth_resolver(parent_resolver, permissions, raise_exception, root, info, **args):
|
||||||
|
"""
|
||||||
|
Middleware resolver to check viewer's permissions
|
||||||
|
:param parent_resolver: Field resolver
|
||||||
|
:param permissions: Field permissions
|
||||||
|
:param raise_exception: If True a PermissionDenied is raised
|
||||||
|
:param root: Schema root
|
||||||
|
:param info: Schema info
|
||||||
|
:param args: Schema args
|
||||||
|
:return: Resolved field. None if the viewer does not have permission to access the field.
|
||||||
|
"""
|
||||||
|
# Get viewer from context
|
||||||
|
if not hasattr(info.context, 'user'):
|
||||||
|
raise PermissionDenied()
|
||||||
|
user = info.context.user
|
||||||
|
|
||||||
|
if has_permissions(user, permissions):
|
||||||
|
if parent_resolver:
|
||||||
|
# A resolver is provided in the class
|
||||||
|
return resolve_bound_resolver(parent_resolver, root, info, **args)
|
||||||
|
elif raise_exception:
|
||||||
|
raise PermissionDenied()
|
||||||
|
return None
|
||||||
|
|
Loading…
Reference in New Issue
Block a user