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.

introspection.py 7.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. from collections import namedtuple
  2. # Structure returned by DatabaseIntrospection.get_table_list()
  3. TableInfo = namedtuple('TableInfo', ['name', 'type'])
  4. # Structure returned by the DB-API cursor.description interface (PEP 249)
  5. FieldInfo = namedtuple('FieldInfo', 'name type_code display_size internal_size precision scale null_ok default')
  6. class BaseDatabaseIntrospection:
  7. """Encapsulate backend-specific introspection utilities."""
  8. data_types_reverse = {}
  9. def __init__(self, connection):
  10. self.connection = connection
  11. def get_field_type(self, data_type, description):
  12. """
  13. Hook for a database backend to use the cursor description to
  14. match a Django field type to a database column.
  15. For Oracle, the column data_type on its own is insufficient to
  16. distinguish between a FloatField and IntegerField, for example.
  17. """
  18. return self.data_types_reverse[data_type]
  19. def table_name_converter(self, name):
  20. """
  21. Apply a conversion to the name for the purposes of comparison.
  22. The default table name converter is for case sensitive comparison.
  23. """
  24. return name
  25. def column_name_converter(self, name):
  26. """
  27. Apply a conversion to the column name for the purposes of comparison.
  28. Use table_name_converter() by default.
  29. """
  30. return self.table_name_converter(name)
  31. def table_names(self, cursor=None, include_views=False):
  32. """
  33. Return a list of names of all tables that exist in the database.
  34. Sort the returned table list by Python's default sorting. Do NOT use
  35. the database's ORDER BY here to avoid subtle differences in sorting
  36. order between databases.
  37. """
  38. def get_names(cursor):
  39. return sorted(ti.name for ti in self.get_table_list(cursor)
  40. if include_views or ti.type == 't')
  41. if cursor is None:
  42. with self.connection.cursor() as cursor:
  43. return get_names(cursor)
  44. return get_names(cursor)
  45. def get_table_list(self, cursor):
  46. """
  47. Return an unsorted list of TableInfo named tuples of all tables and
  48. views that exist in the database.
  49. """
  50. raise NotImplementedError('subclasses of BaseDatabaseIntrospection may require a get_table_list() method')
  51. def django_table_names(self, only_existing=False, include_views=True):
  52. """
  53. Return a list of all table names that have associated Django models and
  54. are in INSTALLED_APPS.
  55. If only_existing is True, include only the tables in the database.
  56. """
  57. from django.apps import apps
  58. from django.db import router
  59. tables = set()
  60. for app_config in apps.get_app_configs():
  61. for model in router.get_migratable_models(app_config, self.connection.alias):
  62. if not model._meta.managed:
  63. continue
  64. tables.add(model._meta.db_table)
  65. tables.update(
  66. f.m2m_db_table() for f in model._meta.local_many_to_many
  67. if f.remote_field.through._meta.managed
  68. )
  69. tables = list(tables)
  70. if only_existing:
  71. existing_tables = self.table_names(include_views=include_views)
  72. tables = [
  73. t
  74. for t in tables
  75. if self.table_name_converter(t) in existing_tables
  76. ]
  77. return tables
  78. def installed_models(self, tables):
  79. """
  80. Return a set of all models represented by the provided list of table
  81. names.
  82. """
  83. from django.apps import apps
  84. from django.db import router
  85. all_models = []
  86. for app_config in apps.get_app_configs():
  87. all_models.extend(router.get_migratable_models(app_config, self.connection.alias))
  88. tables = list(map(self.table_name_converter, tables))
  89. return {
  90. m for m in all_models
  91. if self.table_name_converter(m._meta.db_table) in tables
  92. }
  93. def sequence_list(self):
  94. """
  95. Return a list of information about all DB sequences for all models in
  96. all apps.
  97. """
  98. from django.apps import apps
  99. from django.db import router
  100. sequence_list = []
  101. with self.connection.cursor() as cursor:
  102. for app_config in apps.get_app_configs():
  103. for model in router.get_migratable_models(app_config, self.connection.alias):
  104. if not model._meta.managed:
  105. continue
  106. if model._meta.swapped:
  107. continue
  108. sequence_list.extend(self.get_sequences(cursor, model._meta.db_table, model._meta.local_fields))
  109. for f in model._meta.local_many_to_many:
  110. # If this is an m2m using an intermediate table,
  111. # we don't need to reset the sequence.
  112. if f.remote_field.through is None:
  113. sequence = self.get_sequences(cursor, f.m2m_db_table())
  114. sequence_list.extend(sequence or [{'table': f.m2m_db_table(), 'column': None}])
  115. return sequence_list
  116. def get_sequences(self, cursor, table_name, table_fields=()):
  117. """
  118. Return a list of introspected sequences for table_name. Each sequence
  119. is a dict: {'table': <table_name>, 'column': <column_name>}. An optional
  120. 'name' key can be added if the backend supports named sequences.
  121. """
  122. raise NotImplementedError('subclasses of BaseDatabaseIntrospection may require a get_sequences() method')
  123. def get_key_columns(self, cursor, table_name):
  124. """
  125. Backends can override this to return a list of:
  126. (column_name, referenced_table_name, referenced_column_name)
  127. for all key columns in given table.
  128. """
  129. raise NotImplementedError('subclasses of BaseDatabaseIntrospection may require a get_key_columns() method')
  130. def get_primary_key_column(self, cursor, table_name):
  131. """
  132. Return the name of the primary key column for the given table.
  133. """
  134. for constraint in self.get_constraints(cursor, table_name).values():
  135. if constraint['primary_key']:
  136. return constraint['columns'][0]
  137. return None
  138. def get_constraints(self, cursor, table_name):
  139. """
  140. Retrieve any constraints or keys (unique, pk, fk, check, index)
  141. across one or more columns.
  142. Return a dict mapping constraint names to their attributes,
  143. where attributes is a dict with keys:
  144. * columns: List of columns this covers
  145. * primary_key: True if primary key, False otherwise
  146. * unique: True if this is a unique constraint, False otherwise
  147. * foreign_key: (table, column) of target, or None
  148. * check: True if check constraint, False otherwise
  149. * index: True if index, False otherwise.
  150. * orders: The order (ASC/DESC) defined for the columns of indexes
  151. * type: The type of the index (btree, hash, etc.)
  152. Some backends may return special constraint names that don't exist
  153. if they don't name constraints of a certain type (e.g. SQLite)
  154. """
  155. raise NotImplementedError('subclasses of BaseDatabaseIntrospection may require a get_constraints() method')