|
1 """ |
|
2 Convenience routines for creating non-trivial Field subclasses, as well as |
|
3 backwards compatibility utilities. |
|
4 |
|
5 Add SubfieldBase as the __metaclass__ for your Field subclass, implement |
|
6 to_python() and the other necessary methods and everything will work seamlessly. |
|
7 """ |
|
8 |
|
9 from inspect import getargspec |
|
10 from warnings import warn |
|
11 |
|
12 def call_with_connection(func): |
|
13 arg_names, varargs, varkwargs, defaults = getargspec(func) |
|
14 updated = ('connection' in arg_names or varkwargs) |
|
15 if not updated: |
|
16 warn("A Field class whose %s method hasn't been updated to take a " |
|
17 "`connection` argument." % func.__name__, |
|
18 PendingDeprecationWarning, stacklevel=2) |
|
19 |
|
20 def inner(*args, **kwargs): |
|
21 if 'connection' not in kwargs: |
|
22 from django.db import connection |
|
23 kwargs['connection'] = connection |
|
24 warn("%s has been called without providing a connection argument. " % |
|
25 func.__name__, PendingDeprecationWarning, |
|
26 stacklevel=1) |
|
27 if updated: |
|
28 return func(*args, **kwargs) |
|
29 if 'connection' in kwargs: |
|
30 del kwargs['connection'] |
|
31 return func(*args, **kwargs) |
|
32 return inner |
|
33 |
|
34 def call_with_connection_and_prepared(func): |
|
35 arg_names, varargs, varkwargs, defaults = getargspec(func) |
|
36 updated = ( |
|
37 ('connection' in arg_names or varkwargs) and |
|
38 ('prepared' in arg_names or varkwargs) |
|
39 ) |
|
40 if not updated: |
|
41 warn("A Field class whose %s method hasn't been updated to take " |
|
42 "`connection` and `prepared` arguments." % func.__name__, |
|
43 PendingDeprecationWarning, stacklevel=2) |
|
44 |
|
45 def inner(*args, **kwargs): |
|
46 if 'connection' not in kwargs: |
|
47 from django.db import connection |
|
48 kwargs['connection'] = connection |
|
49 warn("%s has been called without providing a connection argument. " % |
|
50 func.__name__, PendingDeprecationWarning, |
|
51 stacklevel=1) |
|
52 if updated: |
|
53 return func(*args, **kwargs) |
|
54 if 'connection' in kwargs: |
|
55 del kwargs['connection'] |
|
56 if 'prepared' in kwargs: |
|
57 del kwargs['prepared'] |
|
58 return func(*args, **kwargs) |
|
59 return inner |
|
60 |
|
61 class LegacyConnection(type): |
|
62 """ |
|
63 A metaclass to normalize arguments give to the get_db_prep_* and db_type |
|
64 methods on fields. |
|
65 """ |
|
66 def __new__(cls, names, bases, attrs): |
|
67 new_cls = super(LegacyConnection, cls).__new__(cls, names, bases, attrs) |
|
68 for attr in ('db_type', 'get_db_prep_save'): |
|
69 setattr(new_cls, attr, call_with_connection(getattr(new_cls, attr))) |
|
70 for attr in ('get_db_prep_lookup', 'get_db_prep_value'): |
|
71 setattr(new_cls, attr, call_with_connection_and_prepared(getattr(new_cls, attr))) |
|
72 return new_cls |
|
73 |
|
74 class SubfieldBase(LegacyConnection): |
|
75 """ |
|
76 A metaclass for custom Field subclasses. This ensures the model's attribute |
|
77 has the descriptor protocol attached to it. |
|
78 """ |
|
79 def __new__(cls, base, name, attrs): |
|
80 new_class = super(SubfieldBase, cls).__new__(cls, base, name, attrs) |
|
81 new_class.contribute_to_class = make_contrib( |
|
82 attrs.get('contribute_to_class')) |
|
83 return new_class |
|
84 |
|
85 class Creator(object): |
|
86 """ |
|
87 A placeholder class that provides a way to set the attribute on the model. |
|
88 """ |
|
89 def __init__(self, field): |
|
90 self.field = field |
|
91 |
|
92 def __get__(self, obj, type=None): |
|
93 if obj is None: |
|
94 raise AttributeError('Can only be accessed via an instance.') |
|
95 return obj.__dict__[self.field.name] |
|
96 |
|
97 def __set__(self, obj, value): |
|
98 obj.__dict__[self.field.name] = self.field.to_python(value) |
|
99 |
|
100 def make_contrib(func=None): |
|
101 """ |
|
102 Returns a suitable contribute_to_class() method for the Field subclass. |
|
103 |
|
104 If 'func' is passed in, it is the existing contribute_to_class() method on |
|
105 the subclass and it is called before anything else. It is assumed in this |
|
106 case that the existing contribute_to_class() calls all the necessary |
|
107 superclass methods. |
|
108 """ |
|
109 def contribute_to_class(self, cls, name): |
|
110 if func: |
|
111 func(self, cls, name) |
|
112 else: |
|
113 super(self.__class__, self).contribute_to_class(cls, name) |
|
114 setattr(cls, self.name, Creator(self)) |
|
115 |
|
116 return contribute_to_class |