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
68 changes: 68 additions & 0 deletions .github/workflows/check-migrations.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Check migrations

on:
pull_request:
paths:
- 'tests/**/models.py'
- 'tests/**/migrations/**'
- 'tests/**/*migrations/**/*.py'
- 'scripts/check_migrations.py'
push:
branches:
- main

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
check-migrations:
name: Check test migrations
runs-on: ubuntu-latest
timeout-minutes: 60

services:
postgres:
image: postgres:15-alpine
env:
POSTGRES_DB: django
POSTGRES_USER: user
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5

steps:
- name: Checkout
uses: actions/checkout@v6
with:
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.14'
cache: 'pip'
cache-dependency-path: 'tests/requirements/py3.txt'

- name: Install dependencies
run: |
python -m pip install --upgrade pip wheel
python -m pip install -e . psycopg[binary]

- name: Create PostgreSQL settings file
run: mv ./.github/workflows/data/test_postgres.py.tpl ./tests/test_postgres.py

- name: Check for missing migrations
env:
DJANGO_SETTINGS_MODULE: test_postgres
PYTHONPATH: ${{ github.workspace }}/tests:${{ github.workspace }}
run: |
python scripts/check_migrations.py
14 changes: 13 additions & 1 deletion django/db/models/expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1141,7 +1141,7 @@ def allowed_default(self):


@deconstructible(path="django.db.models.Value")
class Value(SQLiteNumericMixin, Expression):
class Value(Expression):
"""Represent a wrapped value as a node within an expression."""

# Provide a default value for `for_save` in order to allow unresolved
Expand Down Expand Up @@ -1182,6 +1182,18 @@ def as_sql(self, compiler, connection):
return "NULL", []
return "%s", [val]

def as_sqlite(self, compiler, connection, **extra_context):
sql, params = self.as_sql(compiler, connection, **extra_context)
try:
if self.output_field.get_internal_type() == "DecimalField":
if isinstance(self.value, Decimal):
sql = "(CAST(%s AS REAL))" % sql
else:
sql = "(CAST(%s AS NUMERIC))" % sql
except FieldError:
pass
return sql, params

def resolve_expression(
self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
):
Expand Down
31 changes: 31 additions & 0 deletions scripts/check_migrations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import sys
from pathlib import Path


def main():
repo_root = Path(__file__).resolve().parent.parent
sys.path[:0] = [str(repo_root / "tests"), str(repo_root)]

from runtests import ALWAYS_INSTALLED_APPS, get_apps_to_install, get_test_modules

import django
from django.apps import apps
from django.core.management import call_command

django.setup()

test_modules = list(get_test_modules(gis_enabled=False))
installed_apps = list(ALWAYS_INSTALLED_APPS)
for app in get_apps_to_install(test_modules):
# Check against the list to prevent duplicate errors.
if app not in installed_apps:
installed_apps.append(app)
apps.set_installed_apps(installed_apps)

# Note: We don't use check=True here because --check calls sys.exit(1)
# instead of raising CommandError when migrations are missing.
call_command("makemigrations", "--check", verbosity=3)


if __name__ == "__main__":
main()
72 changes: 72 additions & 0 deletions tests/db_functions/migrations/0002_create_test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name="Author",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=50)),
("alias", models.CharField(max_length=50, null=True, blank=True)),
("goes_by", models.CharField(max_length=50, null=True, blank=True)),
Expand All @@ -19,6 +28,15 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name="Article",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"authors",
models.ManyToManyField(
Expand All @@ -37,6 +55,15 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name="Fan",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=50)),
("age", models.PositiveSmallIntegerField(default=30)),
(
Expand All @@ -51,6 +78,15 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name="DTModel",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=32)),
("start_datetime", models.DateTimeField(null=True, blank=True)),
("end_datetime", models.DateTimeField(null=True, blank=True)),
Expand All @@ -64,6 +100,15 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name="DecimalModel",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("n1", models.DecimalField(decimal_places=2, max_digits=6)),
(
"n2",
Expand All @@ -76,6 +121,15 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name="IntegerModel",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("big", models.BigIntegerField(null=True, blank=True)),
("normal", models.IntegerField(null=True, blank=True)),
("small", models.SmallIntegerField(null=True, blank=True)),
Expand All @@ -84,13 +138,31 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name="FloatModel",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("f1", models.FloatField(null=True, blank=True)),
("f2", models.FloatField(null=True, blank=True)),
],
),
migrations.CreateModel(
name="UUIDModel",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("uuid", models.UUIDField(null=True)),
("shift", models.DurationField(null=True)),
],
Expand Down
10 changes: 10 additions & 0 deletions tests/expressions/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,16 @@ def test_annotate_values_aggregate(self):
)
self.assertEqual(companies["result"], 2395)

def test_decimal_division_literal_value(self):
"""
Division with a literal Decimal value preserves precision.
"""
num = Number.objects.create(integer=2)
obj = Number.objects.annotate(
val=F("integer") / Value(Decimal("3.0"), output_field=DecimalField())
).get(pk=num.pk)
self.assertAlmostEqual(obj.val, Decimal("0.6667"), places=4)

def test_annotate_values_filter(self):
companies = (
Company.objects.annotate(
Expand Down
2 changes: 1 addition & 1 deletion tests/gis_tests/gdal_tests/test_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"MapInfo File",
"S57",
"DGN",
"Memory" if GDAL_VERSION <= (3, 10) else "MEM",
"Memory" if GDAL_VERSION[:2] <= (3, 10) else "MEM",
"CSV",
"GML",
"KML",
Expand Down
4 changes: 2 additions & 2 deletions tests/gis_tests/rasterapp/migrations/0002_rastermodels.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class Migration(migrations.Migration):
fields=[
(
"id",
models.AutoField(
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
Expand Down Expand Up @@ -49,7 +49,7 @@ class Migration(migrations.Migration):
fields=[
(
"id",
models.AutoField(
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from django.db import migrations
from django.db import migrations, models


def add_book(apps, schema_editor):
Expand All @@ -16,4 +16,29 @@ class Migration(migrations.Migration):
migrations.RunPython(
add_book,
),
migrations.CreateModel(
name="Unmanaged",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("title", models.CharField(max_length=100)),
],
options={
"managed": False,
},
),
migrations.AlterField(
model_name="book",
name="id",
field=models.BigAutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
]
4 changes: 2 additions & 2 deletions tests/postgres_tests/migrations/0002_create_test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ class Migration(migrations.Migration):
("tags", ArrayField(TagField(), blank=True, null=True)),
(
"json",
ArrayField(models.JSONField(default=dict), default=list),
ArrayField(models.JSONField(default=dict), default=list, null=True),
),
("int_ranges", ArrayField(IntegerRangeField(), null=True, blank=True)),
(
Expand Down Expand Up @@ -179,7 +179,7 @@ class Migration(migrations.Migration):
),
(
"field",
ArrayField(models.FloatField(), size=2, null=True, blank=True),
ArrayField(models.FloatField(), size=3),
),
],
options={
Expand Down
Loading