Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/workflows/data/conda/geolibs-pg17-freethreading.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: geodjango
channels:
- conda-forge
dependencies:
- python-freethreading=3.14
- postgresql=17.*
- postgis=3.5.*
- libgdal=3.11.*
- geos=3.14.*
- proj=9.6.*
- pip=26.*
- pip:
- -r ../../../../tests/requirements/py3-free-threading.txt
- -r ../../../../tests/requirements/postgres-free-threading.txt
- -e ../../../../
15 changes: 15 additions & 0 deletions .github/workflows/data/conda/geolibs-pg17-gil.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: geodjango
channels:
- conda-forge
dependencies:
- python-gil=3.14
- postgresql=17.*
- postgis=3.5.*
- libgdal=3.11.*
- geos=3.14.*
- proj=9.6.*
- pip=26.*
- pip:
- -r ../../../../tests/requirements/py3.txt
- -r ../../../../tests/requirements/postgres.txt
- -e ../../../../
11 changes: 0 additions & 11 deletions .github/workflows/data/conda/geolibs-pg17.yml

This file was deleted.

15 changes: 15 additions & 0 deletions .github/workflows/data/conda/geolibs-pg18-freethreading.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: geodjango
channels:
- conda-forge
dependencies:
- python-freethreading=3.14
- postgresql=18.*
- postgis=3.6.*
- libgdal=3.13.*
- geos=3.14.*
- proj=9.8.*
- pip=26.*
- pip:
- -r ../../../../tests/requirements/py3-free-threading.txt
- -r ../../../../tests/requirements/postgres-free-threading.txt
- -e ../../../../
15 changes: 15 additions & 0 deletions .github/workflows/data/conda/geolibs-pg18-gil.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: geodjango
channels:
- conda-forge
dependencies:
- python-gil=3.14
- postgresql=18.*
- postgis=3.6.*
- libgdal=3.13.*
- geos=3.14.*
- proj=9.8.*
- pip=26.*
- pip:
- -r ../../../../tests/requirements/py3.txt
- -r ../../../../tests/requirements/postgres.txt
- -e ../../../../
11 changes: 0 additions & 11 deletions .github/workflows/data/conda/geolibs-pg18.yml

This file was deleted.

27 changes: 17 additions & 10 deletions .github/workflows/postgis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,30 +25,35 @@ jobs:
fail-fast: false
matrix:
include:
- conda-environment-file: ".github/workflows/data/conda/geolibs-pg17.yml"
- conda-environment-file: ".github/workflows/data/conda/geolibs-pg17-gil.yml"
pg_major: "17"
- conda-environment-file: ".github/workflows/data/conda/geolibs-pg18.yml"
python: "3.14"
- conda-environment-file: ".github/workflows/data/conda/geolibs-pg17-freethreading.yml"
pg_major: "17"
python: "3.14t"
- conda-environment-file: ".github/workflows/data/conda/geolibs-pg18-gil.yml"
pg_major: "18"
python: "3.14"
- conda-environment-file: ".github/workflows/data/conda/geolibs-pg18-freethreading.yml"
pg_major: "18"
name: PostgreSQL ${{ matrix.pg_major }}
python: "3.14t"
name: PostgreSQL ${{ matrix.pg_major }}, Python ${{ matrix.python }}
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Install system packages
run: |
sudo apt update
xargs -a .github/workflows/data/apt-packages.txt sudo apt install -y --no-install-recommends
- name: Setup Miniforge
# Pinned to v4.0.1
uses: conda-incubator/setup-miniconda@8ee1f361103df19b6f8c8655fd3967a8ecb162d5
with:
activate-environment: geodjango
environment-file: ${{ matrix.conda-environment-file }}
channel-priority: strict
- name: Install system packages
run: |
sudo apt update
xargs -a .github/workflows/data/apt-packages.txt sudo apt install -y --no-install-recommends
- name: Install and upgrade packaging tools
run: python -m pip install --upgrade pip wheel
- run: python -m pip install -r tests/requirements/py3.txt -r tests/requirements/postgres.txt -e .
- name: Create PostgreSQL settings file
run: mv ./.github/workflows/data/test_postgis.py.tpl ./tests/test_postgis.py
- name: Initialize and start local PostgreSQL
Expand All @@ -59,6 +64,8 @@ jobs:
pg_ctl -D "$GITHUB_WORKSPACE/.tmp/pgdata" -w start
psql -U user -d postgres -c "CREATE EXTENSION IF NOT EXISTS postgis;"
psql -U user -d postgres -c "SELECT PostGIS_full_version();"
- name: Show GIL status
run: python -c "import sys; print('GIL enabled:', sys._is_gil_enabled())"
- name: Print geospatial versions
run: |
python -c "from django.contrib.gis import gdal, geos; print(f'GDAL: {gdal.gdal_version()}'); print(f'GEOS: {geos.geos_version()}')"
Expand Down
4 changes: 2 additions & 2 deletions django/db/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
WindowFrame,
WindowFrameExclusion,
)
from django.db.models.fetch_modes import FETCH_ONE, FETCH_PEERS, RAISE
from django.db.models.fetch_modes import FETCH_ONE, FETCH_PEERS, FETCH_RAISE
from django.db.models.fields import * # NOQA
from django.db.models.fields import __all__ as fields_all
from django.db.models.fields.composite import CompositePrimaryKey
Expand Down Expand Up @@ -115,7 +115,7 @@
"OrderWrt",
"FETCH_ONE",
"FETCH_PEERS",
"RAISE",
"FETCH_RAISE",
"Lookup",
"Transform",
"Manager",
Expand Down
6 changes: 3 additions & 3 deletions django/db/models/fetch_modes.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def __reduce__(self):
FETCH_PEERS = FetchPeers()


