1
0
mirror of https://github.com/django/django.git synced 2025-06-05 03:29:12 +00:00

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("django_timestamp_diff", 2, _sqlite_timestamp_diff)
conn.create_function("regexp", 2, _sqlite_regexp) conn.create_function("regexp", 2, _sqlite_regexp)
conn.create_function("django_format_dtdelta", 3, _sqlite_format_dtdelta) 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('LPAD', 3, _sqlite_lpad)
conn.create_function('REPEAT', 2, operator.mul) conn.create_function('REPEAT', 2, operator.mul)
conn.create_function('RPAD', 3, _sqlite_rpad) 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') conn.execute('PRAGMA foreign_keys = ON')
return conn return conn
@ -483,7 +501,3 @@ def _sqlite_lpad(text, length, fill_text):
def _sqlite_rpad(text, length, fill_text): def _sqlite_rpad(text, length, fill_text):
return (text + fill_text * length)[:length] 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): def combine_expression(self, connector, sub_expressions):
# SQLite doesn't have a power function, so we fake it with a # SQLite doesn't have a ^ operator, so use the user-defined POWER
# user-defined function django_power that's registered in connect(). # function that's registered in connect().
if connector == '^': if connector == '^':
return 'django_power(%s)' % ','.join(sub_expressions) return 'POWER(%s)' % ','.join(sub_expressions)
return super().combine_expression(connector, sub_expressions) return super().combine_expression(connector, sub_expressions)
def combine_duration_expression(self, 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, Now, Trunc, TruncDate, TruncDay, TruncHour, TruncMinute, TruncMonth,
TruncQuarter, TruncSecond, TruncTime, TruncWeek, TruncYear, 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 ( from .text import (
Chr, Concat, ConcatPair, Left, Length, Lower, LPad, LTrim, Ord, Repeat, Chr, Concat, ConcatPair, Left, Length, Lower, LPad, LTrim, Ord, Repeat,
Replace, Right, RPad, RTrim, StrIndex, Substr, Trim, Upper, Replace, Right, RPad, RTrim, StrIndex, Substr, Trim, Upper,
@ -23,6 +27,10 @@ __all__ = [
'ExtractYear', 'Now', 'Trunc', 'TruncDate', 'TruncDay', 'TruncHour', 'ExtractYear', 'Now', 'Trunc', 'TruncDate', 'TruncDay', 'TruncHour',
'TruncMinute', 'TruncMonth', 'TruncQuarter', 'TruncSecond', 'TruncTime', 'TruncMinute', 'TruncMonth', 'TruncQuarter', 'TruncSecond', 'TruncTime',
'TruncWeek', 'TruncYear', '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 # text
'Chr', 'Concat', 'ConcatPair', 'Left', 'Length', 'Lower', 'LPad', 'LTrim', 'Chr', 'Concat', 'ConcatPair', 'Left', 'Length', 'Lower', 'LPad', 'LTrim',
'Ord', 'Repeat', 'Replace', 'Right', 'RPad', 'RTrim', 'StrIndex', 'Substr', '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 2014-06-16 00:00:00+10:00 2
2016-01-01 04:00:00+11:00 1 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:
Text functions Text functions

View File

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

View File

@ -19,6 +19,7 @@ app
appname appname
apps apps
architected architected
arccosine
arg arg
args args
assistive 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): class DecimalModel(models.Model):
n1 = models.DecimalField(decimal_places=2, max_digits=6) n1 = models.DecimalField(decimal_places=2, max_digits=6)
n2 = 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)