1
0

schema.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. from collections import defaultdict
  2. from attr import attrib, attrs
  3. from django.apps import apps
  4. from django.db import models
  5. @attrs
  6. class Schema(object):
  7. # Vertices
  8. abstract_models = attrib()
  9. models = attrib()
  10. proxies = attrib()
  11. # Edges
  12. foreign_keys = attrib()
  13. inheritance = attrib()
  14. many_to_manys = attrib()
  15. one_to_ones = attrib()
  16. def get_app_models():
  17. for app in apps.get_app_configs():
  18. for model in app.get_models():
  19. yield app, model
  20. def get_model_id(model):
  21. app_label = model._meta.app_label
  22. try:
  23. app_name = apps.get_app_config(app_label).name
  24. except LookupError:
  25. app_name = model.__module__
  26. return (app_name, model.__name__)
  27. def get_field_relationships(model):
  28. foreign_keys = []
  29. one_to_one = []
  30. many_to_many = []
  31. model_id = get_model_id(model)
  32. for field in model._meta.get_fields():
  33. # Ignore non-relation fields
  34. if not field.is_relation:
  35. continue
  36. # Skip fields defined on superclasses
  37. if field.model != model:
  38. continue
  39. related_model = field.related_model
  40. # GenericForeignKey
  41. if related_model is None:
  42. continue
  43. related_model_id = get_model_id(related_model)
  44. relationship = (model_id, related_model_id)
  45. # Foreign key
  46. if field.many_to_one:
  47. foreign_keys.append(relationship)
  48. # One-to-one
  49. elif field.one_to_one and not field.auto_created:
  50. one_to_one.append(relationship)
  51. # Many-to-many
  52. elif field.many_to_many and not field.auto_created:
  53. through_model = getattr(model, field.name).through
  54. # We only add the M2M connection if the through-model is auto-created.
  55. # This stops us from creating two sets of connections (because the
  56. # connections will be created by the FK fields on the through model).
  57. if through_model._meta.auto_created:
  58. many_to_many.append(relationship)
  59. return foreign_keys, one_to_one, many_to_many
  60. def is_model_subclass(obj):
  61. if obj is models.Model:
  62. return False
  63. return issubclass(obj, models.Model)
  64. def get_schema():
  65. abstract_nodes = defaultdict(set)
  66. nodes = defaultdict(tuple)
  67. foreign_keys = []
  68. one_to_one = []
  69. many_to_many = []
  70. inheritance = set()
  71. proxy = []
  72. for app, model in get_app_models():
  73. app_label, model_name = model_id = get_model_id(model)
  74. nodes[app_label] += (model_name,)
  75. # Proxy models
  76. if model._meta.proxy:
  77. related_model_id = get_model_id(model._meta.proxy_for_model)
  78. relationship = (model_id, related_model_id)
  79. proxy.append(relationship)
  80. continue
  81. # Subclassing
  82. for base in filter(is_model_subclass, model.__mro__):
  83. base_app_label, base_model_name = get_model_id(base)
  84. if base._meta.abstract:
  85. abstract_nodes[base_app_label].add(base_model_name)
  86. for parent in filter(is_model_subclass, base.__bases__):
  87. inheritance.add(
  88. ((base_app_label, base_model_name), get_model_id(parent))
  89. )
  90. # Fields
  91. fks, o2o, m2m = get_field_relationships(model)
  92. foreign_keys.extend(fks)
  93. one_to_one.extend(o2o)
  94. many_to_many.extend(m2m)
  95. for app_label in nodes:
  96. nodes[app_label] = tuple(sorted(nodes[app_label]))
  97. for app_label in abstract_nodes:
  98. abstract_nodes[app_label] = tuple(sorted(abstract_nodes[app_label]))
  99. return Schema(
  100. # Vertices
  101. abstract_models=dict(abstract_nodes),
  102. models=dict(nodes),
  103. proxies=sorted(proxy),
  104. # Edges
  105. foreign_keys=sorted(foreign_keys),
  106. inheritance=sorted(inheritance),
  107. many_to_manys=sorted(many_to_many),
  108. one_to_ones=sorted(one_to_one),
  109. )