model_meta.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. """
  2. Helper function for returning the field information that is associated
  3. with a model class. This includes returning all the forward and reverse
  4. relationships and their associated metadata.
  5. Usage: `get_field_info(model)` returns a `FieldInfo` instance.
  6. """
  7. from collections import OrderedDict, namedtuple
  8. FieldInfo = namedtuple('FieldResult', [
  9. 'pk', # Model field instance
  10. 'fields', # Dict of field name -> model field instance
  11. 'forward_relations', # Dict of field name -> RelationInfo
  12. 'reverse_relations', # Dict of field name -> RelationInfo
  13. 'fields_and_pk', # Shortcut for 'pk' + 'fields'
  14. 'relations' # Shortcut for 'forward_relations' + 'reverse_relations'
  15. ])
  16. RelationInfo = namedtuple('RelationInfo', [
  17. 'model_field',
  18. 'related_model',
  19. 'to_many',
  20. 'to_field',
  21. 'has_through_model',
  22. 'reverse'
  23. ])
  24. def get_field_info(model):
  25. """
  26. Given a model class, returns a `FieldInfo` instance, which is a
  27. `namedtuple`, containing metadata about the various field types on the model
  28. including information about their relationships.
  29. """
  30. opts = model._meta.concrete_model._meta
  31. pk = _get_pk(opts)
  32. fields = _get_fields(opts)
  33. forward_relations = _get_forward_relationships(opts)
  34. reverse_relations = _get_reverse_relationships(opts)
  35. fields_and_pk = _merge_fields_and_pk(pk, fields)
  36. relationships = _merge_relationships(forward_relations, reverse_relations)
  37. return FieldInfo(pk, fields, forward_relations, reverse_relations,
  38. fields_and_pk, relationships)
  39. def _get_pk(opts):
  40. pk = opts.pk
  41. rel = pk.remote_field
  42. while rel and rel.parent_link:
  43. # If model is a child via multi-table inheritance, use parent's pk.
  44. pk = pk.remote_field.model._meta.pk
  45. rel = pk.remote_field
  46. return pk
  47. def _get_fields(opts):
  48. fields = OrderedDict()
  49. for field in [field for field in opts.fields if field.serialize and not field.remote_field]:
  50. fields[field.name] = field
  51. return fields
  52. def _get_to_field(field):
  53. return getattr(field, 'to_fields', None) and field.to_fields[0]
  54. def _get_forward_relationships(opts):
  55. """
  56. Returns an `OrderedDict` of field names to `RelationInfo`.
  57. """
  58. forward_relations = OrderedDict()
  59. for field in [field for field in opts.fields if field.serialize and field.remote_field]:
  60. forward_relations[field.name] = RelationInfo(
  61. model_field=field,
  62. related_model=field.remote_field.model,
  63. to_many=False,
  64. to_field=_get_to_field(field),
  65. has_through_model=False,
  66. reverse=False
  67. )
  68. # Deal with forward many-to-many relationships.
  69. for field in [field for field in opts.many_to_many if field.serialize]:
  70. forward_relations[field.name] = RelationInfo(
  71. model_field=field,
  72. related_model=field.remote_field.model,
  73. to_many=True,
  74. # manytomany do not have to_fields
  75. to_field=None,
  76. has_through_model=(
  77. not field.remote_field.through._meta.auto_created
  78. ),
  79. reverse=False
  80. )
  81. return forward_relations
  82. def _get_reverse_relationships(opts):
  83. """
  84. Returns an `OrderedDict` of field names to `RelationInfo`.
  85. """
  86. reverse_relations = OrderedDict()
  87. all_related_objects = [r for r in opts.related_objects if not r.field.many_to_many]
  88. for relation in all_related_objects:
  89. accessor_name = relation.get_accessor_name()
  90. reverse_relations[accessor_name] = RelationInfo(
  91. model_field=None,
  92. related_model=relation.related_model,
  93. to_many=relation.field.remote_field.multiple,
  94. to_field=_get_to_field(relation.field),
  95. has_through_model=False,
  96. reverse=True
  97. )
  98. # Deal with reverse many-to-many relationships.
  99. all_related_many_to_many_objects = [r for r in opts.related_objects if r.field.many_to_many]
  100. for relation in all_related_many_to_many_objects:
  101. accessor_name = relation.get_accessor_name()
  102. reverse_relations[accessor_name] = RelationInfo(
  103. model_field=None,
  104. related_model=relation.related_model,
  105. to_many=True,
  106. # manytomany do not have to_fields
  107. to_field=None,
  108. has_through_model=(
  109. (getattr(relation.field.remote_field, 'through', None) is not None) and
  110. not relation.field.remote_field.through._meta.auto_created
  111. ),
  112. reverse=True
  113. )
  114. return reverse_relations
  115. def _merge_fields_and_pk(pk, fields):
  116. fields_and_pk = OrderedDict()
  117. fields_and_pk['pk'] = pk
  118. fields_and_pk[pk.name] = pk
  119. fields_and_pk.update(fields)
  120. return fields_and_pk
  121. def _merge_relationships(forward_relations, reverse_relations):
  122. return OrderedDict(
  123. list(forward_relations.items()) +
  124. list(reverse_relations.items())
  125. )
  126. def is_abstract_model(model):
  127. """
  128. Given a model class, returns a boolean True if it is abstract and False if it is not.
  129. """
  130. return hasattr(model, '_meta') and hasattr(model._meta, 'abstract') and model._meta.abstract