utils.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. from collections import namedtuple
  2. from django.db.models.fields.related import RECURSIVE_RELATIONSHIP_CONSTANT
  3. def resolve_relation(model, app_label=None, model_name=None):
  4. """
  5. Turn a model class or model reference string and return a model tuple.
  6. app_label and model_name are used to resolve the scope of recursive and
  7. unscoped model relationship.
  8. """
  9. if isinstance(model, str):
  10. if model == RECURSIVE_RELATIONSHIP_CONSTANT:
  11. if app_label is None or model_name is None:
  12. raise TypeError(
  13. 'app_label and model_name must be provided to resolve '
  14. 'recursive relationships.'
  15. )
  16. return app_label, model_name
  17. if '.' in model:
  18. app_label, model_name = model.split('.', 1)
  19. return app_label, model_name.lower()
  20. if app_label is None:
  21. raise TypeError(
  22. 'app_label must be provided to resolve unscoped model '
  23. 'relationships.'
  24. )
  25. return app_label, model.lower()
  26. return model._meta.app_label, model._meta.model_name
  27. FieldReference = namedtuple('FieldReference', 'to through')
  28. def field_references(
  29. model_tuple,
  30. field,
  31. reference_model_tuple,
  32. reference_field_name=None,
  33. reference_field=None,
  34. ):
  35. """
  36. Return either False or a FieldReference if `field` references provided
  37. context.
  38. False positives can be returned if `reference_field_name` is provided
  39. without `reference_field` because of the introspection limitation it
  40. incurs. This should not be an issue when this function is used to determine
  41. whether or not an optimization can take place.
  42. """
  43. remote_field = field.remote_field
  44. if not remote_field:
  45. return False
  46. references_to = None
  47. references_through = None
  48. if resolve_relation(remote_field.model, *model_tuple) == reference_model_tuple:
  49. to_fields = getattr(field, 'to_fields', None)
  50. if (
  51. reference_field_name is None or
  52. # Unspecified to_field(s).
  53. to_fields is None or
  54. # Reference to primary key.
  55. (None in to_fields and (reference_field is None or reference_field.primary_key)) or
  56. # Reference to field.
  57. reference_field_name in to_fields
  58. ):
  59. references_to = (remote_field, to_fields)
  60. through = getattr(remote_field, 'through', None)
  61. if through and resolve_relation(through, *model_tuple) == reference_model_tuple:
  62. through_fields = remote_field.through_fields
  63. if (
  64. reference_field_name is None or
  65. # Unspecified through_fields.
  66. through_fields is None or
  67. # Reference to field.
  68. reference_field_name in through_fields
  69. ):
  70. references_through = (remote_field, through_fields)
  71. if not (references_to or references_through):
  72. return False
  73. return FieldReference(references_to, references_through)
  74. def get_references(state, model_tuple, field_tuple=()):
  75. """
  76. Generator of (model_state, name, field, reference) referencing
  77. provided context.
  78. If field_tuple is provided only references to this particular field of
  79. model_tuple will be generated.
  80. """
  81. for state_model_tuple, model_state in state.models.items():
  82. for name, field in model_state.fields.items():
  83. reference = field_references(state_model_tuple, field, model_tuple, *field_tuple)
  84. if reference:
  85. yield model_state, name, field, reference
  86. def field_is_referenced(state, model_tuple, field_tuple):
  87. """Return whether `field_tuple` is referenced by any state models."""
  88. return next(get_references(state, model_tuple, field_tuple), None) is not None