diff --git a/django_postgres/function.py b/django_postgres/function.py index fb3ef6a..b5fbd4b 100644 --- a/django_postgres/function.py +++ b/django_postgres/function.py @@ -126,8 +126,19 @@ def create_functions(models_module, update=True): definition = function_cls.sql - create_function( - connection, function_name, fields, definition) + full_name = u'{module}.{cls}'.format( + module=models_module.__name__, + cls=name) + + try: + created = create_function( + connection, function_name, fields, definition) + except Exception, exc: + exc.function_cls = function_cls + exc.python_name = full_name + raise + else: + yield created, function_cls, full_name def _create_model(name, execute, fields=None, app_label='', module='', diff --git a/django_postgres/management/commands/sync_pgfunctions.py b/django_postgres/management/commands/sync_pgfunctions.py new file mode 100644 index 0000000..8b988f0 --- /dev/null +++ b/django_postgres/management/commands/sync_pgfunctions.py @@ -0,0 +1,51 @@ +from optparse import make_option +import logging + +from django.core.management.base import NoArgsCommand +from django.db import models + +from django_postgres.function import create_functions + + +log = logging.getLogger('django_postgres.sync_pgfunctions') + + +class Command(NoArgsCommand): + help = """Create/update Postgres functions for all installed apps.""" + option_list = NoArgsCommand.option_list + ( + make_option( + '--no-update', + action='store_false', + dest='update', + default=True, + help="""Don't update existing functions, only create new ones."""), + ) + + def handle_noargs(self, force, update, **options): + for module in models.get_apps(): + log.info("Creating functions for %s", module.__name__) + try: + create_result = create_functions(module, update=update) + + for status, function_cls, python_name in create_result: + if status == 'CREATED': + msg = "created" + elif status == 'UPDATED': + msg = "updated" + elif status == 'EXISTS': + msg = "already exists, skipping" + elif status == 'FORCE_REQUIRED': + msg = ( + "exists with incompatible schema, which must be " + "manually removed") + log.info("%(python_name)s (%(function_name)s): %(msg)s" % { + 'python_name': python_name, + 'function_name': function_cls._meta.db_table, + 'msg': msg}) + except Exception, exc: + if not hasattr(exc, 'function_cls'): + raise + log.exception("Error creating function %s (%r)", + exc.python_name, + exc.function_cls._meta.db_table) + diff --git a/tests/test_project/functiontest/tests.py b/tests/test_project/functiontest/tests.py index ddf6c42..c131fe7 100644 --- a/tests/test_project/functiontest/tests.py +++ b/tests/test_project/functiontest/tests.py @@ -9,7 +9,7 @@ _function_exists) -class FunctionTestCase(TestCase): +class FunctionModelTestCase(TestCase): """Test the Function API. """ def test_get_counter(self): @@ -38,6 +38,10 @@ def test_uncalled(self): models.UserTypeCounter.objects.filter, pk=1) + +class LowLeveFunctionTestCase(TestCase): + """Low level tests for function creation. + """ def test_create_function(self): """Create a function with the low-level create_function API. """ @@ -83,7 +87,10 @@ def test_create_functions_from_models(self): """Create functions using the create_functions and passing the models module. """ - create_functions(models) + create_result = create_functions(models) + + for status, _, _ in create_result: + self.assertEqual(status, 'CREATED') # Now check it was created cursor_wrapper = connection.cursor()