mirror of
https://github.com/graphql-python/graphene-django.git
synced 2025-07-16 11:12:20 +03:00
Alias only_fields
as fields
and exclude_fields
as exclude
(#691)
* Create new fields and exclude options that are aliased to exclude_fields and only_fields * Update docs * Add some checking around fields and exclude definitions * Add all fields option * Update docs to include `__all__` option * Actual order of fields is not stable * Update docs/queries.rst Co-Authored-By: Semyon Pupkov <semen.pupkov@gmail.com> * Fix example code * Format code * Start raising PendingDeprecationWarnings for using only_fields and exclude_fields * Update tests
This commit is contained in:
parent
a2103c19f4
commit
b7e4937775
|
@ -41,14 +41,18 @@ Full example
|
||||||
return Question.objects.get(pk=question_id)
|
return Question.objects.get(pk=question_id)
|
||||||
|
|
||||||
|
|
||||||
Fields
|
Specifying which fields to include
|
||||||
------
|
----------------------------------
|
||||||
|
|
||||||
By default, ``DjangoObjectType`` will present all fields on a Model through GraphQL.
|
By default, ``DjangoObjectType`` will present all fields on a Model through GraphQL.
|
||||||
If you don't want to do this you can change this by setting either ``only_fields`` and ``exclude_fields``.
|
If you only want a subset of fields to be present, you can do so using
|
||||||
|
``fields`` or ``exclude``. It is strongly recommended that you explicitly set
|
||||||
|
all fields that should be exposed using the fields attribute.
|
||||||
|
This will make it less likely to result in unintentionally exposing data when
|
||||||
|
your models change.
|
||||||
|
|
||||||
only_fields
|
``fields``
|
||||||
~~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
||||||
Show **only** these fields on the model:
|
Show **only** these fields on the model:
|
||||||
|
|
||||||
|
@ -57,24 +61,35 @@ Show **only** these fields on the model:
|
||||||
class QuestionType(DjangoObjectType):
|
class QuestionType(DjangoObjectType):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Question
|
model = Question
|
||||||
only_fields = ('question_text')
|
fields = ('id', 'question_text')
|
||||||
|
|
||||||
|
You can also set the ``fields`` attribute to the special value ``'__all__'`` to indicate that all fields in the model should be used.
|
||||||
|
|
||||||
exclude_fields
|
For example:
|
||||||
~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Show all fields **except** those in ``exclude_fields``:
|
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
class QuestionType(DjangoObjectType):
|
class QuestionType(DjangoObjectType):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Question
|
model = Question
|
||||||
exclude_fields = ('question_text')
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
Customised fields
|
``exclude``
|
||||||
~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
Show all fields **except** those in ``exclude``:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
class QuestionType(DjangoObjectType):
|
||||||
|
class Meta:
|
||||||
|
model = Question
|
||||||
|
exclude = ('question_text',)
|
||||||
|
|
||||||
|
|
||||||
|
Customising fields
|
||||||
|
------------------
|
||||||
|
|
||||||
You can completely overwrite a field, or add new fields, to a ``DjangoObjectType`` using a Resolver:
|
You can completely overwrite a field, or add new fields, to a ``DjangoObjectType`` using a Resolver:
|
||||||
|
|
||||||
|
@ -84,7 +99,7 @@ You can completely overwrite a field, or add new fields, to a ``DjangoObjectType
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Question
|
model = Question
|
||||||
exclude_fields = ('question_text')
|
fields = ('id', 'question_text')
|
||||||
|
|
||||||
extra_field = graphene.String()
|
extra_field = graphene.String()
|
||||||
|
|
||||||
|
@ -178,7 +193,7 @@ When ``Question`` is published as a ``DjangoObjectType`` and you want to add ``C
|
||||||
class QuestionType(DjangoObjectType):
|
class QuestionType(DjangoObjectType):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Question
|
model = Question
|
||||||
only_fields = ('category',)
|
fields = ('category',)
|
||||||
|
|
||||||
Then all query-able related models must be defined as DjangoObjectType subclass,
|
Then all query-able related models must be defined as DjangoObjectType subclass,
|
||||||
or they will fail to show if you are trying to query those relation fields. You only
|
or they will fail to show if you are trying to query those relation fields. You only
|
||||||
|
|
|
@ -774,7 +774,7 @@ def test_integer_field_filter_type():
|
||||||
model = Pet
|
model = Pet
|
||||||
interfaces = (Node,)
|
interfaces = (Node,)
|
||||||
filter_fields = {"age": ["exact"]}
|
filter_fields = {"age": ["exact"]}
|
||||||
only_fields = ["age"]
|
fields = ("age",)
|
||||||
|
|
||||||
class Query(ObjectType):
|
class Query(ObjectType):
|
||||||
pets = DjangoFilterConnectionField(PetType)
|
pets = DjangoFilterConnectionField(PetType)
|
||||||
|
|
|
@ -57,7 +57,9 @@ def convert_serializer_field(field, is_input=True):
|
||||||
|
|
||||||
|
|
||||||
def convert_serializer_to_input_type(serializer_class):
|
def convert_serializer_to_input_type(serializer_class):
|
||||||
cached_type = convert_serializer_to_input_type.cache.get(serializer_class.__name__, None)
|
cached_type = convert_serializer_to_input_type.cache.get(
|
||||||
|
serializer_class.__name__, None
|
||||||
|
)
|
||||||
if cached_type:
|
if cached_type:
|
||||||
return cached_type
|
return cached_type
|
||||||
serializer = serializer_class()
|
serializer = serializer_class()
|
||||||
|
|
|
@ -18,8 +18,12 @@ class MyFakeChildModel(models.Model):
|
||||||
class MyFakeParentModel(models.Model):
|
class MyFakeParentModel(models.Model):
|
||||||
name = models.CharField(max_length=50)
|
name = models.CharField(max_length=50)
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
child1 = models.OneToOneField(MyFakeChildModel, related_name='parent1', on_delete=models.CASCADE)
|
child1 = models.OneToOneField(
|
||||||
child2 = models.OneToOneField(MyFakeChildModel, related_name='parent2', on_delete=models.CASCADE)
|
MyFakeChildModel, related_name="parent1", on_delete=models.CASCADE
|
||||||
|
)
|
||||||
|
child2 = models.OneToOneField(
|
||||||
|
MyFakeChildModel, related_name="parent2", on_delete=models.CASCADE
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ParentType(DjangoObjectType):
|
class ParentType(DjangoObjectType):
|
||||||
|
|
|
@ -28,7 +28,7 @@ def test_should_query_only_fields():
|
||||||
class ReporterType(DjangoObjectType):
|
class ReporterType(DjangoObjectType):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Reporter
|
model = Reporter
|
||||||
only_fields = ("articles",)
|
fields = ("articles",)
|
||||||
|
|
||||||
schema = graphene.Schema(query=ReporterType)
|
schema = graphene.Schema(query=ReporterType)
|
||||||
query = """
|
query = """
|
||||||
|
@ -44,7 +44,7 @@ def test_should_query_simplelazy_objects():
|
||||||
class ReporterType(DjangoObjectType):
|
class ReporterType(DjangoObjectType):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Reporter
|
model = Reporter
|
||||||
only_fields = ("id",)
|
fields = ("id",)
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
class Query(graphene.ObjectType):
|
||||||
reporter = graphene.Field(ReporterType)
|
reporter = graphene.Field(ReporterType)
|
||||||
|
@ -289,7 +289,7 @@ def test_should_query_connectionfields():
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Reporter
|
model = Reporter
|
||||||
interfaces = (Node,)
|
interfaces = (Node,)
|
||||||
only_fields = ("articles",)
|
fields = ("articles",)
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
class Query(graphene.ObjectType):
|
||||||
all_reporters = DjangoConnectionField(ReporterType)
|
all_reporters = DjangoConnectionField(ReporterType)
|
||||||
|
@ -329,7 +329,7 @@ def test_should_keep_annotations():
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Reporter
|
model = Reporter
|
||||||
interfaces = (Node,)
|
interfaces = (Node,)
|
||||||
only_fields = ("articles",)
|
fields = ("articles",)
|
||||||
|
|
||||||
class ArticleType(DjangoObjectType):
|
class ArticleType(DjangoObjectType):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
@ -48,6 +48,6 @@ def test_should_map_only_few_fields():
|
||||||
class Reporter2(DjangoObjectType):
|
class Reporter2(DjangoObjectType):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Reporter
|
model = Reporter
|
||||||
only_fields = ("id", "email")
|
fields = ("id", "email")
|
||||||
|
|
||||||
assert list(Reporter2._meta.fields.keys()) == ["id", "email"]
|
assert list(Reporter2._meta.fields.keys()) == ["id", "email"]
|
||||||
|
|
|
@ -211,6 +211,8 @@ def with_local_registry(func):
|
||||||
|
|
||||||
@with_local_registry
|
@with_local_registry
|
||||||
def test_django_objecttype_only_fields():
|
def test_django_objecttype_only_fields():
|
||||||
|
with pytest.warns(PendingDeprecationWarning):
|
||||||
|
|
||||||
class Reporter(DjangoObjectType):
|
class Reporter(DjangoObjectType):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ReporterModel
|
model = ReporterModel
|
||||||
|
@ -221,16 +223,101 @@ def test_django_objecttype_only_fields():
|
||||||
|
|
||||||
|
|
||||||
@with_local_registry
|
@with_local_registry
|
||||||
def test_django_objecttype_exclude_fields():
|
def test_django_objecttype_fields():
|
||||||
class Reporter(DjangoObjectType):
|
class Reporter(DjangoObjectType):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ReporterModel
|
model = ReporterModel
|
||||||
exclude_fields = "email"
|
fields = ("id", "email", "films")
|
||||||
|
|
||||||
|
fields = list(Reporter._meta.fields.keys())
|
||||||
|
assert fields == ["id", "email", "films"]
|
||||||
|
|
||||||
|
|
||||||
|
@with_local_registry
|
||||||
|
def test_django_objecttype_only_fields_and_fields():
|
||||||
|
with pytest.raises(Exception):
|
||||||
|
|
||||||
|
class Reporter(DjangoObjectType):
|
||||||
|
class Meta:
|
||||||
|
model = ReporterModel
|
||||||
|
only_fields = ("id", "email", "films")
|
||||||
|
fields = ("id", "email", "films")
|
||||||
|
|
||||||
|
|
||||||
|
@with_local_registry
|
||||||
|
def test_django_objecttype_all_fields():
|
||||||
|
class Reporter(DjangoObjectType):
|
||||||
|
class Meta:
|
||||||
|
model = ReporterModel
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
fields = list(Reporter._meta.fields.keys())
|
||||||
|
assert len(fields) == len(ReporterModel._meta.get_fields())
|
||||||
|
|
||||||
|
|
||||||
|
@with_local_registry
|
||||||
|
def test_django_objecttype_exclude_fields():
|
||||||
|
with pytest.warns(PendingDeprecationWarning):
|
||||||
|
|
||||||
|
class Reporter(DjangoObjectType):
|
||||||
|
class Meta:
|
||||||
|
model = ReporterModel
|
||||||
|
exclude_fields = ["email"]
|
||||||
|
|
||||||
fields = list(Reporter._meta.fields.keys())
|
fields = list(Reporter._meta.fields.keys())
|
||||||
assert "email" not in fields
|
assert "email" not in fields
|
||||||
|
|
||||||
|
|
||||||
|
@with_local_registry
|
||||||
|
def test_django_objecttype_exclude():
|
||||||
|
class Reporter(DjangoObjectType):
|
||||||
|
class Meta:
|
||||||
|
model = ReporterModel
|
||||||
|
exclude = ["email"]
|
||||||
|
|
||||||
|
fields = list(Reporter._meta.fields.keys())
|
||||||
|
assert "email" not in fields
|
||||||
|
|
||||||
|
|
||||||
|
@with_local_registry
|
||||||
|
def test_django_objecttype_exclude_fields_and_exclude():
|
||||||
|
with pytest.raises(Exception):
|
||||||
|
|
||||||
|
class Reporter(DjangoObjectType):
|
||||||
|
class Meta:
|
||||||
|
model = ReporterModel
|
||||||
|
exclude = ["email"]
|
||||||
|
exclude_fields = ["email"]
|
||||||
|
|
||||||
|
|
||||||
|
@with_local_registry
|
||||||
|
def test_django_objecttype_exclude_and_only():
|
||||||
|
with pytest.raises(AssertionError):
|
||||||
|
|
||||||
|
class Reporter(DjangoObjectType):
|
||||||
|
class Meta:
|
||||||
|
model = ReporterModel
|
||||||
|
exclude = ["email"]
|
||||||
|
fields = ["id"]
|
||||||
|
|
||||||
|
|
||||||
|
@with_local_registry
|
||||||
|
def test_django_objecttype_fields_exclude_type_checking():
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
|
||||||
|
class Reporter(DjangoObjectType):
|
||||||
|
class Meta:
|
||||||
|
model = ReporterModel
|
||||||
|
fields = "foo"
|
||||||
|
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
|
||||||
|
class Reporter2(DjangoObjectType):
|
||||||
|
class Meta:
|
||||||
|
model = ReporterModel
|
||||||
|
fields = "foo"
|
||||||
|
|
||||||
|
|
||||||
class TestDjangoObjectType:
|
class TestDjangoObjectType:
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def PetModel(self):
|
def PetModel(self):
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import warnings
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
@ -24,6 +25,9 @@ if six.PY3:
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
|
|
||||||
|
ALL_FIELDS = "__all__"
|
||||||
|
|
||||||
|
|
||||||
def construct_fields(
|
def construct_fields(
|
||||||
model, registry, only_fields, exclude_fields, convert_choices_to_enum
|
model, registry, only_fields, exclude_fields, convert_choices_to_enum
|
||||||
):
|
):
|
||||||
|
@ -74,8 +78,10 @@ class DjangoObjectType(ObjectType):
|
||||||
model=None,
|
model=None,
|
||||||
registry=None,
|
registry=None,
|
||||||
skip_registry=False,
|
skip_registry=False,
|
||||||
only_fields=(),
|
only_fields=(), # deprecated in favour of `fields`
|
||||||
exclude_fields=(),
|
fields=(),
|
||||||
|
exclude_fields=(), # deprecated in favour of `exclude`
|
||||||
|
exclude=(),
|
||||||
filter_fields=None,
|
filter_fields=None,
|
||||||
filterset_class=None,
|
filterset_class=None,
|
||||||
connection=None,
|
connection=None,
|
||||||
|
@ -109,10 +115,48 @@ class DjangoObjectType(ObjectType):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
assert not (fields and exclude), (
|
||||||
|
"Cannot set both 'fields' and 'exclude' options on "
|
||||||
|
"DjangoObjectType {class_name}.".format(class_name=cls.__name__)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Alias only_fields -> fields
|
||||||
|
if only_fields and fields:
|
||||||
|
raise Exception("Can't set both only_fields and fields")
|
||||||
|
if only_fields:
|
||||||
|
warnings.warn(
|
||||||
|
"Defining `only_fields` is deprecated in favour of `fields`.",
|
||||||
|
PendingDeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
fields = only_fields
|
||||||
|
if fields and fields != ALL_FIELDS and not isinstance(fields, (list, tuple)):
|
||||||
|
raise TypeError(
|
||||||
|
'The `fields` option must be a list or tuple or "__all__". '
|
||||||
|
"Got %s." % type(fields).__name__
|
||||||
|
)
|
||||||
|
|
||||||
|
if fields == ALL_FIELDS:
|
||||||
|
fields = None
|
||||||
|
|
||||||
|
# Alias exclude_fields -> exclude
|
||||||
|
if exclude_fields and exclude:
|
||||||
|
raise Exception("Can't set both exclude_fields and exclude")
|
||||||
|
if exclude_fields:
|
||||||
|
warnings.warn(
|
||||||
|
"Defining `exclude_fields` is deprecated in favour of `exclude`.",
|
||||||
|
PendingDeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
exclude = exclude_fields
|
||||||
|
if exclude and not isinstance(exclude, (list, tuple)):
|
||||||
|
raise TypeError(
|
||||||
|
"The `exclude` option must be a list or tuple. Got %s."
|
||||||
|
% type(exclude).__name__
|
||||||
|
)
|
||||||
|
|
||||||
django_fields = yank_fields_from_attrs(
|
django_fields = yank_fields_from_attrs(
|
||||||
construct_fields(
|
construct_fields(model, registry, fields, exclude, convert_choices_to_enum),
|
||||||
model, registry, only_fields, exclude_fields, convert_choices_to_enum
|
|
||||||
),
|
|
||||||
_as=Field,
|
_as=Field,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user