Commit 34ba8670 authored by Anubhav Joshi's avatar Anubhav Joshi Committed by Tim Graham
Browse files

Fixed #14334 -- Query relation lookups now check object types.

Thanks rpbarlow for the suggestion; and loic, akaariai, and jorgecarleitao
for reviews.
parent 81edf2d0
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -569,7 +569,7 @@ class SQLCompiler(object):
            if (len(self.query.get_meta().concrete_fields) == len(self.query.select)
                    and self.connection.features.allows_group_by_pk):
                self.query.group_by = [
                    (self.query.get_meta().db_table, self.query.get_meta().pk.column)
                    (self.query.get_initial_alias(), self.query.get_meta().pk.column)
                ]
                select_cols = []
            seen = set()
+29 −0
Original line number Diff line number Diff line
@@ -1084,6 +1084,32 @@ class Query(object):
                    (lookup, self.get_meta().model.__name__))
        return lookup_parts, field_parts, False

    def check_query_object_type(self, value, opts):
        """
        Checks whether the object passed while querying is of the correct type.
        If not, it raises a ValueError specifying the wrong object.
        """
        if hasattr(value, '_meta'):
            if not (value._meta.concrete_model == opts.concrete_model
                    or opts.concrete_model in value._meta.get_parent_list()
                    or value._meta.concrete_model in opts.get_parent_list()):
                raise ValueError(
                    'Cannot query "%s": Must be "%s" instance.' %
                    (value, opts.object_name))

    def check_related_objects(self, field, value, opts):
        """
        Checks the type of object passed to query relations.
        """
        if field.rel:
            # testing for iterable of models
            if hasattr(value, '__iter__'):
                for v in value:
                    self.check_query_object_type(v, opts)
            else:
                # expecting single model instance here
                self.check_query_object_type(value, opts)

    def build_lookup(self, lookups, lhs, rhs):
        lookups = lookups[:]
        while lookups:
@@ -1159,6 +1185,9 @@ class Query(object):
        try:
            field, sources, opts, join_list, path = self.setup_joins(
                parts, opts, alias, can_reuse=can_reuse, allow_many=allow_many)

            self.check_related_objects(field, value, opts)

            # split_exclude() needs to know which joins were generated for the
            # lookup parts
            self._lookup_joins = join_list
+15 −0
Original line number Diff line number Diff line
@@ -350,6 +350,21 @@ The check also applies to the columns generated in an implicit
and then specify :attr:`~django.db.models.Field.db_column` on its column(s)
as needed.

Query relation lookups now check object types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Querying for model lookups now checks if the object passed is of correct type
and raises a :exc:`ValueError` if not. Previously, Django didn't care if the
object was of correct type; it just used the object's related field attribute
(e.g. ``id``) for the lookup. Now, an error is raised to prevent incorrect
lookups::

    >>> book = Book.objects.create(name="Django")
    >>> book = Book.objects.filter(author=book)
    Traceback (most recent call last):
    ...
    ValueError: Cannot query "<Book: Django>": Must be "Author" instance.

Miscellaneous
~~~~~~~~~~~~~

+0 −1
Original line number Diff line number Diff line
@@ -100,7 +100,6 @@ class OneToOneTests(TestCase):
        assert_filter_waiters(restaurant__place__exact=self.p1)
        assert_filter_waiters(restaurant__place__pk=self.p1.pk)
        assert_filter_waiters(restaurant__exact=self.p1.pk)
        assert_filter_waiters(restaurant__exact=self.p1)
        assert_filter_waiters(restaurant__pk=self.p1.pk)
        assert_filter_waiters(restaurant=self.p1.pk)
        assert_filter_waiters(restaurant=self.r)
+15 −0
Original line number Diff line number Diff line
@@ -409,6 +409,15 @@ class ObjectA(models.Model):
        return self.name


class ProxyObjectA(ObjectA):
    class Meta:
        proxy = True


class ChildObjectA(ObjectA):
    pass


@python_2_unicode_compatible
class ObjectB(models.Model):
    name = models.CharField(max_length=50)
@@ -419,11 +428,17 @@ class ObjectB(models.Model):
        return self.name


class ProxyObjectB(ObjectB):
    class Meta:
        proxy = True


@python_2_unicode_compatible
class ObjectC(models.Model):
    name = models.CharField(max_length=50)
    objecta = models.ForeignKey(ObjectA, null=True)
    objectb = models.ForeignKey(ObjectB, null=True)
    childobjecta = models.ForeignKey(ChildObjectA, null=True, related_name='ca_pk')

    def __str__(self):
        return self.name
Loading