blob: 87ebac2c052fcb751e6c6f29977f6354c9a4fde2 [file] [log] [blame]
showardf828c772010-01-25 21:49:42 +00001from autotest_lib.frontend.shared import exceptions
2
3class ConstraintError(Exception):
4 """Raised when an error occurs applying a Constraint."""
5
6
7class BaseQueryProcessor(object):
8 # maps selector name to (selector, constraint)
9 _selectors = None
10 _alias_counter = 0
11
12
13 @classmethod
14 def _initialize_selectors(cls):
15 if not cls._selectors:
16 cls._selectors = {}
17 cls._add_all_selectors()
18
19
20 @classmethod
21 def _add_all_selectors(cls):
22 """
23 Subclasses should override this to define which selectors they accept.
24 """
25 pass
26
27
28 @classmethod
29 def _add_field_selector(cls, name, field=None, value_transform=None,
30 doc=None):
31 if not field:
32 field = name
33 cls._add_selector(Selector(name, doc),
34 _FieldConstraint(field, value_transform))
35
36
37 @classmethod
38 def _add_related_existence_selector(cls, name, model, field, doc=None):
39 cls._add_selector(Selector(name, doc),
40 _RelatedExistenceConstraint(model, field,
41 cls.make_alias))
42
43
44 @classmethod
45 def _add_selector(cls, selector, constraint):
46 cls._selectors[selector.name] = (selector, constraint)
47
48
49 @classmethod
50 def make_alias(cls):
51 cls._alias_counter += 1
52 return 'alias%s' % cls._alias_counter
53
54
55 @classmethod
56 def selectors(cls):
57 cls._initialize_selectors()
58 return tuple(selector for selector, constraint
59 in cls._selectors.itervalues())
60
61
62 @classmethod
63 def has_selector(cls, selector_name):
64 cls._initialize_selectors()
65 return selector_name in cls._selectors
66
67
68 def apply_selector(self, queryset, selector_name, value,
69 comparison_type='equals', is_inverse=False):
70 _, constraint = self._selectors[selector_name]
71 try:
72 return constraint.apply_constraint(queryset, value, comparison_type,
73 is_inverse)
74 except ConstraintError, exc:
75 raise exceptions.BadRequest('Selector %s: %s'
76 % (selector_name, exc))
77
78
79 # common value conversions
80
81 @classmethod
82 def read_boolean(cls, boolean_input):
83 if boolean_input.lower() == 'true':
84 return True
85 if boolean_input.lower() == 'false':
86 return False
87 raise exceptions.BadRequest('Invalid input for boolean: %r'
88 % boolean_input)
89
90
91class Selector(object):
92 def __init__(self, name, doc):
93 self.name = name
94 self.doc = doc
95
96
97class Constraint(object):
98 def apply_constraint(self, queryset, value, comparison_type, is_inverse):
99 raise NotImplementedError
100
101
102class _FieldConstraint(Constraint):
103 def __init__(self, field, value_transform=None):
104 self._field = field
105 self._value_transform = value_transform
106
107
108 _COMPARISON_MAP = {
109 'equals': 'exact',
110 'lt': 'lt',
111 'le': 'lte',
112 'gt': 'gt',
113 'ge': 'gte',
114 'contains': 'contains',
115 'startswith': 'startswith',
116 'endswith': 'endswith',
117 'in': 'in',
118 }
119
120
121 def apply_constraint(self, queryset, value, comparison_type, is_inverse):
122 if self._value_transform:
123 value = self._value_transform(value)
124
125 kwarg_name = str(self._field + '__' +
126 self._COMPARISON_MAP[comparison_type])
127
128 if is_inverse:
129 return queryset.exclude(**{kwarg_name: value})
130 else:
131 return queryset.filter(**{kwarg_name: value})
132
133
134class _RelatedExistenceConstraint(Constraint):
135 def __init__(self, model, field, make_alias_fn):
136 self._model = model
137 self._field = field
138 self._make_alias_fn = make_alias_fn
139
140
141 def apply_constraint(self, queryset, value, comparison_type, is_inverse):
142 if comparison_type not in (None, 'equals'):
143 raise ConstraintError('Can only use equals or not equals with '
144 'this selector')
145 related_query = self._model.objects.filter(**{self._field: value})
146 if not related_query:
147 raise ConstraintError('%s %s not found' % (self._model_name, value))
148 alias = self._make_alias_fn()
149 queryset = queryset.model.objects.join_custom_field(queryset,
150 related_query,
151 alias)
152 if is_inverse:
153 condition = '%s.%s IS NULL'
154 else:
155 condition = '%s.%s IS NOT NULL'
156 condition %= (alias,
157 queryset.model.objects.key_on_joined_table(related_query))
158
159 queryset = queryset.model.objects.add_where(queryset, condition)
160
161 return queryset