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
11 changes: 8 additions & 3 deletions django/db/backends/sqlite3/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,26 +300,31 @@ def convert_timefield_value(self, value, expression, connection):
value = parse_time(value)
return value

@staticmethod
def _create_decimal(value):
if isinstance(value, (int, str)):
return decimal.Decimal(value)
return decimal.Context(prec=15).create_decimal_from_float(value)

def get_decimalfield_converter(self, expression):
# SQLite stores only 15 significant digits. Digits coming from
# float inaccuracy must be removed.
create_decimal = decimal.Context(prec=15).create_decimal_from_float
if isinstance(expression, Col):
quantize_value = decimal.Decimal(1).scaleb(
-expression.output_field.decimal_places
)

def converter(value, expression, connection):
if value is not None:
return create_decimal(value).quantize(
return self._create_decimal(value).quantize(
quantize_value, context=expression.output_field.context
)

else:

def converter(value, expression, connection):
if value is not None:
return create_decimal(value)
return self._create_decimal(value)

return converter

Expand Down
13 changes: 7 additions & 6 deletions django/db/migrations/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,10 @@ def alter_model_options(self, app_label, model_name, options, option_keys=None):
def remove_model_options(self, app_label, model_name, option_name, value_to_remove):
model_state = self.models[app_label, model_name]
if objs := model_state.options.get(option_name):
model_state.options[option_name] = [
obj for obj in objs if tuple(obj) != tuple(value_to_remove)
]
new_value = [obj for obj in objs if tuple(obj) != tuple(value_to_remove)]
if option_name in {"index_together", "unique_together"}:
new_value = set(normalize_together(new_value))
model_state.options[option_name] = new_value
self.reload_model(app_label, model_name, delay=True)

def alter_model_managers(self, app_label, model_name, managers):
Expand Down Expand Up @@ -339,10 +340,10 @@ def rename_field(self, app_label, model_name, old_name, new_name):
options = model_state.options
for option in ("index_together", "unique_together"):
if option in options:
options[option] = [
[new_name if n == old_name else n for n in together]
options[option] = {
tuple(new_name if n == old_name else n for n in together)
for together in options[option]
]
}
# Fix to_fields to refer to the new field.
delay = True
references = get_references(self, model_key, (old_name, found))
Expand Down
2 changes: 1 addition & 1 deletion docs/internals/howto-release-django.txt
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ permissions.

* A Unix environment with these tools installed (in alphabetical order):

* bash
* bash (version 4.0+)
* git
* GPG
* make
Expand Down
45 changes: 45 additions & 0 deletions tests/migrations/test_autodetector.py
Original file line number Diff line number Diff line change
Expand Up @@ -5590,6 +5590,51 @@ def test_remove_composite_pk(self):
preserve_default=True,
)

def test_does_not_crash_after_rename_on_unique_together(self):
fields = ("first", "second")
before = self.make_project_state(
[
ModelState(
"app",
"Foo",
[
("id", models.AutoField(primary_key=True)),
("first", models.IntegerField()),
("second", models.IntegerField()),
],
options={"unique_together": {fields}},
),
]
)
after = before.clone()
after.rename_field("app", "foo", "first", "first_renamed")

changes = MigrationAutodetector(
before, after, MigrationQuestioner({"ask_rename": True})
)._detect_changes()

self.assertNumberMigrations(changes, "app", 1)
self.assertOperationTypes(
changes, "app", 0, ["RenameField", "AlterUniqueTogether"]
)
self.assertOperationAttributes(
changes,
"app",
0,
0,
model_name="foo",
old_name="first",
new_name="first_renamed",
)
self.assertOperationAttributes(
changes,
"app",
0,
1,
name="foo",
unique_together={("first_renamed", "second")},
)


