Fixed #30271 -- Added the Sign database function.

This commit is contained in:
Nick Pope 2019-03-20 08:27:34 +00:00 committed by Mariusz Felisiak
parent 5935a9aead
commit d26b242443
No known key found for this signature in database
GPG Key ID: 2EF56372BA48CD1B
6 changed files with 88 additions and 2 deletions

View File

@ -231,6 +231,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
conn.create_function('SHA256', 1, none_guard(lambda x: hashlib.sha256(x.encode()).hexdigest())) conn.create_function('SHA256', 1, none_guard(lambda x: hashlib.sha256(x.encode()).hexdigest()))
conn.create_function('SHA384', 1, none_guard(lambda x: hashlib.sha384(x.encode()).hexdigest())) conn.create_function('SHA384', 1, none_guard(lambda x: hashlib.sha384(x.encode()).hexdigest()))
conn.create_function('SHA512', 1, none_guard(lambda x: hashlib.sha512(x.encode()).hexdigest())) conn.create_function('SHA512', 1, none_guard(lambda x: hashlib.sha512(x.encode()).hexdigest()))
conn.create_function('SIGN', 1, none_guard(lambda x: (x > 0) - (x < 0)))
conn.create_function('SIN', 1, none_guard(math.sin)) conn.create_function('SIN', 1, none_guard(math.sin))
conn.create_function('SQRT', 1, none_guard(math.sqrt)) conn.create_function('SQRT', 1, none_guard(math.sqrt))
conn.create_function('TAN', 1, none_guard(math.tan)) conn.create_function('TAN', 1, none_guard(math.tan))

View File

