Repository: django-extensions/django-extensions
Pull request #1665 (2021-05-10 12:23:06)

Add --relation-fields-only flag to graph_models
When using the management command `graph_models` for a database with a lot of models and a lot of fields, the resulting graph can get quite big and confusing. By introducing a flag `--relation-fields-only` only fields of the type `[ForeignKey, ManyToManyField, OneToOneField, RelatedField, OneToOneRel, ManyToOneRel]` are displayed. This leads to a more clean graph with a better overview that highlights the connections between models while hiding all other fields.

Since this is my first contribution to a public project ever I hope I contributed in the right way. Let me know if something should be improved or changed.

OmenApps: A quick glance at the code - this looks great! I have 200+ models in one of my projects, so I understand exactly where you're coming from with this.

And you included tests, too!

I think the maintainers are backlogged a bit right now, but this looks like a pretty solid commit once they get to it.
BergiBerghold: Looks good, I'd like to see this in django-extensions

Patch:
diff --git a/django_extensions/management/commands/graph_models.py b/django_extensions/management/commands/graph_models.py
index 1f2f651c..3e0ef77d 100644
--- a/django_extensions/management/commands/graph_models.py
+++ b/django_extensions/management/commands/graph_models.py
@@ -153,6 +153,12 @@ class Command(BaseCommand):
                 'dest': 'relations_as_fields',
                 'help': 'Do not show relations as fields in the graph.',
             },
+            '--relation-fields-only': {
+                'action': 'store',
+                'default': False,
+                'dest': 'relation_fields_only',
+                'help': 'Only display fields that are relevant for relations',
+            },
             '--disable-sort-fields -S': {
                 'action': 'store_false',
                 'default': True,
diff --git a/django_extensions/management/modelviz.py b/django_extensions/management/modelviz.py
index fc49da6c..81804a3a 100644
--- a/django_extensions/management/modelviz.py
+++ b/django_extensions/management/modelviz.py
@@ -17,6 +17,9 @@ from django.db.models import deletion
 from django.db.models.fields.related import (
     ForeignKey, ManyToManyField, OneToOneField, RelatedField,
 )
+from django.db.models.fields.reverse_related import (
+    OneToOneRel, ManyToOneRel,
+)
 from django.contrib.contenttypes.fields import GenericRelation
 from django.template import Context, Template, loader
 from django.utils.encoding import force_str
@@ -42,6 +45,7 @@ __contributors__ = [
     "Mikkel Munch Mortensen <https://www.detfalskested.dk/>",
     "Andrzej Bistram <andrzej.bistram@gmail.com>",
     "Daniel Lipsitt <danlipsitt@gmail.com>",
+    "Tobias Mitterdorfer <tobias.mitterdorfer97@gmail.com>"
 ]
 
 
@@ -80,6 +84,7 @@ class ModelGraph:
         self.verbose_names = kwargs.get('verbose_names', False)
         self.inheritance = kwargs.get('inheritance', True)
         self.relations_as_fields = kwargs.get("relations_as_fields", True)
+        self.relation_fields_only = kwargs.get("relation_fields_only", False)
         self.sort_fields = kwargs.get("sort_fields", True)
         self.language = kwargs.get('language', None)
         if self.language is not None:
@@ -439,6 +444,12 @@ class ModelGraph:
                     return True
             if field.name in self.exclude_columns:
                 return True
+        if self.relation_fields_only:
+            if not isinstance(
+                field,
+                (ForeignKey, ManyToManyField, OneToOneField, RelatedField, OneToOneRel, ManyToOneRel)
+            ):
+                return True
         return False
 
 


Test patch:
diff --git a/tests/management/commands/test_graph_models.py b/tests/management/commands/test_graph_models.py
index f502c834..206a70b2 100644
--- a/tests/management/commands/test_graph_models.py
+++ b/tests/management/commands/test_graph_models.py
@@ -106,6 +106,26 @@ class GraphModelsOutputTests(TestCase):
         with self.assertRaises(CommandError):
             call_command('graph_models', all_applications=True, pygraphviz=True)
 
+    def test_graph_models_relation_fields_only(self):
+        # use of --relation-fields-only ignores all non-relation-fields
+        stdout = StringIO()
+        call_command('graph_models', all_applications=True, stdout=stdout, json=True)
+        with_no_flag = json.loads(stdout.getvalue())
+
+        stdout = StringIO()
+        call_command('graph_models', all_applications=True, relation_fields_only=True, stdout=stdout, json=True)
+        with_flag = json.loads(stdout.getvalue())
+
+        # delete all entries for fields that do not display relations
+        for graph_idx, graph in reversed(list(enumerate(with_no_flag["graphs"]))):
+            for model_idx, model in reversed(list(enumerate(graph["models"]))):
+                for field_idx, field in reversed(list(enumerate(model["fields"]))):
+                    if field["type"] not in ["ForeignKey (id)", "AutoField", "OneToOneField (id)"] and not field["primary_key"]:
+                        del with_no_flag["graphs"][graph_idx]["models"][model_idx]["fields"][field_idx]
+
+        # assert that manually deleting all non-relation-fields is same as using the flag
+        self.assertEqual(with_no_flag, with_flag)
+
 
 def test_disable_abstract_fields_not_active():
     out = StringIO()