Skip to the content.

Dj-Tximmutability


Dj-Immutability is a small Django utils (Models Mixin, classes) that allows you to create mutability rules to make the model immutable.

CONTRIBUTING

CHANGELOG

Requirements

Dj-Tximmutability requires the following:

Instalation

Install using pip.

pip install git+https://github.com/txerpa/dj-tximmutability.git@master#egg=dj-tximmutability

…or clone the project from github.

git clone https://github.com/txerpa/dj-tximmutability

Pending to punblish on PyPI

Example

Minimal implementation.

Create a MutabilityRule over the state field when it is ** draft **. This means that this model is mutable only when state == 'draft'.

from tximmutability.models import MutableModel
from tximmutability.rule import MutabilityRule

class Article(MutableModel):
    name = models.CharField(max_length=120)
    content = models.TextField()
    state = models.ChoiceField(choices=['draft', 'published'])
    notes = models.TextField( blank=True, default='')
    ...

    mutability_rules = (
        MutabilityRule(
            'state',
            values=('draft',)
        )
    )

Rule options.

field_rule *

Model field name.


values *

Tuple of values of field_rule by which it can be mutable.


exclude_fields

Tuple of fields to be ignored.

Example

...
from django.utils.translation import gettext_lazy
from django.db import models

from tximmutability.models import MutableModel
from tximmutability.rule import MutabilityRule


class Article(MutableModel):
    name = models.CharField(max_length=120)
    content = models.TextField()
    state = models.ChoiceField(choices=['draft', 'published'])
    notes = models.TextField( blank=True, default='')
    ...

    def no_notes(self):
        return self.notes == ''

    mutability_rules = (
        MutabilityRule(
            'state',
            values=('draft',),
            exclude_fields=('notes',)
        ),
    )

Code that executes the above rule.

queryset = Article.objects.all()
queryset.update(name="-")
...
instance = Article.objects.first()
instance.name = "-"
instance.save()

Code that does not execute the above rule.

queryset = Article.objects.all()
queryset.update(name="-")  # excluded field
queryset.update(state="-")  # is the field in which the rule is managed.
...
instance = Article.objects.first()
instance.notes = "-"
instance.save() # excluded field

exclude_on_create

Exclude rule on create.


exclude_on_update

Exclude rule on update instance or queryset.


exclude_on_delete

Exclude rule on delete instance.


inst_conditions

This attribute, effects when the action comes from an instance, is not checked for a queryset. It is a tuple of conditional methods that return Boolean. All conditions must be met to run the rule. If one condition not met, the rule would not be executed, and therefore the instance would continue with the action.

Example

...
from django.utils.translation import gettext_lazy
from django.db import models

from tximmutability.models import MutableModel
from tximmutability.rule import MutabilityRule


class Article(MutableModel):
    name = models.CharField(max_length=120)
    content = models.TextField()
    state = models.ChoiceField(choices=['draft', 'published'])
    notes = models.TextField( blank=True, default='')
    ...

    def no_notes(self):
        return self.notes == ''

    mutability_rules = (
        MutabilityRule(
            'state',
            values=('draft',),
            inst_conditions=(no_notes,)
        ),
    )

Code that executes the above rule.

# Attribute `inst_conditions` has no effect over queryset. Rule will be executed.
queryset = Article.objects.exclude(notes="")
queryset.update(name="-")
...
# Codition met - continue executing Rule
instance = Article.objects.filter(notes="").first()
instance.name = "-"
instance.save()

Code that does not execute the above rule.

# Changes on `state` field has no efect, is the field on which the rule is managed.
instance = Article.objects.filter(notes="").first()
instance.state = "draft"  # `state` field is excluded
instance.save()
...
# Condition of `inst_conditions` not met, rule is excluded.
instance = Article.objects.exclude(notes="")
instance.name = "-"
instance.save()

inst_exclusion_conditions

This attribute, effects when the action comes from an instance, is not checked for a queryset. It is a tuple of conditional methods that return Boolean. No conditions must be met to execute the rule. If a condition is met, the rule will not be executed and thus the instance would continue with the action.

Opossite of inst_conditions.

Example

...
from django.utils.translation import gettext_lazy
from django.db import models

from tximmutability.models import MutableModel
from tximmutability.rule import MutabilityRule


class Article(MutableModel):
    name = models.CharField(max_length=120)
    content = models.TextField()
    state = models.ChoiceField(choices=['draft', 'published'])
    notes = models.TextField( blank=True, default='')
    ...

    def no_notes(self):
        return self.notes == ''

    mutability_rules = (
        MutabilityRule(
            'state',
            values=('draft',),
            inst_exclusion_conditions=(no_notes,)
        ),
    )

Code that executes the above rule.

# Attribute `inst_exclusion_conditions` has no effect over queryset. Rule will be executed.
queryset = Article.objects.filter(notes="")
queryset.update(name="-")
...
# No codition met - continue executing Rule
instance = Article.objects.exclude(notes="").first()
instance.name = "-"
instance.save()

Code that does not execute the above rule.

# Changes on `state` field has no efect, is the field on which the rule is managed.
instance = Article.objects.exclude(notes="").first()
instance.state="draft"
instance.save()
...
# Condition of `inst_exclusion_conditions` met, rule is excluded.
instance = Article.objects.filter(notes="").first()
instance.name = "-"
instance.save()

queryset_conditions

