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