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.

reverse_related.py 9.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. """
  2. "Rel objects" for related fields.
  3. "Rel objects" (for lack of a better name) carry information about the relation
  4. modeled by a related field and provide some utility functions. They're stored
  5. in the ``remote_field`` attribute of the field.
  6. They also act as reverse fields for the purposes of the Meta API because
  7. they're the closest concept currently available.
  8. """
  9. from django.core import exceptions
  10. from django.utils.functional import cached_property
  11. from . import BLANK_CHOICE_DASH
  12. from .mixins import FieldCacheMixin
  13. class ForeignObjectRel(FieldCacheMixin):
  14. """
  15. Used by ForeignObject to store information about the relation.
  16. ``_meta.get_fields()`` returns this class to provide access to the field
  17. flags for the reverse relation.
  18. """
  19. # Field flags
  20. auto_created = True
  21. concrete = False
  22. editable = False
  23. is_relation = True
  24. # Reverse relations are always nullable (Django can't enforce that a
  25. # foreign key on the related model points to this model).
  26. null = True
  27. def __init__(self, field, to, related_name=None, related_query_name=None,
  28. limit_choices_to=None, parent_link=False, on_delete=None):
  29. self.field = field
  30. self.model = to
  31. self.related_name = related_name
  32. self.related_query_name = related_query_name
  33. self.limit_choices_to = {} if limit_choices_to is None else limit_choices_to
  34. self.parent_link = parent_link
  35. self.on_delete = on_delete
  36. self.symmetrical = False
  37. self.multiple = True
  38. # Some of the following cached_properties can't be initialized in
  39. # __init__ as the field doesn't have its model yet. Calling these methods
  40. # before field.contribute_to_class() has been called will result in
  41. # AttributeError
  42. @cached_property
  43. def hidden(self):
  44. return self.is_hidden()
  45. @cached_property
  46. def name(self):
  47. return self.field.related_query_name()
  48. @property
  49. def remote_field(self):
  50. return self.field
  51. @property
  52. def target_field(self):
  53. """
  54. When filtering against this relation, return the field on the remote
  55. model against which the filtering should happen.
  56. """
  57. target_fields = self.get_path_info()[-1].target_fields
  58. if len(target_fields) > 1:
  59. raise exceptions.FieldError("Can't use target_field for multicolumn relations.")
  60. return target_fields[0]
  61. @cached_property
  62. def related_model(self):
  63. if not self.field.model:
  64. raise AttributeError(
  65. "This property can't be accessed before self.field.contribute_to_class has been called.")
  66. return self.field.model
  67. @cached_property
  68. def many_to_many(self):
  69. return self.field.many_to_many
  70. @cached_property
  71. def many_to_one(self):
  72. return self.field.one_to_many
  73. @cached_property
  74. def one_to_many(self):
  75. return self.field.many_to_one
  76. @cached_property
  77. def one_to_one(self):
  78. return self.field.one_to_one
  79. def get_lookup(self, lookup_name):
  80. return self.field.get_lookup(lookup_name)
  81. def get_internal_type(self):
  82. return self.field.get_internal_type()
  83. @property
  84. def db_type(self):
  85. return self.field.db_type
  86. def __repr__(self):
  87. return '<%s: %s.%s>' % (
  88. type(self).__name__,
  89. self.related_model._meta.app_label,
  90. self.related_model._meta.model_name,
  91. )
  92. def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH, ordering=()):
  93. """
  94. Return choices with a default blank choices included, for use
  95. as <select> choices for this field.
  96. Analog of django.db.models.fields.Field.get_choices(), provided
  97. initially for utilization by RelatedFieldListFilter.
  98. """
  99. qs = self.related_model._default_manager.all()
  100. if ordering:
  101. qs = qs.order_by(*ordering)
  102. return (blank_choice if include_blank else []) + [
  103. (x.pk, str(x)) for x in qs
  104. ]
  105. def is_hidden(self):
  106. """Should the related object be hidden?"""
  107. return bool(self.related_name) and self.related_name[-1] == '+'
  108. def get_joining_columns(self):
  109. return self.field.get_reverse_joining_columns()
  110. def get_extra_restriction(self, where_class, alias, related_alias):
  111. return self.field.get_extra_restriction(where_class, related_alias, alias)
  112. def set_field_name(self):
  113. """
  114. Set the related field's name, this is not available until later stages
  115. of app loading, so set_field_name is called from
  116. set_attributes_from_rel()
  117. """
  118. # By default foreign object doesn't relate to any remote field (for
  119. # example custom multicolumn joins currently have no remote field).
  120. self.field_name = None
  121. def get_accessor_name(self, model=None):
  122. # This method encapsulates the logic that decides what name to give an
  123. # accessor descriptor that retrieves related many-to-one or
  124. # many-to-many objects. It uses the lowercased object_name + "_set",
  125. # but this can be overridden with the "related_name" option. Due to
  126. # backwards compatibility ModelForms need to be able to provide an
  127. # alternate model. See BaseInlineFormSet.get_default_prefix().
  128. opts = model._meta if model else self.related_model._meta
  129. model = model or self.related_model
  130. if self.multiple:
  131. # If this is a symmetrical m2m relation on self, there is no reverse accessor.
  132. if self.symmetrical and model == self.model:
  133. return None
  134. if self.related_name:
  135. return self.related_name
  136. return opts.model_name + ('_set' if self.multiple else '')
  137. def get_path_info(self, filtered_relation=None):
  138. return self.field.get_reverse_path_info(filtered_relation)
  139. def get_cache_name(self):
  140. """
  141. Return the name of the cache key to use for storing an instance of the
  142. forward model on the reverse model.
  143. """
  144. return self.get_accessor_name()
  145. class ManyToOneRel(ForeignObjectRel):
  146. """
  147. Used by the ForeignKey field to store information about the relation.
  148. ``_meta.get_fields()`` returns this class to provide access to the field
  149. flags for the reverse relation.
  150. Note: Because we somewhat abuse the Rel objects by using them as reverse
  151. fields we get the funny situation where
  152. ``ManyToOneRel.many_to_one == False`` and
  153. ``ManyToOneRel.one_to_many == True``. This is unfortunate but the actual
  154. ManyToOneRel class is a private API and there is work underway to turn
  155. reverse relations into actual fields.
  156. """
  157. def __init__(self, field, to, field_name, related_name=None, related_query_name=None,
  158. limit_choices_to=None, parent_link=False, on_delete=None):
  159. super().__init__(
  160. field, to,
  161. related_name=related_name,
  162. related_query_name=related_query_name,
  163. limit_choices_to=limit_choices_to,
  164. parent_link=parent_link,
  165. on_delete=on_delete,
  166. )
  167. self.field_name = field_name
  168. def __getstate__(self):
  169. state = self.__dict__.copy()
  170. state.pop('related_model', None)
  171. return state
  172. def get_related_field(self):
  173. """
  174. Return the Field in the 'to' object to which this relationship is tied.
  175. """
  176. field = self.model._meta.get_field(self.field_name)
  177. if not field.concrete:
  178. raise exceptions.FieldDoesNotExist("No related field named '%s'" % self.field_name)
  179. return field
  180. def set_field_name(self):
  181. self.field_name = self.field_name or self.model._meta.pk.name
  182. class OneToOneRel(ManyToOneRel):
  183. """
  184. Used by OneToOneField to store information about the relation.
  185. ``_meta.get_fields()`` returns this class to provide access to the field
  186. flags for the reverse relation.
  187. """
  188. def __init__(self, field, to, field_name, related_name=None, related_query_name=None,
  189. limit_choices_to=None, parent_link=False, on_delete=None):
  190. super().__init__(
  191. field, to, field_name,
  192. related_name=related_name,
  193. related_query_name=related_query_name,
  194. limit_choices_to=limit_choices_to,
  195. parent_link=parent_link,
  196. on_delete=on_delete,
  197. )
  198. self.multiple = False
  199. class ManyToManyRel(ForeignObjectRel):
  200. """
  201. Used by ManyToManyField to store information about the relation.
  202. ``_meta.get_fields()`` returns this class to provide access to the field
  203. flags for the reverse relation.
  204. """
  205. def __init__(self, field, to, related_name=None, related_query_name=None,
  206. limit_choices_to=None, symmetrical=True, through=None,
  207. through_fields=None, db_constraint=True):
  208. super().__init__(
  209. field, to,
  210. related_name=related_name,
  211. related_query_name=related_query_name,
  212. limit_choices_to=limit_choices_to,
  213. )
  214. if through and not db_constraint:
  215. raise ValueError("Can't supply a through model and db_constraint=False")
  216. self.through = through
  217. if through_fields and not through:
  218. raise ValueError("Cannot specify through_fields without a through model")
  219. self.through_fields = through_fields
  220. self.symmetrical = symmetrical
  221. self.db_constraint = db_constraint
  222. def get_related_field(self):
  223. """
  224. Return the field in the 'to' object to which this relationship is tied.
  225. Provided for symmetry with ManyToOneRel.
  226. """
  227. opts = self.through._meta
  228. if self.through_fields:
  229. field = opts.get_field(self.through_fields[0])
  230. else:
  231. for field in opts.fields:
  232. rel = getattr(field, 'remote_field', None)
  233. if rel and rel.model == self.model:
  234. break
  235. return field.foreign_related_fields[0]