Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit daab9dd

Browse filesBrowse files
committed
Merge branch 'system' of github.com:vi3k6i5/python-spanner-django into system
2 parents d05fb2d + 6801f5c commit daab9dd
Copy full SHA for daab9dd

File tree

Expand file treeCollapse file tree

7 files changed

+88
-134
lines changed
Filter options
Expand file treeCollapse file tree

7 files changed

+88
-134
lines changed

‎django_spanner/base.py

Copy file name to clipboardExpand all lines: django_spanner/base.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
3434
"CharField": "STRING(%(max_length)s)",
3535
"DateField": "DATE",
3636
"DateTimeField": "TIMESTAMP",
37-
"DecimalField": "NUMERIC",
37+
"DecimalField": "FLOAT64",
3838
"DurationField": "INT64",
3939
"EmailField": "STRING(%(max_length)s)",
4040
"FileField": "STRING(%(max_length)s)",

‎django_spanner/introspection.py

Copy file name to clipboardExpand all lines: django_spanner/introspection.py
-1Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
2424
TypeCode.INT64: "IntegerField",
2525
TypeCode.STRING: "CharField",
2626
TypeCode.TIMESTAMP: "DateTimeField",
27-
TypeCode.NUMERIC: "DecimalField",
2827
}
2928

3029
def get_field_type(self, data_type, description):

‎django_spanner/lookups.py

Copy file name to clipboardExpand all lines: django_spanner/lookups.py
+7-1Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# license that can be found in the LICENSE file or at
55
# https://developers.google.com/open-source/licenses/bsd
66

7+
from django.db.models import DecimalField
78
from django.db.models.lookups import (
89
Contains,
910
EndsWith,
@@ -232,8 +233,13 @@ def cast_param_to_float(self, compiler, connection):
232233
"""
233234
sql, params = self.as_sql(compiler, connection)
234235
if params:
236+
# Cast to DecimaField lookup values to float because
237+
# google.cloud.spanner_v1._helpers._make_value_pb() doesn't serialize
238+
# decimal.Decimal.
239+
if isinstance(self.lhs.output_field, DecimalField):
240+
params[0] = float(params[0])
235241
# Cast remote field lookups that must be integer but come in as string.
236-
if hasattr(self.lhs.output_field, "get_path_info"):
242+
elif hasattr(self.lhs.output_field, "get_path_info"):
237243
for i, field in enumerate(
238244
self.lhs.output_field.get_path_info()[-1].target_fields
239245
):

‎django_spanner/operations.py

Copy file name to clipboardExpand all lines: django_spanner/operations.py
+31-7Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import re
99
from base64 import b64decode
1010
from datetime import datetime, time
11+
from decimal import Decimal
1112
from uuid import UUID
1213

1314
from django.conf import settings
@@ -189,11 +190,10 @@ def adapt_decimalfield_value(
189190
self, value, max_digits=None, decimal_places=None
190191
):
191192
"""
192-
Convert value from decimal.Decimal to spanner compatible value.
193-
Since spanner supports Numeric storage of decimal and python spanner
194-
takes care of the conversion so this is a no-op method call.
193+
Convert value from decimal.Decimal into float, for a direct mapping
194+
and correct serialization with RPCs to Cloud Spanner.
195195
196-
:type value: :class:`decimal.Decimal`
196+
:type value: :class:`~google.cloud.spanner_v1.types.Numeric`
197197
:param value: A decimal field value.
198198
199199
:type max_digits: int
@@ -203,10 +203,12 @@ def adapt_decimalfield_value(
203203
:param decimal_places: (Optional) The number of decimal places to store
204204
with the number.
205205
206-
:rtype: decimal.Decimal
207-
:returns: decimal value.
206+
:rtype: float
207+
:returns: Formatted value.
208208
"""
209-
return value
209+
if value is None:
210+
return None
211+
return float(value)
210212

211213
def adapt_timefield_value(self, value):
212214
"""
@@ -242,6 +244,8 @@ def get_db_converters(self, expression):
242244
internal_type = expression.output_field.get_internal_type()
243245
if internal_type == "DateTimeField":
244246
converters.append(self.convert_datetimefield_value)
247+
elif internal_type == "DecimalField":
248+
converters.append(self.convert_decimalfield_value)
245249
elif internal_type == "TimeField":
246250
converters.append(self.convert_timefield_value)
247251
elif internal_type == "BinaryField":
@@ -307,6 +311,26 @@ def convert_datetimefield_value(self, value, expression, connection):
307311
else dt
308312
)
309313

314+
def convert_decimalfield_value(self, value, expression, connection):
315+
"""Convert Spanner DecimalField value for Django.
316+
317+
:type value: float
318+
:param value: A decimal field.
319+
320+
:type expression: :class:`django.db.models.expressions.BaseExpression`
321+
:param expression: A query expression.
322+
323+
:type connection: :class:`~google.cloud.cpanner_dbapi.connection.Connection`
324+
:param connection: Reference to a Spanner database connection.
325+
326+
:rtype: :class:`Decimal`
327+
:returns: A converted decimal field.
328+
"""
329+
if value is None:
330+
return value
331+
# Cloud Spanner returns a float.
332+
return Decimal(str(value))
333+
310334
def convert_timefield_value(self, value, expression, connection):
311335
"""Convert Spanner TimeField value for Django.
312336

‎tests/system/django_spanner/models.py

Copy file name to clipboardExpand all lines: tests/system/django_spanner/models.py
-7Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,3 @@ class Author(models.Model):
1414
first_name = models.CharField(max_length=20)
1515
last_name = models.CharField(max_length=20)
1616
rating = models.DecimalField()
17-
18-
19-
class Number(models.Model):
20-
num = models.DecimalField()
21-
22-
def __str__(self):
23-
return str(self.num)

‎tests/system/django_spanner/test_decimal.py

Copy file name to clipboardExpand all lines: tests/system/django_spanner/test_decimal.py
-117Lines changed: 0 additions & 117 deletions
This file was deleted.
+49Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Copyright 2021 Google LLC
2+
#
3+
# Use of this source code is governed by a BSD-style
4+
# license that can be found in the LICENSE file or at
5+
# https://developers.google.com/open-source/licenses/bsd
6+
7+
from .models import Author
8+
from django.test import TransactionTestCase
9+
from django.db import connection
10+
from decimal import Decimal
11+
from .utils import (
12+
setup_instance,
13+
teardown_instance,
14+
setup_database,
15+
teardown_database,
16+
)
17+
18+
19+
class TestQueries(TransactionTestCase):
20+
@classmethod
21+
def setUpClass(cls):
22+
setup_instance()
23+
setup_database()
24+
with connection.schema_editor() as editor:
25+
# Create the tables
26+
editor.create_model(Author)
27+
28+
@classmethod
29+
def tearDownClass(cls):
30+
with connection.schema_editor() as editor:
31+
# delete the table
32+
editor.delete_model(Author)
33+
teardown_database()
34+
teardown_instance()
35+
36+
def test_insert_and_fetch_value(self):
37+
"""
38+
Tests model object creation with Author model.
39+
Inserting data into the model and retrieving it.
40+
"""
41+
author_kent = Author(
42+
first_name="Arthur", last_name="Kent", rating=Decimal("4.1"),
43+
)
44+
author_kent.save()
45+
qs1 = Author.objects.all().values("first_name", "last_name")
46+
self.assertEqual(qs1[0]["first_name"], "Arthur")
47+
self.assertEqual(qs1[0]["last_name"], "Kent")
48+
# Delete data from Author table.
49+
Author.objects.all().delete()

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.