[
  {
    "path": ".gitignore",
    "content": "### Python template\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n # C extensions\n*.so\n # Distribution / packaging\n.Python\nenv/\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\n*.egg-info/\n.installed.cfg\n*.egg\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n # Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*,cover\n.hypothesis/\n.idea/\n\n# Django stuff:\n*.log\nlocal_settings.py\n\n# virtualenv\n.venv/\nvenv/\nENV/\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: python\nsudo: required\ndist: xenial\npython:\n  - 3.6\n  - 3.7\n  - 3.8\n  - 3.9-dev\nscript: make test-coveralls\nnotifications:\n  email: false\nservices:\n  - postgresql\nbefore_script:\n  - psql -c 'CREATE DATABASE pgcrypto_fields' -U postgres\n  - psql -c 'CREATE DATABASE pgcrypto_fields_diff' -U postgres\ninstall:\n  - pip install -e .\n  - pip install -r requirements_dev.txt\n  - pip install $DJANGO\nenv:\n  matrix:\n    - DJANGO='django>=2.2,<2.3'\n    - DJANGO='django>=3.0,<3.1'\n    - DJANGO='django>=3.1,<3.2'\n    - DJANGO='--upgrade --pre django'\nmatrix:\n  fast_finish: true\n  allow_failures:\n    - env: DJANGO='--upgrade --pre django'\n    - python: 3.9-dev\n"
  },
  {
    "path": "AUTHORS.md",
    "content": "# Credits\n\n\n## Development Lead\n\n* Charlie Denton <charlie@meshy.co.uk>\n* Kévin Etienne <etienne.kevin@gmail.com>\n* Peter J. Farrell <pjf@maepub.com>\n* Max Peterson <mail@maxpeterson.co.uk>\n\n## Contributors\n\n* Esther Onfroy <https://twitter.com/U039b>\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# CHANGELOG\n\n## Master (unreleased)\n\n* Added support for BooleanFields (#325)\n\n## 2.6.0\n\n* Added support for Django 3.1.x\n* Updated requirements_dev.txt\n* Dropped support for Python 3.5\n* Dropped support for Django below 2.2.x LTS release\n* Added support for BigIntegerFields (#169)\n* Added documentation for migration existing data (#246)\n\n## 2.5.2\n\n* Added support for Django 3.x\n* Updated requirements_dev.txt\n\n## 2.5.1\n\n* Fixed regression in the definition of EmailPGPPublicKeyField (#77)\n* Removed dead code (remove_validators and RemoveMaxLengthValidatorMixin)\n* Updated requirements_dev.txt\n\n## 2.5.0\n\n* Added new DecimalFields for both public and symmetric key (#64)\n* Added new FloatFields for both public and symmetric key (#64)\n* Added new TimeFields for both public and symmetric key (#64)\n* Added support for different keys based on database (#67)\n\n## 2.4.0\n\n* Added auto-decryption of all encrypted fields including FK tables\n* Removed django-pgcrypto-fields `aggregates`, `PGPManager` and `PGPAdmin` as they are no longer needed\n* Added support for `get_or_create()` and `update_or_create()` (#27)\n* Added support for `get_by_natural_key()` (#23)\n* Added support for `only()` and `defer()` as they were not supported with `PGPManager`\n* Added support for `distinct()` (Django 2.1+ with workaround available for 2.0 and lower)\n* Separated out dev requirements from setup.py requirements\n* Updated packaging / setup.py to include long description\n* Added AUTHORS and updated CONTRIBUTING\n* Updated TravisCI to use Xenial to gain Python 3.7 in the matrix\n\n## 2.3.1\n\n* Added `__range` lookup for Date / DateTime fields (#59)\n* Remove compatibility for `Django 1.8, 1.9, and 1.10` (#62)\n* Improved `setup.py`:\n    * check for Python 3.5+\n    * updated classifiers\n* Improved `make` file for release to use `twine`\n* Added additional shields to `README`\n* Updated Travis config to include Python 3.5 and 3.6\n* Refactored lookups and mixins\n\n## 2.3.0\n\n* Invalid release, bump to 2.3.1\n\n## 2.2.0\n\n* Merge `.coveragerc` into `setup.cfg`\n* Added `.gitignore` file\n* Updated out-dated requirements (latest versions of `Flake8` and `pycodestyle` \nare incompatible with each other)\n* Updated `README` with better explanations of the fields\n* Implemented DatePGPPublicKeyField and DateTimePGPPublicKeyField\n\n## 2.1.1\n\n* Added support for Django 2.x+\n* Updated requirements for testing\n* Updated travis config with Python 3.6 and additional environments\n\n## 2.1.0\n\nThanks to @peterfarrell:\n* Add support for `DatePGPSymmetricKeyField` and `DateTimePGPSymmetricKeyField`\nincluding support for serializing / deserializing django form fields.\n* Add support for auto decryption of symmetric key and public key fields via\nthe PGPManager (and support for disabling it in the Django Admin via the PGPAdmin)\n\n## 2.0.0\n\n* Remove compatibility for `Django 1.7`.\n* Add compatibility for `Django 1.10`.\n* Add `Django 1.9` to the travis matrix.\n\n## v1.0.1\n\n* Exclude tests app from distributed package.\n\n## v1.0.0\n\n* Rename package from `pgcrypto_fields` to `pgcrypto`.\n\n## v0.7.0\n\n* Make `get_placeholder` accepts a new argument `compiler`\n* Fix buggy import to `Aggregate`\n\n**Note: these changes have been done for django > 1.8.0.**\n\n## v0.6.4\n\n* Remove `MaxLengthValidator` from email fields.\n\n## v0.6.3\n\n* Avoid setting `max_length` on PGP fields.\n\n## v0.6.2\n\n* Allow/check `NULL` values for:\n  `TextDigestField`;\n  `TextHMACField`;\n  `EmailPGPPublicKeyField`;\n  `IntegerPGPPublicKeyField`;\n  `TextPGPPublicKeyField`;\n  `EmailPGPSymmetricKeyField`.\n  `IntegerPGPSymmetricKeyField`.\n  `TextPGPSymmetricKeyField`.\n\n## v0.6.1\n\n* Fix `cast`ing bug when sending negative values to integer fields.\n\n## v0.6.0\n\n* Add `EmailPGPPublicKeyField` and `EmailPGPSymmetricKeyField`.\n\n## v0.5.0\n\n* Rename the following fields:\n  `PGPPublicKeyField` to `TextPGPPublicKeyField`;\n  `PGPSymmetricKeyField` to `TextPGPSymmetricKeyField`;\n  `DigestField` to `TextDigestField`;\n  `HMACField` to `TextHMACField`.\n* Add new integer fields:\n  `IntegerPGPPublicKeyField`;\n  `IntegerPGPSymmetricKeyField`.\n\n## v0.4.0\n\n* Make accessing decrypted value transparent. Fix bug when field had a string\nrepresentation of `memoryview` for PGP and keyed hash fields.\n\n## v0.3.1\n\n* Fix `EncryptedProxyField` to select the correct item.\n\n## v0.3.0\n\n* Access `PGPPublicKeyField`  and `PGPSymmetricKeySQL` decrypted values with\nfield's proxy `_decrypted`.\n* Remove descriptor for field's name and raw value.\n\n## v0.2.0\n\n* Add hash based lookup for `DigestField` and `HMACField`.\n* Add `DigestField`, `HMACField`, `PGPPublicKeyAggregate`, `PGPSymmetricKeyAggregate`.\n\n## v0.1.0\n\n* Add decryption through an aggregate class.\n* Add encryption when inserting data to the database.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to Django-PGCrypto-Fields\n\nContributions are welcome, and they are greatly appreciated! Every little bit\nhelps, and credit will always be given.\n\nYou can contribute in many ways:\n\n* Code patches and enhancements\n* Documentation improvements\n* Bug reports and patch reviews\n\n## Types of Contributions\n\n### Report Bugs\n\nReport bugs at https://github.com/incuna/django-pgcrypto-fields/issues\n\nIf you are reporting a bug, please include:\n\n* Your operating system name and version.\n* Any details about your local setup that might be helpful in troubleshooting.\n* Detailed steps to reproduce the bug.\n\n### Fix Bugs\n\nLook through the GitHub issues for bugs. Anything tagged with \"bug\" and \"help\nwanted\" is open to whoever wants to implement it.\n\n### Implement Features\n\nLook through the GitHub issues for features. Anything tagged with \"enhancement\"\nand \"help wanted\" is open to whoever wants to implement it.\n\n### Write Documentation\n\ndjango-pgcrypto-fields could always use more documentation, whether as part of the\nofficial django-pgcrypto-fields docs, in docstrings, or even on the web in blog posts,\narticles, and such.\n\n### Submit Feedback\n\nThe best way to send feedback is to file an issue at https://github.com/incuna/django-pgcrypto-fields/issues\n\nIf you are proposing a feature:\n\n* Explain in detail how it would work.\n* Keep the scope as narrow as possible, to make it easier to implement.\n* Remember that this is a volunteer-driven project, and that contributions\n  are welcome :-)\n\n## Get Started!\n\nReady to contribute? Here's how to set up `django-pgcrypto-fields` for local development.\n\n1. Fork the `django-pgcrypto-fields` repo on GitHub.\n2. Clone your fork locally:\n\n    ```bash\n    $ git clone git@github.com:your_name_here/pgcrypto.git\n    ```\n    \n3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you \nset up your fork for local development:\n\n    ```bash\n    $ mkvirtualenv django-pgcrypto-fields\n    $ cd django-pgcrypto-fields/\n    $ pip install -r requirements_dev.txt --upgrade\n    ```\n\n4. Create a branch for local development:\n    \n    ```bash\n    $ git checkout -b name-of-your-bugfix-or-feature\n    ```\n   \n    Now you can make your changes locally.\n\n5. When you're done making changes, check that your changes pass flake8 and the\n   tests:\n\n    ```bash\n    $ make test\n    ```\n\n   To get flake8 and tox, just pip install them into your virtualenv.\n   \n   You will need a postgres database called `pgcrypto_fields` and `pgcrypto_fields_diff` on localhost without \n   password authentication\n\n6. Commit your changes and push your branch to GitHub:\n\n    ```bash\n    $ git add .\n    $ git commit -m \"Your detailed description of your changes.\"\n    $ git push origin name-of-your-bugfix-or-feature\n    ```\n\n7. Submit a pull request through the GitHub website.\n\n### Pull Request Guidelines\n\nBefore you submit a pull request, check that it meets these guidelines:\n\n1. The pull request should include tests.\n2. If the pull request adds functionality, the docs should be updated. Put\n   your new functionality into a function with a docstring, and add the\n   feature to the list in README.rst.\n3. The pull request should work for Python 3.6, 3.7 and 3.8. Check\n   https://travis-ci.org/incuna/django-pgcrypto-fields/pull_requests\n   and make sure that the tests pass for all supported Python versions.\n\n### Deploying\n\nA reminder for the maintainers on how to deploy.\nMake sure all your changes are committed (including an entry in CHANGELOG.md).\nThen run:\n\n```bash\n$ make release\n```\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2014 Incuna Ltd\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification,\nare permitted provided that the following conditions are met:\n\n    1. Redistributions of source code must retain the above copyright notice,\n    this list of conditions and the following disclaimer.\n\n    2. Redistributions in binary form must reproduce the above copyright notice,\n    this list of conditions and the following disclaimer in the documentation\n    and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "Makefile",
    "content": ".PHONY: clean-build lint help\n.DEFAULT_GOAL := help\n\ndefine BROWSER_PYSCRIPT\nimport os, webbrowser, sys\n\ntry:\n\tfrom urllib import pathname2url\nexcept:\n\tfrom urllib.request import pathname2url\n\nwebbrowser.open(\"file://\" + pathname2url(os.path.abspath(sys.argv[1])))\nendef\nexport BROWSER_PYSCRIPT\n\ndefine PRINT_HELP_PYSCRIPT\nimport re, sys\n\nfor line in sys.stdin:\n\tmatch = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line)\n\tif match:\n\t\ttarget, help = match.groups()\n\t\tprint(\"%-20s %s\" % (target, help))\nendef\nexport PRINT_HELP_PYSCRIPT\n\nBROWSER := python -c \"$$BROWSER_PYSCRIPT\"\n\ndefine BROWSER_PYSCRIPT\nimport os, webbrowser, sys\n\ntry:\n\tfrom urllib import pathname2url\nexcept:\n\tfrom urllib.request import pathname2url\n\nwebbrowser.open(\"file://\" + pathname2url(os.path.abspath(sys.argv[1])))\nendef\nexport BROWSER_PYSCRIPT\n\ndefine PRINT_HELP_PYSCRIPT\nimport re, sys\n\nfor line in sys.stdin:\n\tmatch = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line)\n\tif match:\n\t\ttarget, help = match.groups()\n\t\tprint(\"%-20s %s\" % (target, help))\nendef\nexport PRINT_HELP_PYSCRIPT\n\nBROWSER := python -c \"$$BROWSER_PYSCRIPT\"\n\nhelp:\n\t@python -c \"$$PRINT_HELP_PYSCRIPT\" < $(MAKEFILE_LIST)\n\nlint: ## Check style with flake8\n\t@flake8 . --exit-zero\n\nclean-build: ## Remove build artifacts\n\trm -r -f dist/*\n\trm -r -f build/*\n\trm -fr htmlcov/\n\nbuild: clean-build ## Builds source and wheel package\n\tpython setup.py sdist bdist_wheel\n\tls -l dist\n\nrelease: ## Package and upload a release\n\ttwine upload dist/*\n\ntest: clean-build lint ## Run tests quickly with the default Python\n\t./tests/run.py\n\ntest-coverage: ## Check code coverage quickly with the default Python\n\tcoverage run ./tests/run.py\n\tcoverage report -m\n\ntest-coveralls: test-coverage ## Check code coverage with the default Python and Coveralls\n\tcoveralls\n\ntest-coverage-html: test-coverage  ## Check code coverage quickly with the default Python and show report\n\tcoverage html\n\t$(BROWSER) htmlcov/index.html\n"
  },
  {
    "path": "README.md",
    "content": "# django-pgcrypto-fields\n\n[![Latest Release](https://img.shields.io/pypi/v/django-pgcrypto-fields.svg)](https://pypi.org/pypi/django-pgcrypto-fields/) [![Python Versions](https://img.shields.io/pypi/pyversions/django-pgcrypto-fields.svg)](https://pypi.org/pypi/django-pgcrypto-fields/) [![Build Status](https://travis-ci.org/incuna/django-pgcrypto-fields.svg?branch=master)](https://travis-ci.org/incuna/django-pgcrypto-fields?branch=master) [![Requirements Status](https://requires.io/github/incuna/django-pgcrypto-fields/requirements.svg?branch=master)](https://requires.io/github/incuna/django-pgcrypto-fields/requirements/?branch=master) [![Updates](https://pyup.io/repos/github/incuna/django-pgcrypto-fields/shield.svg)](https://pyup.io/repos/github/incuna/django-pgcrypto-fields/) [![Coverage Status](https://coveralls.io/repos/github/incuna/django-pgcrypto-fields/badge.svg?branch=master)](https://coveralls.io/github/incuna/django-pgcrypto-fields?branch=master)\n\n`django-pgcrypto-fields` is a `Django` extension which relies upon `pgcrypto` to\nencrypt and decrypt data for fields.\n\n## Requirements\n\n - postgres with `pgcrypto`\n - Supports Django 2.2.x, 3.0.x, 3.1.x and 3.2.x\n - Compatible with Python 3 only\n \n Last version of this library that supports `Django` 1.8.x, 1.9.x, 1.10.x\n was `django-pgcrypto-fields` 2.2.0.\n \n Last version of this library that supports `Django` 2.0.x and 2.1.x was\n was `django-pgcrypto-fields` 2.5.2.\n \n\n## Installation\n\n### Install package \n\n```bash\npip install django-pgcrypto-fields\n```\n\n### Django settings\n\nOur library support different crypto keys for multiple databases by \ndefining the keys in your `DATABASES` settings.\n\nIn `settings.py`:\n```python\nimport os\nBASEDIR = os.path.dirname(os.path.dirname(__file__))\nPUBLIC_PGP_KEY_PATH = os.path.abspath(os.path.join(BASEDIR, 'public.key'))\nPRIVATE_PGP_KEY_PATH = os.path.abspath(os.path.join(BASEDIR, 'private.key'))\n\n# Used by PGPPublicKeyField used by default if not specified by the db\nPUBLIC_PGP_KEY = open(PUBLIC_PGP_KEY_PATH).read()\nPRIVATE_PGP_KEY = open(PRIVATE_PGP_KEY_PATH).read()\n\n# Used by TextHMACField and PGPSymmetricKeyField if not specified by the db\nPGCRYPTO_KEY='ultrasecret'\n\nDIFF_PUBLIC_PGP_KEY_PATH = os.path.abspath(\n    os.path.join(BASEDIR, 'tests/keys/public_diff.key')\n)\nDIFF_PRIVATE_PGP_KEY_PATH = os.path.abspath(\n    os.path.join(BASEDIR, 'tests/keys/private_diff.key')\n)\n\n# And add 'pgcrypto' to `INSTALLED_APPS` to create the extension for\n# pgcrypto (in a migration).\nINSTALLED_APPS = (\n    'pgcrypto',\n    # Other installed apps\n)\n\nDATABASES = {\n    # This db will use the default keys above\n    'default': {\n        'ENGINE': 'django.db.backends.postgresql_psycopg2',\n        'NAME': 'pgcryto_fields',\n        'USER': 'pgcryto_fields',\n        'PASSWORD': 'xxxx',\n        'HOST': 'psql.test.com',\n        'PORT': 5432,\n        'OPTIONS': {\n            'sslmode': 'require',\n        }\n    },\n    'diff_keys': {\n        'ENGINE': 'django.db.backends.postgresql_psycopg2',\n        'NAME': 'pgcryto_fields_diff',\n        'USER': 'pgcryto_fields_diff',\n        'PASSWORD': 'xxxx',\n        'HOST': 'psqldiff.test.com',\n        'PORT': 5432,\n        'OPTIONS': {\n            'sslmode': 'require',\n        },\n        'PGCRYPTO_KEY': 'djangorocks',\n        'PUBLIC_PGP_KEY': open(DIFF_PUBLIC_PGP_KEY_PATH, 'r').read(),\n        'PRIVATE_PGP_KEY': open(DIFF_PRIVATE_PGP_KEY_PATH, 'r').read(),\n    },\n}\n```\n\n### Generate GPG keys if using Public Key Encryption\n\nThe public key is going to encrypt the message and the private key will be\nneeded to decrypt the content. The following commands have been taken from the\n[pgcrypto documentation](http://www.postgresql.org/docs/devel/static/pgcrypto.html)\n(see Generating PGP Keys with GnuPG).\n\nGenerating a public and a private key (The preferred key type is \"DSA and Elgamal\".):\n\n```bash\n$ gpg --gen-key\n$ gpg --list-secret-keys\n\n/home/bob/.gnupg/secring.gpg\n---------------------------\nsec   2048R/21 2014-10-23\nuid                  Test Key <example@example.com>\nssb   2048R/42 2014-10-23\n\n\n$ gpg -a --export 42 > public.key\n$ gpg -a --export-secret-keys 21 > private.key\n```\n\n#### Limitations\n\nThis library currently does not support Public Key Encryption private keys that are password protected yet. See Issue #89 to help implement it.\n\n### Upgrading to 2.4.0 from previous versions\n\nThe 2.4.0 version of this library received a large rewrite in order to support \nauto-decryption when getting encrypted field data as well as the ability to filter \non encrypted fields without using the old PGPCrypto aggregate functions available\nin previous versions.\n\nThe following items in this library have been removed and therefore references in \nyour application to these items need to be removed as well:\n\n* `managers.PGPManager`\n* `admin.PGPAdmin`\n* `aggregates.*`\n\n## Fields\n\n`django-pgcrypto-fields` has 3 kinds of fields:\n  - Hash based fields\n  - Public Key (PGP) fields\n  - Symmetric fields\n\n#### Hash Based Fields\n\nSupported hash based fields are:\n - `TextDigestField`\n - `TextHMACField`\n\n`TextDigestField` is hashed in the database using the `digest` pgcrypto function \nusing the `sha512` algorithm.\n\n`TextHMACField` is hashed in the database using the `hmac` pgcrypto function \nusing a key and the `sha512` algorithm. This is similar to the digest version however\nthe hash can only be recalculated knowing the key. This prevents someone from altering \nthe data and also changing the hash to match.\n\n#### Public Key Encryption Fields\n\nSupported PGP public key fields are:\n - `CharPGPPublicKeyField`\n - `EmailPGPPublicKeyField`\n - `TextPGPPublicKeyField`\n - `DatePGPPublicKeyField`\n - `DateTimePGPPublicKeyField`\n - `TimePGPPublicKeyField`\n - `IntegerPGPPublicKeyField`\n - `BigIntegerPGPPublicKeyField`\n - `DecimalPGPPublicKeyField`\n - `FloatPGPPublicKeyField`\n - `BooleanPGPPublicKeyField`\n\nPublic key encryption creates a token generated with a public key to\nencrypt the data and a private key to decrypt it.\n\nPublic and private keys can be set in settings with `PUBLIC_PGP_KEY` and\n`PRIVATE_PGP_KEY`.\n\n#### Symmetric Key Encryption Fields\n\nSupported PGP symmetric key fields are:\n - `CharPGPSymmetricKeyField`\n - `EmailPGPSymmetricKeyField`\n - `TextPGPSymmetricKeyField`\n - `DatePGPSymmetricKeyField`\n - `DateTimePGPSymmetricKeyField`\n - `TimePGPSymmetricKeyField`\n - `IntegerPGPSymmetricKeyField`\n - `BigIntegerPGPSymmetricKeyField`\n - `DecimalPGPSymmetricKeyField`\n - `FloatPGPSymmetricKeyField`\n - `BooleanPGPSymmetricKeyField`\n\n\nEncrypt and decrypt the data with `settings.PGCRYPTO_KEY` which acts like a password.\n\n### Django Model Field Equivalents \n\n| Django Field    | Public Key Field            | Symmetric Key Field            |\n|-----------------|-----------------------------|--------------------------------|\n| `CharField`     | `CharPGPPublicKeyField`     | `CharPGPSymmetricKeyField`     |\n| `EmailField`    | `EmailPGPPublicKeyField`    | `EmailPGPSymmetricKeyField`    |\n| `TextField`     | `TextPGPPublicKeyField`     | `TextPGPSymmetricKeyField`     |\n| `DateField`     | `DatePGPPublicKeyField`     | `DatePGPSymmetricKeyField`     |\n| `DateTimeField` | `DateTimePGPPublicKeyField` | `DateTimePGPSymmetricKeyField` |\n| `TimeField`     | `TimePGPPublicKeyField`     | `TimePGPSymmetricKeyField`     |\n| `IntegerField`  | `IntegerPGPPublicKeyField`  | `IntegerPGPSymmetricKeyField`  |\n| `BigIntegerField`  | `BigIntegerPGPPublicKeyField`  | `BigIntegerPGPSymmetricKeyField`  |\n| `DecimalField`  | `DecimalPGPPublicKeyField`  | `DecimalPGPSymmetricKeyField`  |\n| `FloatField`    | `FloatPGPPublicKeyField`    | `FloatPGPSymmetricKeyField`    |\n| `BooleanField`  | `BooleanPGPPublicKeyField`  | `BooleanPGPSymmetricKeyField`  |\n\n**Other Django model fields are not currently supported. Pull requests are welcomed.**\n\n### Usage\n\n#### Model Definition\n\n```python\nfrom django.db import models\n\nfrom pgcrypto import fields\n\nclass MyModel(models.Model):\n    digest_field = fields.TextDigestField()\n    digest_with_original_field = fields.TextDigestField(original='pgp_sym_field')\n    hmac_field = fields.TextHMACField()\n    hmac_with_original_field = fields.TextHMACField(original='pgp_sym_field')\n\n    email_pgp_pub_field = fields.EmailPGPPublicKeyField()\n    integer_pgp_pub_field = fields.IntegerPGPPublicKeyField()\n    pgp_pub_field = fields.TextPGPPublicKeyField()\n    date_pgp_pub_field = fields.DatePGPPublicKeyField()\n    datetime_pgp_pub_field = fields.DateTimePGPPublicKeyField()\n    time_pgp_pub_field = fields.TimePGPPublicKeyField()\n    decimal_pgp_pub_field = fields.DecimalPGPPublicKeyField()\n    float_pgp_pub_field = fields.FloatPGPPublicKeyField()\n    boolean_pgp_pub_field = fields.BooleanPGPPublicKeyField()\n    \n    email_pgp_sym_field = fields.EmailPGPSymmetricKeyField()\n    integer_pgp_sym_field = fields.IntegerPGPSymmetricKeyField()\n    pgp_sym_field = fields.TextPGPSymmetricKeyField()\n    date_pgp_sym_field = fields.DatePGPSymmetricKeyField()\n    datetime_pgp_sym_field = fields.DateTimePGPSymmetricKeyField()\n    time_pgp_sym_field = fields.TimePGPSymmetricKeyField()\n    decimal_pgp_sym_field = fields.DecimalPGPSymmetricKeyField()\n    float_pgp_sym_field = fields.FloatPGPSymmetricKeyField()\n    boolean_pgp_sym_field = fields.BooleanPGPSymmetricKeyField()\n```\n\n#### Encrypting\n\nData is automatically encrypted when inserted into the database.\n\nExample:\n```\n>>> MyModel.objects.create(value='Value to be encrypted...')\n```\n\nHash fields can have hashes auto updated if you use the `original` attribute. This\nattribute allows you to indicate another field name to base the hash value on.\n\n```python\nfrom django.db import models\n\nfrom pgcrypto import fields\n\nclass User(models.Model):\n    first_name = fields.TextPGPSymmetricKeyField(max_length=20, verbose_name='First Name')\n    first_name_hashed = fields.TextHMACField(original='first_name') \n```\n\nIn the above example, if you specify the optional original attribute it would \ntake the unencrypted value from the first_name model field as the input value \nto create the hash. If you did not specify an original attribute, the field \nwould work as it does now and would remain backwards compatible.\n\n##### PGP fields\n\nWhen accessing the field name attribute on a model instance we are getting the\ndecrypted value.\n\nExample:\n```\n>>> # When using a PGP public key based encryption\n>>> my_model = MyModel.objects.get()\n>>> my_model.value\n'Value decrypted'\n```\n\nFiltering encrypted values is now handled automatically as of 2.4.0. And `aggregate`\nmethods are not longer supported and have been removed from the library.\n\nAlso, auto-decryption is support for `select_related()` models.\n\n```python\nfrom django.db import models\n\nfrom pgcrypto import fields\n\n\nclass EncryptedFKModel(models.Model):\n    fk_pgp_sym_field = fields.TextPGPSymmetricKeyField(blank=True, null=True)\n\n\nclass EncryptedModel(models.Model):\n    pgp_sym_field = fields.TextPGPSymmetricKeyField(blank=True, null=True)\n    fk_model = models.ForeignKey(\n        EncryptedFKModel, blank=True, null=True, on_delete=models.CASCADE\n    )\n```\n\nExample:\n```\n>>> import EncryptedModel\n>>> my_model = EncryptedModel.objects.get().select_releated('fk_model')\n>>> my_model.pgp_sym_field\n'Value decrypted'\n>>> my_model.fk_model.fk_pgp_sym_field\n'Value decrypted'\n```\n\n##### Hash fields\n\nTo filter hash based values we need to compare hashes. This is achieved by using\na `__hash_of` lookup.\n\nExample:\n```\n>>> my_model = MyModel.objects.filter(digest_field__hash_of='value')\n[<MyModel: MyModel object>]\n>>> my_model = MyModel.objects.filter(hmac_field__hash_of='value')\n[<MyModel: MyModel object>]\n\n```\n\n## Limitations\n\n### Unique Indexes\n\nIt is usually not possible to index a `bytea` column in the database as the value in the index exceeds the the pgsql's maximum length allowed for an index (8192 bytes). One solution is to create a digest message of the value that you want unique and apply the unique constraint to the digest.\n\nYou can use the hash field ability to auto-create digest on the value of another field in the same model using the `original` argument. In the example below, a digest is created for unencrypted value that is in the `name` field when the model is saved or updated. A unique constraint exists on the name_digest so no two digests are allowed.  Note well that bulk updates do NOT cause hashes to be updated.\n\n```python\nfrom django.db import models\nfrom pgcrypto import fields\n\nclass Product(models.Model):\n    name_digest = fields.TextDigestField(original='name')\n    name = fields.TextPGPSymmetricKeyField()\n\n    class Meta:\n        constraints = [\n            models.UniqueConstraint(\n                fields=['name_digest', ],\n                name='name_digest_unique'\n            )\n       ]\n```\n\n### `.distinct('encrypted_field_name')`\n\nDue to a missing feature in the Django ORM, using `distinct()` on an encrypted field\ndoes not work for Django 2.0.x and lower.\n\nThe normal distinct works on Django 2.1.x and higher:\n\n```python\nitems = EncryptedFKModel.objects.filter(\n    pgp_sym_field__startswith='P'\n).only(\n    'id', 'pgp_sym_field', 'fk_model__fk_pgp_sym_field'\n).distinct(\n    'pgp_sym_field'\n)\n```\n\nWorkaround for Django 2.0.x and lower:\n\n```python\nfrom django.db import models\n\nitems = EncryptedFKModel.objects.filter(\n    pgp_sym_field__startswith='P'\n).annotate(\n    _distinct=models.F('pgp_sym_field')\n).only(\n    'id', 'pgp_sym_field', 'fk_model__fk_pgp_sym_field'\n).distinct(\n    '_distinct'\n)\n```\n\nThis works because the annotated field is auto-decrypted by Django as a `F` field and that \nfield is used in the `distinct()`.\n\n### Migrating existing fields into PGCrypto Fields\n\nMigrating existing fields into PGCrypto Fields is not performed by this library.  You will need to migrate the data \nin a forwards migration or other means. The only migration that is supported except to create/activate the pgcrypto \nextension in Postgres.\n\nMigrating data is complicated as there might be a few things to consider such as:\n\n* the shape of the data\n* validations/constrains done on the table/model/form and anywhere else\n\nThe library has no way of doing all these guesses or to make all these decisions.\n\nIf you need to migrate data from unencrypted fields to encrypted fields, three ways to solve it:\n\n1. When there's no data in the db it should be possible to start from scratch by recreating the db\n1. When there's no data in the table it should be possible to recreate the table\n1. When there's data or if the project is shared it should be possible to do it in a non destructive way\n\n**Option 1: No data is in the db**\n\n1. Drop the database\n1. Squash the migrations\n1. Recreate the db\n\n**Option 2: No data in the table**\n\n1. Create a migration to drop the table\n1. Create a new migration for the table with the encrypted field\n1. Optionally squash the migration\n\n**Option 3: Migrating in a non-destructive way**\n\nThe goal here is to be able to use to legacy field if something goes wrong.\n\nPart 1:\n\n1. Create new field\n1. When data is saved write both to legacy and new field\n1. Create a data migration to cast data from legacy field to new field\n1. check existing data from legacy and new field are the same if possible\n\nPart 2:\n\n1. Rename the fields and drop legacy fields\n1. Update the code to use only the new field\n\n## Common Errors\n\n### `psycopg2.errors.UndefinedFunction: function pgp_sym_encrypt(numeric, unknown) does not exist`\n\nThis commonly means you do not have the `pgcrypto` extension installed in Postgres.  Run the migration available in this library or install it manually in pgsql console.\n\n\n## Security Limitations\n\nTaken direction from the PostgreSQL documentation:\n\nhttps://www.postgresql.org/docs/9.6/static/pgcrypto.html#AEN187024\n\nAll pgcrypto functions run inside the database server. That means that all the \ndata and passwords move between pgcrypto and client applications in clear text. Thus you must:\n\n1. Connect locally or use SSL connections.\n1. Trust both system and database administrator.\n\nIf you cannot, then better do crypto inside client application.\n\nThe implementation does not resist side-channel attacks. For example, the time \nrequired for a pgcrypto decryption function to complete varies among ciphertexts of \na given size.\n"
  },
  {
    "path": "pgcrypto/__init__.py",
    "content": "DIGEST_SQL = \"digest(%s, 'sha512')\"\nHMAC_SQL = \"hmac(%s, '{}', 'sha512')\"\n\nPGP_PUB_ENCRYPT_SQL_WITH_NULLIF = \"pgp_pub_encrypt(nullif(%s, NULL)::text, dearmor('{}'))\"\nPGP_SYM_ENCRYPT_SQL_WITH_NULLIF = \"pgp_sym_encrypt(nullif(%s, NULL)::text, '{}')\"\n\nPGP_PUB_ENCRYPT_SQL = \"pgp_pub_encrypt(%s, dearmor('{}'))\"\nPGP_SYM_ENCRYPT_SQL = \"pgp_sym_encrypt(%s, '{}')\"\n\nPGP_PUB_DECRYPT_SQL = \"pgp_pub_decrypt(%s, dearmor('{}'))::%s\"\nPGP_SYM_DECRYPT_SQL = \"pgp_sym_decrypt(%s, '{}')::%s\"\n"
  },
  {
    "path": "pgcrypto/fields.py",
    "content": "from django.db import models\n\nfrom pgcrypto import (\n    DIGEST_SQL,\n    HMAC_SQL,\n    PGP_PUB_ENCRYPT_SQL_WITH_NULLIF,\n    PGP_SYM_ENCRYPT_SQL_WITH_NULLIF,\n)\nfrom pgcrypto.lookups import (\n    HashLookup,\n)\nfrom pgcrypto.mixins import (\n    DecimalPGPFieldMixin,\n    get_setting,\n    HashMixin,\n    PGPPublicKeyFieldMixin,\n    PGPSymmetricKeyFieldMixin,\n)\n\n\nclass TextDigestField(HashMixin, models.TextField):\n    \"\"\"Text digest field for postgres.\"\"\"\n    encrypt_sql = DIGEST_SQL\n\n    def get_encrypt_sql(self, connection):\n        \"\"\"Get encrypt sql.\"\"\"\n        return self.encrypt_sql.format(get_setting(connection, 'PGCRYPTO_KEY'))\n\n\nTextDigestField.register_lookup(HashLookup)\n\n\nclass TextHMACField(HashMixin, models.TextField):\n    \"\"\"Text HMAC field for postgres.\"\"\"\n    encrypt_sql = HMAC_SQL\n\n\nTextHMACField.register_lookup(HashLookup)\n\n\nclass EmailPGPPublicKeyField(PGPPublicKeyFieldMixin, models.EmailField):\n    \"\"\"Email PGP public key encrypted field.\"\"\"\n\n\nclass IntegerPGPPublicKeyField(PGPPublicKeyFieldMixin, models.IntegerField):\n    \"\"\"Integer PGP public key encrypted field.\"\"\"\n    encrypt_sql = PGP_PUB_ENCRYPT_SQL_WITH_NULLIF\n    cast_type = 'INT4'\n\n\nclass BigIntegerPGPPublicKeyField(PGPPublicKeyFieldMixin, models.IntegerField):\n    \"\"\"BigInteger PGP public key encrypted field.\"\"\"\n    encrypt_sql = PGP_PUB_ENCRYPT_SQL_WITH_NULLIF\n    cast_type = 'BIGINT'\n\n\nclass TextPGPPublicKeyField(PGPPublicKeyFieldMixin, models.TextField):\n    \"\"\"Text PGP public key encrypted field.\"\"\"\n\n\nclass CharPGPPublicKeyField(PGPPublicKeyFieldMixin, models.CharField):\n    \"\"\"Char PGP public key encrypted field.\"\"\"\n\n\nclass DatePGPPublicKeyField(PGPPublicKeyFieldMixin, models.DateField):\n    \"\"\"Date PGP public key encrypted field for postgres.\"\"\"\n    encrypt_sql = PGP_PUB_ENCRYPT_SQL_WITH_NULLIF\n    cast_type = 'DATE'\n\n\nclass DateTimePGPPublicKeyField(PGPPublicKeyFieldMixin, models.DateTimeField):\n    \"\"\"DateTime PGP public key encrypted field for postgres.\"\"\"\n    encrypt_sql = PGP_PUB_ENCRYPT_SQL_WITH_NULLIF\n    cast_type = 'TIMESTAMPTZ'\n\n\nclass BooleanPGPPublicKeyField(PGPPublicKeyFieldMixin, models.BooleanField):\n    \"\"\"Boolean PGP public key encrypted field.\"\"\"\n    encrypt_sql = PGP_PUB_ENCRYPT_SQL_WITH_NULLIF\n    cast_type = 'BOOL'\n\n\nclass EmailPGPSymmetricKeyField(PGPSymmetricKeyFieldMixin, models.EmailField):\n    \"\"\"Email PGP symmetric key encrypted field.\"\"\"\n\n\nclass IntegerPGPSymmetricKeyField(PGPSymmetricKeyFieldMixin, models.IntegerField):\n    \"\"\"Integer PGP symmetric key encrypted field.\"\"\"\n    encrypt_sql = PGP_SYM_ENCRYPT_SQL_WITH_NULLIF\n    cast_type = 'INT4'\n\n\nclass BigIntegerPGPSymmetricKeyField(PGPSymmetricKeyFieldMixin, models.IntegerField):\n    \"\"\"BigInteger PGP symmetric key encrypted field.\"\"\"\n    encrypt_sql = PGP_SYM_ENCRYPT_SQL_WITH_NULLIF\n    cast_type = 'BIGINT'\n\n\nclass TextPGPSymmetricKeyField(PGPSymmetricKeyFieldMixin, models.TextField):\n    \"\"\"Text PGP symmetric key encrypted field for postgres.\"\"\"\n\n\nclass CharPGPSymmetricKeyField(PGPSymmetricKeyFieldMixin, models.CharField):\n    \"\"\"Char PGP symmetric key encrypted field for postgres.\"\"\"\n\n\nclass DatePGPSymmetricKeyField(PGPSymmetricKeyFieldMixin, models.DateField):\n    \"\"\"Date PGP symmetric key encrypted field for postgres.\"\"\"\n    encrypt_sql = PGP_SYM_ENCRYPT_SQL_WITH_NULLIF\n    cast_type = 'DATE'\n\n\nclass DateTimePGPSymmetricKeyField(PGPSymmetricKeyFieldMixin, models.DateTimeField):\n    \"\"\"DateTime PGP symmetric key encrypted field for postgres.\"\"\"\n    encrypt_sql = PGP_SYM_ENCRYPT_SQL_WITH_NULLIF\n    cast_type = 'TIMESTAMPTZ'\n\n\nclass BooleanPGPSymmetricKeyField(PGPPublicKeyFieldMixin, models.BooleanField):\n    \"\"\"Boolean PGP public key encrypted field.\"\"\"\n    encrypt_sql = PGP_PUB_ENCRYPT_SQL_WITH_NULLIF\n    cast_type = 'BOOL'\n\n\nclass DecimalPGPPublicKeyField(DecimalPGPFieldMixin,\n                               PGPPublicKeyFieldMixin, models.DecimalField):\n    \"\"\"Decimal PGP public key encrypted field for postgres.\"\"\"\n\n\nclass DecimalPGPSymmetricKeyField(DecimalPGPFieldMixin,\n                                  PGPSymmetricKeyFieldMixin, models.DecimalField):\n    \"\"\"Decimal PGP symmetric key encrypted field for postgres.\"\"\"\n\n\nclass FloatPGPPublicKeyField(PGPPublicKeyFieldMixin, models.FloatField):\n    \"\"\"Float PGP public key encrypted field for postgres.\"\"\"\n    encrypt_sql = PGP_PUB_ENCRYPT_SQL_WITH_NULLIF\n    cast_type = 'DOUBLE PRECISION'\n\n\nclass FloatPGPSymmetricKeyField(PGPSymmetricKeyFieldMixin, models.FloatField):\n    \"\"\"Float PGP symmetric key encrypted field for postgres.\"\"\"\n    encrypt_sql = PGP_SYM_ENCRYPT_SQL_WITH_NULLIF\n    cast_type = 'DOUBLE PRECISION'\n\n\nclass TimePGPPublicKeyField(PGPPublicKeyFieldMixin, models.TimeField):\n    \"\"\"Time PGP public key encrypted field for postgres.\"\"\"\n    encrypt_sql = PGP_PUB_ENCRYPT_SQL_WITH_NULLIF\n    cast_type = 'TIME'\n\n\nclass TimePGPSymmetricKeyField(PGPSymmetricKeyFieldMixin, models.TimeField):\n    \"\"\"Float PGP symmetric key encrypted field for postgres.\"\"\"\n    encrypt_sql = PGP_SYM_ENCRYPT_SQL_WITH_NULLIF\n    cast_type = 'TIME'\n"
  },
  {
    "path": "pgcrypto/lookups.py",
    "content": "from django.db.models.lookups import Lookup\n\n\nclass HashLookup(Lookup):\n    \"\"\"Lookup to filter hashed values.\n\n    `HashLookup` is hashing the value on the right hand side with\n    the function specified in `encrypt_sql`.\n    \"\"\"\n    lookup_name = 'hash_of'\n\n    def as_sql(self, qn, connection):\n        \"\"\"Responsible for creating the lookup with the digest SQL.\n\n        Modify the right hand side expression to compare the value passed\n        to a hash.\n        \"\"\"\n        lhs, lhs_params = self.process_lhs(qn, connection)\n        rhs, rhs_params = self.process_rhs(qn, connection)\n        params = lhs_params + rhs_params\n        rhs = self.lhs.field.encrypt_sql % rhs\n        return ('{}::bytea = {}'.format(lhs, rhs)), params\n"
  },
  {
    "path": "pgcrypto/migrations/0001_add_pgcrypto_extension.py",
    "content": "from django.contrib.postgres.operations import CreateExtension\nfrom django.db import migrations\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = []\n\n    operations = [\n        CreateExtension('pgcrypto'),\n    ]\n"
  },
  {
    "path": "pgcrypto/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "pgcrypto/mixins.py",
    "content": "from django.conf import settings\nfrom django.db.models.expressions import Col\nfrom django.utils.functional import cached_property\n\nfrom pgcrypto import (\n    PGP_PUB_DECRYPT_SQL,\n    PGP_PUB_ENCRYPT_SQL,\n    PGP_SYM_DECRYPT_SQL,\n    PGP_SYM_ENCRYPT_SQL,\n)\n\n\ndef get_setting(connection, key):\n    \"\"\"Get key from connection or default to settings.\"\"\"\n    if key in connection.settings_dict:\n        return connection.settings_dict[key]\n    else:\n        return getattr(settings, key)\n\n\nclass DecryptedCol(Col):\n    \"\"\"Provide DecryptedCol support without using `extra` sql.\"\"\"\n\n    def __init__(self, alias, target, output_field=None):\n        \"\"\"Init the decryption.\"\"\"\n        self.target = target\n\n        super(DecryptedCol, self).__init__(alias, target, output_field)\n\n    def as_sql(self, compiler, connection):\n        \"\"\"Build SQL with decryption and casting.\"\"\"\n        sql, params = super(DecryptedCol, self).as_sql(compiler, connection)\n        sql = self.target.get_decrypt_sql(connection) % (sql, self.target.get_cast_sql())\n        return sql, params\n\n\nclass HashMixin:\n    \"\"\"Keyed hash mixin.\n\n    `HashMixin` uses 'pgcrypto' to encrypt data in a postgres database.\n    \"\"\"\n    encrypt_sql = None  # Set in implementation class\n\n    def __init__(self, original=None, *args, **kwargs):\n        \"\"\"Tells the init the original attr.\"\"\"\n        self.original = original\n\n        super(HashMixin, self).__init__(*args, **kwargs)\n\n    def pre_save(self, model_instance, add):\n        \"\"\"Save the original_value.\"\"\"\n        if self.original:\n            original_value = getattr(model_instance, self.original)\n            setattr(model_instance, self.attname, original_value)\n\n        return super(HashMixin, self).pre_save(model_instance, add)\n\n    def get_placeholder(self, value=None, compiler=None, connection=None):\n        \"\"\"\n        Tell postgres to encrypt this field with a hashing function.\n\n        The `value` string is checked to determine if we need to hash or keep\n        the current value.\n\n        `compiler` and `connection` is ignored here as we don't need custom operators.\n        \"\"\"\n        if value is None or value.startswith('\\\\x'):\n            return '%s'\n\n        return self.get_encrypt_sql(connection)\n\n    def get_encrypt_sql(self, connection):\n        \"\"\"Get encrypt sql. This may be overidden by some implementations.\"\"\"\n        return self.encrypt_sql\n\n\nclass PGPMixin:\n    \"\"\"PGP encryption for field's value.\n\n    `PGPMixin` uses 'pgcrypto' to encrypt data in a postgres database.\n    \"\"\"\n    encrypt_sql = None  # Set in implementation class\n    decrypt_sql = None  # Set in implementation class\n    cast_type = None\n\n    def __init__(self, *args, **kwargs):\n        \"\"\"`max_length` should be set to None as encrypted text size is variable.\"\"\"\n        super().__init__(*args, **kwargs)\n\n    def db_type(self, connection=None):\n        \"\"\"Value stored in the database is hexadecimal.\"\"\"\n        return 'bytea'\n\n    def get_placeholder(self, value, compiler, connection):\n        \"\"\"Tell postgres to encrypt this field using PGP.\"\"\"\n        raise NotImplementedError('The `get_placeholder` needs to be implemented.')\n\n    def get_cast_sql(self):\n        \"\"\"Get cast sql. This may be overidden by some implementations.\"\"\"\n        return self.cast_type\n\n    def get_decrypt_sql(self, connection):\n        \"\"\"Get decrypt sql.\"\"\"\n        raise NotImplementedError('The `get_decrypt_sql` needs to be implemented.')\n\n    def get_col(self, alias, output_field=None):\n        \"\"\"Get the decryption for col.\"\"\"\n        if output_field is None:\n            output_field = self\n        if alias != self.model._meta.db_table or output_field != self:\n            return DecryptedCol(\n                alias,\n                self,\n                output_field\n            )\n        else:\n            return self.cached_col\n\n    @cached_property\n    def cached_col(self):\n        \"\"\"Get cached version of decryption for col.\"\"\"\n        return DecryptedCol(\n            self.model._meta.db_table,\n            self\n        )\n\n\nclass PGPPublicKeyFieldMixin(PGPMixin):\n    \"\"\"PGP public key encrypted field mixin for postgres.\"\"\"\n    encrypt_sql = PGP_PUB_ENCRYPT_SQL\n    decrypt_sql = PGP_PUB_DECRYPT_SQL\n    cast_type = 'TEXT'\n\n    def get_placeholder(self, value=None, compiler=None, connection=None):\n        \"\"\"Tell postgres to encrypt this field using PGP.\"\"\"\n        return self.encrypt_sql.format(get_setting(connection, 'PUBLIC_PGP_KEY'))\n\n    def get_decrypt_sql(self, connection):\n        \"\"\"Get decrypt sql.\"\"\"\n        return self.decrypt_sql.format(get_setting(connection, 'PRIVATE_PGP_KEY'))\n\n\nclass PGPSymmetricKeyFieldMixin(PGPMixin):\n    \"\"\"PGP symmetric key encrypted field mixin for postgres.\"\"\"\n    encrypt_sql = PGP_SYM_ENCRYPT_SQL\n    decrypt_sql = PGP_SYM_DECRYPT_SQL\n    cast_type = 'TEXT'\n\n    def get_placeholder(self, value, compiler, connection):\n        \"\"\"Tell postgres to encrypt this field using PGP.\"\"\"\n        return self.encrypt_sql.format(get_setting(connection, 'PGCRYPTO_KEY'))\n\n    def get_decrypt_sql(self, connection):\n        \"\"\"Get decrypt sql.\"\"\"\n        return self.decrypt_sql.format(get_setting(connection, 'PGCRYPTO_KEY'))\n\n\nclass DecimalPGPFieldMixin:\n    \"\"\"Decimal PGP encrypted field mixin for postgres.\"\"\"\n    cast_type = 'NUMERIC(%(max_digits)s, %(decimal_places)s)'\n\n    def get_cast_sql(self):\n        \"\"\"Get cast sql.\"\"\"\n        return self.cast_type % {\n            'max_digits': self.max_digits,\n            'decimal_places': self.decimal_places\n        }\n"
  },
  {
    "path": "pgcrypto/models.py",
    "content": ""
  },
  {
    "path": "requirements.txt",
    "content": "-e .\ndjango>=1.11,<3.2\n"
  },
  {
    "path": "requirements_dev.txt",
    "content": "colour-runner==0.1.1\ncoveralls==3.1.0\ncoverage==5.5\ndj-database-url==0.5.0\nfactory-boy==3.2.0\nflake8-docstrings==1.6.0\nflake8-import-order==0.18.1\nflake8==3.9.2\nincuna-test-utils==8.0.0\npip==22.3\npsycopg2-binary==2.8.6\npyflakes==2.3.1\npycodestyle==2.7.0\nsetuptools==65.5.0\ntwine==3.4.1\nwheel==0.36.2\n"
  },
  {
    "path": "setup.cfg",
    "content": "[flake8]\nmax-line-length = 90\nmax-complexity = 10\nexclude =\n    *migrations*,\n    venv\nignore = D100,D101,D104,D203,D204\nstatistics=true\napplication-import-names = pgcrypto\nimport-order-style = smarkets\n\n[coverage:run]\nsource = pgcrypto\nomit = *migrations*, *tests*\n\n[coverage:report]\nshow_missing = true\nexclude_lines =\n    raise NotImplementedError\n"
  },
  {
    "path": "setup.py",
    "content": "import sys\n\nfrom setuptools import find_packages, setup\n\nCURRENT_PYTHON = sys.version_info[:2]\nREQUIRED_PYTHON = (3, 6)\n\n# This check and everything above must remain compatible with Python 2.7.\nif CURRENT_PYTHON < REQUIRED_PYTHON:\n    sys.stderr.write(\"\"\"\n==========================\nUnsupported Python version\n==========================\nThis version of django-pgcrypto-fields requires Python {}.{}, but you're trying to\ninstall it on Python {}.{}.\nThis may be because you are using a version of pip that doesn't\nunderstand the python_requires classifier. Make sure you\nhave pip >= 9.0 and setuptools >= 24.2, then try again:\n    $ python -m pip install --upgrade pip setuptools\n    $ python -m pip install django-pgcrypto-fields\n\"\"\".format(*(REQUIRED_PYTHON + CURRENT_PYTHON)))\n    sys.exit(1)\n\n\nwith open('README.md') as readme_file:\n    readme = readme_file.read()\n\n\nwith open('CHANGELOG.md') as changelog_file:\n    changelog = changelog_file.read()\n\nversion = '2.6.0'\n\nsetup(\n    name='django-pgcrypto-fields',\n    packages=find_packages(exclude=['tests']),\n    include_package_data=True,\n    version=version,\n    python_requires='>={}.{}'.format(*REQUIRED_PYTHON),\n    license='BSD',\n    description='Encrypted fields for Django dealing with pgcrypto postgres extension.',\n    long_description=readme + '\\n\\n' + changelog,\n    long_description_content_type='text/markdown',\n    classifiers=[\n        'Development Status :: 5 - Production/Stable',\n        'Framework :: Django',\n        'Intended Audience :: Developers',\n        'License :: OSI Approved :: BSD License',\n        'Natural Language :: English',\n        'Programming Language :: Python',\n        'Programming Language :: Python :: 3',\n        'Programming Language :: Python :: 3.6',\n        'Programming Language :: Python :: 3.7',\n        'Programming Language :: Python :: 3.8',\n        'Programming Language :: Python :: 3 :: Only',\n        'Topic :: Database',\n        'Topic :: Security :: Cryptography',\n    ],\n    author='Incuna Ltd',\n    author_email='admin@incuna.com',\n    url='https://github.com/incuna/django-pgcrypto-fields',\n    test_suite='tests',\n)\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/dbrouters.py",
    "content": "class TestRouter(object):\n\n    def db_for_read(self, model, **hints):\n        \"\"\"Read from diff_keys.\"\"\"\n        if model._meta.app_label == 'diff_keys':\n            return 'diff_keys'\n        return 'default'\n\n    def db_for_write(self, model, **hints):\n        \"\"\"Write to diff_keys.\"\"\"\n        if model._meta.app_label == 'diff_keys':\n            return 'diff_keys'\n        return 'default'\n"
  },
  {
    "path": "tests/default/__init__.py",
    "content": ""
  },
  {
    "path": "tests/diff_keys/__init__.py",
    "content": ""
  },
  {
    "path": "tests/diff_keys/models.py",
    "content": "from django.db import models\n\nfrom pgcrypto import fields\n\n\nclass EncryptedDiff(models.Model):\n    CHOICES = (\n        ('a', 'a'),\n        (1, '1'),\n    )\n    pub_field = fields.CharPGPPublicKeyField(blank=True, null=True,\n                                             choices=CHOICES, max_length=1)\n    sym_field = fields.CharPGPSymmetricKeyField(blank=True, null=True,\n                                                choices=CHOICES, max_length=1)\n    digest_field = fields.TextDigestField(blank=True, null=True)\n    hmac_field = fields.TextHMACField(blank=True, null=True)\n\n    class Meta:\n        \"\"\"Sets up the meta for the test model.\"\"\"\n        app_label = 'diff_keys'\n"
  },
  {
    "path": "tests/factories.py",
    "content": "from datetime import date, datetime\nfrom decimal import Decimal\n\nimport factory\n\nfrom .models import EncryptedFKModel, EncryptedModel\n\n\nclass EncryptedFKModelFactory(factory.django.DjangoModelFactory):\n    \"\"\"Factory to generate foreign key data.\"\"\"\n    fk_pgp_sym_field = factory.Sequence('Text with symmetric key {}'.format)\n\n    class Meta:\n        \"\"\"Sets up meta for test factory.\"\"\"\n        model = EncryptedFKModel\n\n\nclass EncryptedModelFactory(factory.django.DjangoModelFactory):\n    \"\"\"Factory to generate hashed and encrypted data.\"\"\"\n\n    digest_field = factory.Sequence('Text digest {}'.format)\n    hmac_field = factory.Sequence('Text hmac {}'.format)\n\n    email_pgp_pub_field = factory.Sequence('email{}@public.key'.format)\n    integer_pgp_pub_field = 42\n    biginteger_pgp_pub_field = 9223372036854775807\n    pgp_pub_field = factory.Sequence('Text with public key {}'.format)\n    char_pub_field = factory.Sequence('Text {}'.format)\n\n    date_pgp_pub_field = date.today()\n    datetime_pgp_pub_field = datetime.now()\n    decimal_pgp_pub_field = Decimal('123456.78')\n    boolean_pgp_pub_field = True\n\n    email_pgp_sym_field = factory.Sequence('email{}@symmetric.key'.format)\n    integer_pgp_sym_field = 43\n    biginteger_pgp_sym_field = 9223372036854775807\n    pgp_sym_field = factory.Sequence('Text with symmetric key {}'.format)\n    char_sym_field = factory.Sequence('Text {}'.format)\n\n    date_pgp_sym_field = date.today()\n    datetime_pgp_sym_field = datetime.now()\n    boolean_pgp_sym_field = False\n\n    fk_model = factory.SubFactory(EncryptedFKModelFactory)\n\n    class Meta:\n        \"\"\"Sets up meta for test factory.\"\"\"\n        model = EncryptedModel\n"
  },
  {
    "path": "tests/forms.py",
    "content": "from django import forms\n\nfrom .models import EncryptedModel\n\n\nclass EncryptedForm(forms.ModelForm):\n    \"\"\"Test for EncryptedModel.\"\"\"\n    class Meta:\n        \"\"\"Meta for form.\"\"\"\n        model = EncryptedModel\n        fields = '__all__'\n        widgets = {\n            'date_pgp_sym_field': forms.DateInput(format='%m/%d/%Y'),\n            'datetime_pgp_sym_field': forms.DateTimeInput(format='%m/%d/%Y %I:%M'),\n            'date_pgp_pub_field': forms.DateInput(format='%m/%d/%Y'),\n            'datetime_pgp_pub_field': forms.DateTimeInput(format='%m/%d/%Y %I:%M'),\n        }\n"
  },
  {
    "path": "tests/keys/README.md",
    "content": "Dragon ahead, do not use these keys. They have been generated for testing purpose.\nYou should never expose your private/secret key.\n"
  },
  {
    "path": "tests/keys/private.key",
    "content": "-----BEGIN PGP PRIVATE KEY BLOCK-----\nVersion: GnuPG v1\n\n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\n=kJd7\n-----END PGP PRIVATE KEY BLOCK-----\n"
  },
  {
    "path": "tests/keys/private_diff.key",
    "content": "-----BEGIN PGP PRIVATE KEY BLOCK-----\nVersion: GnuPG v1\n\n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\nzhkqb4tHw1kFgzXsT9FuzF+r\n=JjTF\n-----END PGP PRIVATE KEY BLOCK-----"
  },
  {
    "path": "tests/keys/public.key",
    "content": "-----BEGIN PGP PUBLIC KEY BLOCK-----\nVersion: GnuPG v1\n\nmQENBFRJDkwBCADVJDHy7al1P8urXWDkTSMfb6vGivD6vW0N2dDFixxKHDlOej52\nOB2F/B/WLFpdXFiKxLSTIWEp/d0AEtJQrNMuMCzUKT08h3YU5ypiBdGIY7fqWwi9\nG18ihm29/ygPni1XzG02DCto/pYD++RieIgGPOm8xlLrHwemehxhiOQ1K9eds8wd\nd0awXtiKdClaxGi24soyBxBC/tLde2Sgyh5y24RyXsmvZxORwWBM5tRZwOxwsQym\nAkt2M7LIGyQVgbLmTe4k5u3Uqv/t5tVK8OdDDbbXVDwJbj4n1Jol68cutHTAahpY\nmkOAuqLp7ZWqx/DsYYoJhv/G7dhcpS+CfNs3ABEBAAG0HlRlc3QgS2V5IDxleGFt\ncGxlQGV4YW1wbGUuY29tPokBOAQTAQIAIgUCVEkOTAIbAwYLCQgHAwIGFQgCCQoL\nBBYCAwECHgECF4AACgkQY3+K26BCfZUZPwgAzM03DFGATxldBcia63gK+vsEoDS+\nxCtHaDpNvestHDlCd3bHU13LcLAXUD9E/s4LpHF+lMACCyscdeTriA/x0IJBIuq9\n4KXMHJPpsh+eyGn09zhSfcfoMnQqgD682tvtBAD+oPb7Z/q+FNbKH26Pq6Daw1Ap\ngDEy9AMabBoGx8db878OFk6eNEZliF38QqRplqr/hsjRq4nwkAAT3qj1zufTTN90\nbHhXt1BP7z5bDv0z1fp6duFzPbfyUEjkhbFKhOgJ6p01IOLnnXEn6TGM55sKqW1W\ndFX276anyb1gJm79E3gymnuwanC31QNSmlXpSNbiG8rbUfiJ40HfUf/lKrkBDQRU\nSQ5MAQgAnztdietX1+rTHqyjK9yDYP+rp5NBL2b98SzTjQpFv68cjevjYy/R7VqI\nrRx1qyrit470TYk9iOH2KBYscLLYylNCfpWfv2gIziZfX5Xxb+BxZPXkrq34ux5M\n1BHgGvZg8XvtJLSbX5SnkibUHSotCD578MgfWzfTfH4aeVwrZVCKb5BRxZHx0ZA9\nq9tNgYWoadhcKNWmT8yn28JW/ME91v1K/nSDJKd8qG9qOEy0XtGcYOZh6qZZcXI3\nBRpyqSUN4G2TvzccYeEbw6hHH2VjKCetkt9412+2Qg+IR8nTehwPuY3hOZqYghN/\nrT6nuPJSzb/UJ+G6vnb2r0I4To3WUQARAQABiQEfBBgBAgAJBQJUSQ5MAhsMAAoJ\nEGN/itugQn2VlBcH/2dRT5yxfS9nTTimfk/wnyXnB+XgqbYOr7H1LFueMulCTSrQ\nsIebVUyIKY2Txzm8UxswMBxRzoIMy8g6NxjwuUzm3/w2evdHdO1mwqNMsDNBskPD\nCzJE4TLftOeVq7Jh268yLFiAxaAbULyBzr7yLVAHrGBDhjediCoy9KHijIHdM04v\nnhgQkV6UpLez7Am5B0Pqd+0kFcoe3IW5cYvphhWVJ4O+w95jujrsvVB/+hg4VPgi\nKMgIdXIAmJQuflAU97ilYDIXXmdQV2yUOsnaMW8JBsu2hsxvk3n+M9BNS6GBk40w\nHGCOLDrMD1U4Zjwsu58qQGp4nQ9rR54y5El2iJU=\n=c2SM\n-----END PGP PUBLIC KEY BLOCK-----\n"
  },
  {
    "path": "tests/keys/public_diff.key",
    "content": "-----BEGIN PGP PUBLIC KEY BLOCK-----\nVersion: GnuPG v1\n\n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\nMTLctweH+r/pbR1m0mXfLKdjs2ApAG4zMoJdnK0=\n=dyCW\n-----END PGP PUBLIC KEY BLOCK-----"
  },
  {
    "path": "tests/models.py",
    "content": "from django.db import models\n\nfrom pgcrypto import fields\n\n\nclass EncryptedFKModel(models.Model):\n    \"\"\"Dummy model used to test FK decryption.\"\"\"\n    fk_pgp_sym_field = fields.TextPGPSymmetricKeyField(blank=True, null=True)\n\n    class Meta:\n        \"\"\"Sets up the meta for the test model.\"\"\"\n        app_label = 'tests'\n\n\nclass EncryptedModelManager(models.Manager):\n\n    def get_by_natural_key(self, email_pgp_pub_field):\n        \"\"\"Get by natual key of email pub field.\"\"\"\n        return self.get(email_pgp_pub_field=email_pgp_pub_field)\n\n\nclass EncryptedModel(models.Model):\n    \"\"\"Dummy model used for tests to check the fields.\"\"\"\n    digest_field = fields.TextDigestField(blank=True, null=True)\n    digest_with_original_field = fields.TextDigestField(blank=True, null=True,\n                                                        original='pgp_sym_field')\n    hmac_field = fields.TextHMACField(blank=True, null=True)\n    hmac_with_original_field = fields.TextHMACField(blank=True, null=True,\n                                                    original='pgp_sym_field')\n\n    email_pgp_pub_field = fields.EmailPGPPublicKeyField(blank=True, null=True,\n                                                        unique=True)\n    integer_pgp_pub_field = fields.IntegerPGPPublicKeyField(blank=True, null=True)\n    biginteger_pgp_pub_field = fields.BigIntegerPGPPublicKeyField(blank=True, null=True)\n    pgp_pub_field = fields.TextPGPPublicKeyField(blank=True, null=True)\n    char_pub_field = fields.CharPGPPublicKeyField(blank=True, null=True, max_length=15)\n    date_pgp_pub_field = fields.DatePGPPublicKeyField(blank=True, null=True)\n    datetime_pgp_pub_field = fields.DateTimePGPPublicKeyField(blank=True, null=True)\n    time_pgp_pub_field = fields.TimePGPPublicKeyField(blank=True, null=True)\n    decimal_pgp_pub_field = fields.DecimalPGPPublicKeyField(\n        max_digits=8, decimal_places=2, null=True, blank=True\n    )\n    float_pgp_pub_field = fields.FloatPGPPublicKeyField(blank=True, null=True)\n    boolean_pgp_pub_field = fields.BooleanPGPPublicKeyField(blank=True, null=True)\n\n    email_pgp_sym_field = fields.EmailPGPSymmetricKeyField(blank=True, null=True)\n    integer_pgp_sym_field = fields.IntegerPGPSymmetricKeyField(blank=True, null=True)\n    biginteger_pgp_sym_field = fields.BigIntegerPGPSymmetricKeyField(\n        blank=True, null=True\n    )\n    pgp_sym_field = fields.TextPGPSymmetricKeyField(blank=True, null=True)\n    char_sym_field = fields.CharPGPPublicKeyField(blank=True, null=True, max_length=15)\n    date_pgp_sym_field = fields.DatePGPSymmetricKeyField(blank=True, null=True)\n    datetime_pgp_sym_field = fields.DateTimePGPSymmetricKeyField(blank=True, null=True)\n    time_pgp_sym_field = fields.TimePGPSymmetricKeyField(blank=True, null=True)\n    decimal_pgp_sym_field = fields.DecimalPGPSymmetricKeyField(\n        max_digits=8, decimal_places=2, null=True, blank=True\n    )\n    float_pgp_sym_field = fields.FloatPGPSymmetricKeyField(blank=True, null=True)\n    boolean_pgp_sym_field = fields.BooleanPGPSymmetricKeyField(blank=True, null=True)\n\n    fk_model = models.ForeignKey(\n        EncryptedFKModel, blank=True, null=True, on_delete=models.CASCADE\n    )\n\n    objects = EncryptedModelManager()\n\n    class Meta:\n        \"\"\"Sets up the meta for the test model.\"\"\"\n        app_label = 'tests'\n\n\nclass EncryptedDateTime(models.Model):\n    value = fields.DateTimePGPSymmetricKeyField()\n\n\nclass RelatedDateTime(models.Model):\n    related = models.ForeignKey(\n        EncryptedDateTime,\n        on_delete=models.CASCADE,\n        related_name='related')\n    related_again = models.ForeignKey(\n        EncryptedDateTime, null=True,\n        on_delete=models.CASCADE, related_name='related_again'\n    )\n"
  },
  {
    "path": "tests/run.py",
    "content": "#! /usr/bin/env python\n\"\"\"From http://stackoverflow.com/a/12260597/400691.\"\"\"\nimport os\nimport sys\n\nimport dj_database_url\nimport django\nfrom colour_runner.django_runner import ColourRunnerMixin\nfrom django.conf import settings\nfrom django.test.runner import DiscoverRunner\n\nBASEDIR = os.path.dirname(os.path.dirname(__file__))\nPUBLIC_PGP_KEY_PATH = os.path.abspath(\n    os.path.join(BASEDIR, 'tests/keys/public.key')\n)\nPRIVATE_PGP_KEY_PATH = os.path.abspath(\n    os.path.join(BASEDIR, 'tests/keys/private.key')\n)\nDIFF_PUBLIC_PGP_KEY_PATH = os.path.abspath(\n    os.path.join(BASEDIR, 'tests/keys/public_diff.key')\n)\nDIFF_PRIVATE_PGP_KEY_PATH = os.path.abspath(\n    os.path.join(BASEDIR, 'tests/keys/private_diff.key')\n)\n\ndiff_keys = dj_database_url.config(\n    default='postgres://localhost/pgcrypto_fields_diff'\n)\n\n# Cannot chain onto the config() call due to error\ndiff_keys.update({\n    'PUBLIC_PGP_KEY': open(DIFF_PUBLIC_PGP_KEY_PATH, 'r').read(),\n    'PRIVATE_PGP_KEY': open(DIFF_PRIVATE_PGP_KEY_PATH, 'r').read(),\n    'PGCRYPTO_KEY': 'djangorocks',\n})\n\nsettings.configure(\n    DATABASES={\n        'default': dj_database_url.config(\n            default='postgres://localhost/pgcrypto_fields'\n        ),\n        'diff_keys': diff_keys,\n    },\n    INSTALLED_APPS=(\n        'pgcrypto',\n        \"tests.diff_keys\",\n        'tests',\n    ),\n    DATABASE_ROUTERS=('dbrouters.TestRouter',),\n    MIDDLEWARE_CLASSES=(),\n    PUBLIC_PGP_KEY=open(PUBLIC_PGP_KEY_PATH, 'r').read(),\n    PRIVATE_PGP_KEY=open(PRIVATE_PGP_KEY_PATH, 'r').read(),\n    PGCRYPTO_KEY='ultrasecret',\n    DEBUG=True,\n)\ndjango.setup()\n\n\nclass TestRunner(ColourRunnerMixin, DiscoverRunner):\n    \"\"\"Enable colorised output.\"\"\"\n\n\ntest_runner = TestRunner(verbosity=1)\nfailures = test_runner.run_tests(['tests'])\nif failures:\n    sys.exit(1)\n"
  },
  {
    "path": "tests/test_fields.py",
    "content": "from datetime import date, datetime\nfrom decimal import Decimal\nfrom unittest.mock import MagicMock\n\nfrom django import VERSION as DJANGO_VERSION\nfrom django.conf import settings\nfrom django.db import connections, models, reset_queries\nfrom django.test import TestCase\nfrom incuna_test_utils.utils import field_names\n\nfrom pgcrypto import fields\nfrom .diff_keys.models import EncryptedDiff\nfrom .factories import EncryptedFKModelFactory, EncryptedModelFactory\nfrom .forms import EncryptedForm\nfrom .models import EncryptedDateTime, EncryptedFKModel, \\\n    EncryptedModel, RelatedDateTime\n\nKEYED_FIELDS = (fields.TextDigestField, fields.TextHMACField)\nEMAIL_PGP_FIELDS = (fields.EmailPGPPublicKeyField, fields.EmailPGPSymmetricKeyField)\nPGP_FIELDS = EMAIL_PGP_FIELDS + (\n    fields.DatePGPSymmetricKeyField,\n    fields.DateTimePGPSymmetricKeyField,\n    fields.IntegerPGPPublicKeyField,\n    fields.IntegerPGPSymmetricKeyField,\n    fields.TextPGPPublicKeyField,\n    fields.TextPGPSymmetricKeyField,\n    fields.BooleanPGPPublicKeyField,\n    fields.BooleanPGPSymmetricKeyField,\n)\n\n\nclass TestTextFieldHash(TestCase):\n    \"\"\"Test hash fields behave properly.\"\"\"\n    def test_get_placeholder(self):\n        \"\"\"Assert `get_placeholder` hash value only once.\"\"\"\n        for field in KEYED_FIELDS:\n            with self.subTest(field=field):\n                placeholder = field().get_placeholder('\\\\x')\n                self.assertEqual(placeholder, '%s')\n\n\nclass TestPGPMixin(TestCase):\n    databases = '__all__'\n    \"\"\"Test `PGPMixin` behave properly.\"\"\"\n    def test_check(self):\n        \"\"\"Assert `max_length` check does not return any error.\"\"\"\n        for field in PGP_FIELDS:\n            with self.subTest(field=field):\n                field.model = MagicMock()\n                self.assertEqual(field(name='field').check(), [])\n\n    def test_db_type(self):\n        \"\"\"Check db_type is `bytea`.\"\"\"\n        for field in PGP_FIELDS:\n            with self.subTest(field=field):\n                self.assertEqual(field().db_type(), 'bytea')\n\n\nclass TestEmailPGPMixin(TestCase):\n    \"\"\"Test emails fields behave properly.\"\"\"\n    def test_max_length_validator(self):\n        \"\"\"Check `MaxLengthValidator` is not set.\"\"\"\n        for field in EMAIL_PGP_FIELDS:\n            with self.subTest(field=field):\n                field_validated = field().run_validators(value='value@value.com')\n                self.assertEqual(field_validated, None)\n\n\nclass TestEncryptedTextFieldModel(TestCase):\n    databases = '__all__'\n    \"\"\"Test `EncryptedTextField` can be integrated in a `Django` model.\"\"\"\n    model = EncryptedModel\n\n    # You have to do it here or queries is empty\n    settings.DEBUG = True\n\n    def test_fields(self):\n        \"\"\"Assert fields are representing our model.\"\"\"\n        fields = field_names(self.model)\n        expected = (\n            'id',\n            'digest_field',\n            'digest_with_original_field',\n            'hmac_field',\n            'hmac_with_original_field',\n            'email_pgp_pub_field',\n            'integer_pgp_pub_field',\n            'biginteger_pgp_pub_field',\n            'pgp_pub_field',\n            'char_pub_field',\n            'decimal_pgp_pub_field',\n            'email_pgp_sym_field',\n            'integer_pgp_sym_field',\n            'biginteger_pgp_sym_field',\n            'pgp_sym_field',\n            'char_sym_field',\n            'date_pgp_sym_field',\n            'datetime_pgp_sym_field',\n            'time_pgp_sym_field',\n            'date_pgp_pub_field',\n            'datetime_pgp_pub_field',\n            'time_pgp_pub_field',\n            'decimal_pgp_sym_field',\n            'float_pgp_pub_field',\n            'float_pgp_sym_field',\n            'boolean_pgp_pub_field',\n            'boolean_pgp_sym_field',\n            'fk_model',\n        )\n        self.assertCountEqual(fields, expected)\n\n    def test_value_returned_is_not_bytea(self):\n        \"\"\"Assert value returned is not a memoryview instance.\"\"\"\n        EncryptedModelFactory.create()\n\n        instance = self.model.objects.get()\n        self.assertIsInstance(instance.digest_field, str)\n        self.assertIsInstance(instance.hmac_field, str)\n\n        self.assertIsInstance(instance.email_pgp_pub_field, str)\n        self.assertIsInstance(instance.integer_pgp_pub_field, int)\n        self.assertIsInstance(instance.biginteger_pgp_pub_field, int)\n        self.assertIsInstance(instance.pgp_pub_field, str)\n        self.assertIsInstance(instance.date_pgp_pub_field, date)\n        self.assertIsInstance(instance.datetime_pgp_pub_field, datetime)\n\n        self.assertIsInstance(instance.email_pgp_sym_field, str)\n        self.assertIsInstance(instance.integer_pgp_sym_field, int)\n        self.assertIsInstance(instance.biginteger_pgp_sym_field, int)\n        self.assertIsInstance(instance.pgp_sym_field, str)\n        self.assertIsInstance(instance.date_pgp_sym_field, date)\n        self.assertIsInstance(instance.datetime_pgp_sym_field, datetime)\n\n    def test_value_query(self):\n        \"\"\"Assert querying the field's value is making zero queries.\"\"\"\n        expected = 'bonjour'\n        temp = None\n        EncryptedModelFactory.create(pgp_pub_field=expected)\n\n        instance = self.model.objects.get()\n\n        with self.assertNumQueries(0):\n            temp = instance.pgp_pub_field\n\n        self.assertEqual(expected, temp)\n\n    def test_value_pgp_pub(self):\n        \"\"\"Assert we can get back the decrypted value.\"\"\"\n        expected = 'bonjour'\n        EncryptedModelFactory.create(pgp_pub_field=expected)\n\n        instance = self.model.objects.get()\n        value = instance.pgp_pub_field\n\n        self.assertEqual(value, expected)\n\n    def test_value_pgp_pub_multiple(self):\n        \"\"\"Assert we get back the correct value when the table contains data.\"\"\"\n        expected = 'bonjour'\n        EncryptedModelFactory.create(pgp_pub_field='au revoir')\n        created = EncryptedModelFactory.create(pgp_pub_field=expected)\n\n        instance = self.model.objects.get(pk=created.pk)\n        value = instance.pgp_pub_field\n\n        self.assertEqual(value, expected)\n\n    def test_value_pgp_sym(self):\n        \"\"\"Assert we can get back the decrypted value.\"\"\"\n        expected = 'bonjour'\n        EncryptedModelFactory.create(pgp_sym_field=expected)\n\n        instance = self.model.objects.get()\n        value = instance.pgp_sym_field\n\n        self.assertEqual(value, expected)\n\n    def test_instance_not_saved(self):\n        \"\"\"Assert not saved instance return the value to be encrypted.\"\"\"\n        expected = 'bonjour'\n        instance = EncryptedModelFactory.build(pgp_pub_field=expected)\n        self.assertEqual(instance.pgp_pub_field, expected)\n        self.assertEqual(instance.pgp_pub_field, expected)\n\n    def test_decrypt_filter(self):\n        \"\"\"Assert we can get filter the decrypted value.\"\"\"\n        expected = 'bonjour'\n        EncryptedModelFactory.create(\n            pgp_pub_field=expected,\n        )\n\n        queryset = self.model.objects.filter(\n            pgp_pub_field=expected\n        )\n\n        instance = queryset.first()\n        self.assertEqual(instance.pgp_pub_field, expected)\n\n        queryset = self.model.objects.filter(\n            pgp_pub_field__contains='jour'\n        )\n\n        instance = queryset.first()\n        self.assertEqual(instance.pgp_pub_field, expected)\n\n        queryset = self.model.objects.filter(\n            pgp_pub_field__startswith='bon'\n        )\n\n        instance = queryset.first()\n        self.assertEqual(instance.pgp_pub_field, expected)\n\n    def test_digest_lookup(self):\n        \"\"\"Assert we can filter a digest value.\"\"\"\n        value = 'bonjour'\n        expected = EncryptedModelFactory.create(digest_field=value)\n        EncryptedModelFactory.create()\n\n        queryset = EncryptedModel.objects.filter(digest_field__hash_of=value)\n\n        self.assertCountEqual(queryset, [expected])\n\n    def test_digest_with_original_lookup(self):\n        \"\"\"Assert we can filter a digest value.\"\"\"\n        value = 'bonjour'\n        expected = EncryptedModelFactory.create(pgp_sym_field=value)\n        EncryptedModelFactory.create()\n\n        queryset = EncryptedModel.objects.filter(\n            digest_with_original_field__hash_of=value\n        )\n        self.assertCountEqual(queryset, [expected])\n\n    def test_hmac_lookup(self):\n        \"\"\"Assert we can filter a digest value.\"\"\"\n        value = 'bonjour'\n        expected = EncryptedModelFactory.create(hmac_field=value)\n        EncryptedModelFactory.create()\n\n        queryset = EncryptedModel.objects.filter(hmac_field__hash_of=value)\n        self.assertCountEqual(queryset, [expected])\n\n    def test_hmac_with_original_lookup(self):\n        \"\"\"Assert we can filter a digest value.\"\"\"\n        value = 'bonjour'\n        expected = EncryptedModelFactory.create(pgp_sym_field=value)\n        EncryptedModelFactory.create()\n\n        queryset = EncryptedModel.objects.filter(hmac_with_original_field__hash_of=value)\n        self.assertCountEqual(queryset, [expected])\n\n    def test_default_lookup(self):\n        \"\"\"Assert default lookup can be called.\"\"\"\n        queryset = EncryptedModel.objects.filter(hmac_field__isnull=True)\n        self.assertFalse(queryset)\n\n    def test_update_attribute_digest_field(self):\n        \"\"\"Assert digest field can be updated through its attribute on the model.\"\"\"\n        expected = 'bonjour'\n        instance = EncryptedModelFactory.create()\n        instance.digest_field = expected\n        instance.save()\n\n        updated_instance = self.model.objects.filter(digest_field__hash_of=expected)\n        self.assertEqual(updated_instance.first(), instance)\n\n    def test_update_attribute_hmac_field(self):\n        \"\"\"Assert hmac field can be updated through its attribute on the model.\"\"\"\n        expected = 'bonjour'\n        instance = EncryptedModelFactory.create()\n        instance.hmac_field = expected\n        instance.save()\n\n        updated_instance = self.model.objects.filter(hmac_field__hash_of=expected)\n        self.assertEqual(updated_instance.first(), instance)\n\n    def test_update_attribute_pgp_pub_field(self):\n        \"\"\"Assert pgp field can be updated through its attribute on the model.\"\"\"\n        expected = 'bonjour'\n        instance = EncryptedModelFactory.create()\n        instance.pgp_pub_field = expected\n        instance.save()\n\n        updated_instance = self.model.objects.get()\n        self.assertEqual(updated_instance.pgp_pub_field, expected)\n\n    def test_update_attribute_pgp_sym_field(self):\n        \"\"\"Assert pgp field can be updated through its attribute on the model.\"\"\"\n        expected = 'bonjour'\n        instance = EncryptedModelFactory.create()\n        instance.pgp_sym_field = expected\n        instance.save()\n\n        updated_instance = self.model.objects.get()\n        self.assertEqual(updated_instance.pgp_sym_field, expected)\n\n    def test_update_one_attribute(self):\n        \"\"\"Assert value are not overriden when updating one attribute.\"\"\"\n        expected = 'initial value'\n        new_value = 'new_value'\n\n        instance = EncryptedModelFactory.create(\n            pgp_pub_field=expected,\n            pgp_sym_field=expected,\n            digest_field=expected,\n            hmac_field=expected,\n        )\n        instance.pgp_sym_field = new_value\n        instance.save()\n\n        updated_instance = self.model.objects.get()\n        self.assertEqual(updated_instance.pgp_pub_field, expected)\n        self.assertEqual(updated_instance.pgp_sym_field, new_value)\n\n        updated_instance = self.model.objects.filter(\n            digest_field__hash_of=expected,\n            hmac_field__hash_of=expected,\n        )\n        self.assertEqual(updated_instance.first(), instance)\n\n    def test_pgp_public_key_negative_number(self):\n        \"\"\"\n        Assert negative value is saved with Public Key integer fields.\n\n        * `IntegerPGPPublicKeyField`\n        * `BigIntegerPGPSymmetricKeyField`\n        \"\"\"\n        expected = -2147483648\n        instance = EncryptedModelFactory.create(integer_pgp_pub_field=expected)\n\n        self.assertEqual(instance.integer_pgp_pub_field, expected)\n\n        expected = -9223372036854775808\n        instance = EncryptedModelFactory.create(biginteger_pgp_pub_field=expected)\n\n        self.assertEqual(instance.biginteger_pgp_pub_field, expected)\n\n    def test_pgp_symmetric_key_negative_number(self):\n        \"\"\"\n        Assert negative value is saved with Symmetric Key fields.\n\n        * `IntegerPGPSymmetricKeyField`\n        * `BigIntegerPGPSymmetricKeyField`\n        \"\"\"\n        expected = -2147483648\n        instance = EncryptedModelFactory.create(integer_pgp_sym_field=expected)\n\n        self.assertEqual(instance.integer_pgp_sym_field, expected)\n\n        expected = -9223372036854775808\n        instance = EncryptedModelFactory.create(biginteger_pgp_sym_field=expected)\n\n        self.assertEqual(instance.biginteger_pgp_sym_field, expected)\n\n    def test_pgp_symmetric_key_date(self):\n        \"\"\"Assert date is save with an `DatePGPSymmetricKeyField` field.\"\"\"\n        expected = date.today()\n        instance = EncryptedModelFactory.create(date_pgp_sym_field=expected)\n        instance.refresh_from_db()  # Ensure the PGSQL casting works right\n\n        self.assertEqual(instance.date_pgp_sym_field, expected)\n\n        instance = EncryptedModel.objects.get(pk=instance.id)\n\n        self.assertEqual(instance.date_pgp_sym_field, expected)\n\n    def test_pgp_pub_key_date(self):\n        \"\"\"Assert date is save with an `DatePGPPublicKeyField` field.\"\"\"\n        expected = date.today()\n        instance = EncryptedModelFactory.create(date_pgp_pub_field=expected)\n        instance.refresh_from_db()  # Ensure the PGSQL casting works right\n\n        self.assertEqual(instance.date_pgp_pub_field, expected)\n\n        instance = EncryptedModel.objects.get(pk=instance.id)\n\n        self.assertEqual(instance.date_pgp_pub_field, expected)\n\n    def test_pgp_symmetric_key_date_form(self):\n        \"\"\"Assert form field and widget for `DateTimePGPSymmetricKeyField` field.\"\"\"\n        expected = date.today()\n        instance = EncryptedModelFactory.create(date_pgp_sym_field=expected)\n        instance.refresh_from_db()  # Ensure the PGSQL casting works right\n\n        payload = {\n            'date_pgp_sym_field': '08/01/2016'\n        }\n\n        form = EncryptedForm(payload, instance=instance)\n        self.assertTrue(form.is_valid())\n\n        cleaned_data = form.cleaned_data\n\n        self.assertTrue(\n            cleaned_data['date_pgp_sym_field'],\n            date(2016, 8, 1)\n        )\n\n    def test_pgp_symmetric_key_datetime_form(self):\n        \"\"\"Assert form field and widget for `DateTimePGPSymmetricKeyField` field.\"\"\"\n        expected = datetime.now()\n        instance = EncryptedModelFactory.create(datetime_pgp_sym_field=expected)\n        instance.refresh_from_db()  # Ensure the PGSQL casting works right\n\n        payload = {\n            'datetime_pgp_sym_field': '08/01/2016 14:00'\n        }\n\n        form = EncryptedForm(payload, instance=instance)\n        self.assertTrue(form.is_valid())\n\n        cleaned_data = form.cleaned_data\n\n        self.assertTrue(\n            cleaned_data['datetime_pgp_sym_field'],\n            datetime(2016, 8, 1, 14, 0, 0)\n        )\n\n    def test_pgp_symmetric_key_time(self):\n        \"\"\"Assert date is save with an `TimePGPSymmetricKeyField` field.\"\"\"\n        expected = datetime.now().time()\n        instance = EncryptedModelFactory.create(time_pgp_sym_field=expected)\n        instance.refresh_from_db()  # Ensure the PGSQL casting works right\n\n        self.assertEqual(instance.time_pgp_sym_field, expected)\n\n        instance = EncryptedModel.objects.get(pk=instance.id)\n\n        self.assertEqual(instance.time_pgp_sym_field, expected)\n\n    def test_pgp_pub_key_time(self):\n        \"\"\"Assert date is save with an `TimePGPPublicKeyField` field.\"\"\"\n        expected = datetime.now().time()\n        instance = EncryptedModelFactory.create(time_pgp_pub_field=expected)\n        instance.refresh_from_db()  # Ensure the PGSQL casting works right\n\n        self.assertEqual(instance.time_pgp_pub_field, expected)\n\n        instance = EncryptedModel.objects.get(pk=instance.id)\n\n        self.assertEqual(instance.time_pgp_pub_field, expected)\n\n    def test_pgp_symmetric_key_time_form(self):\n        \"\"\"Assert form field and widget for `TimePGPSymmetricKeyField` field.\"\"\"\n        expected = datetime.now().time()\n        instance = EncryptedModelFactory.create(time_pgp_sym_field=expected)\n        instance.refresh_from_db()  # Ensure the PGSQL casting works right\n\n        payload = {\n            'time_pgp_sym_field': '{}'.format(expected)\n        }\n\n        form = EncryptedForm(payload, instance=instance)\n        self.assertTrue(form.is_valid())\n\n        cleaned_data = form.cleaned_data\n\n        self.assertTrue(\n            cleaned_data['time_pgp_sym_field'],\n            expected\n        )\n\n    def test_pgp_public_key_time_form(self):\n        \"\"\"Assert form field and widget for `TimePGPSymmetricKeyField` field.\"\"\"\n        expected = datetime.now().time()\n        instance = EncryptedModelFactory.create(time_pgp_pub_field=expected)\n        instance.refresh_from_db()  # Ensure the PGSQL casting works right\n\n        payload = {\n            'time_pgp_pub_field': '{}'.format(expected)\n        }\n\n        form = EncryptedForm(payload, instance=instance)\n        self.assertTrue(form.is_valid())\n\n        cleaned_data = form.cleaned_data\n\n        self.assertTrue(\n            cleaned_data['time_pgp_pub_field'],\n            expected\n        )\n\n    def test_pgp_public_key_char_field(self):\n        \"\"\"Test public key CharField.\"\"\"\n        expect = 'Peter'\n        EncryptedModelFactory.create(char_pub_field=expect)\n\n        instance = EncryptedModel.objects.get()\n\n        self.assertTrue(\n            instance.char_pub_field,\n            expect\n        )\n\n        payload = {\n            'char_pub_field': 'This is beyond 15 max length'\n        }\n\n        form = EncryptedForm(payload, instance=instance)\n        is_valid = form.is_valid()\n        errors = form.errors.as_data()\n        self.assertFalse(is_valid)\n        self.assertTrue(1, len(errors['char_pub_field']))\n\n    def test_pgp_symmetric_key_char_field(self):\n        \"\"\"Test symmetric key CharField.\"\"\"\n        expect = 'Peter'\n        EncryptedModelFactory.create(char_sym_field=expect)\n\n        instance = EncryptedModel.objects.get()\n\n        self.assertTrue(\n            instance.char_sym_field,\n            expect\n        )\n\n        payload = {\n            'char_sym_field': 'This is beyond 15 max length'\n        }\n\n        form = EncryptedForm(payload, instance=instance)\n        is_valid = form.is_valid()\n        errors = form.errors.as_data()\n        self.assertFalse(is_valid)\n        self.assertTrue(1, len(errors['char_sym_field']))\n\n    def test_pgp_symmetric_key_date_lookups(self):\n        \"\"\"Assert lookups `DatePGPSymmetricKeyField` field.\"\"\"\n        EncryptedModelFactory.create(date_pgp_sym_field=date(2016, 7, 1))\n        EncryptedModelFactory.create(date_pgp_sym_field=date(2016, 8, 1))\n        EncryptedModelFactory.create(date_pgp_sym_field=date(2016, 9, 1))\n\n        # EXACT\n        self.assertEqual(\n            1,\n            EncryptedModel.objects.filter(\n                date_pgp_sym_field__exact=date(2016, 8, 1)\n            ).count()\n        )\n        self.assertEqual(\n            0,\n            EncryptedModel.objects.filter(\n                date_pgp_sym_field__exact=date(2016, 8, 2)\n            ).count()\n        )\n\n        # GT\n        self.assertEqual(\n            1,\n            EncryptedModel.objects.filter(\n                date_pgp_sym_field__gt=date(2016, 8, 1)\n            ).count()\n        )\n        self.assertEqual(\n            0,\n            EncryptedModel.objects.filter(\n                date_pgp_sym_field__gt=date(2016, 10, 1)\n            ).count()\n        )\n\n        # GTE\n        self.assertEqual(\n            2,\n            EncryptedModel.objects.filter(\n                date_pgp_sym_field__gte=date(2016, 8, 1)\n            ).count()\n        )\n        self.assertEqual(\n            0,\n            EncryptedModel.objects.filter(\n                date_pgp_sym_field__gte=date(2016, 10, 1)\n            ).count()\n        )\n\n        # LE\n        self.assertEqual(\n            1,\n            EncryptedModel.objects.filter(\n                date_pgp_sym_field__lt=date(2016, 8, 1)\n            ).count()\n        )\n        self.assertEqual(\n            0,\n            EncryptedModel.objects.filter(\n                date_pgp_sym_field__lt=date(2016, 6, 1)\n            ).count()\n        )\n\n        # LTE\n        self.assertEqual(\n            2,\n            EncryptedModel.objects.filter(\n                date_pgp_sym_field__lte=date(2016, 8, 1)\n            ).count()\n        )\n        self.assertEqual(\n            0,\n            EncryptedModel.objects.filter(\n                date_pgp_sym_field__lte=date(2016, 6, 1)\n            ).count()\n        )\n\n        # RANGE\n        self.assertEqual(\n            3,\n            EncryptedModel.objects.filter(\n                date_pgp_sym_field__range=[date(2016, 6, 1), date(2016, 11, 1)]\n            ).count()\n        )\n\n        self.assertEqual(\n            2,\n            EncryptedModel.objects.filter(\n                date_pgp_sym_field__range=[date(2016, 7, 1), date(2016, 8, 1)]\n            ).count()\n        )\n\n        self.assertEqual(\n            0,\n            EncryptedModel.objects.filter(\n                date_pgp_sym_field__range=[date(2016, 10, 2), None]\n            ).count()\n        )\n\n    def test_pgp_pub_key_date_lookups(self):\n        \"\"\"Assert lookups `DatePGPPublicKeyField` field.\"\"\"\n        EncryptedModelFactory.create(date_pgp_pub_field=date(2016, 7, 1))\n        EncryptedModelFactory.create(date_pgp_pub_field=date(2016, 8, 1))\n        EncryptedModelFactory.create(date_pgp_pub_field=date(2016, 9, 1))\n\n        # EXACT\n        self.assertEqual(\n            1,\n            EncryptedModel.objects.filter(\n                date_pgp_pub_field__exact=date(2016, 8, 1)\n            ).count()\n        )\n        self.assertEqual(\n            0,\n            EncryptedModel.objects.filter(\n                date_pgp_pub_field__exact=date(2016, 8, 2)\n            ).count()\n        )\n\n        # GT\n        self.assertEqual(\n            1,\n            EncryptedModel.objects.filter(\n                date_pgp_pub_field__gt=date(2016, 8, 1)\n            ).count()\n        )\n        self.assertEqual(\n            0,\n            EncryptedModel.objects.filter(\n                date_pgp_pub_field__gt=date(2016, 10, 1)\n            ).count()\n        )\n\n        # GTE\n        self.assertEqual(\n            2,\n            EncryptedModel.objects.filter(\n                date_pgp_pub_field__gte=date(2016, 8, 1)\n            ).count()\n        )\n        self.assertEqual(\n            0,\n            EncryptedModel.objects.filter(\n                date_pgp_pub_field__gte=date(2016, 10, 1)\n            ).count()\n        )\n\n        # LE\n        self.assertEqual(\n            1,\n            EncryptedModel.objects.filter(\n                date_pgp_pub_field__lt=date(2016, 8, 1)\n            ).count()\n        )\n        self.assertEqual(\n            0,\n            EncryptedModel.objects.filter(\n                date_pgp_pub_field__lt=date(2016, 6, 1)\n            ).count()\n        )\n\n        # LTE\n        self.assertEqual(\n            2,\n            EncryptedModel.objects.filter(\n                date_pgp_pub_field__lte=date(2016, 8, 1)\n            ).count()\n        )\n        self.assertEqual(\n            0,\n            EncryptedModel.objects.filter(\n                date_pgp_pub_field__lte=date(2016, 6, 1)\n            ).count()\n        )\n\n        # RANGE\n        self.assertEqual(\n            3,\n            EncryptedModel.objects.filter(\n                date_pgp_pub_field__range=[date(2016, 6, 1), date(2016, 11, 1)]\n            ).count()\n        )\n\n        self.assertEqual(\n            2,\n            EncryptedModel.objects.filter(\n                date_pgp_pub_field__range=[date(2016, 7, 1), date(2016, 8, 1)]\n            ).count()\n        )\n\n        self.assertEqual(\n            0,\n            EncryptedModel.objects.filter(\n                date_pgp_pub_field__range=[date(2016, 10, 2), None]\n            ).count()\n        )\n\n    def test_pgp_symmetric_key_datetime_lookups(self):\n        \"\"\"Assert lookups `DateTimePGPSymmetricKeyField` field.\"\"\"\n        EncryptedModelFactory.create(datetime_pgp_sym_field=datetime(2016, 7, 1, 0, 0, 0))\n        EncryptedModelFactory.create(datetime_pgp_sym_field=datetime(2016, 8, 1, 0, 0, 0))\n        EncryptedModelFactory.create(datetime_pgp_sym_field=datetime(2016, 9, 1, 0, 0, 0))\n\n        # EXACT\n        self.assertEqual(\n            1,\n            EncryptedModel.objects.filter(\n                datetime_pgp_sym_field__exact=datetime(2016, 8, 1, 0, 0, 0)\n            ).count()\n        )\n        self.assertEqual(\n            0,\n            EncryptedModel.objects.filter(\n                datetime_pgp_sym_field__exact=datetime(2016, 8, 1, 0, 0, 1)\n            ).count()\n        )\n\n        # GT\n        self.assertEqual(\n            1,\n            EncryptedModel.objects.filter(\n                datetime_pgp_sym_field__gt=datetime(2016, 8, 1, 0, 0, 0)\n            ).count()\n        )\n        self.assertEqual(\n            0,\n            EncryptedModel.objects.filter(\n                datetime_pgp_sym_field__gt=datetime(2016, 10, 1, 0, 0, 0)\n            ).count()\n        )\n\n        # GTE\n        self.assertEqual(\n            2,\n            EncryptedModel.objects.filter(\n                datetime_pgp_sym_field__gte=datetime(2016, 8, 1, 0, 0, 0)\n            ).count()\n        )\n        self.assertEqual(\n            0,\n            EncryptedModel.objects.filter(\n                datetime_pgp_sym_field__gte=datetime(2016, 10, 1, 0, 0, 0)\n            ).count()\n        )\n\n        # LE\n        self.assertEqual(\n            1,\n            EncryptedModel.objects.filter(\n                datetime_pgp_sym_field__lt=datetime(2016, 8, 1, 0, 0, 0)\n            ).count()\n        )\n        self.assertEqual(\n            0,\n            EncryptedModel.objects.filter(\n                datetime_pgp_sym_field__lt=datetime(2016, 6, 1, 0, 0, 0)\n            ).count()\n        )\n\n        # LTE\n        self.assertEqual(\n            2,\n            EncryptedModel.objects.filter(\n                datetime_pgp_sym_field__lte=datetime(2016, 8, 1, 0, 0, 0)\n            ).count()\n        )\n        self.assertEqual(\n            0,\n            EncryptedModel.objects.filter(\n                datetime_pgp_sym_field__lte=datetime(2016, 6, 1, 0, 0, 0)\n            ).count()\n        )\n\n        # RANGE\n        self.assertEqual(\n            3,\n            EncryptedModel.objects.filter(\n                datetime_pgp_sym_field__range=[\n                    datetime(2016, 6, 1, 0, 0, 0),\n                    datetime(2016, 11, 1, 23, 59, 59)\n                ]\n            ).count()\n        )\n\n        self.assertEqual(\n            2,\n            EncryptedModel.objects.filter(\n                datetime_pgp_sym_field__range=[\n                    datetime(2016, 7, 1, 0, 0, 0),\n                    datetime(2016, 8, 1, 0, 0, 0)\n                ]\n            ).count()\n        )\n\n        self.assertEqual(\n            0,\n            EncryptedModel.objects.filter(\n                datetime_pgp_sym_field__range=[\n                    datetime(2016, 10, 1, 0, 0, 1),\n                    None\n                ]\n            ).count()\n        )\n\n    def test_pgp_public_key_datetime_lookups(self):\n        \"\"\"Assert lookups `DateTimePGPPublicKeyField` field.\"\"\"\n        EncryptedModelFactory.create(datetime_pgp_pub_field=datetime(2016, 7, 1, 0, 0, 0))\n        EncryptedModelFactory.create(datetime_pgp_pub_field=datetime(2016, 8, 1, 0, 0, 0))\n        EncryptedModelFactory.create(datetime_pgp_pub_field=datetime(2016, 9, 1, 0, 0, 0))\n\n        # EXACT\n        self.assertEqual(\n            1,\n            EncryptedModel.objects.filter(\n                datetime_pgp_pub_field__exact=datetime(2016, 8, 1, 0, 0, 0)\n            ).count()\n        )\n        self.assertEqual(\n            0,\n            EncryptedModel.objects.filter(\n                datetime_pgp_pub_field__exact=datetime(2016, 8, 1, 0, 0, 1)\n            ).count()\n        )\n\n        # GT\n        self.assertEqual(\n            1,\n            EncryptedModel.objects.filter(\n                datetime_pgp_pub_field__gt=datetime(2016, 8, 1, 0, 0, 0)\n            ).count()\n        )\n        self.assertEqual(\n            0,\n            EncryptedModel.objects.filter(\n                datetime_pgp_pub_field__gt=datetime(2016, 10, 1, 0, 0, 0)\n            ).count()\n        )\n\n        # GTE\n        self.assertEqual(\n            2,\n            EncryptedModel.objects.filter(\n                datetime_pgp_pub_field__gte=datetime(2016, 8, 1, 0, 0, 0)\n            ).count()\n        )\n        self.assertEqual(\n            0,\n            EncryptedModel.objects.filter(\n                datetime_pgp_pub_field__gte=datetime(2016, 10, 1, 0, 0, 0)\n            ).count()\n        )\n\n        # LE\n        self.assertEqual(\n            1,\n            EncryptedModel.objects.filter(\n                datetime_pgp_pub_field__lt=datetime(2016, 8, 1, 0, 0, 0)\n            ).count()\n        )\n        self.assertEqual(\n            0,\n            EncryptedModel.objects.filter(\n                datetime_pgp_pub_field__lt=datetime(2016, 6, 1, 0, 0, 0)\n            ).count()\n        )\n\n        # LTE\n        self.assertEqual(\n            2,\n            EncryptedModel.objects.filter(\n                datetime_pgp_pub_field__lte=datetime(2016, 8, 1, 0, 0, 0)\n            ).count()\n        )\n        self.assertEqual(\n            0,\n            EncryptedModel.objects.filter(\n                datetime_pgp_pub_field__lte=datetime(2016, 6, 1, 0, 0, 0)\n            ).count()\n        )\n\n        # RANGE\n        self.assertEqual(\n            3,\n            EncryptedModel.objects.filter(\n                datetime_pgp_pub_field__range=[\n                    datetime(2016, 6, 1, 0, 0, 0),\n                    datetime(2016, 11, 1, 23, 59, 59)\n                ]\n            ).count()\n        )\n\n        self.assertEqual(\n            2,\n            EncryptedModel.objects.filter(\n                datetime_pgp_pub_field__range=[\n                    datetime(2016, 7, 1, 0, 0, 0),\n                    datetime(2016, 8, 1, 0, 0, 0)\n                ]\n            ).count()\n        )\n\n        self.assertEqual(\n            0,\n            EncryptedModel.objects.filter(\n                datetime_pgp_pub_field__range=[\n                    datetime(2016, 10, 1, 0, 0, 1),\n                    None\n                ]\n            ).count()\n        )\n\n    def test_decimal_pgp_pub_field(self):\n        \"\"\"Test DecimalPGPPublicKeyField.\"\"\"\n        expected = '100000.99'\n        EncryptedModelFactory.create(decimal_pgp_pub_field=expected)\n\n        instance = EncryptedModel.objects.get()\n\n        self.assertIsInstance(\n            instance.decimal_pgp_pub_field,\n            Decimal\n        )\n\n        self.assertEqual(\n            instance.decimal_pgp_pub_field,\n            Decimal(expected)\n        )\n\n        items = EncryptedModel.objects.filter(decimal_pgp_pub_field__gte='100')\n\n        self.assertEqual(\n            1,\n            len(items)\n        )\n\n        items = EncryptedModel.objects.filter(decimal_pgp_pub_field__gte='100001.00')\n\n        self.assertEqual(\n            0,\n            len(items)\n        )\n\n    def test_decimal_pgp_sym_field(self):\n        \"\"\"Test DecimalPGPSymmetricKeyField.\"\"\"\n        expected = '100000.99'\n        EncryptedModelFactory.create(decimal_pgp_sym_field=expected)\n\n        instance = EncryptedModel.objects.get()\n\n        self.assertIsInstance(\n            instance.decimal_pgp_sym_field,\n            Decimal\n        )\n\n        self.assertEqual(\n            instance.decimal_pgp_sym_field,\n            Decimal(expected)\n        )\n\n        items = EncryptedModel.objects.filter(decimal_pgp_sym_field__gte='100')\n\n        self.assertEqual(\n            1,\n            len(items)\n        )\n\n        items = EncryptedModel.objects.filter(decimal_pgp_sym_field__gte='100001.00')\n\n        self.assertEqual(\n            0,\n            len(items)\n        )\n\n    def test_pgp_public_key_decimal_form(self):\n        \"\"\"Assert form field and widget for `DecimalPGPSymmetricKeyField` field.\"\"\"\n        expected = '100000.99'\n        instance = EncryptedModelFactory.create(decimal_pgp_pub_field=expected)\n\n        payload = {\n            'decimal_pgp_pub_field': expected\n        }\n\n        form = EncryptedForm(payload, instance=instance)\n        self.assertTrue(form.is_valid())\n\n        cleaned_data = form.cleaned_data\n\n        self.assertTrue(\n            cleaned_data['decimal_pgp_pub_field'],\n            Decimal(expected)\n        )\n\n    def test_pgp_symmetric_key_decimal_form(self):\n        \"\"\"Assert form field and widget for `DecimalPGPSymmetricKeyField` field.\"\"\"\n        expected = '100000.99'\n        instance = EncryptedModelFactory.create(decimal_pgp_sym_field=expected)\n\n        payload = {\n            'decimal_pgp_sym_field': expected\n        }\n\n        form = EncryptedForm(payload, instance=instance)\n        self.assertTrue(form.is_valid())\n\n        cleaned_data = form.cleaned_data\n\n        self.assertTrue(\n            cleaned_data['decimal_pgp_sym_field'],\n            Decimal(expected)\n        )\n\n    def test_float_pgp_pub_field(self):\n        \"\"\"Test FloatPGPPublicKeyField.\"\"\"\n        expected = 1234.6788\n        EncryptedModelFactory.create(float_pgp_pub_field=expected)\n\n        instance = EncryptedModel.objects.get()\n\n        self.assertIsInstance(\n            instance.float_pgp_pub_field,\n            float\n        )\n\n        self.assertEqual(\n            instance.float_pgp_pub_field,\n            expected\n        )\n\n        items = EncryptedModel.objects.filter(float_pgp_pub_field__gte='100')\n\n        self.assertEqual(\n            1,\n            len(items)\n        )\n\n        items = EncryptedModel.objects.filter(float_pgp_pub_field__gte='100001.00')\n\n        self.assertEqual(\n            0,\n            len(items)\n        )\n\n    def test_float_pgp_sym_field(self):\n        \"\"\"Test FloatPGPSymmetricKeyField.\"\"\"\n        expected = float(1234.6788)\n        EncryptedModelFactory.create(float_pgp_sym_field=expected)\n\n        instance = EncryptedModel.objects.get()\n\n        self.assertIsInstance(\n            instance.float_pgp_sym_field,\n            float\n        )\n\n        self.assertEqual(\n            instance.float_pgp_sym_field,\n            expected\n        )\n\n        items = EncryptedModel.objects.filter(float_pgp_sym_field__gte='100')\n\n        self.assertEqual(\n            1,\n            len(items)\n        )\n\n        items = EncryptedModel.objects.filter(float_pgp_sym_field__gte='100001.00')\n\n        self.assertEqual(\n            0,\n            len(items)\n        )\n\n    def test_pgp_public_key_float_form(self):\n        \"\"\"Assert form field and widget for `FloatPGPPublicKeyField` field.\"\"\"\n        expected = '100000.99'\n        instance = EncryptedModelFactory.create(float_pgp_pub_field=expected)\n\n        payload = {\n            'float_pgp_pub_field': expected\n        }\n\n        form = EncryptedForm(payload, instance=instance)\n        self.assertTrue(form.is_valid())\n\n        cleaned_data = form.cleaned_data\n\n        self.assertTrue(\n            cleaned_data['float_pgp_pub_field'],\n            float(expected)\n        )\n\n    def test_pgp_symmetric_key_float_form(self):\n        \"\"\"Assert form field and widget for `FloatPGPSymmetricKeyField` field.\"\"\"\n        expected = '100000.99'\n        instance = EncryptedModelFactory.create(float_pgp_sym_field=expected)\n\n        payload = {\n            'float_pgp_sym_field': expected\n        }\n\n        form = EncryptedForm(payload, instance=instance)\n        self.assertTrue(form.is_valid())\n\n        cleaned_data = form.cleaned_data\n\n        self.assertTrue(\n            cleaned_data['float_pgp_sym_field'],\n            float(expected)\n        )\n\n    def test_boolean_pgp_pub_field(self):\n        \"\"\"Test BooleanPGPPublicKeyField.\"\"\"\n        expected = True\n        EncryptedModelFactory.create(boolean_pgp_pub_field=expected)\n\n        instance = EncryptedModel.objects.get()\n\n        self.assertIsInstance(\n            instance.boolean_pgp_pub_field,\n            bool\n        )\n\n        self.assertEqual(\n            instance.boolean_pgp_pub_field,\n            expected\n        )\n\n        items = EncryptedModel.objects.filter(boolean_pgp_pub_field=True)\n\n        self.assertEqual(\n            1,\n            len(items)\n        )\n\n        items = EncryptedModel.objects.filter(boolean_pgp_pub_field=False)\n\n        self.assertEqual(\n            0,\n            len(items)\n        )\n\n    def test_boolean_pgp_sym_field(self):\n        \"\"\"Test BooleanPGPSymmetricKeyField.\"\"\"\n        expected = False\n        EncryptedModelFactory.create(boolean_pgp_sym_field=expected)\n\n        instance = EncryptedModel.objects.get()\n\n        self.assertIsInstance(\n            instance.boolean_pgp_sym_field,\n            bool\n        )\n\n        self.assertEqual(\n            instance.boolean_pgp_sym_field,\n            expected\n        )\n\n        items = EncryptedModel.objects.filter(boolean_pgp_sym_field=False)\n\n        self.assertEqual(\n            1,\n            len(items)\n        )\n\n        items = EncryptedModel.objects.filter(float_pgp_sym_field=True)\n\n        self.assertEqual(\n            0,\n            len(items)\n        )\n\n    def test_pgp_public_key_boolean_form(self):\n        \"\"\"Assert form field and widget for `BooleanPGPPublicKeyField` field.\"\"\"\n        expected = False\n        instance = EncryptedModelFactory.create(boolean_pgp_pub_field=expected)\n\n        payload = {\n            'boolean_pgp_pub_field': expected\n        }\n\n        form = EncryptedForm(payload, instance=instance)\n        self.assertTrue(form.is_valid())\n\n        cleaned_data = form.cleaned_data\n\n        self.assertEqual(\n            cleaned_data['boolean_pgp_pub_field'],\n            expected\n        )\n\n    def test_pgp_symmetric_key_boolean_form(self):\n        \"\"\"Assert form field and widget for `BooleanPGPSymmetricKeyField` field.\"\"\"\n        expected = True\n        instance = EncryptedModelFactory.create(boolean_pgp_sym_field=expected)\n\n        payload = {\n            'boolean_pgp_sym_field': expected\n        }\n\n        form = EncryptedForm(payload, instance=instance)\n        self.assertTrue(form.is_valid())\n\n        cleaned_data = form.cleaned_data\n\n        self.assertEqual(\n            cleaned_data['boolean_pgp_sym_field'],\n            expected\n        )\n\n    def test_null(self):\n        \"\"\"Assert `NULL` values are saved.\"\"\"\n        instance = EncryptedModel.objects.create()\n        fields = field_names(self.model)\n        fields.remove('id')\n\n        for field in fields:\n            with self.subTest(instance=instance, field=field):\n                value = getattr(instance, field)\n                self.assertEqual(\n                    value,\n                    None,\n                    msg='Field {}, Value: {}'.format(field, value)\n                )\n\n    def test_defer(self):\n        \"\"\"Test defer() functionality.\"\"\"\n        expected = 'bonjour'\n        EncryptedModelFactory.create(pgp_sym_field=expected)\n        instance = self.model.objects.defer('pgp_sym_field').get()\n\n        # Assert that accessing a field that is in defer() causes a query\n        with self.assertNumQueries(1):\n            temp = instance.pgp_sym_field\n\n        self.assertEqual(temp, expected)\n\n    def test_only(self):\n        \"\"\"Test only() functionality.\"\"\"\n        expected = 'bonjour'\n        EncryptedModelFactory.create(pgp_sym_field=expected, pgp_pub_field=expected)\n        instance = self.model.objects.only('pgp_sym_field').get()\n\n        # Assert that accessing a field in only() does not cause a query\n        with self.assertNumQueries(0):\n            temp = instance.pgp_sym_field\n\n        self.assertEqual(temp, expected)\n\n        # Assert that accessing a field not in only() causes a query\n        with self.assertNumQueries(1):\n            temp = instance.pgp_pub_field\n\n        self.assertEqual(temp, expected)\n\n    def test_fk_auto_decryption(self):\n        \"\"\"Test auto decryption of FK when select related is defined.\"\"\"\n        expected = 'bonjour'\n        EncryptedModelFactory.create(fk_model__fk_pgp_sym_field=expected)\n        instance = self.model.objects.select_related('fk_model').get()\n\n        # Assert no additional queries are made to decrypt\n        with self.assertNumQueries(0):\n            temp = instance.fk_model.fk_pgp_sym_field\n\n        self.assertEqual(temp, expected)\n\n    def test_get_by_natural_key(self):\n        \"\"\"Test get_by_natual_key() support.\"\"\"\n        expected = 'peter@test.com'\n        EncryptedModelFactory.create(email_pgp_pub_field=expected)\n\n        instance = self.model.objects.get_by_natural_key(expected)\n\n        self.assertEqual(instance.email_pgp_pub_field, expected)\n\n    def test_get_or_create(self):\n        \"\"\"Test get_or_create() support.\"\"\"\n        expected = 'peter@test.com'\n        original = EncryptedModelFactory.create(email_pgp_pub_field=expected)\n\n        instance, created = self.model.objects.get_or_create(\n            email_pgp_pub_field=expected\n        )\n\n        self.assertFalse(created)\n        self.assertEqual(instance.id, original.id)\n        self.assertEqual(instance.email_pgp_pub_field, original.email_pgp_pub_field)\n\n        instance, created = self.model.objects.get_or_create(\n            email_pgp_pub_field='jessica@test.com'\n        )\n\n        self.assertTrue(created)\n        self.assertNotEqual(instance.id, original.id)\n        self.assertEqual(instance.email_pgp_pub_field, 'jessica@test.com')\n\n    def test_update_or_create(self):\n        \"\"\"Test update_or_create() support.\"\"\"\n        expected = 'peter@test.com'\n        original = EncryptedModelFactory.create(\n            email_pgp_pub_field=expected,\n            pgp_sym_field='Test'\n        )\n\n        instance, created = self.model.objects.update_or_create(\n            email_pgp_pub_field='jessica@test.com'\n        )\n\n        self.assertTrue(created)\n        self.assertNotEqual(instance.id, original.id)\n        self.assertEqual(instance.email_pgp_pub_field, 'jessica@test.com')\n\n        instance, created = self.model.objects.update_or_create(\n            email_pgp_pub_field='jessica@test.com',\n            defaults={\n                'pgp_sym_field': 'Blue',\n            }\n        )\n\n        self.assertFalse(created)\n        self.assertNotEqual(instance.id, original.id)\n        self.assertEqual(instance.pgp_sym_field, 'Blue')\n\n    def test_aggregates(self):\n        \"\"\"Test aggregate support.\"\"\"\n        EncryptedModelFactory.create(datetime_pgp_sym_field=datetime(2016, 7, 1, 0, 0, 0))\n        EncryptedModelFactory.create(datetime_pgp_sym_field=datetime(2016, 7, 2, 0, 0, 0))\n        EncryptedModelFactory.create(datetime_pgp_sym_field=datetime(2016, 8, 1, 0, 0, 0))\n        EncryptedModelFactory.create(datetime_pgp_sym_field=datetime(2016, 9, 1, 0, 0, 0))\n        EncryptedModelFactory.create(datetime_pgp_sym_field=datetime(2016, 9, 2, 0, 0, 0))\n\n        total_2016 = self.model.objects.aggregate(\n            count=models.Count('datetime_pgp_sym_field')\n        )\n\n        self.assertEqual(5, total_2016['count'])\n\n        total_july = self.model.objects.filter(\n            datetime_pgp_sym_field__range=[\n                datetime(2016, 7, 1, 0, 0, 0),\n                datetime(2016, 7, 30, 23, 59, 59)\n            ]\n        ).aggregate(\n            count=models.Count('datetime_pgp_sym_field')\n        )\n\n        self.assertEqual(2, total_july['count'])\n\n        total_2016 = self.model.objects.aggregate(\n            count=models.Count('datetime_pgp_sym_field'),\n            min=models.Min('datetime_pgp_sym_field'),\n            max=models.Max('datetime_pgp_sym_field'),\n        )\n\n        self.assertEqual(5, total_2016['count'])\n        self.assertEqual(datetime(2016, 7, 1, 0, 0, 0), total_2016['min'])\n        self.assertEqual(datetime(2016, 9, 2, 0, 0, 0), total_2016['max'])\n\n        total_july = self.model.objects.filter(\n            datetime_pgp_sym_field__range=[\n                datetime(2016, 7, 1, 0, 0, 0),\n                datetime(2016, 7, 30, 23, 59, 59)\n            ]\n        ).aggregate(\n            count=models.Count('datetime_pgp_sym_field'),\n            min=models.Min('datetime_pgp_sym_field'),\n            max=models.Max('datetime_pgp_sym_field'),\n        )\n\n        self.assertEqual(2, total_july['count'])\n        self.assertEqual(datetime(2016, 7, 1, 0, 0, 0), total_july['min'])\n        self.assertEqual(datetime(2016, 7, 2, 0, 0, 0), total_july['max'])\n\n    def test_distinct(self):\n        \"\"\"Test distinct support.\"\"\"\n        EncryptedModelFactory.create(pgp_sym_field='Paul')\n        EncryptedModelFactory.create(pgp_sym_field='Paul')\n        EncryptedModelFactory.create(pgp_sym_field='Peter')\n        EncryptedModelFactory.create(pgp_sym_field='Peter')\n        EncryptedModelFactory.create(pgp_sym_field='Jessica')\n        EncryptedModelFactory.create(pgp_sym_field='Jessica')\n\n        items = self.model.objects.filter(\n            pgp_sym_field__startswith='P'\n        ).annotate(\n            _distinct=models.F('pgp_sym_field')\n        ).only(\n            'id', 'pgp_sym_field', 'fk_model__fk_pgp_sym_field'\n        ).distinct(\n            '_distinct'\n        )\n\n        self.assertEqual(\n            2,\n            len(items)\n        )\n\n        # This only works on Django 2.1+\n        if DJANGO_VERSION[0] >= 2 and DJANGO_VERSION[1] >= 1:\n            items = self.model.objects.filter(\n                pgp_sym_field__startswith='P'\n            ).only(\n                'id', 'pgp_sym_field', 'fk_model__fk_pgp_sym_field'\n            ).distinct(\n                'pgp_sym_field'\n            )\n\n            self.assertEqual(\n                2,\n                len(items)\n            )\n\n    def test_annotate(self):\n        \"\"\"Test annotate support.\"\"\"\n        efk = EncryptedFKModelFactory.create()\n        EncryptedModelFactory.create(pgp_sym_field='Paul', fk_model=efk)\n        EncryptedModelFactory.create(pgp_sym_field='Peter', fk_model=efk)\n        EncryptedModelFactory.create(pgp_sym_field='Peter', fk_model=efk)\n        EncryptedModelFactory.create(pgp_sym_field='Jessica', fk_model=efk)\n\n        items = EncryptedFKModel.objects.annotate(\n            name_count=models.Count('encryptedmodel')\n        )\n\n        self.assertEqual(\n            4,\n            items[0].name_count\n        )\n\n        items = EncryptedFKModel.objects.filter(\n            encryptedmodel__pgp_sym_field__startswith='J'\n        ).annotate(\n            name_count=models.Count('encryptedmodel')\n        )\n\n        self.assertEqual(\n            1,\n            items[0].name_count\n        )\n\n    def test_get_col(self):\n        \"\"\"Test get_col for related alias.\"\"\"\n        related = EncryptedDateTime.objects.create(value=datetime.now())\n        related_again = EncryptedDateTime.objects.create(value=datetime.now())\n\n        RelatedDateTime.objects.create(related=related, related_again=related_again)\n\n        instance = RelatedDateTime.objects.select_related(\n            'related', 'related_again'\n        ).get()\n\n        self.assertIsInstance(instance, RelatedDateTime)\n\n    def test_char_field_choices(self):\n        \"\"\"Test CharField choices.\"\"\"\n        expected = 1\n        instance = EncryptedDiff.objects.create(\n            pub_field=expected,\n            sym_field=expected,\n        )\n        instance.refresh_from_db()\n\n        # choices always come back as strings\n        self.assertTrue(\n            '{}'.format(expected),\n            instance.pub_field\n        )\n\n        self.assertTrue(\n            '{}'.format(expected),\n            instance.sym_field\n        )\n\n    def test_write_to_diff_keys(self):\n        \"\"\"Test writing to diff_keys db which uses different keys.\"\"\"\n        expected = 'a'\n        instance = EncryptedDiff.objects.create(\n            pub_field=expected,\n            sym_field=expected,\n            digest_field=expected,\n            hmac_field=expected,\n        )\n\n        reset_queries()  # Required for Django 1.11\n        instance = EncryptedDiff.objects.get()\n\n        self.assertTrue(\n            instance.pub_field,\n            expected\n        )\n        self.assertTrue(\n            instance.sym_field,\n            expected\n        )\n\n        conn = connections['diff_keys']\n        query = conn.queries[0]\n\n        self.assertIn(\n            'djangorocks',\n            str(query)\n        )\n\n        self.assertIn(\n            'lQNTBFvGJCARCAD',\n            str(query)\n        )\n\n        instance = EncryptedDiff.objects.get(digest_field__hash_of=expected)\n\n        self.assertTrue(\n            instance.digest_field,\n            expected\n        )\n\n        instance = EncryptedDiff.objects.get(hmac_field__hash_of=expected)\n\n        self.assertTrue(\n            instance.hmac_field,\n            expected\n        )\n"
  }
]