Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

model_checks.py 6.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. import inspect
  2. import types
  3. from itertools import chain
  4. from django.apps import apps
  5. from django.core.checks import Error, Tags, register
  6. @register(Tags.models)
  7. def check_all_models(app_configs=None, **kwargs):
  8. errors = []
  9. if app_configs is None:
  10. models = apps.get_models()
  11. else:
  12. models = chain.from_iterable(app_config.get_models() for app_config in app_configs)
  13. for model in models:
  14. if not inspect.ismethod(model.check):
  15. errors.append(
  16. Error(
  17. "The '%s.check()' class method is currently overridden by %r."
  18. % (model.__name__, model.check),
  19. obj=model,
  20. id='models.E020'
  21. )
  22. )
  23. else:
  24. errors.extend(model.check(**kwargs))
  25. return errors
  26. def _check_lazy_references(apps, ignore=None):
  27. """
  28. Ensure all lazy (i.e. string) model references have been resolved.
  29. Lazy references are used in various places throughout Django, primarily in
  30. related fields and model signals. Identify those common cases and provide
  31. more helpful error messages for them.
  32. The ignore parameter is used by StateApps to exclude swappable models from
  33. this check.
  34. """
  35. pending_models = set(apps._pending_operations) - (ignore or set())
  36. # Short circuit if there aren't any errors.
  37. if not pending_models:
  38. return []
  39. from django.db.models import signals
  40. model_signals = {
  41. signal: name for name, signal in vars(signals).items()
  42. if isinstance(signal, signals.ModelSignal)
  43. }
  44. def extract_operation(obj):
  45. """
  46. Take a callable found in Apps._pending_operations and identify the
  47. original callable passed to Apps.lazy_model_operation(). If that
  48. callable was a partial, return the inner, non-partial function and
  49. any arguments and keyword arguments that were supplied with it.
  50. obj is a callback defined locally in Apps.lazy_model_operation() and
  51. annotated there with a `func` attribute so as to imitate a partial.
  52. """
  53. operation, args, keywords = obj, [], {}
  54. while hasattr(operation, 'func'):
  55. # The or clauses are redundant but work around a bug (#25945) in
  56. # functools.partial in Python <= 3.5.1.
  57. args.extend(getattr(operation, 'args', []) or [])
  58. keywords.update(getattr(operation, 'keywords', {}) or {})
  59. operation = operation.func
  60. return operation, args, keywords
  61. def app_model_error(model_key):
  62. try:
  63. apps.get_app_config(model_key[0])
  64. model_error = "app '%s' doesn't provide model '%s'" % model_key
  65. except LookupError:
  66. model_error = "app '%s' isn't installed" % model_key[0]
  67. return model_error
  68. # Here are several functions which return CheckMessage instances for the
  69. # most common usages of lazy operations throughout Django. These functions
  70. # take the model that was being waited on as an (app_label, modelname)
  71. # pair, the original lazy function, and its positional and keyword args as
  72. # determined by extract_operation().
  73. def field_error(model_key, func, args, keywords):
  74. error_msg = (
  75. "The field %(field)s was declared with a lazy reference "
  76. "to '%(model)s', but %(model_error)s."
  77. )
  78. params = {
  79. 'model': '.'.join(model_key),
  80. 'field': keywords['field'],
  81. 'model_error': app_model_error(model_key),
  82. }
  83. return Error(error_msg % params, obj=keywords['field'], id='fields.E307')
  84. def signal_connect_error(model_key, func, args, keywords):
  85. error_msg = (
  86. "%(receiver)s was connected to the '%(signal)s' signal with a "
  87. "lazy reference to the sender '%(model)s', but %(model_error)s."
  88. )
  89. receiver = args[0]
  90. # The receiver is either a function or an instance of class
  91. # defining a `__call__` method.
  92. if isinstance(receiver, types.FunctionType):
  93. description = "The function '%s'" % receiver.__name__
  94. elif isinstance(receiver, types.MethodType):
  95. description = "Bound method '%s.%s'" % (receiver.__self__.__class__.__name__, receiver.__name__)
  96. else:
  97. description = "An instance of class '%s'" % receiver.__class__.__name__
  98. signal_name = model_signals.get(func.__self__, 'unknown')
  99. params = {
  100. 'model': '.'.join(model_key),
  101. 'receiver': description,
  102. 'signal': signal_name,
  103. 'model_error': app_model_error(model_key),
  104. }
  105. return Error(error_msg % params, obj=receiver.__module__, id='signals.E001')
  106. def default_error(model_key, func, args, keywords):
  107. error_msg = "%(op)s contains a lazy reference to %(model)s, but %(model_error)s."
  108. params = {
  109. 'op': func,
  110. 'model': '.'.join(model_key),
  111. 'model_error': app_model_error(model_key),
  112. }
  113. return Error(error_msg % params, obj=func, id='models.E022')
  114. # Maps common uses of lazy operations to corresponding error functions
  115. # defined above. If a key maps to None, no error will be produced.
  116. # default_error() will be used for usages that don't appear in this dict.
  117. known_lazy = {
  118. ('django.db.models.fields.related', 'resolve_related_class'): field_error,
  119. ('django.db.models.fields.related', 'set_managed'): None,
  120. ('django.dispatch.dispatcher', 'connect'): signal_connect_error,
  121. }
  122. def build_error(model_key, func, args, keywords):
  123. key = (func.__module__, func.__name__)
  124. error_fn = known_lazy.get(key, default_error)
  125. return error_fn(model_key, func, args, keywords) if error_fn else None
  126. return sorted(filter(None, (
  127. build_error(model_key, *extract_operation(func))
  128. for model_key in pending_models
  129. for func in apps._pending_operations[model_key]
  130. )), key=lambda error: error.msg)
  131. @register(Tags.models)
  132. def check_lazy_references(app_configs=None, **kwargs):
  133. return _check_lazy_references(apps)