Metadata-Version: 2.1
Name: django_queryset_constraint
Version: 1.0.5
Summary: A library for writing reliable data invariants in Django.
Home-page: https://github.com/magenta-aps/django_queryset_constraint
Author: Emil Madsen
Author-email: emil@magenta.dk
License: MPL-2.0
Download-URL: https://github.com/magenta-aps/django_queryset_constraint/archive/master.zip
Project-URL: Source, https://github.com/magenta-aps/django_queryset_constraint
Description: Django Queryset Constraint
        ==========================
        
        [![Build Status](https://travis-ci.com/magenta-aps/django_queryset_constraint.svg?branch=master)](https://travis-ci.com/magenta-aps/django_queryset_constraint)
        [![Release Status](https://img.shields.io/pypi/v/django_queryset_constraint)](https://pypi.org/project/django-queryset-constraint/)
        [![License](https://img.shields.io/pypi/l/django_queryset_constraint)](https://github.com/magenta-aps/django_queryset_constraint/blob/master/LICENSE)
        [![Python Versions](https://img.shields.io/pypi/pyversions/django_queryset_constraint)](https://www.python.org/downloads/)
        [![Django Versions](https://img.shields.io/pypi/djversions/django_queryset_constraint)](https://www.djangoproject.com/download/)
        
        
        
        This library enables one to write reliable data invariants, by compiling Django
        Querysets to database insert/update triggers, via. the migration system.
        
        Motivation
        ==========
        Django has a built-in signal system, which emits signals on various events, for
        instance model creations, updates and deletions. However these signals are not
        emmited for queryset operations, and as such cannot be used to maintain data
        invariants.
        
        An attempt to ratify this was made with the [Django Queryset Signals](https://github.com/magenta-aps/django-queryset-signals) library.
        While this library comes closer to a reliable solution, it does not succeed,
        as it is stil possible to break the data invariants by accessing the database
        directly.
        
        Database Constraint Triggers will effectively protect against all scenarios.
        
        Installation
        ============
        ```
        pip install django_queryset_constraint
        ```
        
        Usage
        =====
        
        - Add the `django_queryset_constraint` app to `INSTALLED_APPS`:
        
        ```
        # settings.py
        INSTALLED_APPS = {
            'django_queryset_constraint',
            ...
        }
        ```
        
        *Note: This should be done **before** any apps that will be checked*
        
        - Add `QuerysetConstraint` to `constraints` to the Meta class of the models to checked:
        
        ```
        # models.py
        from django.db import models
        from django.db.models import Count
        from django_queryset_constraint import M, QuerysetConstraint
        
        
        class Topping(models.Model):
            name = models.CharField(max_length=30)
        
        
        class PizzaTopping(models.Model):
            class Meta:
                unique_together = ("pizza", "topping")
                constraints = [
                    # A pizza with more than 5 toppings gets soggy
                    QuerysetConstraint(
                        name='At most 5 toppings',
                        queryset=M().objects.values(
                            'pizza'
                        ).annotate(
                            num_toppings=Count('topping')
                        ).filter(
                            num_toppings__gt=5
                        ),
                    ),
                    # This constraint should be self-explanatory for civilized people
                    QuerysetConstraint(
                        name='No pineapple',
                        queryset=M().objects.filter(
                            topping__name="Pineapple"
                        )
                    ),
                ]
        
            pizza = models.ForeignKey('Pizza', on_delete=models.CASCADE)
            topping = models.ForeignKey(Topping, on_delete=models.CASCADE)
        
        class Pizza(models.Model):
            name = models.CharField(max_length=30)
            toppings = models.ManyToManyField(Topping, through=PizzaTopping)
        ```
        
        - Make migrations: `python manage.py makemigrations`
        - Run migrations: `python manage.py migrate`
        
        *Note: Complex triggers introduce performance overhead.*
        
        Support Matrix
        ==============
        This app supports the following combinations of Django and Python:
        
        | Django | Python   |
        | ------ | -------- |
        | 2.2    | 3.6, 3.7 |
        
Keywords: django
Platform: UNKNOWN
Classifier: Environment :: Web Environment
Classifier: Framework :: Django
Classifier: Framework :: Django :: 2.2
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved
Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.6
Description-Content-Type: text/markdown
