From 549c74093224c27dce15a68a55a0c53e951741dd Mon Sep 17 00:00:00 2001 From: Konsta Vesterinen Date: Tue, 22 Jul 2014 11:31:49 +0300 Subject: [PATCH] Improve has_changes --- sqlalchemy_utils/functions/orm.py | 62 ++++++++++++++++++++++++----- tests/functions/test_has_changes.py | 25 +++++++++++- 2 files changed, 75 insertions(+), 12 deletions(-) diff --git a/sqlalchemy_utils/functions/orm.py b/sqlalchemy_utils/functions/orm.py index d31de8d..5cbe06a 100644 --- a/sqlalchemy_utils/functions/orm.py +++ b/sqlalchemy_utils/functions/orm.py @@ -5,6 +5,7 @@ except ImportError: from functools import partial from inspect import isclass from operator import attrgetter +import six import sqlalchemy as sa from sqlalchemy import inspect from sqlalchemy.ext.hybrid import hybrid_property @@ -480,10 +481,13 @@ def getdotattr(obj_or_class, dot_path): return last -def has_changes(obj, attr): +def has_changes(obj, attrs=None, exclude=None): """ - Simple shortcut function for checking if given attribute of given - declarative model object has changed during the transaction. + Simple shortcut function for checking if given attribute(s) of given + declarative model object has changed during the transaction. Without + parameters this checks if given object has any modificiations. Additionally + exclude parameter can be given which check if given object has any changes + in any attributes other than the ones given in exclude. :: @@ -500,18 +504,52 @@ def has_changes(obj, attr): has_changes(user, 'name') # True + has_changes(user) # True + + + You can check multiple attributes as well. + :: + + + has_changes(user, ['age']) # True + + has_changes(user, ['name', 'age']) # True + + + This function also supports excluding certain attributes. + + :: + + has_changes(user, exclude=['name']) # False + + has_changes(user, exclude=['age']) # True + + .. versionchanged: 0.26.6 + Added support for multiple attributes and exclude parameter. :param obj: SQLAlchemy declarative model object - :param attr: Name of the attribute + :param attrs: Name(s) of the attribute .. seealso:: :func:`has_any_changes` """ - return ( - sa.inspect(obj) - .attrs - .get(attr) - .history - .has_changes() - ) + if attrs: + if isinstance(attrs, six.string_types): + return ( + sa.inspect(obj) + .attrs + .get(attrs) + .history + .has_changes() + ) + else: + return any(has_changes(obj, attr) for attr in attrs) + else: + if exclude is None: + exclude = [] + return any( + attr.history.has_changes() + for key, attr in sa.inspect(obj).attrs.items() + if key not in exclude + ) def has_any_changes(model, columns): @@ -536,6 +574,8 @@ def has_any_changes(model, columns): .. versionadded: 0.26.3 + .. deprecated:: 0.26.6 + User :func:`has_changes` instead. :param obj: SQLAlchemy declarative model object :param attrs: Names of the attributes diff --git a/tests/functions/test_has_changes.py b/tests/functions/test_has_changes.py index ed103d1..6fc2684 100644 --- a/tests/functions/test_has_changes.py +++ b/tests/functions/test_has_changes.py @@ -4,7 +4,7 @@ from sqlalchemy.ext.declarative import declarative_base from sqlalchemy_utils import has_changes -class TestHasChanges(object): +class HasChangesTestCase(object): def setup_method(self, method): Base = declarative_base() @@ -15,6 +15,8 @@ class TestHasChanges(object): self.Article = Article + +class TestHasChangesWithStringAttr(HasChangesTestCase): def test_without_changed_attr(self): article = self.Article() assert not has_changes(article, 'title') @@ -22,3 +24,24 @@ class TestHasChanges(object): def test_with_changed_attr(self): article = self.Article(title='Some title') assert has_changes(article, 'title') + + +class TestHasChangesWithMultipleAttrs(HasChangesTestCase): + def test_without_changed_attr(self): + article = self.Article() + assert not has_changes(article, ['title']) + + def test_with_changed_attr(self): + article = self.Article(title='Some title') + assert has_changes(article, ['title', 'id']) + + +class TestHasChangesWithExclude(HasChangesTestCase): + def test_without_changed_attr(self): + article = self.Article() + assert not has_changes(article, exclude=['id']) + + def test_with_changed_attr(self): + article = self.Article(title='Some title') + assert has_changes(article, exclude=['id']) + assert not has_changes(article, exclude=['title'])