public
Last active

Conveniently register model methods as constraints, that will check before saving the model

  • Download Gist
models.py
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
from functools import wraps
from django.db import models
from django.core.exceptions import ValidationError
 
class CoreModel(TimestampModel):
 
class Meta:
abstract = True
 
def _fill_constraints_cache(self):
cache = []
for prop in dir(self):
if prop.startswith('check_') and hasattr(getattr(self, prop), '__call__'):
_method = getattr(self, prop)
if getattr(_method, '_is_constraint', False):
cache.append(prop)
self._constraints_cache = tuple(cache)
 
def _constraints(self):
try:
self._constraints_cache
except AttributeError:
self._fill_constraints_cache()
return self._constraints_cache
constraints = property(_constraints)
 
def clean(self):
for constraint in self.constraints:
res = getattr(self, constraint).__call__(self)
 
if isinstance(res, basestring):
raise ValidationError(res)
 
self._cleaned = True
 
def save(self, *args, **kwargs):
if hasattr(self, '_compute'):
if hasattr(getattr(self, '_compute'), '__call__'):
self._compute()
 
# 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'):
self.full_clean()
super(CoreModel, self).save(*args, **kwargs)
 
 
class constraint(object):
def __init__(self, func):
self.func = func
self._is_constraint = True
 
def __call__(self, *args, **kwargs):
return self.func(*args, **kwargs)
 
 
"""
Example:
 
- 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
@constraint
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.'
 
@constraint
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'

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.