summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorInvalidInterrupt <InvalidInterrupt@users.noreply.github.com>2016-08-18 15:42:11 -0700
committerTim Graham <timograham@gmail.com>2016-12-07 17:50:51 -0500
commit98359109eb0ed68a5821476bcd797455723aaaba (patch)
treef54f4fd3acffe5551873b4d00faab512a9e583d5 /django
parentb5f0b3478dfcf0335f8ac2038d59f54b4a05f2a0 (diff)
Fixed #17002 -- Allowed using a ManyToManyField through model that inherits another.
Diffstat (limited to 'django')
-rw-r--r--django/db/models/fields/related.py14
-rw-r--r--django/db/models/fields/related_descriptors.py2
-rw-r--r--django/db/models/options.py45
-rw-r--r--django/db/models/sql/query.py22
4 files changed, 66 insertions, 17 deletions
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index a872e6ffbd..3ba957f2d4 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -1529,7 +1529,21 @@ class ManyToManyField(RelatedField):
else:
join1infos = linkfield2.get_reverse_path_info()
join2infos = linkfield1.get_path_info()
+
+ # Get join infos between the last model of join 1 and the first model
+ # of join 2. Assume the only reason these may differ is due to model
+ # inheritance.
+ join1_final = join1infos[-1].to_opts
+ join2_initial = join2infos[0].from_opts
+ if join1_final is join2_initial:
+ intermediate_infos = []
+ elif issubclass(join1_final.model, join2_initial.model):
+ intermediate_infos = join1_final.get_path_to_parent(join2_initial.model)
+ else:
+ intermediate_infos = join2_initial.get_path_from_parent(join1_final.model)
+
pathinfos.extend(join1infos)
+ pathinfos.extend(intermediate_infos)
pathinfos.extend(join2infos)
return pathinfos
diff --git a/django/db/models/fields/related_descriptors.py b/django/db/models/fields/related_descriptors.py
index a94a8949ed..9b693ed196 100644
--- a/django/db/models/fields/related_descriptors.py
+++ b/django/db/models/fields/related_descriptors.py
@@ -895,7 +895,7 @@ def create_forward_many_to_many_manager(superclass, rel, reverse):
# For non-autocreated 'through' models, can't assume we are
# dealing with PK values.
fk = self.through._meta.get_field(self.source_field_name)
- join_table = self.through._meta.db_table
+ join_table = fk.model._meta.db_table
connection = connections[queryset.db]
qn = connection.ops.quote_name
queryset = queryset.extra(select={
diff --git a/django/db/models/options.py b/django/db/models/options.py
index d19a5a02f1..fd0b317865 100644
--- a/django/db/models/options.py
+++ b/django/db/models/options.py
@@ -14,6 +14,7 @@ from django.db.models import Manager
from django.db.models.fields import AutoField
from django.db.models.fields.proxy import OrderWrt
from django.db.models.fields.related import OneToOneField
+from django.db.models.query_utils import PathInfo
from django.utils import six
from django.utils.datastructures import ImmutableList, OrderedSet
from django.utils.deprecation import (
@@ -670,6 +671,50 @@ class Options(object):
# links
return self.parents[parent] or parent_link
+ def get_path_to_parent(self, parent):
+ """
+ Return a list of PathInfos containing the path from the current
+ model to the parent model, or an empty list if parent is not a
+ parent of the current model.
+ """
+ if self.model is parent:
+ return []
+ # Skip the chain of proxy to the concrete proxied model.
+ proxied_model = self.concrete_model
+ path = []
+ opts = self
+ for int_model in self.get_base_chain(parent):
+ if int_model is proxied_model:
+ opts = int_model._meta
+ else:
+ final_field = opts.parents[int_model]
+ targets = (final_field.remote_field.get_related_field(),)
+ opts = int_model._meta
+ path.append(PathInfo(final_field.model._meta, opts, targets, final_field, False, True))
+ return path
+
+ def get_path_from_parent(self, parent):
+ """
+ Return a list of PathInfos containing the path from the parent
+ model to the current model, or an empty list if parent is not a
+ parent of the current model.
+ """
+ if self.model is parent:
+ return []
+ model = self.concrete_model
+ # Get a reversed base chain including both the current and parent
+ # models.
+ chain = model._meta.get_base_chain(parent)
+ chain.reverse()
+ chain.append(model)
+ # Construct a list of the PathInfos between models in chain.
+ path = []
+ for i, ancestor in enumerate(chain[:-1]):
+ child = chain[i + 1]
+ link = child._meta.get_ancestor_link(ancestor)
+ path.extend(link.get_reverse_path_info())
+ return path
+
def _populate_directed_relation_graph(self):
"""
This method is used by each model to find its reverse objects. As this
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index a1e476c592..5eea5ad939 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -19,7 +19,7 @@ from django.db.models.constants import LOOKUP_SEP
from django.db.models.expressions import Col, Ref
from django.db.models.fields.related_lookups import MultiColSource
from django.db.models.query_utils import (
- PathInfo, Q, check_rel_lookup_compatibility, refs_expression,
+ Q, check_rel_lookup_compatibility, refs_expression,
)
from django.db.models.sql.constants import (
INNER, LOUTER, ORDER_DIR, ORDER_PATTERN, QUERY_TERMS, SINGLE,
@@ -1342,21 +1342,11 @@ class Query(object):
# field lives in parent, but we are currently in one of its
# children)
if model is not opts.model:
- # The field lives on a base class of the current model.
- # Skip the chain of proxy to the concrete proxied model
- proxied_model = opts.concrete_model
-
- for int_model in opts.get_base_chain(model):
- if int_model is proxied_model:
- opts = int_model._meta
- else:
- final_field = opts.parents[int_model]
- targets = (final_field.remote_field.get_related_field(),)
- opts = int_model._meta
- path.append(PathInfo(final_field.model._meta, opts, targets, final_field, False, True))
- cur_names_with_path[1].append(
- PathInfo(final_field.model._meta, opts, targets, final_field, False, True)
- )
+ path_to_parent = opts.get_path_to_parent(model)
+ if path_to_parent:
+ path.extend(path_to_parent)
+ cur_names_with_path[1].extend(path_to_parent)
+ opts = path_to_parent[-1].to_opts
if hasattr(field, 'get_path_info'):
pathinfos = field.get_path_info()
if not allow_many: