Repository: graphql-python/graphene-django

Issue:
How to get `only_fields = ((),)` behavior? Goal: only shows fields declared in object type.
First: Apologies for this if it's not descriptive or covered by graphene-django. I actually think this is something that's requested in other issues, via #671 #97

When updating graphene-django from 2.4.0 -> 2.8.0, behavior with `only_fields = ((), )` changed

I inherited this as a code snippet and don't know where it's from.

What it used to do: Only fields that were in defined in the graphene object type would be shown.

Is there a way to get the equivalent to that?

Also, is there a way to get computed fields to also show via `fields = ()`?

Assume this example:

```python
from django.db import models

class Reporter(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)
    email = models.EmailField()

    def website(self):
        return "https://www.google.com"
```

Before: Only showed model fields declared in object type
Now: 

`fields = ((),)` gives:

```
  File "lib/python3.7/site-packages/graphene/utils/subclass_with_meta.py", line 52, in __init_subclass__
    super_class.__init_subclass_with_meta__(**options)
  File "lib/python3.7/site-packages/graphene_django/types.py", line 174, in __init_subclass_with_meta__
    construct_fields(model, registry, fields, exclude, convert_choices_to_enum),
  File "lib/python3.7/site-packages/graphene_django/types.py", line 42, in construct_fields
    if hasattr(model, name):
```

Changing to `fields = []` will show all fields on model.

The problem with this is its very exhaustive maintain a blacklist of fields via `exclude`. Since the model lives elsewhere in the code, it's thought of a bit separately. (Aside: This issue also exists in `django.contrib.admin` itself.)

```python
class Reporter(DjangoObjectType):
    first_name = graphene.String(required=True, description='First name')
    class Meta:
        model = ReporterModel
        fields = []

# expected
fields = list(Reporter._meta.fields.keys())
assert fields == ["first_name"]
```

Before: Allowed computed fields
Now: `Exception: "my_computed field" exists on model <class 'mymodel.Reporter'> but it's not a field.`

```python
class Reporter(DjangoObjectType):
    website = graphene.String(required=True, description='First name')
    class Meta:
        model = ReporterModel
        fields = ['website']

# Expected
fields = list(Reporter._meta.fields.keys())
assert fields == ["website"]
```


Discussion:
@tony if you don't want any fields from the Django model then why are you using a `DjangoObjectType` and not just a plain `ObjectType`?

Also the issue with computed types should be resolved in #862 which I'm hoping to release as v2.8.1 soon.
@jkimbo 

> @tony if you don't want any fields from the Django model then why are you using a DjangoObjectType and not just a plain ObjectType?

In practice, I use fields from the django model. The point is I only want to "opt-in" to fields in the Django models declared in the object type.

Does that make sense?

> Also the issue with computed types should be resolved in #862 which I'm hoping to release as v2.8.1 soon.

That will be good! In that case, when v2.8.1 is released I can update the original issue that it's resolved for that aspect. (Assuming this issue isn't closed as a `wontfix` :laughing: )

Resolving patch:
diff --git a/graphene_django/types.py b/graphene_django/types.py
--- a/graphene_django/types.py
+++ b/graphene_django/types.py
@@ -35,9 +35,15 @@ def construct_fields(
 
     fields = OrderedDict()
     for name, field in _model_fields:
-        is_not_in_only = only_fields and name not in only_fields
+        is_not_in_only = (
+            only_fields is not None
+            and only_fields != ALL_FIELDS
+            and name not in only_fields
+        )
         # is_already_created = name in options.fields
-        is_excluded = name in exclude_fields  # or is_already_created
+        is_excluded = (
+            exclude_fields is not None and name in exclude_fields
+        )  # or is_already_created
         # https://docs.djangoproject.com/en/1.10/ref/models/fields/#django.db.models.ForeignKey.related_query_name
         is_no_backref = str(name).endswith("+")
         if is_not_in_only or is_excluded or is_no_backref:
@@ -65,6 +71,7 @@ def construct_fields(
 def validate_fields(type_, model, fields, only_fields, exclude_fields):
     # Validate the given fields against the model's fields and custom fields
     all_field_names = set(fields.keys())
+    only_fields = only_fields if only_fields is not ALL_FIELDS else ()
     for name in only_fields or ():
         if name in all_field_names:
             continue
@@ -142,10 +149,10 @@ def __init_subclass_with_meta__(
         model=None,
         registry=None,
         skip_registry=False,
-        only_fields=(),  # deprecated in favour of `fields`
-        fields=(),
-        exclude_fields=(),  # deprecated in favour of `exclude`
-        exclude=(),
+        only_fields=None,  # deprecated in favour of `fields`
+        fields=None,
+        exclude_fields=None,  # deprecated in favour of `exclude`
+        exclude=None,
         filter_fields=None,
         filterset_class=None,
         connection=None,
@@ -200,9 +207,6 @@ def __init_subclass_with_meta__(
                 "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")


Test patch:
diff --git a/graphene_django/tests/test_types.py b/graphene_django/tests/test_types.py
--- a/graphene_django/tests/test_types.py
+++ b/graphene_django/tests/test_types.py
@@ -230,6 +230,17 @@ class Meta:
     assert fields == ["id", "email", "films"]
 
 
+@with_local_registry
+def test_django_objecttype_fields_empty():
+    class Reporter(DjangoObjectType):
+        class Meta:
+            model = ReporterModel
+            fields = ()
+
+    fields = list(Reporter._meta.fields.keys())
+    assert fields == []
+
+
 @with_local_registry
 def test_django_objecttype_only_fields_and_fields():
     with pytest.raises(Exception):