Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Conveniently register model methods as constraints, that will check before saving the model
from functools import wraps
from django.db import models
from django.core.exceptions import ValidationError
class ConstraintModel(models.Model):
class Meta:
abstract = True
def _fill_constraints_register(self):
cache = []
is_callable = lambda obj, prop: hasattr(getattr(obj, prop), '__call__')
for prop in dir(self):
if prop.startswith('check_') and is_callable(self, prop):
_method = getattr(self, prop)
if getattr(_method, '_is_constraint', False):
self._constraints_register = set(cache)
def _constraints(self):
except AttributeError:
return self._constraints_register
constraints = property(_constraints)
def clean(self):
for constraint_method_name in self.constraints:
constraint_method = getattr(self, constraint_method_name)
if constraint_method.assert_on_create:
if constraint_method.assert_on_update:
self._cleaned = True
def delete(self, using=None):
for constraint_method_name in self.constraints:
constraint_method = getattr(self, constraint_method_name)
if constraint_method.assert_on_delete:
super(ConstraintModel, self).delete(using)
def save(self, *args, **kwargs):
# Do not run clean twice if was already done, but make sure it executes
# if it have not been called
if not hasattr(self, '_cleaned'):
# TODO: Implementar desnormalizacion de relaciones y otros campos
# if hasattr(self, '_compute'):
# if hasattr(getattr(self, '_compute'), '__call__'):
# self._compute()
super(ConstraintModel, self).save(*args, **kwargs)
def constraint(on_create=True, on_update=True, on_delete=False):
def decorator(constraint_func):
constraint_func._is_constraint = True
constraint_func.assert_on_delete = on_delete
constraint_func.assert_on_update = on_update
constraint_func.assert_on_create = on_create
def _wrapped_method(self, **kwargs):
res = constraint_func(self, **kwargs)
if isinstance(res, str):
raise ValidationError(res)
return _wrapped_method
return decorator
- Create a model and extend from ConstraintModel
- Using the @constraint decorator register what tests to check
- Return a string to raise a validation Error
class Loan(ConstraintModel):
user = models.ForeignKey('auth.User')
amount = models.IntegerField()
def check_amount(self):
# Do not not lend less than 100
if self.amount < 100:
# Return an error.
return 'Unable to lend less than 100$'
check_amount._is_constraint = True
def check_unpaid_loans(self):
# Do not not lend less than 100
if self.user.total_unpaid_loans() > 1000:
return 'Not allowed, the user has already more than 1000$ in unpaid loans.'
def check_luckyness(self):
import random
lucky = lambda p, m: True if m() < p else False
if not lucky(0.2, random.random):
return 'We are sorry, but after a detailed review we are unable to lend money to this user'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment