From d34d39ade76e6b67299d8d88a7e5a2278a793dc3 Mon Sep 17 00:00:00 2001 From: Adam Chainz Date: Mon, 1 Jun 2015 18:00:34 +0100 Subject: [PATCH] Fixed #24894 -- Added contrib.postgres.functions.TransactionNow --- django/contrib/postgres/functions.py | 11 +++++++ docs/ref/contrib/postgres/functions.txt | 31 +++++++++++++++++++ docs/ref/contrib/postgres/index.txt | 1 + docs/ref/models/database-functions.txt | 10 +++++- docs/releases/1.9.txt | 5 +++ .../migrations/0002_create_test_models.py | 7 +++++ tests/postgres_tests/models.py | 4 +++ tests/postgres_tests/test_functions.py | 28 +++++++++++++++++ 8 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 django/contrib/postgres/functions.py create mode 100644 docs/ref/contrib/postgres/functions.txt create mode 100644 tests/postgres_tests/test_functions.py diff --git a/django/contrib/postgres/functions.py b/django/contrib/postgres/functions.py new file mode 100644 index 0000000000..3184c492fc --- /dev/null +++ b/django/contrib/postgres/functions.py @@ -0,0 +1,11 @@ +from django.db.models import DateTimeField +from django.db.models.functions import Func + + +class TransactionNow(Func): + template = 'CURRENT_TIMESTAMP' + + def __init__(self, output_field=None, **extra): + if output_field is None: + output_field = DateTimeField() + super(TransactionNow, self).__init__(output_field=output_field, **extra) diff --git a/docs/ref/contrib/postgres/functions.txt b/docs/ref/contrib/postgres/functions.txt new file mode 100644 index 0000000000..c97af99bfb --- /dev/null +++ b/docs/ref/contrib/postgres/functions.txt @@ -0,0 +1,31 @@ +PostgreSQL specific database functions +====================================== + +All of these functions are available from the +``django.contrib.postgres.functions`` module. + +.. currentmodule:: django.contrib.postgres.functions + +TransactionNow +-------------- + +.. class:: TransactionNow() + +.. versionadded:: 1.9 + +Returns the date and time on the database server that the current transaction +started. If you are not in a transaction it will return the date and time of +the current statement. This is a complement to +:class:`django.db.models.functions.Now`, which returns the date and time of the +current statement. + +Note that only the outermost call to :func:`~django.db.transaction.atomic()` +sets up a transaction and thus sets the time that ``TransactionNow()`` will +return; nested calls create savepoints which do not affect the transaction +time. + +Usage example:: + + >>> from django.contrib.postgres.functions import TransactionNow + >>> Article.objects.filter(published__lte=TransactionNow()) + [] diff --git a/docs/ref/contrib/postgres/index.txt b/docs/ref/contrib/postgres/index.txt index 7ff66d0770..a1536680c7 100644 --- a/docs/ref/contrib/postgres/index.txt +++ b/docs/ref/contrib/postgres/index.txt @@ -34,6 +34,7 @@ Psycopg2 2.5 or higher is required. aggregates fields forms + functions lookups operations validators diff --git a/docs/ref/models/database-functions.txt b/docs/ref/models/database-functions.txt index f1293ba741..336e4afc05 100644 --- a/docs/ref/models/database-functions.txt +++ b/docs/ref/models/database-functions.txt @@ -203,7 +203,8 @@ Now .. versionadded:: 1.9 -Returns the database server's current date and time when the query is executed. +Returns the database server's current date and time when the query is executed, +typically using the SQL ``CURRENT_TIMESTAMP``. Usage example:: @@ -211,6 +212,13 @@ Usage example:: >>> Article.objects.filter(published__lte=Now()) [] +.. admonition:: PostgreSQL considerations + + On PostgreSQL, the SQL ``CURRENT_TIMESTAMP`` returns the time that the + current transaction started. Therefore for cross-database compatibility, + ``Now()`` uses ``STATEMENT_TIMESTAMP`` instead. If you need the transaction + timestamp, use :class:`django.contrib.postgres.functions.TransactionNow`. + Substr ------ diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt index e858838b4e..e1f7439b88 100644 --- a/docs/releases/1.9.txt +++ b/docs/releases/1.9.txt @@ -141,13 +141,18 @@ Minor features * Added support for the :lookup:`rangefield.contained_by` lookup for some built in fields which correspond to the range fields. + * Added :class:`~django.contrib.postgres.fields.JSONField`. + * Added :doc:`/ref/contrib/postgres/aggregates`. * Fixed serialization of :class:`~django.contrib.postgres.fields.DateRangeField` and :class:`~django.contrib.postgres.fields.DateTimeRangeField`. +* Added the :class:`~django.contrib.postgres.functions.TransactionNow` database + function. + :mod:`django.contrib.redirects` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/postgres_tests/migrations/0002_create_test_models.py b/tests/postgres_tests/migrations/0002_create_test_models.py index f1d06cc2d9..c8fe842397 100644 --- a/tests/postgres_tests/migrations/0002_create_test_models.py +++ b/tests/postgres_tests/migrations/0002_create_test_models.py @@ -130,6 +130,13 @@ class Migration(migrations.Migration): ('related_field', models.ForeignKey('postgres_tests.AggregateTestModel', null=True)), ] ), + migrations.CreateModel( + name='NowTestModel', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('when', models.DateTimeField(null=True, default=None)), + ] + ), ] pg_92_operations = [ diff --git a/tests/postgres_tests/models.py b/tests/postgres_tests/models.py index bef77b7b21..4233092c79 100644 --- a/tests/postgres_tests/models.py +++ b/tests/postgres_tests/models.py @@ -109,3 +109,7 @@ class StatTestModel(models.Model): int1 = models.IntegerField() int2 = models.IntegerField() related_field = models.ForeignKey(AggregateTestModel, null=True) + + +class NowTestModel(models.Model): + when = models.DateTimeField(null=True, default=None) diff --git a/tests/postgres_tests/test_functions.py b/tests/postgres_tests/test_functions.py new file mode 100644 index 0000000000..620b561325 --- /dev/null +++ b/tests/postgres_tests/test_functions.py @@ -0,0 +1,28 @@ +from datetime import datetime +from time import sleep + +from django.contrib.postgres.functions import TransactionNow + +from . import PostgreSQLTestCase +from .models import NowTestModel + + +class TestTransactionNow(PostgreSQLTestCase): + + def test_transaction_now(self): + """ + The test case puts everything under a transaction, so two models + updated with a short gap should have the same time. + """ + m1 = NowTestModel.objects.create() + m2 = NowTestModel.objects.create() + + NowTestModel.objects.filter(id=m1.id).update(when=TransactionNow()) + sleep(0.1) + NowTestModel.objects.filter(id=m2.id).update(when=TransactionNow()) + + m1.refresh_from_db() + m2.refresh_from_db() + + self.assertIsInstance(m1.when, datetime) + self.assertEqual(m1.when, m2.when)