/litclub/django/db/models/fields/subclassing.py

https://github.com/andrewkuzmych/litclub · Python · 53 lines · 40 code · 2 blank · 11 comment · 0 complexity · 0e4fd75c20c3aff162145e31969ca660 MD5 · raw file

  1. """
  2. Convenience routines for creating non-trivial Field subclasses.
  3. Add SubfieldBase as the __metaclass__ for your Field subclass, implement
  4. to_python() and the other necessary methods and everything will work seamlessly.
  5. """
  6. from django.utils.maxlength import LegacyMaxlength
  7. class SubfieldBase(LegacyMaxlength):
  8. """
  9. A metaclass for custom Field subclasses. This ensures the model's attribute
  10. has the descriptor protocol attached to it.
  11. """
  12. def __new__(cls, base, name, attrs):
  13. new_class = super(SubfieldBase, cls).__new__(cls, base, name, attrs)
  14. new_class.contribute_to_class = make_contrib(
  15. attrs.get('contribute_to_class'))
  16. return new_class
  17. class Creator(object):
  18. """
  19. A placeholder class that provides a way to set the attribute on the model.
  20. """
  21. def __init__(self, field):
  22. self.field = field
  23. def __get__(self, obj, type=None):
  24. if obj is None:
  25. raise AttributeError('Can only be accessed via an instance.')
  26. return obj.__dict__[self.field.name]
  27. def __set__(self, obj, value):
  28. obj.__dict__[self.field.name] = self.field.to_python(value)
  29. def make_contrib(func=None):
  30. """
  31. Returns a suitable contribute_to_class() method for the Field subclass.
  32. If 'func' is passed in, it is the existing contribute_to_class() method on
  33. the subclass and it is called before anything else. It is assumed in this
  34. case that the existing contribute_to_class() calls all the necessary
  35. superclass methods.
  36. """
  37. def contribute_to_class(self, cls, name):
  38. if func:
  39. func(self, cls, name)
  40. else:
  41. super(self.__class__, self).contribute_to_class(cls, name)
  42. setattr(cls, self.name, Creator(self))
  43. return contribute_to_class