|
0
|
1 |
""" |
|
29
|
2 |
Convenience routines for creating non-trivial Field subclasses, as well as |
|
|
3 |
backwards compatibility utilities. |
|
0
|
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 |
|
|
29
|
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): |
|
0
|
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.') |
|
29
|
95 |
return obj.__dict__[self.field.name] |
|
0
|
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 |