Repository: django-extensions/django-extensions

Issue:
Excluded models displayed as an underscore
Thanks for building the `graph_models` command, we find it very useful for our documentation. Unfortunately, we noticed a problem after we upgraded to a more recent version. I expected that excluding a model from `graph_models` would completely hide it from the graph. Instead, all my hidden models appear as an underscore when they are referenced by visible models.

Here is a simple example:

    # container/models.py
    from django.db import models
    
    
    class Foo(models.Model):
        name = models.CharField(max_length=30)
    
    
    class Bar(models.Model):
        name = models.CharField(max_length=30)
        foo = models.ForeignKey(Foo)

I graph that with this command:

    ./manage.py graph_models --pygraphviz container -o container.png

That produces the expected graph:

![good container]

When I exclude the `Foo` model with this command:

    ./manage.py graph_models --pygraphviz container -o container.png --exclude-models Foo

The model is replaced by an underscore. If the model is referenced by many other models, they are all linking back to this underscore.

![underscore container]

Is there a workaround for this? My current plan is to edit the dot file after it's generated, but I'd appreciate an easier alternative.

I tested this with django extensions 2.1.0.

[good container]: https://user-images.githubusercontent.com/1639148/43482308-2d2ab91c-94bd-11e8-8055-334cb9d0d54e.png
[underscore container]: https://user-images.githubusercontent.com/1639148/43482448-929630ba-94bd-11e8-9e9e-a0d21f9b191f.png


Discussion:
Pretty sure that this is a bug :-) Thanks for reporting it !



Resolving patch:
diff --git a/django_extensions/management/modelviz.py b/django_extensions/management/modelviz.py
--- a/django_extensions/management/modelviz.py
+++ b/django_extensions/management/modelviz.py
@@ -319,20 +319,27 @@ def process_local_fields(self, field, model, abstract_fields):
             # excluding fields inherited from abstract classes. they too show as local_fields
             return newmodel
         if isinstance(field, OneToOneField):
-            newmodel['relations'].append(self.add_relation(field, newmodel, '[arrowhead=none, arrowtail=none, dir=both]'))
+            relation = self.add_relation(field, newmodel, '[arrowhead=none, arrowtail=none, dir=both]')
         elif isinstance(field, ForeignKey):
-            newmodel['relations'].append(self.add_relation(field, newmodel, '[arrowhead=none, arrowtail=dot, dir=both]'))
+            relation = self.add_relation(field, newmodel, '[arrowhead=none, arrowtail=dot, dir=both]')
+        else:
+            relation = None
+        if relation is not None:
+            newmodel['relations'].append(relation)
         return newmodel
 
     def process_local_many_to_many(self, field, model):
         newmodel = model.copy()
         if self.skip_field(field):
             return newmodel
+        relation = None
         if isinstance(field, ManyToManyField):
             if hasattr(field.remote_field.through, '_meta') and field.remote_field.through._meta.auto_created:
-                newmodel['relations'].append(self.add_relation(field, newmodel, '[arrowhead=dot arrowtail=dot, dir=both]'))
+                relation = self.add_relation(field, newmodel, '[arrowhead=dot arrowtail=dot, dir=both]')
         elif isinstance(field, GenericRelation):
-            newmodel['relations'].append(self.add_relation(field, newmodel, mark_safe('[style="dotted", arrowhead=normal, arrowtail=normal, dir=both]')))
+            relation = self.add_relation(field, newmodel, mark_safe('[style="dotted", arrowhead=normal, arrowtail=normal, dir=both]'))
+        if relation is not None:
+            newmodel['relations'].append(relation)
         return newmodel
 
     def process_parent(self, parent, appmodel, model):


Test patch:
diff --git a/tests/management/commands/test_graph_models.py b/tests/management/commands/test_graph_models.py
--- a/tests/management/commands/test_graph_models.py
+++ b/tests/management/commands/test_graph_models.py
@@ -128,3 +128,19 @@ def test_disable_abstract_fields_active():
 
     output = out.getvalue()
     assert 'my_field_that_my_child_will_inherit' not in output
+
+
+def test_exclude_models_hides_relationships():
+    """ Expose bug #1229 where excluded models appear in relationships.
+
+    They are replaced with an underscore, but the relationship is still there.
+    """
+    out = StringIO()
+    call_command('graph_models',
+                 'django_extensions',
+                 exclude_models=['Personality', 'Note'],
+                 stdout=out)
+
+    output = out.getvalue()
+    assert 'tests_testapp_models_Person -> tests_testapp_models_Name' in output
+    assert 'tests_testapp_models_Person -> _' not in output