The current code is below. Usage:
@with_natural_key(["name"])
class Widget(models.Model):
name = models.CharField(max_length=200)
another_field = models.IntegerField()
This adds a natural_key
method on Widget
and creates a Manager
at objects
with a get_by_natural_key
method to match.
"""
Decorators for Django models
"""
from inspect import signature, Parameter
from django.db import models
def with_natural_key(fields, manager_name=None):
"""
Decorator to add DRY natural key support to a Django model.
Adds a Manager class with get_by_natural_key and a corresponding natural_key
method on the model.
TODO: add support for dependencies
"""
assert 'self' not in fields
def natural_key_wrapper(klass):
def _natural_key(self):
return tuple([self.__dict__[x] for x in fields])
klass.natural_key = _natural_key
klass.natural_key.__name__ = "natural_key"
klass.natural_key.__doc__ = "Return a natural key for the model: ({})".format(
", ".join(fields))
class NaturalKeyManager(models.Manager):
"""
Implements get_by_natural_key for {}
"""
def get_by_natural_key(self, *args):
"""
Find an object by its natural key
"""
if len(args) != len(fields):
raise RuntimeError("expected {} arguments ({}), got {}".format(
len(fields), ", ".join(fields), len(args)
))
return self.get(**dict(zip(fields, args)))
NaturalKeyManager.__name__ = klass.__name__ + "NaturalKeyManager"
NaturalKeyManager.__doc__ = NaturalKeyManager.__doc__.format(klass.__name__)
# fix up the help() to show the expected positional parameters instead of "*args"
sig = signature(NaturalKeyManager.get_by_natural_key)
sig = sig.replace(parameters=[Parameter(f, Parameter.POSITIONAL_ONLY) for f in ['self'] + fields])
setattr(NaturalKeyManager.get_by_natural_key, "__signature__", sig)
_m = NaturalKeyManager()
_m.contribute_to_class(klass, manager_name or "objects")
return klass
return natural_key_wrapper