@ -7,7 +7,7 @@ from .datetime import (
) )
from .math import ( from .math import (
Abs, ACos, ASin, ATan, ATan2, Ceil, Cos, Cot, Degrees, Exp, Floor, Ln, Log, Abs, ACos, ASin, ATan, ATan2, Ceil, Cos, Cot, Degrees, Exp, Floor, Ln, Log,
Mod, Pi, Power, Radians, Round, Sin, Sqrt, Tan, Mod, Pi, Power, Radians, Round, Sign, Sin, Sqrt, Tan,
) )
from .text import ( from .text import (
MD5, SHA1, SHA224, SHA256, SHA384, SHA512, Chr, Concat, ConcatPair, Left, MD5, SHA1, SHA224, SHA256, SHA384, SHA512, Chr, Concat, ConcatPair, Left,
@ -32,7 +32,7 @@ __all__ = [
# math # math
'Abs', 'ACos', 'ASin', 'ATan', 'ATan2', 'Ceil', 'Cos', 'Cot', 'Degrees', 'Abs', 'ACos', 'ASin', 'ATan', 'ATan2', 'Ceil', 'Cos', 'Cot', 'Degrees',
'Exp', 'Floor', 'Ln', 'Log', 'Mod', 'Pi', 'Power', 'Radians', 'Round', 'Exp', 'Floor', 'Ln', 'Log', 'Mod', 'Pi', 'Power', 'Radians', 'Round',
'Sin', 'Sqrt', 'Tan', 'Sign', 'Sin', 'Sqrt', 'Tan',
# text # text
'MD5', 'SHA1', 'SHA224', 'SHA256', 'SHA384', 'SHA512', 'Chr', 'Concat', 'MD5', 'SHA1', 'SHA224', 'SHA256', 'SHA384', 'SHA512', 'Chr', 'Concat',
'ConcatPair', 'Left', 'Length', 'Lower', 'LPad', 'LTrim', 'Ord', 'Repeat', 'ConcatPair', 'Left', 'Length', 'Lower', 'LPad', 'LTrim', 'Ord', 'Repeat',

View File

@ -146,6 +146,11 @@ class Round(Transform):
lookup_name = 'round' lookup_name = 'round'
class Sign(Transform):
function = 'SIGN'
lookup_name = 'sign'
class Sin(NumericOutputFieldMixin, Transform): class Sin(NumericOutputFieldMixin, Transform):
function = 'SIN' function = 'SIN'
lookup_name = 'sin' lookup_name = 'sin'

View File

@ -1099,6 +1099,31 @@ It can also be registered as a transform. For example::
>>> # Get vectors whose round() is less than 20 >>> # Get vectors whose round() is less than 20
>>> vectors = Vector.objects.filter(x__round__lt=20, y__round__lt=20) >>> vectors = Vector.objects.filter(x__round__lt=20, y__round__lt=20)
``Sign``
--------
.. class:: Sign(expression, **extra)
.. versionadded:: 3.0
Returns the sign (-1, 0, 1) of a numeric field or expression.
Usage example::
>>> from django.db.models.functions import Sign
>>> Vector.objects.create(x=5.4, y=-2.3)
>>> vector = Vector.objects.annotate(x_sign=Sign('x'), y_sign=Sign('y')).get()
>>> vector.x_sign, vector.y_sign
(1, -1)
It can also be registered as a transform. For example::
>>> from django.db.models import FloatField
>>> from django.db.models.functions import Sign
>>> FloatField.register_lookup(Sign)
>>> # Get vectors whose signs of components are less than 0.
>>> vectors = Vector.objects.filter(x__sign__lt=0, y__sign__lt=0)
``Sin`` ``Sin``
------- -------

View File

@ -178,6 +178,8 @@ Models
:class:`~django.db.models.functions.SHA384`, and :class:`~django.db.models.functions.SHA384`, and
:class:`~django.db.models.functions.SHA512`. :class:`~django.db.models.functions.SHA512`.
* Added the :class:`~django.db.models.functions.Sign` database function.
* The new ``is_dst`` parameter of the * The new ``is_dst`` parameter of the
:class:`~django.db.models.functions.Trunc` database functions determines the :class:`~django.db.models.functions.Trunc` database functions determines the
treatment of nonexistent and ambiguous datetimes. treatment of nonexistent and ambiguous datetimes.

View File

@ -0,0 +1,53 @@
from decimal import Decimal
from django.db.models import DecimalField
from django.db.models.functions import Sign
from django.test import TestCase
from django.test.utils import register_lookup
from ..models import DecimalModel, FloatModel, IntegerModel
class SignTests(TestCase):
def test_null(self):
IntegerModel.objects.create()
obj = IntegerModel.objects.annotate(null_sign=Sign('normal')).first()
self.assertIsNone(obj.null_sign)
def test_decimal(self):
DecimalModel.objects.create(n1=Decimal('-12.9'), n2=Decimal('0.6'))
obj = DecimalModel.objects.annotate(n1_sign=Sign('n1'), n2_sign=Sign('n2')).first()
self.assertIsInstance(obj.n1_sign, Decimal)
self.assertIsInstance(obj.n2_sign, Decimal)
self.assertEqual(obj.n1_sign, Decimal('-1'))
self.assertEqual(obj.n2_sign, Decimal('1'))
def test_float(self):
FloatModel.objects.create(f1=-27.5, f2=0.33)
obj = FloatModel.objects.annotate(f1_sign=Sign('f1'), f2_sign=Sign('f2')).first()
self.assertIsInstance(obj.f1_sign, float)
self.assertIsInstance(obj.f2_sign, float)
self.assertEqual(obj.f1_sign, -1.0)
self.assertEqual(obj.f2_sign, 1.0)
def test_integer(self):
IntegerModel.objects.create(small=-20, normal=0, big=20)
obj = IntegerModel.objects.annotate(
small_sign=Sign('small'),
normal_sign=Sign('normal'),
big_sign=Sign('big'),
).first()
self.assertIsInstance(obj.small_sign, int)
self.assertIsInstance(obj.normal_sign, int)
self.assertIsInstance(obj.big_sign, int)
self.assertEqual(obj.small_sign, -1)
self.assertEqual(obj.normal_sign, 0)
self.assertEqual(obj.big_sign, 1)
def test_transform(self):
with register_lookup(DecimalField, Sign):
DecimalModel.objects.create(n1=Decimal('5.4'), n2=Decimal('0'))
DecimalModel.objects.create(n1=Decimal('-0.1'), n2=Decimal('0'))
obj = DecimalModel.objects.filter(n1__sign__lt=0, n2__sign=0).get()
self.assertEqual(obj.n1, Decimal('-0.1'))