diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index 7951143fd09..20905c83b85 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -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 diff --git a/django/db/backends/sqlite3/operations.py b/django/db/backends/sqlite3/operations.py index ee197d34b4c..0ad95eb479c 100644 --- a/django/db/backends/sqlite3/operations.py +++ b/django/db/backends/sqlite3/operations.py @@ -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): diff --git a/django/db/models/functions/__init__.py b/django/db/models/functions/__init__.py index 7515cc67d90..5f5a8bd1dc5 100644 --- a/django/db/models/functions/__init__.py +++ b/django/db/models/functions/__init__.py @@ -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', diff --git a/django/db/models/functions/math.py b/django/db/models/functions/math.py new file mode 100644 index 00000000000..6341e101a6a --- /dev/null +++ b/django/db/models/functions/math.py @@ -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' diff --git a/docs/ref/models/database-functions.txt b/docs/ref/models/database-functions.txt index d39ed5001b2..be576914805 100644 --- a/docs/ref/models/database-functions.txt +++ b/docs/ref/models/database-functions.txt @@ -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 diff --git a/docs/releases/2.2.txt b/docs/releases/2.2.txt index a68f3135e4d..86d9ee46c0d 100644 --- a/docs/releases/2.2.txt +++ b/docs/releases/2.2.txt @@ -166,6 +166,8 @@ Models * Added support for PostgreSQL operator classes (:attr:`.Index.opclasses`). +* Added many :ref:`math database functions `. + Requests and Responses ~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/spelling_wordlist b/docs/spelling_wordlist index e02548cfd7c..8dae9099316 100644 --- a/docs/spelling_wordlist +++ b/docs/spelling_wordlist @@ -19,6 +19,7 @@ app appname apps architected +arccosine arg args assistive diff --git a/tests/db_functions/math/__init__.py b/tests/db_functions/math/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/db_functions/math/test_abs.py b/tests/db_functions/math/test_abs.py new file mode 100644 index 00000000000..99f2a336f71 --- /dev/null +++ b/tests/db_functions/math/test_abs.py @@ -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) diff --git a/tests/db_functions/math/test_acos.py b/tests/db_functions/math/test_acos.py new file mode 100644 index 00000000000..041412123b5 --- /dev/null +++ b/tests/db_functions/math/test_acos.py @@ -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) diff --git a/tests/db_functions/math/test_asin.py b/tests/db_functions/math/test_asin.py new file mode 100644 index 00000000000..55724498342 --- /dev/null +++ b/tests/db_functions/math/test_asin.py @@ -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) diff --git a/tests/db_functions/math/test_atan.py b/tests/db_functions/math/test_atan.py new file mode 100644 index 00000000000..62af5d15871 --- /dev/null +++ b/tests/db_functions/math/test_atan.py @@ -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) diff --git a/tests/db_functions/math/test_atan2.py b/tests/db_functions/math/test_atan2.py new file mode 100644 index 00000000000..195892dfdd3 --- /dev/null +++ b/tests/db_functions/math/test_atan2.py @@ -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)) diff --git a/tests/db_functions/math/test_ceil.py b/tests/db_functions/math/test_ceil.py new file mode 100644 index 00000000000..93e9713eb28 --- /dev/null +++ b/tests/db_functions/math/test_ceil.py @@ -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) diff --git a/tests/db_functions/math/test_cos.py b/tests/db_functions/math/test_cos.py new file mode 100644 index 00000000000..4d6a16f5fcb --- /dev/null +++ b/tests/db_functions/math/test_cos.py @@ -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) diff --git a/tests/db_functions/math/test_cot.py b/tests/db_functions/math/test_cot.py new file mode 100644 index 00000000000..a1a13c9ee52 --- /dev/null +++ b/tests/db_functions/math/test_cot.py @@ -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) diff --git a/tests/db_functions/math/test_degrees.py b/tests/db_functions/math/test_degrees.py new file mode 100644 index 00000000000..d3c70fcbee7 --- /dev/null +++ b/tests/db_functions/math/test_degrees.py @@ -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) diff --git a/tests/db_functions/math/test_exp.py b/tests/db_functions/math/test_exp.py new file mode 100644 index 00000000000..c44f7adefe3 --- /dev/null +++ b/tests/db_functions/math/test_exp.py @@ -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) diff --git a/tests/db_functions/math/test_floor.py b/tests/db_functions/math/test_floor.py new file mode 100644 index 00000000000..8404184c3ed --- /dev/null +++ b/tests/db_functions/math/test_floor.py @@ -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) diff --git a/tests/db_functions/math/test_ln.py b/tests/db_functions/math/test_ln.py new file mode 100644 index 00000000000..f30cf8e84ef --- /dev/null +++ b/tests/db_functions/math/test_ln.py @@ -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) diff --git a/tests/db_functions/math/test_log.py b/tests/db_functions/math/test_log.py new file mode 100644 index 00000000000..02cbe084d3c --- /dev/null +++ b/tests/db_functions/math/test_log.py @@ -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)) diff --git a/tests/db_functions/math/test_mod.py b/tests/db_functions/math/test_mod.py new file mode 100644 index 00000000000..0e90175ddc1 --- /dev/null +++ b/tests/db_functions/math/test_mod.py @@ -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)) diff --git a/tests/db_functions/math/test_pi.py b/tests/db_functions/math/test_pi.py new file mode 100644 index 00000000000..2446420fd33 --- /dev/null +++ b/tests/db_functions/math/test_pi.py @@ -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) diff --git a/tests/db_functions/math/test_power.py b/tests/db_functions/math/test_power.py new file mode 100644 index 00000000000..01ca2b34d9a --- /dev/null +++ b/tests/db_functions/math/test_power.py @@ -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) diff --git a/tests/db_functions/math/test_radians.py b/tests/db_functions/math/test_radians.py new file mode 100644 index 00000000000..296c21e692f --- /dev/null +++ b/tests/db_functions/math/test_radians.py @@ -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) diff --git a/tests/db_functions/math/test_round.py b/tests/db_functions/math/test_round.py new file mode 100644 index 00000000000..9ad390bd241 --- /dev/null +++ b/tests/db_functions/math/test_round.py @@ -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) diff --git a/tests/db_functions/math/test_sin.py b/tests/db_functions/math/test_sin.py new file mode 100644 index 00000000000..efcf3319f07 --- /dev/null +++ b/tests/db_functions/math/test_sin.py @@ -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) diff --git a/tests/db_functions/math/test_sqrt.py b/tests/db_functions/math/test_sqrt.py new file mode 100644 index 00000000000..c391a6f5a9c --- /dev/null +++ b/tests/db_functions/math/test_sqrt.py @@ -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) diff --git a/tests/db_functions/math/test_tan.py b/tests/db_functions/math/test_tan.py new file mode 100644 index 00000000000..1df294150f5 --- /dev/null +++ b/tests/db_functions/math/test_tan.py @@ -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) diff --git a/tests/db_functions/models.py b/tests/db_functions/models.py index 24ffba78fc0..c31de39b85e 100644 --- a/tests/db_functions/models.py +++ b/tests/db_functions/models.py @@ -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)