From 0a26121797b5e0aa07ada07fe1577a79d3853c40 Mon Sep 17 00:00:00 2001 From: Sergey Fedoseev Date: Sat, 31 Oct 2015 15:01:08 +0500 Subject: [PATCH] Refs #25629 -- Added `arity` class attribute to `Func` expressions --- django/db/models/expressions.py | 10 ++++++++++ django/db/models/functions.py | 6 ------ django/db/models/lookups.py | 5 +---- docs/ref/models/expressions.txt | 9 +++++++++ docs/releases/1.10.txt | 4 ++++ tests/db_functions/tests.py | 3 +++ 6 files changed, 27 insertions(+), 10 deletions(-) diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index d9540055b9..1497681d57 100644 --- a/django/db/models/expressions.py +++ b/django/db/models/expressions.py @@ -482,8 +482,18 @@ class Func(Expression): function = None template = '%(function)s(%(expressions)s)' arg_joiner = ', ' + arity = None # The number of arguments the function accepts. def __init__(self, *expressions, **extra): + if self.arity is not None and len(expressions) != self.arity: + raise TypeError( + "'%s' takes exactly %s %s (%s given)" % ( + self.__class__.__name__, + self.arity, + "argument" if self.arity == 1 else "arguments", + len(expressions), + ) + ) output_field = extra.pop('output_field', None) super(Func, self).__init__(output_field=output_field) self.source_expressions = self._parse_expressions(*expressions) diff --git a/django/db/models/functions.py b/django/db/models/functions.py index ffa3e36084..4cbcc9c899 100644 --- a/django/db/models/functions.py +++ b/django/db/models/functions.py @@ -145,9 +145,6 @@ class Lower(Transform): function = 'LOWER' lookup_name = 'lower' - def __init__(self, expression, **extra): - super(Lower, self).__init__(expression, **extra) - class Now(Func): template = 'CURRENT_TIMESTAMP' @@ -197,6 +194,3 @@ class Substr(Func): class Upper(Transform): function = 'UPPER' lookup_name = 'upper' - - def __init__(self, expression, **extra): - super(Upper, self).__init__(expression, **extra) diff --git a/django/db/models/lookups.py b/django/db/models/lookups.py index b50711ada4..067722b6bb 100644 --- a/django/db/models/lookups.py +++ b/django/db/models/lookups.py @@ -123,10 +123,7 @@ class Transform(RegisterLookupMixin, Func): first examine self and then check output_field. """ bilateral = False - - def __init__(self, expression, **extra): - # Restrict Transform to allow only a single expression. - super(Transform, self).__init__(expression, **extra) + arity = 1 @property def lhs(self): diff --git a/docs/ref/models/expressions.txt b/docs/ref/models/expressions.txt index 87c3108509..98ba323171 100644 --- a/docs/ref/models/expressions.txt +++ b/docs/ref/models/expressions.txt @@ -252,6 +252,15 @@ The ``Func`` API is as follows: A class attribute that denotes the character used to join the list of ``expressions`` together. Defaults to ``', '``. + .. attribute:: arity + + .. versionadded:: 1.10 + + A class attribute that denotes the number of arguments the function + accepts. If this attribute is set and the function is called with a + different number of expressions, ``TypeError`` will be raised. Defaults + to ``None``. + The ``*expressions`` argument is a list of positional expressions that the function will be applied to. The expressions will be converted to strings, joined together with ``arg_joiner``, and then interpolated into the ``template`` diff --git a/docs/releases/1.10.txt b/docs/releases/1.10.txt index 8846dce242..20afbd93b8 100644 --- a/docs/releases/1.10.txt +++ b/docs/releases/1.10.txt @@ -175,6 +175,10 @@ Models accessible as a descriptor on the proxied model class and may be referenced in queryset filtering. +* The :attr:`~django.db.models.Func.arity` class attribute is added to + :class:`~django.db.models.Func`. This attribute can be used to set the number + of arguments the function accepts. + Requests and Responses ^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/db_functions/tests.py b/tests/db_functions/tests.py index c18a4b25af..491ce6cefe 100644 --- a/tests/db_functions/tests.py +++ b/tests/db_functions/tests.py @@ -389,6 +389,9 @@ class FunctionTests(TestCase): lambda a: (a.lower_name, a.name) ) + with self.assertRaisesMessage(TypeError, "'Lower' takes exactly 1 argument (2 given)"): + Author.objects.update(name=Lower('name', 'name')) + def test_upper(self): Author.objects.create(name='John Smith', alias='smithj') Author.objects.create(name='Rhonda')