|
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 class SubfieldBase(type): |
|
9 """ |
|
10 A metaclass for custom Field subclasses. This ensures the model's attribute |
|
11 has the descriptor protocol attached to it. |
|
12 """ |
|
13 def __new__(cls, base, name, attrs): |
|
14 new_class = super(SubfieldBase, cls).__new__(cls, base, name, attrs) |
|
15 new_class.contribute_to_class = make_contrib( |
|
16 attrs.get('contribute_to_class')) |
|
17 return new_class |
|
18 |
|
19 class Creator(object): |
|
20 """ |
|
21 A placeholder class that provides a way to set the attribute on the model. |
|
22 """ |
|
23 def __init__(self, field): |
|
24 self.field = field |
|
25 |
|
26 def __get__(self, obj, type=None): |
|
27 if obj is None: |
|
28 raise AttributeError('Can only be accessed via an instance.') |
|
29 return obj.__dict__[self.field.name] |
|
30 |
|
31 def __set__(self, obj, value): |
|
32 obj.__dict__[self.field.name] = self.field.to_python(value) |
|
33 |
|
34 def make_contrib(func=None): |
|
35 """ |
|
36 Returns a suitable contribute_to_class() method for the Field subclass. |
|
37 |
|
38 If 'func' is passed in, it is the existing contribute_to_class() method on |
|
39 the subclass and it is called before anything else. It is assumed in this |
|
40 case that the existing contribute_to_class() calls all the necessary |
|
41 superclass methods. |
|
42 """ |
|
43 def contribute_to_class(self, cls, name): |
|
44 if func: |
|
45 func(self, cls, name) |
|
46 else: |
|
47 super(self.__class__, self).contribute_to_class(cls, name) |
|
48 setattr(cls, self.name, Creator(self)) |
|
49 |
|
50 return contribute_to_class |