class Raise(FetchMode):
class FetchRaise(FetchMode):
__slots__ = ()

def fetch(self, fetcher, instance):
Expand All @@ -55,7 +55,7 @@ def fetch(self, fetcher, instance):
raise FieldFetchBlocked(f"Fetching of {klass}.{field_name} blocked.") from None

def __reduce__(self):
return "RAISE"
return "FETCH_RAISE"


RAISE = Raise()
FETCH_RAISE = FetchRaise()
2 changes: 1 addition & 1 deletion docs/ref/exceptions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ Django core exception classes are defined in ``django.core.exceptions``.
.. exception:: FieldFetchBlocked

Raised when a field would be fetched on-demand and the
:attr:`~django.db.models.RAISE` fetch mode is active.
:attr:`~django.db.models.FETCH_RAISE` fetch mode is active.

``ValidationError``
-------------------
Expand Down
19 changes: 18 additions & 1 deletion docs/ref/models/constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,19 @@ ensures the age field is never less than 18.

.. class:: UniqueConstraint(*expressions, fields=(), name, condition=None, deferrable=None, include=None, opclasses=(), nulls_distinct=None, violation_error_code=None, violation_error_message=None)

Creates a unique constraint in the database.
Creates a uniqueness guarantee in the database, enforced by either a
unique constraint or a unique index depending on the options used.

.. admonition:: Constraint vs. index implementation

Setting only :attr:`UniqueConstraint.fields` creates a true database
constraint (``ADD CONSTRAINT ... UNIQUE``). Specifying any of
:attr:`UniqueConstraint.expressions`, :attr:`UniqueConstraint.opclasses`,
:attr:`UniqueConstraint.condition`, or :attr:`UniqueConstraint.include`
creates a unique index (``CREATE UNIQUE INDEX``) instead.

In this documentation, the term "unique constraint" is used for both
cases to mean a uniqueness guarantee enforced by the database.

``expressions``
---------------
Expand Down Expand Up @@ -203,6 +215,11 @@ By default constraints are not deferred. A deferred constraint will not be
enforced until the end of the transaction. An immediate constraint will be
enforced immediately after every command.

Unique constraints with :attr:`~UniqueConstraint.condition`,
:attr:`~UniqueConstraint.include`, :attr:`~UniqueConstraint.opclasses`, or
:attr:`~UniqueConstraint.expressions` may be implemented as unique indexes
rather than unique constraints. In that case, ``deferrable`` cannot be set.

.. admonition:: MySQL, MariaDB, and SQLite.

Deferrable unique constraints are ignored on MySQL, MariaDB, and SQLite as
Expand Down
2 changes: 1 addition & 1 deletion docs/releases/6.1.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Django provides three fetch modes:
cases of the "N+1 queries problem" to two queries without any work to
maintain a list of fields to prefetch.

3. ``RAISE`` raises a :exc:`~django.core.exceptions.FieldFetchBlocked`
3. ``FETCH_RAISE`` raises a :exc:`~django.core.exceptions.FieldFetchBlocked`
exception.