This attribute, effects when the action comes from a Queryset, is not checked for a single instance. It is a tuple of conditional methods that return Boolean. Those methods must come from the model manager. All conditions must be met to run the rule. If one condition not met, the rule would not be executed, and therefore the instance would continue with the action.

Similar to inst_conditions, but for queryset.

Example

...
from django.utils.translation import gettext_lazy
from django.db import models

from tximmutability.models import MutableModel
from tximmutability.rule import MutabilityRule
from tximmutability.models import MutableQuerySet

class ArticleQuerySet(MutableQuerySet, models.QuerySet):

    def no_notes(self):
        return self.count() == self.filter(notes='').count()

class Article(MutableModel):
    name = models.CharField(max_length=120)
    content = models.TextField()
    state = models.ChoiceField(choices=['draft', 'published'])
    notes = models.TextField( blank=True, default='')
    objects = ArticleQuerySet.as_manager()
    ...

    mutability_rules = (
        MutabilityRule(
            'state',
            values=('draft',),
            queryset_conditions=(object.no_notes,)
        ),
    )

Code that executes the above rule.

# Codition met - continue executing Rule
queryset = Article.objects.filter(notes="")
queryset.update(name="-")
...
# Attribute `queryset_conditions` has no effect over single instance. Rule will be executed.
instance = Article.objects.exclude(notes="").first()
instance.name = "-"
instance.save()

Code that does not execute the above rule.

# Changes on `state` field has no efect, is the field on which the rule is managed.
queryset_0 = Article.objects.filter(notes="")
queryset_0.update(state="draft")
...
# Condition of `queryset_conditions` not met, rule is excluded.
queryset_1 = Article.objects.exclude(notes="")
queryset_1.update(name="-")  # `queryset_conditions` not met

queryset_exclusion_conditions

This attribute, effects when the action comes from a Queryset, is not checked for a single instance. It is a tuple of conditional methods that return Boolean. Those methods must come from the model manager. No conditions must be met to execute the rule. If one condition is met, the rule would not be executed, and therefore the instance would continue with the action.

Similar to inst_exclusion_conditions, but for queryset.

Example

...
from django.utils.translation import gettext_lazy
from django.db import models

from tximmutability.models import MutableModel
from tximmutability.rule import MutabilityRule
from tximmutability.models import MutableQuerySet

class ArticleQuerySet(MutableQuerySet, models.QuerySet):

    def no_notes(self):
        return self.count() == self.filter(notes='').count()

class Article(MutableModel):
    name = models.CharField(max_length=120)
    content = models.TextField()
    state = models.ChoiceField(choices=['draft', 'published'])
    notes = models.TextField( blank=True, default='')
    objects = ArticleQuerySet.as_manager()
    ...

    mutability_rules = (
        MutabilityRule(
            'state',
            values=('draft',),
            queryset_exclusion_conditions=(object.no_notes,)
        ),
    )

Code that executes the above rule.

# Codition not met - continue executing Rule
queryset = Article.objects.exclude(notes="")
queryset.update(name="-")  # state is excluded
...
# Attribute `queryset_exclusion_conditions` has no effect over single instance. Rule will be executed.
instance = Article.objects.filter(notes="").first()
instance.name = "-"
instance.save()  # For a single instance `queryset_exclusion_conditions` has no effect.

Code that does not execute the above rule.

# Changes on `state` field has no efect, is the field on which the rule is managed.
queryset_0 = Article.objects.exclude(notes="")
queryset_0.update(state="draft")
...
# Condition of `queryset_exclusion_conditions` met, rule is excluded.
queryset_1 = Article.objects.filter(notes="")
queryset_1.update(name="-")

error_message

Expect a String. It accepts custom string formatting by variable substitutions. Accepted variables [{action} {field_rule} {values}].

Example

...
from django.utils.translation import gettext_lazy

from tximmutability.models import MutableModel
from tximmutability.rule import MutabilityRule

class Article(MutableModel):
    name = models.CharField(max_length=120)
    content = models.TextField()
    state = models.ChoiceField(choices=['draft', 'published'])
    notes = models.TextField( blank=True, default='')
    ...

    mutability_rules = (
        MutabilityRule(
            'state',
            values=('draft',),
            error_message=gettext_lazy("Article can not be {action}, {field_rule} is not \"{values}\"")
        ),
    )

error_code

If the Rule fails it return RuleMutableException or OrMutableException, both inherit from ValidationError. This has an attribute (code), where it can be handled later on the catch error. Therefore, error_code provides the ability to pass the value to both exceptions.

Example

...
from django.utils.translation import gettext_lazy

from tximmutability.models import MutableModel
from tximmutability.rule import MutabilityRule

class Article(MutableModel):
    name = models.CharField(max_length=120)
    content = models.TextField()
    state = models.ChoiceField(choices=['draft', 'published'])
    notes = models.TextField( blank=True, default='')
    ...

    mutability_rules = (
        MutabilityRule(
            'state',
            values=('draft',),
            error_code="0001"
        ),
    )

# Example how can be handled depending on our purpose
try:
    instance.save()
except RuleMutableException as exc:
    if exc.code == "0001":
        # Do whaterever, send email, execute task, etc.

Exclude rules.

In some cases, you will need to ignore the model rules.

instance.save(force_mutability=True, ...)

instance.delete(force_mutability=True, ...)

Running Tests

Does the code actually work?