Metadata-Version: 2.1
Name: django-check-constraint
Version: 1.0.17
Summary: Extends django check constraint to support annotations.
Home-page: https://github.com/jackton1/django-check-constraint
Author: Tonye Jack
Author-email: jtonye@ymail.com
Maintainer: Tonye Jack
Maintainer-email: jtonye@ymail.com
License: MIT/Apache-2.0
Keywords: django,django-check-constraint,django check constraint,check constraint-django,model constraints
Platform: UNKNOWN
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Natural Language :: English
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Framework :: Django :: 2.2
Classifier: Framework :: Django :: 3.0
Requires-Python: >=3.5
Description-Content-Type: text/markdown
Requires-Dist: Django (>=2.2.10)
Provides-Extra: deploy
Requires-Dist: bump2version (==1.0.0) ; extra == 'deploy'
Requires-Dist: readme-renderer[md] ; extra == 'deploy'
Requires-Dist: changes (==0.7.0) ; extra == 'deploy'
Requires-Dist: git-changelog (==0.1.0) ; extra == 'deploy'
Requires-Dist: twine (==1.3.1) ; extra == 'deploy'
Provides-Extra: development
Requires-Dist: pip-tools (==4.4.1) ; extra == 'development'
Requires-Dist: check-manifest (==0.37) ; extra == 'development'
Requires-Dist: psycopg2 (>=2.5.4) ; extra == 'development'
Requires-Dist: mysqlclient (>=1.3.13) ; extra == 'development'
Requires-Dist: Django (>=2.2.10) ; extra == 'development'
Requires-Dist: nox (==2019.11.9) ; extra == 'development'
Requires-Dist: pluggy (>=0.7) ; extra == 'development'
Requires-Dist: mock (==2.0.0) ; extra == 'development'
Requires-Dist: unittest-xml-reporting (==2.5.2) ; extra == 'development'
Requires-Dist: codacy-coverage (==1.3.11) ; extra == 'development'
Requires-Dist: flake8 (==3.4.1) ; extra == 'development'
Requires-Dist: yamllint (==1.10.0) ; extra == 'development'
Requires-Dist: isort (==4.2.15) ; extra == 'development'
Requires-Dist: pre-commit (==2.0.1) ; extra == 'development'
Provides-Extra: lint
Requires-Dist: flake8 (==3.4.1) ; extra == 'lint'
Requires-Dist: yamllint (==1.10.0) ; extra == 'lint'
Requires-Dist: isort (==4.2.15) ; extra == 'lint'
Requires-Dist: pre-commit (==2.0.1) ; extra == 'lint'
Provides-Extra: nox
Requires-Dist: nox (==2019.11.9) ; extra == 'nox'
Requires-Dist: pluggy (>=0.7) ; extra == 'nox'
Requires-Dist: mock (==2.0.0) ; extra == 'nox'
Requires-Dist: unittest-xml-reporting (==2.5.2) ; extra == 'nox'
Requires-Dist: codacy-coverage (==1.3.11) ; extra == 'nox'
Provides-Extra: test
Requires-Dist: nox (==2019.11.9) ; extra == 'test'
Requires-Dist: pluggy (>=0.7) ; extra == 'test'
Requires-Dist: mock (==2.0.0) ; extra == 'test'
Requires-Dist: unittest-xml-reporting (==2.5.2) ; extra == 'test'
Requires-Dist: codacy-coverage (==1.3.11) ; extra == 'test'

# django-check-constraint
![django check constraint test.](https://github.com/jackton1/django-check-constraint/workflows/django%20check%20constraint%20test./badge.svg?branch=master)
[![PyPI version](https://badge.fury.io/py/django-check-constraint.svg)](https://badge.fury.io/py/django-check-constraint)
[![PyPI - License](https://img.shields.io/pypi/l/django-check-constraint.svg)](https://github.com/jackton1/django-check-constraint/blob/master/LICENSE)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/django-check-constraint.svg)](https://pypi.org/project/django-check-constraint)
[![PyPI - Django Version](https://img.shields.io/pypi/djversions/django-check-constraint.svg)](https://docs.djangoproject.com/en/2.2/releases/)
![Upload Python Package](https://github.com/jackton1/django-check-constraint/workflows/Upload%20Python%20Package/badge.svg)
![Create New Release](https://github.com/jackton1/django-check-constraint/workflows/Create%20New%20Release/badge.svg)


Extends [Django's Check](https://docs.djangoproject.com/en/3.0/ref/models/options/#constraints)
constraint with support for UDF(User defined functions/db functions) and annotations.


#### Installation

```bash
$ pip install django-check-constraint
```

ADD `check_constraint` to list of *INSTALLED* *APPS*.

```python
INSTALLED_APPS = [
  ...
  "check_constraint",
  ...
]

```


#### Scenario:

Suppose you have a database function that returns the counts of null values in `[i, ...n]`.

```postgresql
CREATE OR REPLACE FUNCTION public.non_null_count(VARIADIC arg_array ANYARRAY)
  RETURNS BIGINT AS
  $$
    SELECT COUNT(x) FROM UNNEST($1) AS x
  $$ LANGUAGE SQL IMMUTABLE;

```

#### Example:
```postgresql
SELECT public.non_null_count(1, null, null);
```

#### Outputs:

```sql
non_null_count
----------------
              1
(1 row)
```

Defining a check constraint with this function

The equivalent of (PostgresSQL)

```postgresql
ALTER TABLE app_name_test_modoel ADD CONSTRAINT app_name_test_model_optional_field_provided
    CHECK(non_null_count(amount::integer , amount_off::integer, percentage::integer) = 1);
```

## Usage

Converting this to django functions and annotated check contraints can be done using:

`function.py`

```python
from django.db.models import Func, SmallIntegerField, TextField
from django.db.models.functions import Cast


class NotNullCount(Func):
    function = 'non_null_count'

    def __init__(self, *expressions, **extra):
        filter_exp = [
            Cast(exp, TextField()) for exp in expressions if isinstance(exp, str)
        ]
        if 'output_field' not in extra:
            extra['output_field'] = SmallIntegerField()

        if len(expressions) < 2:
            raise ValueError('NotNullCount must take at least two expressions')

        super().__init__(*filter_exp, **extra)
```



#### Creating annotated check constraints


```python
from django.db import models
from django.db.models import Q
from check_constraint.models import AnnotatedCheckConstraint

class TestModel(models.Model):
    amount = models.DecimalField(max_digits=9, decimal_places=2, null=True, blank=True)
    amount_off = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True)
    percentage = models.DecimalField(max_digits=3, decimal_places=0, null=True, blank=True)


    class Meta:
        constraints = [
            AnnotatedCheckConstraint(
                check=Q(not_null_count=1),
                annotations={
                    'not_null_count': (
                        NotNullCount(
                            'amount',
                            'amount_off',
                            'percentage',
                        )
                    ),
                },
                name='%(app_label)s_%(class)s_optional_field_provided',
            ),
        ]

```


TODO's
------

- [ ] Add support for schema based functions.
- [ ] Add warning about mysql lack of user defined check constraint support.
- [ ] Remove skipped sqlite3 test.