class MigrationSuggestNameTests(SimpleTestCase):
def test_no_operations(self):
Expand Down
7 changes: 3 additions & 4 deletions tests/migrations/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,14 +290,13 @@ def set_up_test_model(
):
"""Creates a test model state and database table."""
# Make the "current" state.
model_options = {
"swappable": "TEST_SWAP_MODEL",
"unique_together": [["pink", "weight"]] if unique_together else [],
}
model_options = {"swappable": "TEST_SWAP_MODEL"}
if options:
model_options["permissions"] = [("can_groom", "Can groom")]
if db_table:
model_options["db_table"] = db_table
if unique_together:
model_options["unique_together"] = {("pink", "weight")}
operations = [
migrations.CreateModel(
"Pony",
Expand Down
21 changes: 14 additions & 7 deletions tests/migrations/test_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -3336,11 +3336,11 @@ def test_rename_field_unique_together(self):
# unique_together has the renamed column.
self.assertIn(
"blue",
new_state.models["test_rnflut", "pony"].options["unique_together"][0],
list(new_state.models["test_rnflut", "pony"].options["unique_together"])[0],
)
self.assertNotIn(
"pink",
new_state.models["test_rnflut", "pony"].options["unique_together"][0],
list(new_state.models["test_rnflut", "pony"].options["unique_together"])[0],
)
# Rename field.
self.assertColumnExists("test_rnflut_pony", "pink")
Expand Down Expand Up @@ -3377,7 +3377,7 @@ def test_rename_field_index_together(self):
("weight", models.FloatField()),
],
options={
"index_together": [("weight", "pink")],
"index_together": {("weight", "pink")},
},
),
]
Expand All @@ -3390,10 +3390,12 @@ def test_rename_field_index_together(self):
self.assertNotIn("pink", new_state.models["test_rnflit", "pony"].fields)
# index_together has the renamed column.
self.assertIn(
"blue", new_state.models["test_rnflit", "pony"].options["index_together"][0]
"blue",
list(new_state.models["test_rnflit", "pony"].options["index_together"])[0],
)
self.assertNotIn(
"pink", new_state.models["test_rnflit", "pony"].options["index_together"][0]
"pink",
list(new_state.models["test_rnflit", "pony"].options["index_together"])[0],
)

# Rename field.
Expand Down Expand Up @@ -3952,7 +3954,7 @@ def test_rename_index_unnamed_index(self):
("weight", models.FloatField()),
],
options={
"index_together": [("weight", "pink")],
"index_together": {("weight", "pink")},
},
),
]
Expand All @@ -3972,6 +3974,11 @@ def test_rename_index_unnamed_index(self):
)
new_state = project_state.clone()
operation.state_forwards(app_label, new_state)
# Ensure the model state has the correct type for the index_together
# option.
self.assertIsInstance(
new_state.models[app_label, "pony"].options["index_together"], set
)
# Rename index.
with connection.schema_editor() as editor:
operation.database_forwards(app_label, editor, project_state, new_state)
Expand Down Expand Up @@ -4079,7 +4086,7 @@ def test_rename_index_state_forwards_unnamed_index(self):
("weight", models.FloatField()),
],
options={
"index_together": [("weight", "pink")],
"index_together": {("weight", "pink")},
},
),
]
Expand Down
1 change: 1 addition & 0 deletions tests/model_fields/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ def get_choices():

class BigD(models.Model):
d = models.DecimalField(max_digits=32, decimal_places=30)
large_int = models.DecimalField(max_digits=16, decimal_places=0, null=True)


class FloatModel(models.Model):
Expand Down
18 changes: 18 additions & 0 deletions tests/model_fields/test_decimalfield.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from django.core import validators
from django.core.exceptions import ValidationError
from django.db import connection, models
from django.db.models import Max
from django.test import TestCase

from .models import BigD, Foo
Expand Down Expand Up @@ -140,3 +141,20 @@ def test_roundtrip_with_trailing_zeros(self):
obj = Foo.objects.create(a="bar", d=Decimal("8.320"))
obj.refresh_from_db()
self.assertEqual(obj.d.compare_total(Decimal("8.320")), Decimal("0"))

def test_large_integer_precision(self):
large_int_val = Decimal("9999999999999999")
obj = BigD.objects.create(large_int=large_int_val, d=Decimal("0"))
obj.refresh_from_db()
self.assertEqual(obj.large_int, large_int_val)

def test_large_integer_precision_aggregation(self):
large_int_val = Decimal("9999999999999999")
BigD.objects.create(large_int=large_int_val, d=Decimal("0"))
result = BigD.objects.aggregate(max_val=Max("large_int"))
self.assertEqual(result["max_val"], large_int_val)

def test_roundtrip_integer_with_trailing_zeros(self):
obj = Foo.objects.create(a="bar", d=Decimal("8"))
obj.refresh_from_db()
self.assertEqual(obj.d.compare_total(Decimal("8.000")), Decimal("0"))