2
0
Эх сурвалжийг харах

Fixed #24894 -- Added contrib.postgres.functions.TransactionNow

Adam Chainz 9 жил өмнө
parent
commit
d34d39ade7

+ 11 - 0
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)

+ 31 - 0
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())
+    [<Article: How to Django>]

+ 1 - 0
docs/ref/contrib/postgres/index.txt

@@ -34,6 +34,7 @@ Psycopg2 2.5 or higher is required.
     aggregates
     fields
     forms
+    functions
     lookups
     operations
     validators

+ 9 - 1
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())
     [<Article: How to Django>]
 
+.. 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
 ------
 

+ 5 - 0
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`
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 

+ 7 - 0
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 = [

+ 4 - 0
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)

+ 28 - 0
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)