Fixed #30240 -- Added SHA1, SHA224, SHA256, SHA384, and SHA512 database functions.
Thanks Mariusz Felisiak and Tim Graham for reviews.
This commit is contained in:
parent
0193bf874f
commit
0b70985f42
|
@ -226,6 +226,11 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
||||||
conn.create_function('REPEAT', 2, none_guard(operator.mul))
|
conn.create_function('REPEAT', 2, none_guard(operator.mul))
|
||||||
conn.create_function('REVERSE', 1, none_guard(lambda x: x[::-1]))
|
conn.create_function('REVERSE', 1, none_guard(lambda x: x[::-1]))
|
||||||
conn.create_function('RPAD', 3, _sqlite_rpad)
|
conn.create_function('RPAD', 3, _sqlite_rpad)
|
||||||
|
conn.create_function('SHA1', 1, none_guard(lambda x: hashlib.sha1(x.encode()).hexdigest()))
|
||||||
|
conn.create_function('SHA224', 1, none_guard(lambda x: hashlib.sha224(x.encode()).hexdigest()))
|
||||||
|
conn.create_function('SHA256', 1, none_guard(lambda x: hashlib.sha256(x.encode()).hexdigest()))
|
||||||
|
conn.create_function('SHA384', 1, none_guard(lambda x: hashlib.sha384(x.encode()).hexdigest()))
|
||||||
|
conn.create_function('SHA512', 1, none_guard(lambda x: hashlib.sha512(x.encode()).hexdigest()))
|
||||||
conn.create_function('SIN', 1, none_guard(math.sin))
|
conn.create_function('SIN', 1, none_guard(math.sin))
|
||||||
conn.create_function('SQRT', 1, none_guard(math.sqrt))
|
conn.create_function('SQRT', 1, none_guard(math.sqrt))
|
||||||
conn.create_function('TAN', 1, none_guard(math.tan))
|
conn.create_function('TAN', 1, none_guard(math.tan))
|
||||||
|
|
|
@ -10,9 +10,9 @@ from .math import (
|
||||||
Mod, Pi, Power, Radians, Round, Sin, Sqrt, Tan,
|
Mod, Pi, Power, Radians, Round, Sin, Sqrt, Tan,
|
||||||
)
|
)
|
||||||
from .text import (
|
from .text import (
|
||||||
MD5, Chr, Concat, ConcatPair, Left, Length, Lower, LPad, LTrim, Ord,
|
MD5, SHA1, SHA224, SHA256, SHA384, SHA512, Chr, Concat, ConcatPair, Left,
|
||||||
Repeat, Replace, Reverse, Right, RPad, RTrim, StrIndex, Substr, Trim,
|
Length, Lower, LPad, LTrim, Ord, Repeat, Replace, Reverse, Right, RPad,
|
||||||
Upper,
|
RTrim, StrIndex, Substr, Trim, Upper,
|
||||||
)
|
)
|
||||||
from .window import (
|
from .window import (
|
||||||
CumeDist, DenseRank, FirstValue, Lag, LastValue, Lead, NthValue, Ntile,
|
CumeDist, DenseRank, FirstValue, Lag, LastValue, Lead, NthValue, Ntile,
|
||||||
|
@ -34,9 +34,10 @@ __all__ = [
|
||||||
'Exp', 'Floor', 'Ln', 'Log', 'Mod', 'Pi', 'Power', 'Radians', 'Round',
|
'Exp', 'Floor', 'Ln', 'Log', 'Mod', 'Pi', 'Power', 'Radians', 'Round',
|
||||||
'Sin', 'Sqrt', 'Tan',
|
'Sin', 'Sqrt', 'Tan',
|
||||||
# text
|
# text
|
||||||
'MD5', 'Chr', 'Concat', 'ConcatPair', 'Left', 'Length', 'Lower', 'LPad',
|
'MD5', 'SHA1', 'SHA224', 'SHA256', 'SHA384', 'SHA512', 'Chr', 'Concat',
|
||||||
'LTrim', 'Ord', 'Repeat', 'Replace', 'Reverse', 'Right', 'RPad', 'RTrim',
|
'ConcatPair', 'Left', 'Length', 'Lower', 'LPad', 'LTrim', 'Ord', 'Repeat',
|
||||||
'StrIndex', 'Substr', 'Trim', 'Upper',
|
'Replace', 'Reverse', 'Right', 'RPad', 'RTrim', 'StrIndex', 'Substr',
|
||||||
|
'Trim', 'Upper',
|
||||||
# window
|
# window
|
||||||
'CumeDist', 'DenseRank', 'FirstValue', 'Lag', 'LastValue', 'Lead',
|
'CumeDist', 'DenseRank', 'FirstValue', 'Lag', 'LastValue', 'Lead',
|
||||||
'NthValue', 'Ntile', 'PercentRank', 'Rank', 'RowNumber',
|
'NthValue', 'Ntile', 'PercentRank', 'Rank', 'RowNumber',
|
||||||
|
|
|
@ -2,6 +2,7 @@ from django.db.models.expressions import Func, Value
|
||||||
from django.db.models.fields import IntegerField
|
from django.db.models.fields import IntegerField
|
||||||
from django.db.models.functions import Coalesce
|
from django.db.models.functions import Coalesce
|
||||||
from django.db.models.lookups import Transform
|
from django.db.models.lookups import Transform
|
||||||
|
from django.db.utils import NotSupportedError
|
||||||
|
|
||||||
|
|
||||||
class BytesToCharFieldConversionMixin:
|
class BytesToCharFieldConversionMixin:
|
||||||
|
@ -20,6 +21,40 @@ class BytesToCharFieldConversionMixin:
|
||||||
return super().convert_value(value, expression, connection)
|
return super().convert_value(value, expression, connection)
|
||||||
|
|
||||||
|
|
||||||
|
class MySQLSHA2Mixin:
|
||||||
|
def as_mysql(self, compiler, connection, **extra_content):
|
||||||
|
return super().as_sql(
|
||||||
|
compiler,
|
||||||
|
connection,
|
||||||
|
template='SHA2(%%(expressions)s, %s)' % self.function[3:],
|
||||||
|
**extra_content,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class OracleHashMixin:
|
||||||
|
def as_oracle(self, compiler, connection, **extra_context):
|
||||||
|
return super().as_sql(
|
||||||
|
compiler,
|
||||||
|
connection,
|
||||||
|
template=(
|
||||||
|
"LOWER(RAWTOHEX(STANDARD_HASH(UTL_I18N.STRING_TO_RAW("
|
||||||
|
"%(expressions)s, 'AL32UTF8'), '%(function)s')))"
|
||||||
|
),
|
||||||
|
**extra_context,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PostgreSQLSHAMixin:
|
||||||
|
def as_postgresql(self, compiler, connection, **extra_content):
|
||||||
|
return super().as_sql(
|
||||||
|
compiler,
|
||||||
|
connection,
|
||||||
|
template="ENCODE(DIGEST(%(expressions)s, '%(function)s'), 'hex')",
|
||||||
|
function=self.function.lower(),
|
||||||
|
**extra_content,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Chr(Transform):
|
class Chr(Transform):
|
||||||
function = 'CHR'
|
function = 'CHR'
|
||||||
lookup_name = 'chr'
|
lookup_name = 'chr'
|
||||||
|
@ -150,21 +185,10 @@ class LTrim(Transform):
|
||||||
lookup_name = 'ltrim'
|
lookup_name = 'ltrim'
|
||||||
|
|
||||||
|
|
||||||
class MD5(Transform):
|
class MD5(OracleHashMixin, Transform):
|
||||||
function = 'MD5'
|
function = 'MD5'
|
||||||
lookup_name = 'md5'
|
lookup_name = 'md5'
|
||||||
|
|
||||||
def as_oracle(self, compiler, connection, **extra_context):
|
|
||||||
return super().as_sql(
|
|
||||||
compiler,
|
|
||||||
connection,
|
|
||||||
template=(
|
|
||||||
"LOWER(RAWTOHEX(STANDARD_HASH(UTL_I18N.STRING_TO_RAW("
|
|
||||||
"%(expressions)s, 'AL32UTF8'), '%(function)s')))"
|
|
||||||
),
|
|
||||||
**extra_context,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Ord(Transform):
|
class Ord(Transform):
|
||||||
function = 'ASCII'
|
function = 'ASCII'
|
||||||
|
@ -235,6 +259,34 @@ class RTrim(Transform):
|
||||||
lookup_name = 'rtrim'
|
lookup_name = 'rtrim'
|
||||||
|
|
||||||
|
|
||||||
|
class SHA1(OracleHashMixin, PostgreSQLSHAMixin, Transform):
|
||||||
|
function = 'SHA1'
|
||||||
|
lookup_name = 'sha1'
|
||||||
|
|
||||||
|
|
||||||
|
class SHA224(MySQLSHA2Mixin, PostgreSQLSHAMixin, Transform):
|
||||||
|
function = 'SHA224'
|
||||||
|
lookup_name = 'sha224'
|
||||||
|
|
||||||
|
def as_oracle(self, compiler, connection, **extra_context):
|
||||||
|
raise NotSupportedError('SHA224 is not supported on Oracle.')
|
||||||
|
|
||||||
|
|
||||||
|
class SHA256(MySQLSHA2Mixin, OracleHashMixin, PostgreSQLSHAMixin, Transform):
|
||||||
|
function = 'SHA256'
|
||||||
|
lookup_name = 'sha256'
|
||||||
|
|
||||||
|
|
||||||
|
class SHA384(MySQLSHA2Mixin, OracleHashMixin, PostgreSQLSHAMixin, Transform):
|
||||||
|
function = 'SHA384'
|
||||||
|
lookup_name = 'sha384'
|
||||||
|
|
||||||
|
|
||||||
|
class SHA512(MySQLSHA2Mixin, OracleHashMixin, PostgreSQLSHAMixin, Transform):
|
||||||
|
function = 'SHA512'
|
||||||
|
lookup_name = 'sha512'
|
||||||
|
|
||||||
|
|
||||||
class StrIndex(Func):
|
class StrIndex(Func):
|
||||||
"""
|
"""
|
||||||
Return a positive integer corresponding to the 1-indexed position of the
|
Return a positive integer corresponding to the 1-indexed position of the
|
||||||
|
|
|
@ -1441,6 +1441,41 @@ side.
|
||||||
Similar to :class:`~django.db.models.functions.Trim`, but removes only trailing
|
Similar to :class:`~django.db.models.functions.Trim`, but removes only trailing
|
||||||
spaces.
|
spaces.
|
||||||
|
|
||||||
|
``SHA1``, ``SHA224``, ``SHA256``, ``SHA384``, and ``SHA512``
|
||||||
|
------------------------------------------------------------
|
||||||
|
|
||||||
|
.. class:: SHA1(expression, **extra)
|
||||||
|
.. class:: SHA224(expression, **extra)
|
||||||
|
.. class:: SHA256(expression, **extra)
|
||||||
|
.. class:: SHA384(expression, **extra)
|
||||||
|
.. class:: SHA512(expression, **extra)
|
||||||
|
|
||||||
|
.. versionadded:: 3.0
|
||||||
|
|
||||||
|
Accepts a single text field or expression and returns the particular hash of
|
||||||
|
the string.
|
||||||
|
|
||||||
|
They can also be registered as transforms as described in :class:`Length`.
|
||||||
|
|
||||||
|
Usage example::
|
||||||
|
|
||||||
|
>>> from django.db.models.functions import SHA1
|
||||||
|
>>> Author.objects.create(name='Margaret Smith')
|
||||||
|
>>> author = Author.objects.annotate(name_sha1=SHA1('name')).get()
|
||||||
|
>>> print(author.name_sha1)
|
||||||
|
b87efd8a6c991c390be5a68e8a7945a7851c7e5c
|
||||||
|
|
||||||
|
.. admonition:: PostgreSQL
|
||||||
|
|
||||||
|
The `pgcrypto extension <https://www.postgresql.org/docs/current/static/
|
||||||
|
pgcrypto.html>`_ must be installed. You can use the
|
||||||
|
:class:`~django.contrib.postgres.operations.CryptoExtension` migration
|
||||||
|
operation to install it.
|
||||||
|
|
||||||
|
.. admonition:: Oracle
|
||||||
|
|
||||||
|
Oracle doesn't support the ``SHA224`` function.
|
||||||
|
|
||||||
``StrIndex``
|
``StrIndex``
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
|
|
@ -168,7 +168,12 @@ Migrations
|
||||||
Models
|
Models
|
||||||
~~~~~~
|
~~~~~~
|
||||||
|
|
||||||
* Added the :class:`~django.db.models.functions.MD5` database function.
|
* Added hash database functions :class:`~django.db.models.functions.MD5`,
|
||||||
|
:class:`~django.db.models.functions.SHA1`,
|
||||||
|
:class:`~django.db.models.functions.SHA224`,
|
||||||
|
:class:`~django.db.models.functions.SHA256`,
|
||||||
|
:class:`~django.db.models.functions.SHA384`, and
|
||||||
|
:class:`~django.db.models.functions.SHA512`.
|
||||||
|
|
||||||
* The new ``is_dst`` parameter of the
|
* The new ``is_dst`` parameter of the
|
||||||
:class:`~django.db.models.functions.Trunc` database functions determines the
|
:class:`~django.db.models.functions.Trunc` database functions determines the
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
try:
|
||||||
|
from django.contrib.postgres.operations import CryptoExtension
|
||||||
|
except ImportError:
|
||||||
|
CryptoExtension = mock.Mock()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
# Required for the SHA database functions.
|
||||||
|
operations = [CryptoExtension()]
|
|
@ -0,0 +1,77 @@
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('db_functions', '0001_setup_extensions'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Author',
|
||||||
|
fields=[
|
||||||
|
('name', models.CharField(max_length=50)),
|
||||||
|
('alias', models.CharField(max_length=50, null=True, blank=True)),
|
||||||
|
('goes_by', models.CharField(max_length=50, null=True, blank=True)),
|
||||||
|
('age', models.PositiveSmallIntegerField(default=30)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Article',
|
||||||
|
fields=[
|
||||||
|
('authors', models.ManyToManyField('db_functions.Author', related_name='articles')),
|
||||||
|
('title', models.CharField(max_length=50)),
|
||||||
|
('summary', models.CharField(max_length=200, null=True, blank=True)),
|
||||||
|
('text', models.TextField()),
|
||||||
|
('written', models.DateTimeField()),
|
||||||
|
('published', models.DateTimeField(null=True, blank=True)),
|
||||||
|
('updated', models.DateTimeField(null=True, blank=True)),
|
||||||
|
('views', models.PositiveIntegerField(default=0)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Fan',
|
||||||
|
fields=[
|
||||||
|
('name', models.CharField(max_length=50)),
|
||||||
|
('age', models.PositiveSmallIntegerField(default=30)),
|
||||||
|
('author', models.ForeignKey('db_functions.Author', models.CASCADE, related_name='fans')),
|
||||||
|
('fan_since', models.DateTimeField(null=True, blank=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='DTModel',
|
||||||
|
fields=[
|
||||||
|
('name', models.CharField(max_length=32)),
|
||||||
|
('start_datetime', models.DateTimeField(null=True, blank=True)),
|
||||||
|
('end_datetime', models.DateTimeField(null=True, blank=True)),
|
||||||
|
('start_date', models.DateField(null=True, blank=True)),
|
||||||
|
('end_date', models.DateField(null=True, blank=True)),
|
||||||
|
('start_time', models.TimeField(null=True, blank=True)),
|
||||||
|
('end_time', models.TimeField(null=True, blank=True)),
|
||||||
|
('duration', models.DurationField(null=True, blank=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='DecimalModel',
|
||||||
|
fields=[
|
||||||
|
('n1', models.DecimalField(decimal_places=2, max_digits=6)),
|
||||||
|
('n2', models.DecimalField(decimal_places=2, max_digits=6)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='IntegerModel',
|
||||||
|
fields=[
|
||||||
|
('big', models.BigIntegerField(null=True, blank=True)),
|
||||||
|
('normal', models.IntegerField(null=True, blank=True)),
|
||||||
|
('small', models.SmallIntegerField(null=True, blank=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='FloatModel',
|
||||||
|
fields=[
|
||||||
|
('f1', models.FloatField(null=True, blank=True)),
|
||||||
|
('f2', models.FloatField(null=True, blank=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,42 @@
|
||||||
|
from django.db import connection
|
||||||
|
from django.db.models import CharField
|
||||||
|
from django.db.models.functions import SHA1
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.test.utils import register_lookup
|
||||||
|
|
||||||
|
from ..models import Author
|
||||||
|
|
||||||
|
|
||||||
|
class SHA1Tests(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
Author.objects.bulk_create([
|
||||||
|
Author(alias='John Smith'),
|
||||||
|
Author(alias='Jordan Élena'),
|
||||||
|
Author(alias='皇帝'),
|
||||||
|
Author(alias=''),
|
||||||
|
Author(alias=None),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_basic(self):
|
||||||
|
authors = Author.objects.annotate(
|
||||||
|
sha1_alias=SHA1('alias'),
|
||||||
|
).values_list('sha1_alias', flat=True).order_by('pk')
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
authors,
|
||||||
|
[
|
||||||
|
'e61a3587b3f7a142b8c7b9263c82f8119398ecb7',
|
||||||
|
'0781e0745a2503e6ded05ed5bc554c421d781b0c',
|
||||||
|
'198d15ea139de04060caf95bc3e0ec5883cba881',
|
||||||
|
'da39a3ee5e6b4b0d3255bfef95601890afd80709',
|
||||||
|
'da39a3ee5e6b4b0d3255bfef95601890afd80709'
|
||||||
|
if connection.features.interprets_empty_strings_as_nulls else None,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_transform(self):
|
||||||
|
with register_lookup(CharField, SHA1):
|
||||||
|
authors = Author.objects.filter(
|
||||||
|
alias__sha1='e61a3587b3f7a142b8c7b9263c82f8119398ecb7',
|
||||||
|
).values_list('alias', flat=True)
|
||||||
|
self.assertSequenceEqual(authors, ['John Smith'])
|
|
@ -0,0 +1,53 @@
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from django.db import connection
|
||||||
|
from django.db.models import CharField
|
||||||
|
from django.db.models.functions import SHA224
|
||||||
|
from django.db.utils import NotSupportedError
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.test.utils import register_lookup
|
||||||
|
|
||||||
|
from ..models import Author
|
||||||
|
|
||||||
|
|
||||||
|
class SHA224Tests(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
Author.objects.bulk_create([
|
||||||
|
Author(alias='John Smith'),
|
||||||
|
Author(alias='Jordan Élena'),
|
||||||
|
Author(alias='皇帝'),
|
||||||
|
Author(alias=''),
|
||||||
|
Author(alias=None),
|
||||||
|
])
|
||||||
|
|
||||||
|
@unittest.skipIf(connection.vendor == 'oracle', "Oracle doesn't support SHA224.")
|
||||||
|
def test_basic(self):
|
||||||
|
authors = Author.objects.annotate(
|
||||||
|
sha224_alias=SHA224('alias'),
|
||||||
|
).values_list('sha224_alias', flat=True).order_by('pk')
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
authors,
|
||||||
|
[
|
||||||
|
'a61303c220731168452cb6acf3759438b1523e768f464e3704e12f70',
|
||||||
|
'2297904883e78183cb118fc3dc21a610d60daada7b6ebdbc85139f4d',
|
||||||
|
'eba942746e5855121d9d8f79e27dfdebed81adc85b6bf41591203080',
|
||||||
|
'd14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f',
|
||||||
|
'd14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f'
|
||||||
|
if connection.features.interprets_empty_strings_as_nulls else None,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
@unittest.skipIf(connection.vendor == 'oracle', "Oracle doesn't support SHA224.")
|
||||||
|
def test_transform(self):
|
||||||
|
with register_lookup(CharField, SHA224):
|
||||||
|
authors = Author.objects.filter(
|
||||||
|
alias__sha224='a61303c220731168452cb6acf3759438b1523e768f464e3704e12f70',
|
||||||
|
).values_list('alias', flat=True)
|
||||||
|
self.assertSequenceEqual(authors, ['John Smith'])
|
||||||
|
|
||||||
|
@unittest.skipUnless(connection.vendor == 'oracle', "Oracle doesn't support SHA224.")
|
||||||
|
def test_unsupported(self):
|
||||||
|
msg = 'SHA224 is not supported on Oracle.'
|
||||||
|
with self.assertRaisesMessage(NotSupportedError, msg):
|
||||||
|
Author.objects.annotate(sha224_alias=SHA224('alias')).first()
|
|
@ -0,0 +1,42 @@
|
||||||
|
from django.db import connection
|
||||||
|
from django.db.models import CharField
|
||||||
|
from django.db.models.functions import SHA256
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.test.utils import register_lookup
|
||||||
|
|
||||||
|
from ..models import Author
|
||||||
|
|
||||||
|
|
||||||
|
class SHA256Tests(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
Author.objects.bulk_create([
|
||||||
|
Author(alias='John Smith'),
|
||||||
|
Author(alias='Jordan Élena'),
|
||||||
|
Author(alias='皇帝'),
|
||||||
|
Author(alias=''),
|
||||||
|
Author(alias=None),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_basic(self):
|
||||||
|
authors = Author.objects.annotate(
|
||||||
|
sha256_alias=SHA256('alias'),
|
||||||
|
).values_list('sha256_alias', flat=True).order_by('pk')
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
authors,
|
||||||
|
[
|
||||||
|
'ef61a579c907bbed674c0dbcbcf7f7af8f851538eef7b8e58c5bee0b8cfdac4a',
|
||||||
|
'6e4cce20cd83fc7c202f21a8b2452a68509cf24d1c272a045b5e0cfc43f0d94e',
|
||||||
|
'3ad2039e3ec0c88973ae1c0fce5a3dbafdd5a1627da0a92312c54ebfcf43988e',
|
||||||
|
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
|
||||||
|
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
|
||||||
|
if connection.features.interprets_empty_strings_as_nulls else None,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_transform(self):
|
||||||
|
with register_lookup(CharField, SHA256):
|
||||||
|
authors = Author.objects.filter(
|
||||||
|
alias__sha256='ef61a579c907bbed674c0dbcbcf7f7af8f851538eef7b8e58c5bee0b8cfdac4a',
|
||||||
|
).values_list('alias', flat=True)
|
||||||
|
self.assertSequenceEqual(authors, ['John Smith'])
|
|
@ -0,0 +1,44 @@
|
||||||
|
from django.db import connection
|
||||||
|
from django.db.models import CharField
|
||||||
|
from django.db.models.functions import SHA384
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.test.utils import register_lookup
|
||||||
|
|
||||||
|
from ..models import Author
|
||||||
|
|
||||||
|
|
||||||
|
class SHA384Tests(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
Author.objects.bulk_create([
|
||||||
|
Author(alias='John Smith'),
|
||||||
|
Author(alias='Jordan Élena'),
|
||||||
|
Author(alias='皇帝'),
|
||||||
|
Author(alias=''),
|
||||||
|
Author(alias=None),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_basic(self):
|
||||||
|
authors = Author.objects.annotate(
|
||||||
|
sha384_alias=SHA384('alias'),
|
||||||
|
).values_list('sha384_alias', flat=True).order_by('pk')
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
authors,
|
||||||
|
[
|
||||||
|
'9df976bfbcf96c66fbe5cba866cd4deaa8248806f15b69c4010a404112906e4ca7b57e53b9967b80d77d4f5c2982cbc8',
|
||||||
|
'72202c8005492016cc670219cce82d47d6d2d4273464c742ab5811d691b1e82a7489549e3a73ffa119694f90678ba2e3',
|
||||||
|
'eda87fae41e59692c36c49e43279c8111a00d79122a282a944e8ba9a403218f049a48326676a43c7ba378621175853b0',
|
||||||
|
'38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b',
|
||||||
|
'38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b'
|
||||||
|
if connection.features.interprets_empty_strings_as_nulls else None,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_transform(self):
|
||||||
|
with register_lookup(CharField, SHA384):
|
||||||
|
authors = Author.objects.filter(
|
||||||
|
alias__sha384=(
|
||||||
|
'9df976bfbcf96c66fbe5cba866cd4deaa8248806f15b69c4010a404112906e4ca7b57e53b9967b80d77d4f5c2982cbc8'
|
||||||
|
),
|
||||||
|
).values_list('alias', flat=True)
|
||||||
|
self.assertSequenceEqual(authors, ['John Smith'])
|
|
@ -0,0 +1,51 @@
|
||||||
|
from django.db import connection
|
||||||
|
from django.db.models import CharField
|
||||||
|
from django.db.models.functions import SHA512
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.test.utils import register_lookup
|
||||||
|
|
||||||
|
from ..models import Author
|
||||||
|
|
||||||
|
|
||||||
|
class SHA512Tests(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
Author.objects.bulk_create([
|
||||||
|
Author(alias='John Smith'),
|
||||||
|
Author(alias='Jordan Élena'),
|
||||||
|
Author(alias='皇帝'),
|
||||||
|
Author(alias=''),
|
||||||
|
Author(alias=None),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_basic(self):
|
||||||
|
authors = Author.objects.annotate(
|
||||||
|
sha512_alias=SHA512('alias'),
|
||||||
|
).values_list('sha512_alias', flat=True).order_by('pk')
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
authors,
|
||||||
|
[
|
||||||
|
'ed014a19bb67a85f9c8b1d81e04a0e7101725be8627d79d02ca4f3bd803f33cf'
|
||||||
|
'3b8fed53e80d2a12c0d0e426824d99d110f0919298a5055efff040a3fc091518',
|
||||||
|
'b09c449f3ba49a32ab44754982d4749ac938af293e4af2de28858858080a1611'
|
||||||
|
'2b719514b5e48cb6ce54687e843a4b3e69a04cdb2a9dc99c3b99bdee419fa7d0',
|
||||||
|
'b554d182e25fb487a3f2b4285bb8672f98956b5369138e681b467d1f079af116'
|
||||||
|
'172d88798345a3a7666faf5f35a144c60812d3234dcd35f444624f2faee16857',
|
||||||
|
'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce'
|
||||||
|
'47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e',
|
||||||
|
'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce'
|
||||||
|
'47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e'
|
||||||
|
if connection.features.interprets_empty_strings_as_nulls else None,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_transform(self):
|
||||||
|
with register_lookup(CharField, SHA512):
|
||||||
|
authors = Author.objects.filter(
|
||||||
|
alias__sha512=(
|
||||||
|
'ed014a19bb67a85f9c8b1d81e04a0e7101725be8627d79d02ca4f3bd8'
|
||||||
|
'03f33cf3b8fed53e80d2a12c0d0e426824d99d110f0919298a5055eff'
|
||||||
|
'f040a3fc091518'
|
||||||
|
),
|
||||||
|
).values_list('alias', flat=True)
|
||||||
|
self.assertSequenceEqual(authors, ['John Smith'])
|
Loading…
Reference in New Issue