diff --git a/django/contrib/gis/forms/fields.py b/django/contrib/gis/forms/fields.py index 3c73e12dea44..1d61ea0dddf6 100644 --- a/django/contrib/gis/forms/fields.py +++ b/django/contrib/gis/forms/fields.py @@ -1,5 +1,4 @@ from django import forms -from django.contrib.gis.gdal import GDALException from django.contrib.gis.geos import GEOSException, GEOSGeometry from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ @@ -41,10 +40,7 @@ def to_python(self, value): if not isinstance(value, GEOSGeometry): if hasattr(self.widget, "deserialize"): - try: - value = self.widget.deserialize(value) - except GDALException: - value = None + value = self.widget.deserialize(value) else: try: value = GEOSGeometry(value) diff --git a/django/contrib/gis/forms/widgets.py b/django/contrib/gis/forms/widgets.py index c8f9f1208e82..904005875083 100644 --- a/django/contrib/gis/forms/widgets.py +++ b/django/contrib/gis/forms/widgets.py @@ -1,6 +1,7 @@ import logging from django.contrib.gis import gdal +from django.contrib.gis.gdal import GDALException from django.contrib.gis.geometry import json_regex from django.contrib.gis.geos import GEOSException, GEOSGeometry from django.forms.widgets import Widget @@ -36,7 +37,7 @@ def serialize(self, value): def deserialize(self, value): try: return GEOSGeometry(value) - except (GEOSException, ValueError, TypeError) as err: + except (GEOSException, GDALException, ValueError, TypeError) as err: logger.error("Error creating geometry from value '%s' (%s)", value, err) return None diff --git a/django/db/models/query.py b/django/db/models/query.py index 5649a83428f7..76d0f449a67e 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -1158,7 +1158,7 @@ async def alatest(self, *fields): def first(self): """Return the first object of a query or None if no match is found.""" - if self.ordered: + if self.ordered or not self.query.default_ordering: queryset = self else: self._check_ordering_first_last_queryset_aggregation(method="first") @@ -1171,7 +1171,7 @@ async def afirst(self): def last(self): """Return the last object of a query or None if no match is found.""" - if self.ordered: + if self.ordered or not self.query.default_ordering: queryset = self.reverse() else: self._check_ordering_first_last_queryset_aggregation(method="last") @@ -1679,6 +1679,7 @@ def _combinator_query(self, combinator, *other_qs, all=False): clone = self._chain() # Clear limits and ordering so they can be reapplied clone.query.clear_ordering(force=True) + clone.query.default_ordering = True clone.query.clear_limits() clone.query.combined_queries = (self.query, *(qs.query for qs in other_qs)) clone.query.combinator = combinator diff --git a/tests/get_earliest_or_latest/models.py b/tests/get_earliest_or_latest/models.py index bbf2075d368a..91725865b194 100644 --- a/tests/get_earliest_or_latest/models.py +++ b/tests/get_earliest_or_latest/models.py @@ -21,6 +21,14 @@ class Comment(models.Model): likes_count = models.PositiveIntegerField() +class OrderedArticle(models.Model): + headline = models.CharField(max_length=100) + pub_date = models.DateField() + + class Meta: + ordering = ["headline"] + + # Ticket #23555 - model with an intentionally broken QuerySet.__iter__ method. diff --git a/tests/get_earliest_or_latest/tests.py b/tests/get_earliest_or_latest/tests.py index 49c803b73a0f..793fb12bb9c1 100644 --- a/tests/get_earliest_or_latest/tests.py +++ b/tests/get_earliest_or_latest/tests.py @@ -1,9 +1,10 @@ from datetime import datetime +from unittest.mock import patch from django.db.models import Avg from django.test import TestCase -from .models import Article, Comment, IndexErrorArticle, Person +from .models import Article, Comment, IndexErrorArticle, OrderedArticle, Person class EarliestOrLatestTests(TestCase): @@ -265,3 +266,30 @@ def test_first_last_unordered_qs_aggregation_error(self): qs.first() with self.assertRaisesMessage(TypeError, msg % "last"): qs.last() + + def test_first_last_empty_order_by_has_no_pk_ordering(self): + Article.objects.create( + headline="Article 1", + pub_date=datetime(2006, 9, 10), + expire_date=datetime(2056, 9, 11), + ) + + qs = Article.objects.order_by() + with patch.object(type(qs), "order_by") as mock_order_by: + qs.first() + mock_order_by.assert_not_called() + qs.last() + mock_order_by.assert_not_called() + + def test_first_last_empty_order_by_clears_default_ordering(self): + OrderedArticle.objects.create( + headline="Article 1", + pub_date=datetime(2006, 9, 10), + ) + + qs = OrderedArticle.objects.order_by() + with patch.object(type(qs), "order_by") as mock_order_by: + qs.first() + mock_order_by.assert_not_called() + qs.last() + mock_order_by.assert_not_called() diff --git a/tests/gis_tests/test_geoforms.py b/tests/gis_tests/test_geoforms.py index 4b229d889779..4119902ead86 100644 --- a/tests/gis_tests/test_geoforms.py +++ b/tests/gis_tests/test_geoforms.py @@ -494,6 +494,19 @@ def test_get_context_attrs(self): context = widget.get_context("geometry", None, None) self.assertEqual(context["widget"]["attrs"]["geom_name"], "Geometry") + def test_invalid_values(self): + bad_inputs = [ + "POINT(5)", + "MULTI POLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))", + "BLAH(0 0, 1 1)", + '{"type": "FeatureCollection", "features": [' + '{"geometry": {"type": "Point", "coordinates": [508375, 148905]}, ' + '"type": "Feature"}]}', + ] + for input in bad_inputs: + with self.subTest(input=input): + self.assertIsNone(BaseGeometryWidget().deserialize(input)) + def test_subwidgets(self): widget = forms.BaseGeometryWidget() self.assertEqual( diff --git a/tests/queries/test_qs_combinators.py b/tests/queries/test_qs_combinators.py index 2b4cd2bbbdd1..7e1e01bd4be4 100644 --- a/tests/queries/test_qs_combinators.py +++ b/tests/queries/test_qs_combinators.py @@ -418,6 +418,15 @@ def test_union_with_first(self): qs2 = base_qs.filter(name="a2") self.assertEqual(qs1.union(qs2).first(), a1) + @skipUnlessDBFeature("supports_slicing_ordering_in_compound") + def test_union_applies_default_ordering_afterward(self): + c = Tag.objects.create(name="C") + Tag.objects.create(name="B") + a = Tag.objects.create(name="A") + qs1 = Tag.objects.filter(name__in=["A", "B"])[:1] + qs2 = Tag.objects.filter(name__in=["C"])[:1] + self.assertSequenceEqual(qs1.union(qs2), [a, c]) + def test_union_multiple_models_with_values_list_and_order(self): reserved_name = ReservedName.objects.create(name="rn1", order=0) qs1 = Celebrity.objects.all()