This mode can prevent unintentional queries in performance-critical
Expand Down
6 changes: 3 additions & 3 deletions docs/topics/db/fetch-modes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,12 @@ much effort.
The "peer" instances are tracked in a list of weak references, to avoid
memory leaks where some peer instances are discarded.

.. attribute:: RAISE
.. attribute:: FETCH_RAISE

Raises a :exc:`~django.core.exceptions.FieldFetchBlocked` exception.

Using ``RAISE`` for the above example would raise an exception at the access of
``book.author``, like:
Using ``FETCH_RAISE`` for the above example would raise an exception at the
access of ``book.author``, like:

.. code-block:: python

Expand Down
10 changes: 5 additions & 5 deletions tests/basic/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1104,19 +1104,19 @@ def test_refresh_copies_fetch_mode_from_plucked_instance(self):
a = Article.objects.create(pub_date=datetime.now())
fa = FeaturedArticle.objects.fetch_mode(models.FETCH_PEERS).create(article=a)

from_queryset = FeaturedArticle.objects.fetch_mode(models.RAISE).select_related(
"article"
)
from_queryset = FeaturedArticle.objects.fetch_mode(
models.FETCH_RAISE
).select_related("article")
fa.refresh_from_db(from_queryset=from_queryset)
self.assertEqual(fa._state.fetch_mode, models.FETCH_PEERS)
self.assertEqual(fa.article._state.fetch_mode, models.RAISE)
self.assertEqual(fa.article._state.fetch_mode, models.FETCH_RAISE)

def test_refresh_ignores_fetch_mode_if_no_instance_plucked(self):
a = Article.objects.create(pub_date=datetime.now())
fa = FeaturedArticle.objects.fetch_mode(models.FETCH_PEERS).create(article=a)

# This queryset's fetch mode is not used because no fields are plucked.
from_queryset = FeaturedArticle.objects.fetch_mode(models.RAISE)
from_queryset = FeaturedArticle.objects.fetch_mode(models.FETCH_RAISE)
fa.refresh_from_db(from_queryset=from_queryset)
self.assertEqual(fa._state.fetch_mode, models.FETCH_PEERS)
self.assertEqual(fa.article._state.fetch_mode, models.FETCH_PEERS)
6 changes: 3 additions & 3 deletions tests/defer/tests.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from django.core.exceptions import FieldDoesNotExist, FieldError, FieldFetchBlocked
from django.db.models import FETCH_PEERS, RAISE
from django.db.models import FETCH_PEERS, FETCH_RAISE
from django.test import SimpleTestCase, TestCase
from django.test.utils import ignore_warnings
from django.utils.deprecation import RemovedInDjango70Warning
Expand Down Expand Up @@ -241,15 +241,15 @@ def test_only_fk_fetch_mode_fetch_peers(self):
self.assertEqual(p2.related, self.s1)

def test_only_fetch_mode_raise(self):
p1 = Primary.objects.fetch_mode(RAISE).only("name").get(name="p1")
p1 = Primary.objects.fetch_mode(FETCH_RAISE).only("name").get(name="p1")
msg = "Fetching of Primary.value blocked."
with self.assertRaisesMessage(FieldFetchBlocked, msg) as cm:
p1.value
self.assertIsNone(cm.exception.__cause__)
self.assertTrue(cm.exception.__suppress_context__)

def test_defer_fetch_mode_raise(self):
p1 = Primary.objects.fetch_mode(RAISE).defer("value").get(name="p1")
p1 = Primary.objects.fetch_mode(FETCH_RAISE).defer("value").get(name="p1")
msg = "Fetching of Primary.value blocked."
with self.assertRaisesMessage(FieldFetchBlocked, msg) as cm:
p1.value
Expand Down
4 changes: 2 additions & 2 deletions tests/generic_relations/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from django.contrib.contenttypes.prefetch import GenericPrefetch
from django.core.exceptions import FieldError, FieldFetchBlocked
from django.db.models import Q, prefetch_related_objects
from django.db.models.fetch_modes import FETCH_PEERS, RAISE
from django.db.models.fetch_modes import FETCH_PEERS, FETCH_RAISE
from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature

from .models import (
Expand Down Expand Up @@ -813,7 +813,7 @@ def test_fetch_mode_fetch_peers(self):
self.assertEqual(quartz_tag.content_object, self.quartz)

def test_fetch_mode_raise(self):
tag = TaggedItem.objects.fetch_mode(RAISE).get(tag="yellow")
tag = TaggedItem.objects.fetch_mode(FETCH_RAISE).get(tag="yellow")
msg = "Fetching of TaggedItem.content_object blocked."
with self.assertRaisesMessage(FieldFetchBlocked, msg) as cm:
tag.content_object
Expand Down
4 changes: 2 additions & 2 deletions tests/many_to_one/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
MultipleObjectsReturned,
)
from django.db import IntegrityError, models, transaction
from django.db.models import FETCH_PEERS, RAISE
from django.db.models import FETCH_PEERS, FETCH_RAISE
from django.test import TestCase
from django.utils.translation import gettext_lazy

Expand Down Expand Up @@ -947,7 +947,7 @@ def test_fetch_mode_fetch_peers_forward(self):
a2.reporter

def test_fetch_mode_raise_forward(self):
a = Article.objects.fetch_mode(RAISE).get(pk=self.a.pk)
a = Article.objects.fetch_mode(FETCH_RAISE).get(pk=self.a.pk)
msg = "Fetching of Article.reporter blocked."
with self.assertRaisesMessage(FieldFetchBlocked, msg) as cm:
a.reporter
Expand Down
6 changes: 3 additions & 3 deletions tests/one_to_one/tests.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.core.exceptions import FieldFetchBlocked
from django.db import IntegrityError, connection, transaction
from django.db.models import FETCH_PEERS, RAISE
from django.db.models import FETCH_PEERS, FETCH_RAISE
from django.test import TestCase

from .models import (
Expand Down Expand Up @@ -643,15 +643,15 @@ def test_fetch_mode_fetch_peers_reverse(self):
p2.restaurant

def test_fetch_mode_raise_forward(self):
r = Restaurant.objects.fetch_mode(RAISE).get(pk=self.r1.pk)
r = Restaurant.objects.fetch_mode(FETCH_RAISE).get(pk=self.r1.pk)
msg = "Fetching of Restaurant.place blocked."
with self.assertRaisesMessage(FieldFetchBlocked, msg) as cm:
r.place
self.assertIsNone(cm.exception.__cause__)
self.assertTrue(cm.exception.__suppress_context__)

def test_fetch_mode_raise_reverse(self):
p = Place.objects.fetch_mode(RAISE).get(pk=self.p1.pk)
p = Place.objects.fetch_mode(FETCH_RAISE).get(pk=self.p1.pk)
msg = "Fetching of Place.restaurant blocked."
with self.assertRaisesMessage(FieldFetchBlocked, msg) as cm:
p.restaurant
Expand Down
6 changes: 4 additions & 2 deletions tests/prefetch_related/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
QuerySet,
prefetch_related_objects,
)
from django.db.models.fetch_modes import RAISE
from django.db.models.fetch_modes import FETCH_RAISE
from django.db.models.query import get_prefetcher
from django.db.models.sql import Query
from django.test import (
Expand Down Expand Up @@ -137,7 +137,9 @@ def test_fetch_mode_copied_fetching_many(self):
)

def test_fetch_mode_raise(self):
authors = list(Author.objects.fetch_mode(RAISE).prefetch_related("first_book"))
authors = list(
Author.objects.fetch_mode(FETCH_RAISE).prefetch_related("first_book")
)
authors[0].first_book # No exception, already loaded

def test_foreignkey_reverse(self):
Expand Down
6 changes: 3 additions & 3 deletions tests/queryset_pickle/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -372,11 +372,11 @@ def test_fetch_mode_fetch_peers(self):
self.assertEqual(restored[0]._state.peers, ())

def test_fetch_mode_raise(self):
objs = list(Happening.objects.fetch_mode(models.RAISE))
self.assertEqual(objs[0]._state.fetch_mode, models.RAISE)
objs = list(Happening.objects.fetch_mode(models.FETCH_RAISE))
self.assertEqual(objs[0]._state.fetch_mode, models.FETCH_RAISE)

restored = pickle.loads(pickle.dumps(objs))
self.assertIs(restored[0]._state.fetch_mode, models.RAISE)
self.assertIs(restored[0]._state.fetch_mode, models.FETCH_RAISE)


class InLookupTests(TestCase):
Expand Down
Loading
Loading