Refs #28643 -- Added math database functions.

Thanks Nick Pope for much review.
This commit is contained in:
Junyi Jiao 2018-07-05 11:02:12 -04:00 committed by Tim Graham
parent 48aeca44d8
commit a0b19a0f5b
30 changed files with 1644 additions and 8 deletions

View File

@ -173,10 +173,28 @@ class DatabaseWrapper(BaseDatabaseWrapper):
conn.create_function("django_timestamp_diff", 2, _sqlite_timestamp_diff)
conn.create_function("regexp", 2, _sqlite_regexp)
conn.create_function("django_format_dtdelta", 3, _sqlite_format_dtdelta)
conn.create_function("django_power", 2, _sqlite_power)
conn.create_function('LPAD', 3, _sqlite_lpad)
conn.create_function('REPEAT', 2, operator.mul)
conn.create_function('RPAD', 3, _sqlite_rpad)
conn.create_function('ACOS', 1, math.acos)
conn.create_function('ASIN', 1, math.asin)
conn.create_function('ATAN', 1, math.atan)
conn.create_function('ATAN2', 2, math.atan2)
conn.create_function('CEILING', 1, math.ceil)
conn.create_function('COS', 1, math.cos)
conn.create_function('COT', 1, lambda x: 1 / math.tan(x))
conn.create_function('DEGREES', 1, math.degrees)
conn.create_function('EXP', 1, math.exp)
conn.create_function('FLOOR', 1, math.floor)
conn.create_function('LN', 1, math.log)
conn.create_function('LOG', 2, lambda x, y: math.log(y, x))
conn.create_function('MOD', 2, math.fmod)
conn.create_function('PI', 0, lambda: math.pi)
conn.create_function('POWER', 2, operator.pow)
conn.create_function('RADIANS', 1, math.radians)
conn.create_function('SIN', 1, math.sin)
conn.create_function('SQRT', 1, math.sqrt)
conn.create_function('TAN', 1, math.tan)
conn.execute('PRAGMA foreign_keys = ON')
return conn
@ -483,7 +501,3 @@ def _sqlite_lpad(text, length, fill_text):
def _sqlite_rpad(text, length, fill_text):
return (text + fill_text * length)[:length]
def _sqlite_power(x, y):
return x ** y

View File

@ -273,10 +273,10 @@ class DatabaseOperations(BaseDatabaseOperations):
)
def combine_expression(self, connector, sub_expressions):
# SQLite doesn't have a power function, so we fake it with a
# user-defined function django_power that's registered in connect().
# SQLite doesn't have a ^ operator, so use the user-defined POWER
# function that's registered in connect().
if connector == '^':
return 'django_power(%s)' % ','.join(sub_expressions)
return 'POWER(%s)' % ','.join(sub_expressions)
return super().combine_expression(connector, sub_expressions)
def combine_duration_expression(self, connector, sub_expressions):

View File

