reverse_related.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  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. import warnings
  10. from django.core import exceptions
  11. from django.utils.deprecation import RemovedInDjango60Warning
  12. from django.utils.functional import cached_property
  13. from django.utils.hashable import make_hashable
  14. from . import BLANK_CHOICE_DASH
  15. from .mixins import FieldCacheMixin
  16. class ForeignObjectRel(FieldCacheMixin):
  17. """
  18. Used by ForeignObject to store information about the relation.
  19. ``_meta.get_fields()`` returns this class to provide access to the field
  20. flags for the reverse relation.
  21. """
  22. # Field flags
  23. auto_created = True
  24. concrete = False
  25. editable = False
  26. is_relation = True
  27. # Reverse relations are always nullable (Django can't enforce that a
  28. # foreign key on the related model points to this model).
  29. null = True
  30. empty_strings_allowed = False
  31. def __init__(
  32. self,
  33. field,
  34. to,
  35. related_name=None,
  36. related_query_name=None,
  37. limit_choices_to=None,
  38. parent_link=False,
  39. on_delete=None,
  40. ):
  41. self.field = field
  42. self.model = to
  43. self.related_name = related_name
  44. self.related_query_name = related_query_name
  45. self.limit_choices_to = {} if limit_choices_to is None else limit_choices_to
  46. self.parent_link = parent_link
  47. self.on_delete = on_delete
  48. self.symmetrical = False
  49. self.multiple = True
  50. # Some of the following cached_properties can't be initialized in
  51. # __init__ as the field doesn't have its model yet. Calling these methods
  52. # before field.contribute_to_class() has been called will result in
  53. # AttributeError
  54. @cached_property
  55. def hidden(self):
  56. """Should the related object be hidden?"""
  57. return bool(self.related_name) and self.related_name[-1] == "+"
  58. @cached_property
  59. def name(self):
  60. return self.field.related_query_name()
  61. @property
  62. def remote_field(self):
  63. return self.field
  64. @property
  65. def target_field(self):
  66. """
  67. When filtering against this relation, return the field on the remote
  68. model against which the filtering should happen.
  69. """
  70. target_fields = self.path_infos[-1].target_fields
  71. if len(target_fields) > 1:
  72. raise exceptions.FieldError(
  73. "Can't use target_field for multicolumn relations."
  74. )
  75. return target_fields[0]
  76. @cached_property
  77. def related_model(self):
  78. if not self.field.model:
  79. raise AttributeError(
  80. "This property can't be accessed before self.field.contribute_to_class "
  81. "has been called."
  82. )
  83. return self.field.model
  84. @cached_property
  85. def many_to_many(self):
  86. return self.field.many_to_many
  87. @cached_property
  88. def many_to_one(self):
  89. return self.field.one_to_many
  90. @cached_property
  91. def one_to_many(self):
  92. return self.field.many_to_one
  93. @cached_property
  94. def one_to_one(self):
  95. return self.field.one_to_one
  96. def get_lookup(self, lookup_name):
  97. return self.field.get_lookup(lookup_name)
  98. def get_lookups(self):
  99. return self.field.get_lookups()
  100. def get_transform(self, name):
  101. return self.field.get_transform(name)
  102. def get_internal_type(self):
  103. return self.field.get_internal_type()
  104. @property
  105. def db_type(self):
  106. return self.field.db_type
  107. def __repr__(self):
  108. return "<%s: %s.%s>" % (
  109. type(self).__name__,
  110. self.related_model._meta.app_label,
  111. self.related_model._meta.model_name,
  112. )
  113. @property
  114. def identity(self):
  115. return (
  116. self.field,
  117. self.model,
  118. self.related_name,
  119. self.related_query_name,
  120. make_hashable(self.limit_choices_to),
  121. self.parent_link,
  122. self.on_delete,
  123. self.symmetrical,
  124. self.multiple,
  125. )
  126. def __eq__(self, other):
  127. if not isinstance(other, self.__class__):
  128. return NotImplemented
  129. return self.identity == other.identity
  130. def __hash__(self):
  131. return hash(self.identity)
  132. def __getstate__(self):
  133. state = self.__dict__.copy()
  134. # Delete the path_infos cached property because it can be recalculated
  135. # at first invocation after deserialization. The attribute must be
  136. # removed because subclasses like ManyToOneRel may have a PathInfo
  137. # which contains an intermediate M2M table that's been dynamically
  138. # created and doesn't exist in the .models module.
  139. # This is a reverse relation, so there is no reverse_path_infos to
  140. # delete.
  141. state.pop("path_infos", None)
  142. return state
  143. def get_choices(
  144. self,
  145. include_blank=True,
  146. blank_choice=BLANK_CHOICE_DASH,
  147. limit_choices_to=None,
  148. ordering=(),
  149. ):
  150. """
  151. Return choices with a default blank choices included, for use
  152. as <select> choices for this field.
  153. Analog of django.db.models.fields.Field.get_choices(), provided
  154. initially for utilization by RelatedFieldListFilter.
  155. """
  156. limit_choices_to = limit_choices_to or self.limit_choices_to
  157. qs = self.related_model._default_manager.complex_filter(limit_choices_to)
  158. if ordering:
  159. qs = qs.order_by(*ordering)
  160. return (blank_choice if include_blank else []) + [(x.pk, str(x)) for x in qs]
  161. def get_joining_columns(self):
  162. warnings.warn(
  163. "ForeignObjectRel.get_joining_columns() is deprecated. Use "
  164. "get_joining_fields() instead.",
  165. RemovedInDjango60Warning,
  166. )
  167. return self.field.get_reverse_joining_columns()
  168. def get_joining_fields(self):
  169. return self.field.get_reverse_joining_fields()
  170. def get_extra_restriction(self, alias, related_alias):
  171. return self.field.get_extra_restriction(related_alias, alias)
  172. def set_field_name(self):
  173. """
  174. Set the related field's name, this is not available until later stages
  175. of app loading, so set_field_name is called from
  176. set_attributes_from_rel()
  177. """
  178. # By default foreign object doesn't relate to any remote field (for
  179. # example custom multicolumn joins currently have no remote field).
  180. self.field_name = None
  181. @cached_property
  182. def accessor_name(self):
  183. return self.get_accessor_name()
  184. def get_accessor_name(self, model=None):
  185. # This method encapsulates the logic that decides what name to give an
  186. # accessor descriptor that retrieves related many-to-one or
  187. # many-to-many objects. It uses the lowercased object_name + "_set",
  188. # but this can be overridden with the "related_name" option. Due to
  189. # backwards compatibility ModelForms need to be able to provide an
  190. # alternate model. See BaseInlineFormSet.get_default_prefix().
  191. opts = model._meta if model else self.related_model._meta
  192. model = model or self.related_model
  193. if self.multiple:
  194. # If this is a symmetrical m2m relation on self, there is no
  195. # reverse accessor.
  196. if self.symmetrical and model == self.model:
  197. return None
  198. if self.related_name:
  199. return self.related_name
  200. return opts.model_name + ("_set" if self.multiple else "")
  201. def get_path_info(self, filtered_relation=None):
  202. if filtered_relation:
  203. return self.field.get_reverse_path_info(filtered_relation)
  204. else:
  205. return self.field.reverse_path_infos
  206. @cached_property
  207. def path_infos(self):
  208. return self.get_path_info()
  209. @cached_property
  210. def cache_name(self):
  211. """
  212. Return the name of the cache key to use for storing an instance of the
  213. forward model on the reverse model.
  214. """
  215. return self.accessor_name
  216. class ManyToOneRel(ForeignObjectRel):
  217. """
  218. Used by the ForeignKey field to store information about the relation.
  219. ``_meta.get_fields()`` returns this class to provide access to the field
  220. flags for the reverse relation.
  221. Note: Because we somewhat abuse the Rel objects by using them as reverse
  222. fields we get the funny situation where
  223. ``ManyToOneRel.many_to_one == False`` and
  224. ``ManyToOneRel.one_to_many == True``. This is unfortunate but the actual
  225. ManyToOneRel class is a private API and there is work underway to turn
  226. reverse relations into actual fields.
  227. """
  228. def __init__(
  229. self,
  230. field,
  231. to,
  232. field_name,
  233. related_name=None,
  234. related_query_name=None,
  235. limit_choices_to=None,
  236. parent_link=False,
  237. on_delete=None,
  238. ):
  239. super().__init__(
  240. field,
  241. to,
  242. related_name=related_name,
  243. related_query_name=related_query_name,
  244. limit_choices_to=limit_choices_to,
  245. parent_link=parent_link,
  246. on_delete=on_delete,
  247. )
  248. self.field_name = field_name
  249. def __getstate__(self):
  250. state = super().__getstate__()
  251. state.pop("related_model", None)
  252. return state
  253. @property
  254. def identity(self):
  255. return super().identity + (self.field_name,)
  256. def get_related_field(self):
  257. """
  258. Return the Field in the 'to' object to which this relationship is tied.
  259. """
  260. field = self.model._meta.get_field(self.field_name)
  261. if not field.concrete:
  262. raise exceptions.FieldDoesNotExist(
  263. "No related field named '%s'" % self.field_name
  264. )
  265. return field
  266. def set_field_name(self):
  267. self.field_name = self.field_name or self.model._meta.pk.name
  268. class OneToOneRel(ManyToOneRel):
  269. """
  270. Used by OneToOneField to store information about the relation.
  271. ``_meta.get_fields()`` returns this class to provide access to the field
  272. flags for the reverse relation.
  273. """
  274. def __init__(
  275. self,
  276. field,
  277. to,
  278. field_name,
  279. related_name=None,
  280. related_query_name=None,
  281. limit_choices_to=None,
  282. parent_link=False,
  283. on_delete=None,
  284. ):
  285. super().__init__(
  286. field,
  287. to,
  288. field_name,
  289. related_name=related_name,
  290. related_query_name=related_query_name,
  291. limit_choices_to=limit_choices_to,
  292. parent_link=parent_link,
  293. on_delete=on_delete,
  294. )
  295. self.multiple = False
  296. class ManyToManyRel(ForeignObjectRel):
  297. """
  298. Used by ManyToManyField to store information about the relation.
  299. ``_meta.get_fields()`` returns this class to provide access to the field
  300. flags for the reverse relation.
  301. """
  302. def __init__(
  303. self,
  304. field,
  305. to,
  306. related_name=None,
  307. related_query_name=None,
  308. limit_choices_to=None,
  309. symmetrical=True,
  310. through=None,
  311. through_fields=None,
  312. db_constraint=True,
  313. ):
  314. super().__init__(
  315. field,
  316. to,
  317. related_name=related_name,
  318. related_query_name=related_query_name,
  319. limit_choices_to=limit_choices_to,
  320. )
  321. if through and not db_constraint:
  322. raise ValueError("Can't supply a through model and db_constraint=False")
  323. self.through = through
  324. if through_fields and not through:
  325. raise ValueError("Cannot specify through_fields without a through model")
  326. self.through_fields = through_fields
  327. self.symmetrical = symmetrical
  328. self.db_constraint = db_constraint
  329. @property
  330. def identity(self):
  331. return super().identity + (
  332. self.through,
  333. make_hashable(self.through_fields),
  334. self.db_constraint,
  335. )
  336. def get_related_field(self):
  337. """
  338. Return the field in the 'to' object to which this relationship is tied.
  339. Provided for symmetry with ManyToOneRel.
  340. """
  341. opts = self.through._meta
  342. if self.through_fields:
  343. field = opts.get_field(self.through_fields[0])
  344. else:
  345. for field in opts.fields:
  346. rel = getattr(field, "remote_field", None)
  347. if rel and rel.model == self.model:
  348. break
  349. return field.foreign_related_fields[0]