@ -5,6 +5,10 @@ from .datetime import (
Now, Trunc, TruncDate, TruncDay, TruncHour, TruncMinute, TruncMonth,
TruncQuarter, TruncSecond, TruncTime, TruncWeek, TruncYear,
)
from .math import (
Abs, ACos, ASin, ATan, ATan2, Ceil, Cos, Cot, Degrees, Exp, Floor, Ln, Log,
Mod, Pi, Power, Radians, Round, Sin, Sqrt, Tan,
)
from .text import (
Chr, Concat, ConcatPair, Left, Length, Lower, LPad, LTrim, Ord, Repeat,
Replace, Right, RPad, RTrim, StrIndex, Substr, Trim, Upper,
@ -23,6 +27,10 @@ __all__ = [
'ExtractYear', 'Now', 'Trunc', 'TruncDate', 'TruncDay', 'TruncHour',
'TruncMinute', 'TruncMonth', 'TruncQuarter', 'TruncSecond', 'TruncTime',
'TruncWeek', 'TruncYear',
# math
'Abs', 'ACos', 'ASin', 'ATan', 'ATan2', 'Ceil', 'Cos', 'Cot', 'Degrees',
'Exp', 'Floor', 'Ln', 'Log', 'Mod', 'Pi', 'Power', 'Radians', 'Round',
'Sin', 'Sqrt', 'Tan',
# text
'Chr', 'Concat', 'ConcatPair', 'Left', 'Length', 'Lower', 'LPad', 'LTrim',
'Ord', 'Repeat', 'Replace', 'Right', 'RPad', 'RTrim', 'StrIndex', 'Substr',

View File

@ -0,0 +1,174 @@
import math
import sys
from django.db.models import (
DecimalField, FloatField, Func, IntegerField, Transform,
)
from django.db.models.functions import Cast
class DecimalInputMixin:
def as_postgresql(self, compiler, connection):
# Cast FloatField to DecimalField as PostgreSQL doesn't support the
# following function signatures:
# - LOG(double, double)
# - MOD(double, double)
output_field = DecimalField(decimal_places=sys.float_info.dig, max_digits=1000)
clone = self.copy()
clone.set_source_expressions([
Cast(expression, output_field) if isinstance(expression.output_field, FloatField)
else expression for expression in self.get_source_expressions()
])
return clone.as_sql(compiler, connection)
class OutputFieldMixin:
def _resolve_output_field(self):
has_decimals = any(isinstance(s.output_field, DecimalField) for s in self.get_source_expressions())
return DecimalField() if has_decimals else FloatField()
class Abs(Transform):
function = 'ABS'
lookup_name = 'abs'
class ACos(OutputFieldMixin, Transform):
function = 'ACOS'
lookup_name = 'acos'
class ASin(OutputFieldMixin, Transform):
function = 'ASIN'
lookup_name = 'asin'
class ATan(OutputFieldMixin, Transform):
function = 'ATAN'
lookup_name = 'atan'
class ATan2(OutputFieldMixin, Func):
function = 'ATAN2'
arity = 2
def as_sqlite(self, compiler, connection):
if not getattr(connection.ops, 'spatialite', False) or connection.ops.spatial_version < (4, 3, 0):
return self.as_sql(compiler, connection)
# This function is usually ATan2(y, x), returning the inverse tangent
# of y / x, but it's ATan2(x, y) on SpatiaLite 4.3+.
# Cast integers to float to avoid inconsistent/buggy behavior if the
# arguments are mixed between integer and float or decimal.
# https://www.gaia-gis.it/fossil/libspatialite/tktview?name=0f72cca3a2
clone = self.copy()
clone.set_source_expressions([
Cast(expression, FloatField()) if isinstance(expression.output_field, IntegerField)
else expression for expression in self.get_source_expressions()[::-1]
])
return clone.as_sql(compiler, connection)
class Ceil(Transform):
function = 'CEILING'
lookup_name = 'ceil'
def as_oracle(self, compiler, connection):
return super().as_sql(compiler, connection, function='CEIL')
class Cos(OutputFieldMixin, Transform):
function = 'COS'
lookup_name = 'cos'
class Cot(OutputFieldMixin, Transform):
function = 'COT'
lookup_name = 'cot'
def as_oracle(self, compiler, connection):
return super().as_sql(compiler, connection, template='(1 / TAN(%(expressions)s))')
class Degrees(OutputFieldMixin, Transform):
function = 'DEGREES'
lookup_name = 'degrees'
def as_oracle(self, compiler, connection):
return super().as_sql(compiler, connection, template='((%%(expressions)s) * 180 / %s)' % math.pi)
class Exp(OutputFieldMixin, Transform):
function = 'EXP'
lookup_name = 'exp'
class Floor(Transform):
function = 'FLOOR'
lookup_name = 'floor'
class Ln(OutputFieldMixin, Transform):
function = 'LN'
lookup_name = 'ln'
class Log(DecimalInputMixin, OutputFieldMixin, Func):
function = 'LOG'
arity = 2
def as_sqlite(self, compiler, connection):
if not getattr(connection.ops, 'spatialite', False):
return self.as_sql(compiler, connection)
# This function is usually Log(b, x) returning the logarithm of x to
# the base b, but on SpatiaLite it's Log(x, b).
clone = self.copy()
clone.set_source_expressions(self.get_source_expressions()[::-1])
return clone.as_sql(compiler, connection)
class Mod(DecimalInputMixin, OutputFieldMixin, Func):
function = 'MOD'
arity = 2
class Pi(OutputFieldMixin, Func):
function = 'PI'
arity = 0
def as_oracle(self, compiler, connection):
return super().as_sql(compiler, connection, template=str(math.pi))
class Power(OutputFieldMixin, Func):
function = 'POWER'
arity = 2
class Radians(OutputFieldMixin, Transform):
function = 'RADIANS'
lookup_name = 'radians'
def as_oracle(self, compiler, connection):
return super().as_sql(compiler, connection, template='((%%(expressions)s) * %s / 180)' % math.pi)
class Round(Transform):
function = 'ROUND'
lookup_name = 'round'
class Sin(OutputFieldMixin, Transform):
function = 'SIN'
lookup_name = 'sin'
class Sqrt(OutputFieldMixin, Transform):
function = 'SQRT'
lookup_name = 'sqrt'
class Tan(OutputFieldMixin, Transform):
function = 'TAN'
lookup_name = 'tan'

View File

@ -673,6 +673,463 @@ that deal with time-parts can be used with ``TimeField``::
2014-06-16 00:00:00+10:00 2
2016-01-01 04:00:00+11:00 1
.. _math-functions:
Math Functions
==============
.. versionadded:: 2.2
We'll be using the following model in math function examples::
class Vector(models.Model):
x = models.FloatField()
y = models.FloatField()
``Abs``
-------
.. class:: Abs(expression, **extra)
Returns the absolute value of a numeric field or expression.
Usage example::
>>> from django.db.models.functions import Abs
>>> Vector.objects.create(x=-0.5, y=1.1)
>>> vector = Vector.objects.annotate(x_abs=Abs('x'), y_abs=Abs('y')).get()
>>> vector.x_abs, vector.y_abs
(0.5, 1.1)
It can also be registered as a transform. For example::
>>> from django.db.models import FloatField
>>> from django.db.models.functions import Abs
>>> FloatField.register_lookup(Abs)
>>> # Get vectors inside the unit cube
>>> vectors = Vector.objects.filter(x__abs__lt=1, y__abs__lt=1)
``ACos``
--------
.. class:: ACos(expression, **extra)
Returns the arccosine of a numeric field or expression. The expression value
must be within the range -1 to 1.
Usage example::
>>> from django.db.models.functions import ACos
>>> Vector.objects.create(x=0.5, y=-0.9)
>>> vector = Vector.objects.annotate(x_acos=ACos('x'), y_acos=ACos('y')).get()
>>> vector.x_acos, vector.y_acos
(1.0471975511965979, 2.6905658417935308)
It can also be registered as a transform. For example::
>>> from django.db.models import FloatField
>>> from django.db.models.functions import ACos
>>> FloatField.register_lookup(ACos)
>>> # Get vectors whose arccosine is less than 1
>>> vectors = Vector.objects.filter(x__acos__lt=1, y__acos__lt=1)
``ASin``
--------
.. class:: ASin(expression, **extra)
Returns the arcsine of a numeric field or expression. The expression value must
be in the range -1 to 1.
Usage example::
>>> from django.db.models.functions import ASin
>>> Vector.objects.create(x=0, y=1)
>>> vector = Vector.objects.annotate(x_asin=ASin('x'), y_asin=ASin('y')).get()
>>> vector.x_asin, vector.y_asin
(0.0, 1.5707963267948966)
It can also be registered as a transform. For example::
>>> from django.db.models import FloatField
>>> from django.db.models.functions import ASin
>>> FloatField.register_lookup(ASin)
>>> # Get vectors whose arcsine is less than 1
>>> vectors = Vector.objects.filter(x__asin__lt=1, y__asin__lt=1)
``ATan``
--------
.. class:: ATan(expression, **extra)
Returns the arctangent of a numeric field or expression.
Usage example::
>>> from django.db.models.functions import ATan
>>> Vector.objects.create(x=3.12, y=6.987)
>>> vector = Vector.objects.annotate(x_atan=ATan('x'), y_atan=ATan('y')).get()
>>> vector.x_atan, vector.y_atan
(1.2606282660069106, 1.428638798133829)
It can also be registered as a transform. For example::
>>> from django.db.models import FloatField
>>> from django.db.models.functions import ATan
>>> FloatField.register_lookup(ATan)
>>> # Get vectors whose arctangent is less than 2
>>> vectors = Vector.objects.filter(x__atan__lt=2, y__atan__lt=2)
``ATan2``
---------
.. class:: ATan2(expression1, expression2, **extra)
Returns the arctangent of ``expression1 / expression2``.
Usage example::
>>> from django.db.models.functions import ATan2
>>> Vector.objects.create(x=2.5, y=1.9)
>>> vector = Vector.objects.annotate(atan2=ATan2('x', 'y')).get()
>>> vector.atan2
0.9209258773829491
``Ceil``
--------
.. class:: Ceil(expression, **extra)
Returns the smallest integer greater than or equal to a numeric field or
expression.
Usage example::
>>> from django.db.models.functions import Ceil
>>> Vector.objects.create(x=3.12, y=7.0)
>>> vector = Vector.objects.annotate(x_ceil=Ceil('x'), y_ceil=Ceil('y')).get()
>>> vector.x_ceil, vector.y_ceil
(4.0, 7.0)
It can also be registered as a transform. For example::
>>> from django.db.models import FloatField
>>> from django.db.models.functions import Ceil
>>> FloatField.register_lookup(Ceil)
>>> # Get vectors whose ceil is less than 10
>>> vectors = Vector.objects.filter(x__ceil__lt=10, y__ceil__lt=10)
``Cos``
-------
.. class:: Cos(expression, **extra)
Returns the cosine of a numeric field or expression.
Usage example::
>>> from django.db.models.functions import Cos
>>> Vector.objects.create(x=-8.0, y=3.1415926)
>>> vector = Vector.objects.annotate(x_cos=Cos('x'), y_cos=Cos('y')).get()
>>> vector.x_cos, vector.y_cos
(-0.14550003380861354, -0.9999999999999986)
It can also be registered as a transform. For example::
>>> from django.db.models import FloatField
>>> from django.db.models.functions import Cos
>>> FloatField.register_lookup(Cos)
>>> # Get vectors whose cosine is less than 0.5
>>> vectors = Vector.objects.filter(x__cos__lt=0.5, y__cos__lt=0.5)
``Cot``
-------
.. class:: Cot(expression, **extra)
Returns the cotangent of a numeric field or expression.
Usage example::
>>> from django.db.models.functions import Cot
>>> Vector.objects.create(x=12.0, y=1.0)
>>> vector = Vector.objects.annotate(x_cot=Cot('x'), y_cot=Cot('y')).get()
>>> vector.x_cot, vector.y_cot
(-1.5726734063976826, 0.642092615934331)
It can also be registered as a transform. For example::
>>> from django.db.models import FloatField
>>> from django.db.models.functions import Cot
>>> FloatField.register_lookup(Cot)
>>> # Get vectors whose cotangent is less than 1
>>> vectors = Vector.objects.filter(x__cot__lt=1, y__cot__lt=1)
``Degrees``
-----------
.. class:: Degrees(expression, **extra)
Converts a numeric field or expression from radians to degrees.
Usage example::
>>> from django.db.models.functions import Degrees
>>> Vector.objects.create(x=-1.57, y=3.14)
>>> vector = Vector.objects.annotate(x_d=Degrees('x'), y_d=Degrees('y')).get()
>>> vector.x_d, vector.y_d
(-89.95437383553924, 179.9087476710785)
It can also be registered as a transform. For example::
>>> from django.db.models import FloatField
>>> from django.db.models.functions import Degrees
>>> FloatField.register_lookup(Degrees)
>>> # Get vectors whose degrees are less than 360
>>> vectors = Vector.objects.filter(x__degrees__lt=360, y__degrees__lt=360)
``Exp``
-------
.. class:: Exp(expression, **extra)
Returns the value of ``e`` (the natural logarithm base) raised to the power of
a numeric field or expression.
Usage example::
>>> from django.db.models.functions import Exp
>>> Vector.objects.create(x=5.4, y=-2.0)
>>> vector = Vector.objects.annotate(x_exp=Exp('x'), y_exp=Exp('y')).get()
>>> vector.x_exp, vector.y_exp
(221.40641620418717, 0.1353352832366127)
It can also be registered as a transform. For example::
>>> from django.db.models import FloatField
>>> from django.db.models.functions import Exp
>>> FloatField.register_lookup(Exp)
>>> # Get vectors whose exp() is greater than 10
>>> vectors = Vector.objects.filter(x__exp__gt=10, y__exp__gt=10)
``Floor``
---------
.. class:: Floor(expression, **extra)
Returns the largest integer value not greater than a numeric field or
expression.
Usage example::
>>> from django.db.models.functions import Floor
>>> Vector.objects.create(x=5.4, y=-2.3)
>>> vector = Vector.objects.annotate(x_floor=Floor('x'), y_floor=Floor('y')).get()
>>> vector.x_floor, vector.y_floor
(5.0, -3.0)
It can also be registered as a transform. For example::
>>> from django.db.models import FloatField
>>> from django.db.models.functions import Floor
>>> FloatField.register_lookup(Floor)
>>> # Get vectors whose floor() is greater than 10
>>> vectors = Vector.objects.filter(x__floor__gt=10, y__floor__gt=10)
``Ln``
------
.. class:: Ln(expression, **extra)
Returns the natural logarithm a numeric field or expression.
Usage example::
>>> from django.db.models.functions import Ln
>>> Vector.objects.create(x=5.4, y=233.0)
>>> vector = Vector.objects.annotate(x_ln=Ln('x'), y_ln=Ln('y')).get()
>>> vector.x_ln, vector.y_ln
(1.6863989535702288, 5.4510384535657)
It can also be registered as a transform. For example::
>>> from django.db.models import FloatField
>>> from django.db.models.functions import Ln
>>> FloatField.register_lookup(Ln)
>>> # Get vectors whose value greater than e
>>> vectors = Vector.objects.filter(x__ln__gt=1, y__ln__gt=1)
``Log``
-------
.. class:: Log(expression1, expression2, **extra)
Accepts two numeric fields or expressions and returns the logarithm of
the first to base of the second.
Usage example::
>>> from django.db.models.functions import Log
>>> Vector.objects.create(x=2.0, y=4.0)
>>> vector = Vector.objects.annotate(log=Log('x', 'y')).get()
>>> vector.log
2.0
``Mod``
-------
.. class:: Mod(expression1, expression2, **extra)
Accepts two numeric fields or expressions and returns the remainder of
the first divided by the second (modulo operation).
Usage example::
>>> from django.db.models.functions import Mod
>>> Vector.objects.create(x=5.4, y=2.3)
>>> vector = Vector.objects.annotate(mod=Mod('x', 'y')).get()
>>> vector.mod
0.8
``Pi``
------
.. class:: Pi(**extra)
Returns the value of the mathematical constant ``π``.
``Power``
---------
.. class:: Power(expression1, expression2, **extra)
Accepts two numeric fields or expressions and returns the value of the first
raised to the power of the second.
Usage example::
>>> from django.db.models.functions import Power
>>> Vector.objects.create(x=2, y=-2)
>>> vector = Vector.objects.annotate(power=Power('x', 'y')).get()
>>> vector.power
0.25
``Radians``
-----------
.. class:: Radians(expression, **extra)
Converts a numeric field or expression from degrees to radians.
Usage example::
>>> from django.db.models.functions import Radians
>>> Vector.objects.create(x=-90, y=180)
>>> vector = Vector.objects.annotate(x_r=Radians('x'), y_r=Radians('y')).get()
>>> vector.x_r, vector.y_r
(-1.5707963267948966, 3.141592653589793)
It can also be registered as a transform. For example::
>>> from django.db.models import FloatField
>>> from django.db.models.functions import Radians
>>> FloatField.register_lookup(Radians)
>>> # Get vectors whose radians are less than 1
>>> vectors = Vector.objects.filter(x__radians__lt=1, y__radians__lt=1)
``Round``
---------
.. class:: Round(expression, **extra)
Rounds a numeric field or expression to the nearest integer. Whether half
values are rounded up or down depends on the database.
Usage example::
>>> from django.db.models.functions import Round
>>> Vector.objects.create(x=5.4, y=-2.3)
>>> vector = Vector.objects.annotate(x_r=Round('x'), y_r=Round('y')).get()
>>> vector.x_r, vector.y_r
(5.0, -2.0)
It can also be registered as a transform. For example::
>>> from django.db.models import FloatField
>>> from django.db.models.functions import Round
>>> FloatField.register_lookup(Round)
>>> # Get vectors whose round() is less than 20
>>> vectors = Vector.objects.filter(x__round__lt=20, y__round__lt=20)
``Sin``
-------
.. class:: Sin(expression, **extra)
Returns the sine of a numeric field or expression.
Usage example::
>>> from django.db.models.functions import Sin
>>> Vector.objects.create(x=5.4, y=-2.3)
>>> vector = Vector.objects.annotate(x_sin=Sin('x'), y_sin=Sin('y')).get()
>>> vector.x_sin, vector.y_sin
(-0.7727644875559871, -0.7457052121767203)
It can also be registered as a transform. For example::
>>> from django.db.models import FloatField
>>> from django.db.models.functions import Sin
>>> FloatField.register_lookup(Sin)
>>> # Get vectors whose sin() is less than 0
>>> vectors = Vector.objects.filter(x__sin__lt=0, y__sin__lt=0)
``Sqrt``
--------
.. class:: Sqrt(expression, **extra)
Returns the square root of a nonnegative numeric field or expression.
Usage example::
>>> from django.db.models.functions import Sqrt
>>> Vector.objects.create(x=4.0, y=12.0)
>>> vector = Vector.objects.annotate(x_sqrt=Sqrt('x'), y_sqrt=Sqrt('y')).get()
>>> vector.x_sqrt, vector.y_sqrt
(2.0, 3.46410)
It can also be registered as a transform. For example::
>>> from django.db.models import FloatField
>>> from django.db.models.functions import Sqrt
>>> FloatField.register_lookup(Sqrt)
>>> # Get vectors whose sqrt() is less than 5
>>> vectors = Vector.objects.filter(x__sqrt__lt=5, y__sqrt__lt=5)
``Tan``
-------
.. class:: Tan(expression, **extra)
Returns the tangent of a numeric field or expression.
Usage example::
>>> from django.db.models.functions import Tan
>>> Vector.objects.create(x=0, y=12)
>>> vector = Vector.objects.annotate(x_tan=Tan('x'), y_tan=Tan('y')).get()
>>> vector.x_tan, vector.y_tan
(0.0, -0.6358599286615808)
It can also be registered as a transform. For example::
>>> from django.db.models import FloatField
>>> from django.db.models.functions import Tan
>>> FloatField.register_lookup(Tan)
>>> # Get vectors whose tangent is less than 0
>>> vectors = Vector.objects.filter(x__tan__lt=0, y__tan__lt=0)
.. _text-functions:
Text functions

View File

@ -166,6 +166,8 @@ Models
* Added support for PostgreSQL operator classes (:attr:`.Index.opclasses`).
* Added many :ref:`math database functions <math-functions>`.
Requests and Responses
~~~~~~~~~~~~~~~~~~~~~~

View File

@ -19,6 +19,7 @@ app
appname
apps
architected
arccosine
arg
args
assistive

View File

View File

@ -0,0 +1,50 @@
from decimal import Decimal
from django.db.models import DecimalField
from django.db.models.functions import Abs
from django.test import TestCase
from ..models import DecimalModel, FloatModel, IntegerModel
class AbsTests(TestCase):
def test_decimal(self):
DecimalModel.objects.create(n1=Decimal('-0.8'), n2=Decimal('1.2'))
obj = DecimalModel.objects.annotate(n1_abs=Abs('n1'), n2_abs=Abs('n2')).first()
self.assertIsInstance(obj.n1_abs, Decimal)
self.assertIsInstance(obj.n2_abs, Decimal)
self.assertEqual(obj.n1, -obj.n1_abs)
self.assertEqual(obj.n2, obj.n2_abs)
def test_float(self):
obj = FloatModel.objects.create(f1=-0.5, f2=12)
obj = FloatModel.objects.annotate(f1_abs=Abs('f1'), f2_abs=Abs('f2')).first()
self.assertIsInstance(obj.f1_abs, float)
self.assertIsInstance(obj.f2_abs, float)
self.assertEqual(obj.f1, -obj.f1_abs)
self.assertEqual(obj.f2, obj.f2_abs)
def test_integer(self):
IntegerModel.objects.create(small=12, normal=0, big=-45)
obj = IntegerModel.objects.annotate(
small_abs=Abs('small'),
normal_abs=Abs('normal'),
big_abs=Abs('big'),
).first()
self.assertIsInstance(obj.small_abs, int)
self.assertIsInstance(obj.normal_abs, int)
self.assertIsInstance(obj.big_abs, int)
self.assertEqual(obj.small, obj.small_abs)
self.assertEqual(obj.normal, obj.normal_abs)
self.assertEqual(obj.big, -obj.big_abs)
def test_transform(self):
try:
DecimalField.register_lookup(Abs)
DecimalModel.objects.create(n1=Decimal('-1.5'), n2=Decimal('0'))
DecimalModel.objects.create(n1=Decimal('-0.5'), n2=Decimal('0'))
objs = DecimalModel.objects.filter(n1__abs__gt=1)
self.assertQuerysetEqual(objs, [Decimal('-1.5')], lambda a: a.n1)
finally:
DecimalField._unregister_lookup(Abs)

View File

@ -0,0 +1,51 @@
import math
from decimal import Decimal
from django.db.models import DecimalField
from django.db.models.functions import ACos
from django.test import TestCase
from ..models import DecimalModel, FloatModel, IntegerModel
class ACosTests(TestCase):
def test_decimal(self):
DecimalModel.objects.create(n1=Decimal('-0.9'), n2=Decimal('0.6'))
obj = DecimalModel.objects.annotate(n1_acos=ACos('n1'), n2_acos=ACos('n2')).first()
self.assertIsInstance(obj.n1_acos, Decimal)
self.assertIsInstance(obj.n2_acos, Decimal)
self.assertAlmostEqual(obj.n1_acos, Decimal(math.acos(obj.n1)))
self.assertAlmostEqual(obj.n2_acos, Decimal(math.acos(obj.n2)))
def test_float(self):
FloatModel.objects.create(f1=-0.5, f2=0.33)
obj = FloatModel.objects.annotate(f1_acos=ACos('f1'), f2_acos=ACos('f2')).first()
self.assertIsInstance(obj.f1_acos, float)
self.assertIsInstance(obj.f2_acos, float)
self.assertAlmostEqual(obj.f1_acos, math.acos(obj.f1))
self.assertAlmostEqual(obj.f2_acos, math.acos(obj.f2))
def test_integer(self):
IntegerModel.objects.create(small=0, normal=1, big=-1)
obj = IntegerModel.objects.annotate(
small_acos=ACos('small'),
normal_acos=ACos('normal'),
big_acos=ACos('big'),
).first()
self.assertIsInstance(obj.small_acos, float)
self.assertIsInstance(obj.normal_acos, float)
self.assertIsInstance(obj.big_acos, float)
self.assertAlmostEqual(obj.small_acos, math.acos(obj.small))
self.assertAlmostEqual(obj.normal_acos, math.acos(obj.normal))
self.assertAlmostEqual(obj.big_acos, math.acos(obj.big))
def test_transform(self):
try:
DecimalField.register_lookup(ACos)
DecimalModel.objects.create(n1=Decimal('0.5'), n2=Decimal('0'))
DecimalModel.objects.create(n1=Decimal('-0.9'), n2=Decimal('0'))
objs = DecimalModel.objects.filter(n1__acos__lt=2)
self.assertQuerysetEqual(objs, [Decimal('0.5')], lambda a: a.n1)
finally:
DecimalField._unregister_lookup(ACos)

View File

@ -0,0 +1,51 @@
import math
from decimal import Decimal
from django.db.models import DecimalField
from django.db.models.functions import ASin
from django.test import TestCase
from ..models import DecimalModel, FloatModel, IntegerModel
class ASinTests(TestCase):
def test_decimal(self):
DecimalModel.objects.create(n1=Decimal('0.9'), n2=Decimal('0.6'))
obj = DecimalModel.objects.annotate(n1_asin=ASin('n1'), n2_asin=ASin('n2')).first()
self.assertIsInstance(obj.n1_asin, Decimal)
self.assertIsInstance(obj.n2_asin, Decimal)
self.assertAlmostEqual(obj.n1_asin, Decimal(math.asin(obj.n1)))
self.assertAlmostEqual(obj.n2_asin, Decimal(math.asin(obj.n2)))
def test_float(self):
FloatModel.objects.create(f1=-0.5, f2=0.87)
obj = FloatModel.objects.annotate(f1_asin=ASin('f1'), f2_asin=ASin('f2')).first()
self.assertIsInstance(obj.f1_asin, float)
self.assertIsInstance(obj.f2_asin, float)
self.assertAlmostEqual(obj.f1_asin, math.asin(obj.f1))
self.assertAlmostEqual(obj.f2_asin, math.asin(obj.f2))
def test_integer(self):
IntegerModel.objects.create(small=0, normal=1, big=-1)
obj = IntegerModel.objects.annotate(
small_asin=ASin('small'),
normal_asin=ASin('normal'),
big_asin=ASin('big'),
).first()
self.assertIsInstance(obj.small_asin, float)
self.assertIsInstance(obj.normal_asin, float)
self.assertIsInstance(obj.big_asin, float)
self.assertAlmostEqual(obj.small_asin, math.asin(obj.small))
self.assertAlmostEqual(obj.normal_asin, math.asin(obj.normal))
self.assertAlmostEqual(obj.big_asin, math.asin(obj.big))
def test_transform(self):
try:
DecimalField.register_lookup(ASin)
DecimalModel.objects.create(n1=Decimal('0.1'), n2=Decimal('0'))
DecimalModel.objects.create(n1=Decimal('1.0'), n2=Decimal('0'))
objs = DecimalModel.objects.filter(n1__asin__gt=1)
self.assertQuerysetEqual(objs, [Decimal('1.0')], lambda a: a.n1)
finally:
DecimalField._unregister_lookup(ASin)

View File

@ -0,0 +1,51 @@
import math
from decimal import Decimal
from django.db.models import DecimalField
from django.db.models.functions import ATan
from django.test import TestCase
from ..models import DecimalModel, FloatModel, IntegerModel
class ATanTests(TestCase):
def test_decimal(self):
DecimalModel.objects.create(n1=Decimal('-12.9'), n2=Decimal('0.6'))
obj = DecimalModel.objects.annotate(n1_atan=ATan('n1'), n2_atan=ATan('n2')).first()
self.assertIsInstance(obj.n1_atan, Decimal)
self.assertIsInstance(obj.n2_atan, Decimal)
self.assertAlmostEqual(obj.n1_atan, Decimal(math.atan(obj.n1)))
self.assertAlmostEqual(obj.n2_atan, Decimal(math.atan(obj.n2)))
def test_float(self):
FloatModel.objects.create(f1=-27.5, f2=0.33)
obj = FloatModel.objects.annotate(f1_atan=ATan('f1'), f2_atan=ATan('f2')).first()
self.assertIsInstance(obj.f1_atan, float)
self.assertIsInstance(obj.f2_atan, float)
self.assertAlmostEqual(obj.f1_atan, math.atan(obj.f1))
self.assertAlmostEqual(obj.f2_atan, math.atan(obj.f2))
def test_integer(self):
IntegerModel.objects.create(small=-20, normal=15, big=-1)
obj = IntegerModel.objects.annotate(
small_atan=ATan('small'),
normal_atan=ATan('normal'),
big_atan=ATan('big'),
).first()
self.assertIsInstance(obj.small_atan, float)
self.assertIsInstance(obj.normal_atan, float)
self.assertIsInstance(obj.big_atan, float)
self.assertAlmostEqual(obj.small_atan, math.atan(obj.small))
self.assertAlmostEqual(obj.normal_atan, math.atan(obj.normal))
self.assertAlmostEqual(obj.big_atan, math.atan(obj.big))
def test_transform(self):
try:
DecimalField.register_lookup(ATan)
DecimalModel.objects.create(n1=Decimal('3.12'), n2=Decimal('0'))
DecimalModel.objects.create(n1=Decimal('-5'), n2=Decimal('0'))
objs = DecimalModel.objects.filter(n1__atan__gt=0)
self.assertQuerysetEqual(objs, [Decimal('3.12')], lambda a: a.n1)
finally:
DecimalField._unregister_lookup(ATan)

View File

@ -0,0 +1,33 @@
import math
from decimal import Decimal
from django.db.models.functions import ATan2
from django.test import TestCase
from ..models import DecimalModel, FloatModel, IntegerModel
class ATan2Tests(TestCase):
def test_decimal(self):
DecimalModel.objects.create(n1=Decimal('-9.9'), n2=Decimal('4.6'))
obj = DecimalModel.objects.annotate(n_atan2=ATan2('n1', 'n2')).first()
self.assertIsInstance(obj.n_atan2, Decimal)
self.assertAlmostEqual(obj.n_atan2, Decimal(math.atan2(obj.n1, obj.n2)))
def test_float(self):
FloatModel.objects.create(f1=-25, f2=0.33)
obj = FloatModel.objects.annotate(f_atan2=ATan2('f1', 'f2')).first()
self.assertIsInstance(obj.f_atan2, float)
self.assertAlmostEqual(obj.f_atan2, math.atan2(obj.f1, obj.f2))
def test_integer(self):
IntegerModel.objects.create(small=0, normal=1, big=10)
obj = IntegerModel.objects.annotate(
atan2_sn=ATan2('small', 'normal'),
atan2_nb=ATan2('normal', 'big'),
).first()
self.assertIsInstance(obj.atan2_sn, float)
self.assertIsInstance(obj.atan2_nb, float)
self.assertAlmostEqual(obj.atan2_sn, math.atan2(obj.small, obj.normal))
self.assertAlmostEqual(obj.atan2_nb, math.atan2(obj.normal, obj.big))

View File

@ -0,0 +1,51 @@
import math
from decimal import Decimal
from django.db.models import DecimalField
from django.db.models.functions import Ceil
from django.test import TestCase
from ..models import DecimalModel, FloatModel, IntegerModel
class CeilTests(TestCase):
def test_decimal(self):
DecimalModel.objects.create(n1=Decimal('12.9'), n2=Decimal('0.6'))
obj = DecimalModel.objects.annotate(n1_ceil=Ceil('n1'), n2_ceil=Ceil('n2')).first()
self.assertIsInstance(obj.n1_ceil, Decimal)
self.assertIsInstance(obj.n2_ceil, Decimal)
self.assertEqual(obj.n1_ceil, Decimal(math.ceil(obj.n1)))
self.assertEqual(obj.n2_ceil, Decimal(math.ceil(obj.n2)))
def test_float(self):
FloatModel.objects.create(f1=-12.5, f2=21.33)
obj = FloatModel.objects.annotate(f1_ceil=Ceil('f1'), f2_ceil=Ceil('f2')).first()
self.assertIsInstance(obj.f1_ceil, float)
self.assertIsInstance(obj.f2_ceil, float)
self.assertEqual(obj.f1_ceil, math.ceil(obj.f1))
self.assertEqual(obj.f2_ceil, math.ceil(obj.f2))
def test_integer(self):
IntegerModel.objects.create(small=-11, normal=0, big=-100)
obj = IntegerModel.objects.annotate(
small_ceil=Ceil('small'),
normal_ceil=Ceil('normal'),
big_ceil=Ceil('big'),
).first()
self.assertIsInstance(obj.small_ceil, int)
self.assertIsInstance(obj.normal_ceil, int)
self.assertIsInstance(obj.big_ceil, int)
self.assertEqual(obj.small_ceil, math.ceil(obj.small))
self.assertEqual(obj.normal_ceil, math.ceil(obj.normal))
self.assertEqual(obj.big_ceil, math.ceil(obj.big))
def test_transform(self):
try:
DecimalField.register_lookup(Ceil)
DecimalModel.objects.create(n1=Decimal('3.12'), n2=Decimal('0'))
DecimalModel.objects.create(n1=Decimal('1.25'), n2=Decimal('0'))
objs = DecimalModel.objects.filter(n1__ceil__gt=3)
self.assertQuerysetEqual(objs, [Decimal('3.12')], lambda a: a.n1)
finally:
DecimalField._unregister_lookup(Ceil)

View File

@ -0,0 +1,51 @@
import math
from decimal import Decimal
from django.db.models import DecimalField
from django.db.models.functions import Cos
from django.test import TestCase
from ..models import DecimalModel, FloatModel, IntegerModel
class CosTests(TestCase):
def test_decimal(self):
DecimalModel.objects.create(n1=Decimal('-12.9'), n2=Decimal('0.6'))
obj = DecimalModel.objects.annotate(n1_cos=Cos('n1'), n2_cos=Cos('n2')).first()
self.assertIsInstance(obj.n1_cos, Decimal)
self.assertIsInstance(obj.n2_cos, Decimal)
self.assertAlmostEqual(obj.n1_cos, Decimal(math.cos(obj.n1)))
self.assertAlmostEqual(obj.n2_cos, Decimal(math.cos(obj.n2)))
def test_float(self):
FloatModel.objects.create(f1=-27.5, f2=0.33)
obj = FloatModel.objects.annotate(f1_cos=Cos('f1'), f2_cos=Cos('f2')).first()
self.assertIsInstance(obj.f1_cos, float)
self.assertIsInstance(obj.f2_cos, float)
self.assertAlmostEqual(obj.f1_cos, math.cos(obj.f1))
self.assertAlmostEqual(obj.f2_cos, math.cos(obj.f2))
def test_integer(self):
IntegerModel.objects.create(small=-20, normal=15, big=-1)
obj = IntegerModel.objects.annotate(
small_cos=Cos('small'),
normal_cos=Cos('normal'),
big_cos=Cos('big'),
).first()
self.assertIsInstance(obj.small_cos, float)
self.assertIsInstance(obj.normal_cos, float)
self.assertIsInstance(obj.big_cos, float)
self.assertAlmostEqual(obj.small_cos, math.cos(obj.small))
self.assertAlmostEqual(obj.normal_cos, math.cos(obj.normal))
self.assertAlmostEqual(obj.big_cos, math.cos(obj.big))
def test_transform(self):
try:
DecimalField.register_lookup(Cos)
DecimalModel.objects.create(n1=Decimal('-8.0'), n2=Decimal('0'))
DecimalModel.objects.create(n1=Decimal('3.14'), n2=Decimal('0'))
objs = DecimalModel.objects.filter(n1__cos__gt=-0.2)
self.assertQuerysetEqual(objs, [Decimal('-8.0')], lambda a: a.n1)
finally:
DecimalField._unregister_lookup(Cos)

View File

@ -0,0 +1,51 @@
import math
from decimal import Decimal
from django.db.models import DecimalField
from django.db.models.functions import Cot
from django.test import TestCase
from ..models import DecimalModel, FloatModel, IntegerModel
class CotTests(TestCase):
def test_decimal(self):
DecimalModel.objects.create(n1=Decimal('-12.9'), n2=Decimal('0.6'))
obj = DecimalModel.objects.annotate(n1_cot=Cot('n1'), n2_cot=Cot('n2')).first()
self.assertIsInstance(obj.n1_cot, Decimal)
self.assertIsInstance(obj.n2_cot, Decimal)
self.assertAlmostEqual(obj.n1_cot, Decimal(1 / math.tan(obj.n1)))
self.assertAlmostEqual(obj.n2_cot, Decimal(1 / math.tan(obj.n2)))
def test_float(self):
FloatModel.objects.create(f1=-27.5, f2=0.33)
obj = FloatModel.objects.annotate(f1_cot=Cot('f1'), f2_cot=Cot('f2')).first()
self.assertIsInstance(obj.f1_cot, float)
self.assertIsInstance(obj.f2_cot, float)
self.assertAlmostEqual(obj.f1_cot, 1 / math.tan(obj.f1))
self.assertAlmostEqual(obj.f2_cot, 1 / math.tan(obj.f2))
def test_integer(self):
IntegerModel.objects.create(small=-5, normal=15, big=-1)
obj = IntegerModel.objects.annotate(
small_cot=Cot('small'),
normal_cot=Cot('normal'),
big_cot=Cot('big'),
).first()
self.assertIsInstance(obj.small_cot, float)
self.assertIsInstance(obj.normal_cot, float)
self.assertIsInstance(obj.big_cot, float)
self.assertAlmostEqual(obj.small_cot, 1 / math.tan(obj.small))
self.assertAlmostEqual(obj.normal_cot, 1 / math.tan(obj.normal))
self.assertAlmostEqual(obj.big_cot, 1 / math.tan(obj.big))
def test_transform(self):
try:
DecimalField.register_lookup(Cot)
DecimalModel.objects.create(n1=Decimal('12.0'), n2=Decimal('0'))
DecimalModel.objects.create(n1=Decimal('1.0'), n2=Decimal('0'))
objs = DecimalModel.objects.filter(n1__cot__gt=0)
self.assertQuerysetEqual(objs, [Decimal('1.0')], lambda a: a.n1)
finally:
DecimalField._unregister_lookup(Cot)

View File

@ -0,0 +1,51 @@
import math
from decimal import Decimal
from django.db.models import DecimalField
from django.db.models.functions import Degrees
from django.test import TestCase
from ..models import DecimalModel, FloatModel, IntegerModel
class DegreesTests(TestCase):
def test_decimal(self):
DecimalModel.objects.create(n1=Decimal('-12.9'), n2=Decimal('0.6'))
obj = DecimalModel.objects.annotate(n1_degrees=Degrees('n1'), n2_degrees=Degrees('n2')).first()
self.assertIsInstance(obj.n1_degrees, Decimal)
self.assertIsInstance(obj.n2_degrees, Decimal)
self.assertAlmostEqual(obj.n1_degrees, Decimal(math.degrees(obj.n1)))
self.assertAlmostEqual(obj.n2_degrees, Decimal(math.degrees(obj.n2)))
def test_float(self):
FloatModel.objects.create(f1=-27.5, f2=0.33)
obj = FloatModel.objects.annotate(f1_degrees=Degrees('f1'), f2_degrees=Degrees('f2')).first()
self.assertIsInstance(obj.f1_degrees, float)
self.assertIsInstance(obj.f2_degrees, float)
self.assertAlmostEqual(obj.f1_degrees, math.degrees(obj.f1))
self.assertAlmostEqual(obj.f2_degrees, math.degrees(obj.f2))
def test_integer(self):
IntegerModel.objects.create(small=-20, normal=15, big=-1)
obj = IntegerModel.objects.annotate(
small_degrees=Degrees('small'),
normal_degrees=Degrees('normal'),
big_degrees=Degrees('big'),
).first()
self.assertIsInstance(obj.small_degrees, float)
self.assertIsInstance(obj.normal_degrees, float)
self.assertIsInstance(obj.big_degrees, float)
self.assertAlmostEqual(obj.small_degrees, math.degrees(obj.small))
self.assertAlmostEqual(obj.normal_degrees, math.degrees(obj.normal))
self.assertAlmostEqual(obj.big_degrees, math.degrees(obj.big))
def test_transform(self):
try:
DecimalField.register_lookup(Degrees)
DecimalModel.objects.create(n1=Decimal('5.4'), n2=Decimal('0'))
DecimalModel.objects.create(n1=Decimal('-30'), n2=Decimal('0'))
objs = DecimalModel.objects.filter(n1__degrees__gt=0)
self.assertQuerysetEqual(objs, [Decimal('5.4')], lambda a: a.n1)
finally:
DecimalField._unregister_lookup(Degrees)

View File

@ -0,0 +1,51 @@
import math
from decimal import Decimal
from django.db.models import DecimalField
from django.db.models.functions import Exp
from django.test import TestCase
from ..models import DecimalModel, FloatModel, IntegerModel
class ExpTests(TestCase):
def test_decimal(self):
DecimalModel.objects.create(n1=Decimal('-12.9'), n2=Decimal('0.6'))
obj = DecimalModel.objects.annotate(n1_exp=Exp('n1'), n2_exp=Exp('n2')).first()
self.assertIsInstance(obj.n1_exp, Decimal)
self.assertIsInstance(obj.n2_exp, Decimal)
self.assertAlmostEqual(obj.n1_exp, Decimal(math.exp(obj.n1)))
self.assertAlmostEqual(obj.n2_exp, Decimal(math.exp(obj.n2)))
def test_float(self):
FloatModel.objects.create(f1=-27.5, f2=0.33)
obj = FloatModel.objects.annotate(f1_exp=Exp('f1'), f2_exp=Exp('f2')).first()
self.assertIsInstance(obj.f1_exp, float)
self.assertIsInstance(obj.f2_exp, float)
self.assertAlmostEqual(obj.f1_exp, math.exp(obj.f1))
self.assertAlmostEqual(obj.f2_exp, math.exp(obj.f2))
def test_integer(self):
IntegerModel.objects.create(small=-20, normal=15, big=-1)
obj = IntegerModel.objects.annotate(
small_exp=Exp('small'),
normal_exp=Exp('normal'),
big_exp=Exp('big'),
).first()
self.assertIsInstance(obj.small_exp, float)
self.assertIsInstance(obj.normal_exp, float)
self.assertIsInstance(obj.big_exp, float)
self.assertAlmostEqual(obj.small_exp, math.exp(obj.small))
self.assertAlmostEqual(obj.normal_exp, math.exp(obj.normal))
self.assertAlmostEqual(obj.big_exp, math.exp(obj.big))
def test_transform(self):
try:
DecimalField.register_lookup(Exp)
DecimalModel.objects.create(n1=Decimal('12.0'), n2=Decimal('0'))
DecimalModel.objects.create(n1=Decimal('-1.0'), n2=Decimal('0'))
objs = DecimalModel.objects.filter(n1__exp__gt=10)
self.assertQuerysetEqual(objs, [Decimal('12.0')], lambda a: a.n1)
finally:
DecimalField._unregister_lookup(Exp)

View File

@ -0,0 +1,51 @@
import math
from decimal import Decimal
from django.db.models import DecimalField
from django.db.models.functions import Floor
from django.test import TestCase
from ..models import DecimalModel, FloatModel, IntegerModel
class FloorTests(TestCase):
def test_decimal(self):
DecimalModel.objects.create(n1=Decimal('-12.9'), n2=Decimal('0.6'))
obj = DecimalModel.objects.annotate(n1_floor=Floor('n1'), n2_floor=Floor('n2')).first()
self.assertIsInstance(obj.n1_floor, Decimal)
self.assertIsInstance(obj.n2_floor, Decimal)
self.assertEqual(obj.n1_floor, Decimal(math.floor(obj.n1)))
self.assertEqual(obj.n2_floor, Decimal(math.floor(obj.n2)))
def test_float(self):
FloatModel.objects.create(f1=-27.5, f2=0.33)
obj = FloatModel.objects.annotate(f1_floor=Floor('f1'), f2_floor=Floor('f2')).first()
self.assertIsInstance(obj.f1_floor, float)
self.assertIsInstance(obj.f2_floor, float)
self.assertEqual(obj.f1_floor, math.floor(obj.f1))
self.assertEqual(obj.f2_floor, math.floor(obj.f2))
def test_integer(self):
IntegerModel.objects.create(small=-20, normal=15, big=-1)
obj = IntegerModel.objects.annotate(
small_floor=Floor('small'),
normal_floor=Floor('normal'),
big_floor=Floor('big'),
).first()
self.assertIsInstance(obj.small_floor, int)
self.assertIsInstance(obj.normal_floor, int)
self.assertIsInstance(obj.big_floor, int)
self.assertEqual(obj.small_floor, math.floor(obj.small))
self.assertEqual(obj.normal_floor, math.floor(obj.normal))
self.assertEqual(obj.big_floor, math.floor(obj.big))
def test_transform(self):
try:
DecimalField.register_lookup(Floor)
DecimalModel.objects.create(n1=Decimal('5.4'), n2=Decimal('0'))
DecimalModel.objects.create(n1=Decimal('3.4'), n2=Decimal('0'))
objs = DecimalModel.objects.filter(n1__floor__gt=4)
self.assertQuerysetEqual(objs, [Decimal('5.4')], lambda a: a.n1)
finally:
DecimalField._unregister_lookup(Floor)

View File

@ -0,0 +1,51 @@
import math
from decimal import Decimal
from django.db.models import DecimalField
from django.db.models.functions import Ln
from django.test import TestCase
from ..models import DecimalModel, FloatModel, IntegerModel
class LnTests(TestCase):
def test_decimal(self):
DecimalModel.objects.create(n1=Decimal('12.9'), n2=Decimal('0.6'))
obj = DecimalModel.objects.annotate(n1_ln=Ln('n1'), n2_ln=Ln('n2')).first()
self.assertIsInstance(obj.n1_ln, Decimal)
self.assertIsInstance(obj.n2_ln, Decimal)
self.assertAlmostEqual(obj.n1_ln, Decimal(math.log(obj.n1)))
self.assertAlmostEqual(obj.n2_ln, Decimal(math.log(obj.n2)))
def test_float(self):
FloatModel.objects.create(f1=27.5, f2=0.33)
obj = FloatModel.objects.annotate(f1_ln=Ln('f1'), f2_ln=Ln('f2')).first()
self.assertIsInstance(obj.f1_ln, float)
self.assertIsInstance(obj.f2_ln, float)
self.assertAlmostEqual(obj.f1_ln, math.log(obj.f1))
self.assertAlmostEqual(obj.f2_ln, math.log(obj.f2))
def test_integer(self):
IntegerModel.objects.create(small=20, normal=15, big=1)
obj = IntegerModel.objects.annotate(
small_ln=Ln('small'),
normal_ln=Ln('normal'),
big_ln=Ln('big'),
).first()
self.assertIsInstance(obj.small_ln, float)
self.assertIsInstance(obj.normal_ln, float)
self.assertIsInstance(obj.big_ln, float)
self.assertAlmostEqual(obj.small_ln, math.log(obj.small))
self.assertAlmostEqual(obj.normal_ln, math.log(obj.normal))
self.assertAlmostEqual(obj.big_ln, math.log(obj.big))
def test_transform(self):
try:
DecimalField.register_lookup(Ln)
DecimalModel.objects.create(n1=Decimal('12.0'), n2=Decimal('0'))
DecimalModel.objects.create(n1=Decimal('1.0'), n2=Decimal('0'))
objs = DecimalModel.objects.filter(n1__ln__gt=0)
self.assertQuerysetEqual(objs, [Decimal('12.0')], lambda a: a.n1)
finally:
DecimalField._unregister_lookup(Ln)

View File

@ -0,0 +1,36 @@
import math
from decimal import Decimal
from django.db.models.functions import Log
from django.test import TestCase
from ..models import DecimalModel, FloatModel, IntegerModel
class LogTests(TestCase):
def test_decimal(self):
DecimalModel.objects.create(n1=Decimal('12.9'), n2=Decimal('3.6'))
obj = DecimalModel.objects.annotate(n_log=Log('n1', 'n2')).first()
self.assertIsInstance(obj.n_log, Decimal)
self.assertAlmostEqual(obj.n_log, Decimal(math.log(obj.n2, obj.n1)))
def test_float(self):
FloatModel.objects.create(f1=2.0, f2=4.0)
obj = FloatModel.objects.annotate(f_log=Log('f1', 'f2')).first()
self.assertIsInstance(obj.f_log, float)
self.assertAlmostEqual(obj.f_log, math.log(obj.f2, obj.f1))
def test_integer(self):
IntegerModel.objects.create(small=4, normal=8, big=2)
obj = IntegerModel.objects.annotate(
small_log=Log('small', 'big'),
normal_log=Log('normal', 'big'),
big_log=Log('big', 'big'),
).first()
self.assertIsInstance(obj.small_log, float)
self.assertIsInstance(obj.normal_log, float)
self.assertIsInstance(obj.big_log, float)
self.assertAlmostEqual(obj.small_log, math.log(obj.big, obj.small))
self.assertAlmostEqual(obj.normal_log, math.log(obj.big, obj.normal))
self.assertAlmostEqual(obj.big_log, math.log(obj.big, obj.big))

View File

@ -0,0 +1,36 @@
import math
from decimal import Decimal
from django.db.models.functions import Mod
from django.test import TestCase
from ..models import DecimalModel, FloatModel, IntegerModel
class ModTests(TestCase):
def test_decimal(self):
DecimalModel.objects.create(n1=Decimal('-9.9'), n2=Decimal('4.6'))
obj = DecimalModel.objects.annotate(n_mod=Mod('n1', 'n2')).first()
self.assertIsInstance(obj.n_mod, Decimal)
self.assertAlmostEqual(obj.n_mod, Decimal(math.fmod(obj.n1, obj.n2)))
def test_float(self):
FloatModel.objects.create(f1=-25, f2=0.33)
obj = FloatModel.objects.annotate(f_mod=Mod('f1', 'f2')).first()
self.assertIsInstance(obj.f_mod, float)
self.assertAlmostEqual(obj.f_mod, math.fmod(obj.f1, obj.f2))
def test_integer(self):
IntegerModel.objects.create(small=20, normal=15, big=1)
obj = IntegerModel.objects.annotate(
small_mod=Mod('small', 'normal'),
normal_mod=Mod('normal', 'big'),
big_mod=Mod('big', 'small'),
).first()
self.assertIsInstance(obj.small_mod, float)
self.assertIsInstance(obj.normal_mod, float)
self.assertIsInstance(obj.big_mod, float)
self.assertEqual(obj.small_mod, math.fmod(obj.small, obj.normal))
self.assertEqual(obj.normal_mod, math.fmod(obj.normal, obj.big))
self.assertEqual(obj.big_mod, math.fmod(obj.big, obj.small))

View File

@ -0,0 +1,15 @@
import math
from django.db.models.functions import Pi
from django.test import TestCase
from ..models import FloatModel
class PiTests(TestCase):
def test(self):
FloatModel.objects.create(f1=2.5, f2=15.9)
obj = FloatModel.objects.annotate(pi=Pi()).first()
self.assertIsInstance(obj.pi, float)
self.assertAlmostEqual(obj.pi, math.pi, places=5)

View File

@ -0,0 +1,35 @@
from decimal import Decimal
from django.db.models.functions import Power
from django.test import TestCase
from ..models import DecimalModel, FloatModel, IntegerModel
class PowerTests(TestCase):
def test_decimal(self):
DecimalModel.objects.create(n1=Decimal('1.0'), n2=Decimal('-0.6'))
obj = DecimalModel.objects.annotate(n_power=Power('n1', 'n2')).first()
self.assertIsInstance(obj.n_power, Decimal)
self.assertAlmostEqual(obj.n_power, Decimal(obj.n1 ** obj.n2))
def test_float(self):
FloatModel.objects.create(f1=2.3, f2=1.1)
obj = FloatModel.objects.annotate(f_power=Power('f1', 'f2')).first()
self.assertIsInstance(obj.f_power, float)
self.assertAlmostEqual(obj.f_power, obj.f1 ** obj.f2)
def test_integer(self):
IntegerModel.objects.create(small=-1, normal=20, big=3)
obj = IntegerModel.objects.annotate(
small_power=Power('small', 'normal'),
normal_power=Power('normal', 'big'),
big_power=Power('big', 'small'),
).first()
self.assertIsInstance(obj.small_power, float)
self.assertIsInstance(obj.normal_power, float)
self.assertIsInstance(obj.big_power, float)
self.assertAlmostEqual(obj.small_power, obj.small ** obj.normal)
self.assertAlmostEqual(obj.normal_power, obj.normal ** obj.big)
self.assertAlmostEqual(obj.big_power, obj.big ** obj.small)

View File

@ -0,0 +1,51 @@
import math
from decimal import Decimal
from django.db.models import DecimalField
from django.db.models.functions import Radians
from django.test import TestCase
from ..models import DecimalModel, FloatModel, IntegerModel
class RadiansTests(TestCase):
def test_decimal(self):
DecimalModel.objects.create(n1=Decimal('-12.9'), n2=Decimal('0.6'))
obj = DecimalModel.objects.annotate(n1_radians=Radians('n1'), n2_radians=Radians('n2')).first()
self.assertIsInstance(obj.n1_radians, Decimal)
self.assertIsInstance(obj.n2_radians, Decimal)
self.assertAlmostEqual(obj.n1_radians, Decimal(math.radians(obj.n1)))
self.assertAlmostEqual(obj.n2_radians, Decimal(math.radians(obj.n2)))
def test_float(self):
FloatModel.objects.create(f1=-27.5, f2=0.33)
obj = FloatModel.objects.annotate(f1_radians=Radians('f1'), f2_radians=Radians('f2')).first()
self.assertIsInstance(obj.f1_radians, float)
self.assertIsInstance(obj.f2_radians, float)
self.assertAlmostEqual(obj.f1_radians, math.radians(obj.f1))
self.assertAlmostEqual(obj.f2_radians, math.radians(obj.f2))
def test_integer(self):
IntegerModel.objects.create(small=-20, normal=15, big=-1)
obj = IntegerModel.objects.annotate(
small_radians=Radians('small'),
normal_radians=Radians('normal'),
big_radians=Radians('big'),
).first()
self.assertIsInstance(obj.small_radians, float)
self.assertIsInstance(obj.normal_radians, float)
self.assertIsInstance(obj.big_radians, float)
self.assertAlmostEqual(obj.small_radians, math.radians(obj.small))
self.assertAlmostEqual(obj.normal_radians, math.radians(obj.normal))
self.assertAlmostEqual(obj.big_radians, math.radians(obj.big))
def test_transform(self):
try:
DecimalField.register_lookup(Radians)
DecimalModel.objects.create(n1=Decimal('2.0'), n2=Decimal('0'))
DecimalModel.objects.create(n1=Decimal('-1.0'), n2=Decimal('0'))
objs = DecimalModel.objects.filter(n1__radians__gt=0)
self.assertQuerysetEqual(objs, [Decimal('2.0')], lambda a: a.n1)
finally:
DecimalField._unregister_lookup(Radians)

View File

@ -0,0 +1,50 @@
from decimal import Decimal
from django.db.models import DecimalField
from django.db.models.functions import Round
from django.test import TestCase
from ..models import DecimalModel, FloatModel, IntegerModel
class RoundTests(TestCase):
def test_decimal(self):
DecimalModel.objects.create(n1=Decimal('-12.9'), n2=Decimal('0.6'))
obj = DecimalModel.objects.annotate(n1_round=Round('n1'), n2_round=Round('n2')).first()
self.assertIsInstance(obj.n1_round, Decimal)
self.assertIsInstance(obj.n2_round, Decimal)
self.assertAlmostEqual(obj.n1_round, round(obj.n1))
self.assertAlmostEqual(obj.n2_round, round(obj.n2))
def test_float(self):
FloatModel.objects.create(f1=-27.55, f2=0.55)
obj = FloatModel.objects.annotate(f1_round=Round('f1'), f2_round=Round('f2')).first()
self.assertIsInstance(obj.f1_round, float)
self.assertIsInstance(obj.f2_round, float)
self.assertAlmostEqual(obj.f1_round, round(obj.f1))
self.assertAlmostEqual(obj.f2_round, round(obj.f2))
def test_integer(self):
IntegerModel.objects.create(small=-20, normal=15, big=-1)
obj = IntegerModel.objects.annotate(
small_round=Round('small'),
normal_round=Round('normal'),
big_round=Round('big'),
).first()
self.assertIsInstance(obj.small_round, int)
self.assertIsInstance(obj.normal_round, int)
self.assertIsInstance(obj.big_round, int)
self.assertEqual(obj.small_round, round(obj.small))
self.assertEqual(obj.normal_round, round(obj.normal))
self.assertEqual(obj.big_round, round(obj.big))
def test_transform(self):
try:
DecimalField.register_lookup(Round)
DecimalModel.objects.create(n1=Decimal('2.0'), n2=Decimal('0'))
DecimalModel.objects.create(n1=Decimal('-1.0'), n2=Decimal('0'))
objs = DecimalModel.objects.filter(n1__round__gt=0)
self.assertQuerysetEqual(objs, [Decimal('2.0')], lambda a: a.n1)
finally:
DecimalField._unregister_lookup(Round)

View File

@ -0,0 +1,51 @@
import math
from decimal import Decimal
from django.db.models import DecimalField
from django.db.models.functions import Sin
from django.test import TestCase
from ..models import DecimalModel, FloatModel, IntegerModel
class SinTests(TestCase):
def test_decimal(self):
DecimalModel.objects.create(n1=Decimal('-12.9'), n2=Decimal('0.6'))
obj = DecimalModel.objects.annotate(n1_sin=Sin('n1'), n2_sin=Sin('n2')).first()
self.assertIsInstance(obj.n1_sin, Decimal)
self.assertIsInstance(obj.n2_sin, Decimal)
self.assertAlmostEqual(obj.n1_sin, Decimal(math.sin(obj.n1)))
self.assertAlmostEqual(obj.n2_sin, Decimal(math.sin(obj.n2)))
def test_float(self):
FloatModel.objects.create(f1=-27.5, f2=0.33)
obj = FloatModel.objects.annotate(f1_sin=Sin('f1'), f2_sin=Sin('f2')).first()
self.assertIsInstance(obj.f1_sin, float)
self.assertIsInstance(obj.f2_sin, float)
self.assertAlmostEqual(obj.f1_sin, math.sin(obj.f1))
self.assertAlmostEqual(obj.f2_sin, math.sin(obj.f2))
def test_integer(self):
IntegerModel.objects.create(small=-20, normal=15, big=-1)
obj = IntegerModel.objects.annotate(
small_sin=Sin('small'),
normal_sin=Sin('normal'),
big_sin=Sin('big'),
).first()
self.assertIsInstance(obj.small_sin, float)
self.assertIsInstance(obj.normal_sin, float)
self.assertIsInstance(obj.big_sin, float)
self.assertAlmostEqual(obj.small_sin, math.sin(obj.small))
self.assertAlmostEqual(obj.normal_sin, math.sin(obj.normal))
self.assertAlmostEqual(obj.big_sin, math.sin(obj.big))
def test_transform(self):
try:
DecimalField.register_lookup(Sin)
DecimalModel.objects.create(n1=Decimal('5.4'), n2=Decimal('0'))
DecimalModel.objects.create(n1=Decimal('0.1'), n2=Decimal('0'))
objs = DecimalModel.objects.filter(n1__sin__lt=0)
self.assertQuerysetEqual(objs, [Decimal('5.4')], lambda a: a.n1)
finally:
DecimalField._unregister_lookup(Sin)

View File

@ -0,0 +1,51 @@
import math
from decimal import Decimal
from django.db.models import DecimalField
from django.db.models.functions import Sqrt
from django.test import TestCase
from ..models import DecimalModel, FloatModel, IntegerModel
class SqrtTests(TestCase):
def test_decimal(self):
DecimalModel.objects.create(n1=Decimal('12.9'), n2=Decimal('0.6'))
obj = DecimalModel.objects.annotate(n1_sqrt=Sqrt('n1'), n2_sqrt=Sqrt('n2')).first()
self.assertIsInstance(obj.n1_sqrt, Decimal)
self.assertIsInstance(obj.n2_sqrt, Decimal)
self.assertAlmostEqual(obj.n1_sqrt, Decimal(math.sqrt(obj.n1)))
self.assertAlmostEqual(obj.n2_sqrt, Decimal(math.sqrt(obj.n2)))
def test_float(self):
FloatModel.objects.create(f1=27.5, f2=0.33)
obj = FloatModel.objects.annotate(f1_sqrt=Sqrt('f1'), f2_sqrt=Sqrt('f2')).first()
self.assertIsInstance(obj.f1_sqrt, float)
self.assertIsInstance(obj.f2_sqrt, float)
self.assertAlmostEqual(obj.f1_sqrt, math.sqrt(obj.f1))
self.assertAlmostEqual(obj.f2_sqrt, math.sqrt(obj.f2))
def test_integer(self):
IntegerModel.objects.create(small=20, normal=15, big=1)
obj = IntegerModel.objects.annotate(
small_sqrt=Sqrt('small'),
normal_sqrt=Sqrt('normal'),
big_sqrt=Sqrt('big'),
).first()
self.assertIsInstance(obj.small_sqrt, float)
self.assertIsInstance(obj.normal_sqrt, float)
self.assertIsInstance(obj.big_sqrt, float)
self.assertAlmostEqual(obj.small_sqrt, math.sqrt(obj.small))
self.assertAlmostEqual(obj.normal_sqrt, math.sqrt(obj.normal))
self.assertAlmostEqual(obj.big_sqrt, math.sqrt(obj.big))
def test_transform(self):
try:
DecimalField.register_lookup(Sqrt)
DecimalModel.objects.create(n1=Decimal('6.0'), n2=Decimal('0'))
DecimalModel.objects.create(n1=Decimal('1.0'), n2=Decimal('0'))
objs = DecimalModel.objects.filter(n1__sqrt__gt=2)
self.assertQuerysetEqual(objs, [Decimal('6.0')], lambda a: a.n1)
finally:
DecimalField._unregister_lookup(Sqrt)

View File

@ -0,0 +1,51 @@
import math
from decimal import Decimal
from django.db.models import DecimalField
from django.db.models.functions import Tan
from django.test import TestCase
from ..models import DecimalModel, FloatModel, IntegerModel
class TanTests(TestCase):
def test_decimal(self):
DecimalModel.objects.create(n1=Decimal('-12.9'), n2=Decimal('0.6'))
obj = DecimalModel.objects.annotate(n1_tan=Tan('n1'), n2_tan=Tan('n2')).first()
self.assertIsInstance(obj.n1_tan, Decimal)
self.assertIsInstance(obj.n2_tan, Decimal)
self.assertAlmostEqual(obj.n1_tan, Decimal(math.tan(obj.n1)))
self.assertAlmostEqual(obj.n2_tan, Decimal(math.tan(obj.n2)))
def test_float(self):
FloatModel.objects.create(f1=-27.5, f2=0.33)
obj = FloatModel.objects.annotate(f1_tan=Tan('f1'), f2_tan=Tan('f2')).first()
self.assertIsInstance(obj.f1_tan, float)
self.assertIsInstance(obj.f2_tan, float)
self.assertAlmostEqual(obj.f1_tan, math.tan(obj.f1))
self.assertAlmostEqual(obj.f2_tan, math.tan(obj.f2))
def test_integer(self):
IntegerModel.objects.create(small=-20, normal=15, big=-1)
obj = IntegerModel.objects.annotate(
small_tan=Tan('small'),
normal_tan=Tan('normal'),
big_tan=Tan('big'),
).first()
self.assertIsInstance(obj.small_tan, float)
self.assertIsInstance(obj.normal_tan, float)
self.assertIsInstance(obj.big_tan, float)
self.assertAlmostEqual(obj.small_tan, math.tan(obj.small))
self.assertAlmostEqual(obj.normal_tan, math.tan(obj.normal))
self.assertAlmostEqual(obj.big_tan, math.tan(obj.big))
def test_transform(self):
try:
DecimalField.register_lookup(Tan)
DecimalModel.objects.create(n1=Decimal('0.0'), n2=Decimal('0'))
DecimalModel.objects.create(n1=Decimal('12.0'), n2=Decimal('0'))
objs = DecimalModel.objects.filter(n1__tan__lt=0)
self.assertQuerysetEqual(objs, [Decimal('12.0')], lambda a: a.n1)
finally:
DecimalField._unregister_lookup(Tan)

View File

@ -54,3 +54,14 @@ class DTModel(models.Model):
class DecimalModel(models.Model):
n1 = models.DecimalField(decimal_places=2, max_digits=6)
n2 = models.DecimalField(decimal_places=2, max_digits=6)
class IntegerModel(models.Model):
big = models.BigIntegerField(null=True, blank=True)
normal = models.IntegerField(null=True, blank=True)
small = models.SmallIntegerField(null=True, blank=True)
class FloatModel(models.Model):
f1 = models.FloatField(null=True, blank=True)
f2 = models.FloatField(null=True, blank=True)