root / snf-django-lib / snf_django / management / utils.py @ 4267cb32
History | View | Annotate | Download (8.3 kB)
1 | 91c788ec | Christos Stavrakakis | # Copyright 2012 - 2014 GRNET S.A. All rights reserved.
|
---|---|---|---|
2 | 225cea18 | Christos Stavrakakis | #
|
3 | 225cea18 | Christos Stavrakakis | # Redistribution and use in source and binary forms, with or
|
4 | 225cea18 | Christos Stavrakakis | # without modification, are permitted provided that the following
|
5 | 225cea18 | Christos Stavrakakis | # conditions are met:
|
6 | 225cea18 | Christos Stavrakakis | #
|
7 | 225cea18 | Christos Stavrakakis | # 1. Redistributions of source code must retain the above
|
8 | 225cea18 | Christos Stavrakakis | # copyright notice, this list of conditions and the following
|
9 | 225cea18 | Christos Stavrakakis | # disclaimer.
|
10 | 225cea18 | Christos Stavrakakis | #
|
11 | 225cea18 | Christos Stavrakakis | # 2. Redistributions in binary form must reproduce the above
|
12 | 225cea18 | Christos Stavrakakis | # copyright notice, this list of conditions and the following
|
13 | 225cea18 | Christos Stavrakakis | # disclaimer in the documentation and/or other materials
|
14 | 225cea18 | Christos Stavrakakis | # provided with the distribution.
|
15 | 225cea18 | Christos Stavrakakis | #
|
16 | 225cea18 | Christos Stavrakakis | # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
|
17 | 225cea18 | Christos Stavrakakis | # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
18 | 225cea18 | Christos Stavrakakis | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
19 | 225cea18 | Christos Stavrakakis | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
|
20 | 225cea18 | Christos Stavrakakis | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
21 | 225cea18 | Christos Stavrakakis | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
22 | 225cea18 | Christos Stavrakakis | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
23 | 225cea18 | Christos Stavrakakis | # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
24 | 225cea18 | Christos Stavrakakis | # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
25 | 225cea18 | Christos Stavrakakis | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
26 | 225cea18 | Christos Stavrakakis | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
27 | 225cea18 | Christos Stavrakakis | # POSSIBILITY OF SUCH DAMAGE.
|
28 | 225cea18 | Christos Stavrakakis | #
|
29 | 225cea18 | Christos Stavrakakis | # The views and conclusions contained in the software and
|
30 | 225cea18 | Christos Stavrakakis | # documentation are those of the authors and should not be
|
31 | 225cea18 | Christos Stavrakakis | # interpreted as representing official policies, either expressed
|
32 | 225cea18 | Christos Stavrakakis | # or implied, of GRNET S.A.
|
33 | 225cea18 | Christos Stavrakakis | |
34 | b0e7f310 | Christos Stavrakakis | import json |
35 | f105d79d | Christos Stavrakakis | import csv |
36 | 48233747 | Christos Stavrakakis | import operator |
37 | 91c788ec | Christos Stavrakakis | import locale |
38 | 91c788ec | Christos Stavrakakis | import unicodedata |
39 | 91c788ec | Christos Stavrakakis | |
40 | 225cea18 | Christos Stavrakakis | from datetime import datetime |
41 | 225cea18 | Christos Stavrakakis | from django.utils.timesince import timesince, timeuntil |
42 | 48233747 | Christos Stavrakakis | from django.db.models.query import QuerySet |
43 | 91c788ec | Christos Stavrakakis | from django.utils.encoding import smart_unicode, smart_str |
44 | 91c788ec | Christos Stavrakakis | |
45 | 91c788ec | Christos Stavrakakis | |
46 | 91c788ec | Christos Stavrakakis | def smart_locale_unicode(s, **kwargs): |
47 | 91c788ec | Christos Stavrakakis | """Wrapper around 'smart_unicode' using user's preferred encoding."""
|
48 | 91c788ec | Christos Stavrakakis | encoding = locale.getpreferredencoding() |
49 | 91c788ec | Christos Stavrakakis | return smart_unicode(s, encoding=encoding, **kwargs)
|
50 | 91c788ec | Christos Stavrakakis | |
51 | 91c788ec | Christos Stavrakakis | |
52 | 91c788ec | Christos Stavrakakis | def smart_locale_str(s, errors='replace', **kwargs): |
53 | 91c788ec | Christos Stavrakakis | """Wrapper around 'smart_str' using user's preferred encoding."""
|
54 | 91c788ec | Christos Stavrakakis | encoding = locale.getpreferredencoding() |
55 | 91c788ec | Christos Stavrakakis | return smart_str(s, encoding=encoding, errors=errors, **kwargs)
|
56 | 91c788ec | Christos Stavrakakis | |
57 | 91c788ec | Christos Stavrakakis | |
58 | 91c788ec | Christos Stavrakakis | def safe_string(s): |
59 | 91c788ec | Christos Stavrakakis | """Escape control characters from unicode and string objects."""
|
60 | 91c788ec | Christos Stavrakakis | if not isinstance(s, basestring): |
61 | 91c788ec | Christos Stavrakakis | return s
|
62 | 91c788ec | Christos Stavrakakis | if isinstance(s, unicode): |
63 | 91c788ec | Christos Stavrakakis | return "".join(ch.encode("unicode_escape") |
64 | 91c788ec | Christos Stavrakakis | if unicodedata.category(ch)[0] == "C" else |
65 | 91c788ec | Christos Stavrakakis | ch for ch in s) |
66 | 91c788ec | Christos Stavrakakis | return s.encode("string-escape") |
67 | 225cea18 | Christos Stavrakakis | |
68 | 225cea18 | Christos Stavrakakis | |
69 | 225cea18 | Christos Stavrakakis | def parse_bool(value, strict=True): |
70 | 225cea18 | Christos Stavrakakis | """Convert a string to boolen value.
|
71 | 225cea18 | Christos Stavrakakis |
|
72 | ec5ebdf5 | Giorgos Korfiatis | If strict is True, then ValueError will be raised, if the string can not be
|
73 | 225cea18 | Christos Stavrakakis | converted to boolean. Otherwise the string will be returned as is.
|
74 | 225cea18 | Christos Stavrakakis |
|
75 | 225cea18 | Christos Stavrakakis | """
|
76 | 225cea18 | Christos Stavrakakis | if value.lower() in ("yes", "true", "t", "1"): |
77 | 225cea18 | Christos Stavrakakis | return True |
78 | 225cea18 | Christos Stavrakakis | if value.lower() in ("no", "false", "f", "0"): |
79 | 225cea18 | Christos Stavrakakis | return False |
80 | 225cea18 | Christos Stavrakakis | |
81 | 225cea18 | Christos Stavrakakis | if strict:
|
82 | ec5ebdf5 | Giorgos Korfiatis | raise ValueError("Cannot convert '%s' to boolean value" % value) |
83 | 225cea18 | Christos Stavrakakis | else:
|
84 | 225cea18 | Christos Stavrakakis | return value
|
85 | 225cea18 | Christos Stavrakakis | |
86 | 225cea18 | Christos Stavrakakis | |
87 | 225cea18 | Christos Stavrakakis | def format_bool(b): |
88 | 225cea18 | Christos Stavrakakis | """Convert a boolean value to YES or NO."""
|
89 | 225cea18 | Christos Stavrakakis | return "YES" if b else "NO" |
90 | 225cea18 | Christos Stavrakakis | |
91 | 225cea18 | Christos Stavrakakis | |
92 | 225cea18 | Christos Stavrakakis | def format_date(d): |
93 | 225cea18 | Christos Stavrakakis | if not d: |
94 | 225cea18 | Christos Stavrakakis | return "" |
95 | 225cea18 | Christos Stavrakakis | |
96 | 225cea18 | Christos Stavrakakis | if d < datetime.now():
|
97 | 225cea18 | Christos Stavrakakis | return timesince(d) + " ago" |
98 | 225cea18 | Christos Stavrakakis | else:
|
99 | 225cea18 | Christos Stavrakakis | return "in " + timeuntil(d) |
100 | 225cea18 | Christos Stavrakakis | |
101 | 225cea18 | Christos Stavrakakis | |
102 | 48233747 | Christos Stavrakakis | def filter_results(results, filters): |
103 | 48233747 | Christos Stavrakakis | if isinstance(results, QuerySet): |
104 | 48233747 | Christos Stavrakakis | return filter_queryset_results(results, filters)
|
105 | 48233747 | Christos Stavrakakis | elif isinstance(results, list): |
106 | 48233747 | Christos Stavrakakis | return filter_object_results(results, filters)
|
107 | 48233747 | Christos Stavrakakis | else:
|
108 | 48233747 | Christos Stavrakakis | raise ValueError("Invalid type for results argument: %s", results) |
109 | 48233747 | Christos Stavrakakis | |
110 | 48233747 | Christos Stavrakakis | |
111 | 48233747 | Christos Stavrakakis | def parse_queryset_filters(filters): |
112 | 225cea18 | Christos Stavrakakis | """Parse a string into lookup parameters for QuerySet.filter(**kwargs).
|
113 | 225cea18 | Christos Stavrakakis |
|
114 | 225cea18 | Christos Stavrakakis | This functions converts a string of comma-separated key 'cond' val triples
|
115 | 225cea18 | Christos Stavrakakis | to two dictionaries, containing lookup parameters to be used for filter
|
116 | 225cea18 | Christos Stavrakakis | and exclude functions of QuerySet.
|
117 | 225cea18 | Christos Stavrakakis |
|
118 | 225cea18 | Christos Stavrakakis | e.g. filter_by="foo>=2, baz!=4" -> ({"foo__gte": "2"}, {"baz": "4"})
|
119 | 225cea18 | Christos Stavrakakis |
|
120 | 225cea18 | Christos Stavrakakis | """
|
121 | 48233747 | Christos Stavrakakis | OP_MAP = [ |
122 | 48233747 | Christos Stavrakakis | (">=", "__gte"), |
123 | 48233747 | Christos Stavrakakis | ("=>", "__gte"), |
124 | 48233747 | Christos Stavrakakis | (">", "__gt"), |
125 | 48233747 | Christos Stavrakakis | ("<=", "__lte"), |
126 | 48233747 | Christos Stavrakakis | ("=<", "__lte"), |
127 | 48233747 | Christos Stavrakakis | ("<", "__lt"), |
128 | 48233747 | Christos Stavrakakis | ("=", ""), |
129 | 48233747 | Christos Stavrakakis | ] |
130 | 225cea18 | Christos Stavrakakis | |
131 | 225cea18 | Christos Stavrakakis | filter_dict = {} |
132 | 225cea18 | Christos Stavrakakis | exclude_dict = {} |
133 | 48233747 | Christos Stavrakakis | for filter_str in filters.split(","): |
134 | 48233747 | Christos Stavrakakis | if "!=" in filter_str: |
135 | 48233747 | Christos Stavrakakis | key, val = filter_str.split("!=")
|
136 | 225cea18 | Christos Stavrakakis | exclude_dict[key] = parse_bool(val, strict=False)
|
137 | 48233747 | Christos Stavrakakis | continue
|
138 | 78c79ac7 | Giorgos Korfiatis | for op, new_op in OP_MAP: |
139 | 48233747 | Christos Stavrakakis | if op in filter_str: |
140 | 48233747 | Christos Stavrakakis | key, val = filter_str.split(op) |
141 | 225cea18 | Christos Stavrakakis | filter_dict[key + new_op] = parse_bool(val, strict=False)
|
142 | 48233747 | Christos Stavrakakis | break
|
143 | 48233747 | Christos Stavrakakis | else:
|
144 | 48233747 | Christos Stavrakakis | raise ValueError("Unknown filter expression: %s" % filter_str) |
145 | 225cea18 | Christos Stavrakakis | |
146 | 225cea18 | Christos Stavrakakis | return (filter_dict, exclude_dict)
|
147 | 225cea18 | Christos Stavrakakis | |
148 | 225cea18 | Christos Stavrakakis | |
149 | 48233747 | Christos Stavrakakis | def filter_queryset_results(results, filters): |
150 | 48233747 | Christos Stavrakakis | filter_dict, exclude_dict = parse_queryset_filters(filters) |
151 | 48233747 | Christos Stavrakakis | return results.exclude(**exclude_dict).filter(**filter_dict)
|
152 | 48233747 | Christos Stavrakakis | |
153 | 48233747 | Christos Stavrakakis | |
154 | 48233747 | Christos Stavrakakis | def parse_object_filters(filters): |
155 | 48233747 | Christos Stavrakakis | OP_MAP = [ |
156 | 48233747 | Christos Stavrakakis | (">=", operator.ge),
|
157 | 48233747 | Christos Stavrakakis | ("=>", operator.ge),
|
158 | 48233747 | Christos Stavrakakis | (">", operator.gt),
|
159 | 48233747 | Christos Stavrakakis | ("<=", operator.le),
|
160 | 48233747 | Christos Stavrakakis | ("=<", operator.le),
|
161 | 48233747 | Christos Stavrakakis | ("<", operator.lt),
|
162 | 48233747 | Christos Stavrakakis | ("!=", operator.ne),
|
163 | 48233747 | Christos Stavrakakis | ("=", operator.eq),
|
164 | 48233747 | Christos Stavrakakis | ] |
165 | 48233747 | Christos Stavrakakis | filters = [] |
166 | 48233747 | Christos Stavrakakis | for filter_str in filters.split(","): |
167 | 48233747 | Christos Stavrakakis | for op, op_func in OP_MAP: |
168 | 48233747 | Christos Stavrakakis | if op in filter_str: |
169 | 48233747 | Christos Stavrakakis | key, val = filter_str.split(op) |
170 | 48233747 | Christos Stavrakakis | filters.append((key.strip(), op_func, val.strip())) |
171 | 48233747 | Christos Stavrakakis | break
|
172 | 48233747 | Christos Stavrakakis | else:
|
173 | 48233747 | Christos Stavrakakis | raise ValueError("Unknown filter expression: %s" % filter_str) |
174 | 48233747 | Christos Stavrakakis | return filters
|
175 | 48233747 | Christos Stavrakakis | |
176 | 48233747 | Christos Stavrakakis | |
177 | 48233747 | Christos Stavrakakis | def filter_object_results(results, filters): |
178 | 48233747 | Christos Stavrakakis | results = list(results)
|
179 | 48233747 | Christos Stavrakakis | if results is []: |
180 | 48233747 | Christos Stavrakakis | return results
|
181 | 48233747 | Christos Stavrakakis | zero_result = results[0]
|
182 | 48233747 | Christos Stavrakakis | for key, op_func, val in parse_object_filters(filters): |
183 | 48233747 | Christos Stavrakakis | val_type = type(getattr(zero_result, key)) |
184 | 48233747 | Christos Stavrakakis | results = filter(lambda x: op_func(getattr(x, key), val_type(val)), |
185 | 48233747 | Christos Stavrakakis | results) |
186 | 48233747 | Christos Stavrakakis | return results
|
187 | 48233747 | Christos Stavrakakis | |
188 | 48233747 | Christos Stavrakakis | |
189 | b0e7f310 | Christos Stavrakakis | def pprint_table(out, table, headers=None, output_format='pretty', |
190 | b482fbcc | Giorgos Korfiatis | separator=None, vertical=False, title=None): |
191 | 225cea18 | Christos Stavrakakis | """Print a pretty, aligned string representation of table.
|
192 | 225cea18 | Christos Stavrakakis |
|
193 | 225cea18 | Christos Stavrakakis | Works by finding out the max width of each column and padding to data
|
194 | 225cea18 | Christos Stavrakakis | to this value.
|
195 | 225cea18 | Christos Stavrakakis | """
|
196 | 225cea18 | Christos Stavrakakis | |
197 | 225cea18 | Christos Stavrakakis | assert(isinstance(table, (list, tuple))), "Invalid table type" |
198 | 225cea18 | Christos Stavrakakis | if headers:
|
199 | 225cea18 | Christos Stavrakakis | assert(isinstance(headers, (list, tuple))), "Invalid headers type" |
200 | 225cea18 | Christos Stavrakakis | |
201 | b0e7f310 | Christos Stavrakakis | sep = separator if separator else " " |
202 | b0e7f310 | Christos Stavrakakis | |
203 | f105d79d | Christos Stavrakakis | if headers:
|
204 | 01c660b6 | Christos Stavrakakis | headers = map(smart_unicode, headers)
|
205 | 8c911970 | Christos Stavrakakis | table = [map(safe_string, row) for row in table] |
206 | 4267cb32 | Christos Stavrakakis | table = [map(smart_unicode, row) for row in table] |
207 | b0e7f310 | Christos Stavrakakis | |
208 | b0e7f310 | Christos Stavrakakis | if output_format == "json": |
209 | f105d79d | Christos Stavrakakis | assert(headers is not None), "json output format requires headers" |
210 | b0e7f310 | Christos Stavrakakis | table = [dict(zip(headers, row)) for row in table] |
211 | b0e7f310 | Christos Stavrakakis | out.write(json.dumps(table, indent=4))
|
212 | b0e7f310 | Christos Stavrakakis | out.write("\n")
|
213 | b0e7f310 | Christos Stavrakakis | elif output_format == "csv": |
214 | f105d79d | Christos Stavrakakis | cw = csv.writer(out) |
215 | b0e7f310 | Christos Stavrakakis | if headers:
|
216 | f105d79d | Christos Stavrakakis | table.insert(0, headers)
|
217 | f105d79d | Christos Stavrakakis | cw.writerows(table) |
218 | b0e7f310 | Christos Stavrakakis | elif output_format == "pretty": |
219 | 28578e52 | Christos Stavrakakis | if vertical:
|
220 | 28578e52 | Christos Stavrakakis | assert(len(table) == 1) |
221 | 28578e52 | Christos Stavrakakis | row = table[0]
|
222 | 28578e52 | Christos Stavrakakis | max_key = max(map(len, headers)) |
223 | 28578e52 | Christos Stavrakakis | for row in table: |
224 | 28578e52 | Christos Stavrakakis | for (k, v) in zip(headers, row): |
225 | 01c660b6 | Christos Stavrakakis | k = k.ljust(max_key) |
226 | 28578e52 | Christos Stavrakakis | out.write("%s: %s\n" % (k, v))
|
227 | 28578e52 | Christos Stavrakakis | else:
|
228 | 28578e52 | Christos Stavrakakis | # Find out the max width of each column
|
229 | 28578e52 | Christos Stavrakakis | columns = [headers] + table if headers else table |
230 | 28578e52 | Christos Stavrakakis | widths = [max(map(len, col)) for col in zip(*(columns))] |
231 | 28578e52 | Christos Stavrakakis | |
232 | 28578e52 | Christos Stavrakakis | t_length = sum(widths) + len(sep) * (len(widths) - 1) |
233 | b482fbcc | Giorgos Korfiatis | if title is not None: |
234 | d9f2a9e1 | Christos Stavrakakis | t_length = max(t_length, len(title)) |
235 | a859190b | Christos Stavrakakis | out.write("-" * t_length + "\n") |
236 | b482fbcc | Giorgos Korfiatis | out.write(title.center(t_length) + "\n")
|
237 | a859190b | Christos Stavrakakis | out.write("-" * t_length + "\n") |
238 | 28578e52 | Christos Stavrakakis | if headers:
|
239 | 28578e52 | Christos Stavrakakis | # pretty print the headers
|
240 | 01c660b6 | Christos Stavrakakis | line = sep.join(v.rjust(w) |
241 | 28578e52 | Christos Stavrakakis | for v, w in zip(headers, widths)) |
242 | 28578e52 | Christos Stavrakakis | out.write(line + "\n")
|
243 | 28578e52 | Christos Stavrakakis | out.write("-" * t_length + "\n") |
244 | 28578e52 | Christos Stavrakakis | |
245 | 28578e52 | Christos Stavrakakis | # print the rest table
|
246 | 28578e52 | Christos Stavrakakis | for row in table: |
247 | 01c660b6 | Christos Stavrakakis | line = sep.join(v.rjust(w) for v, w in zip(row, widths)) |
248 | 28578e52 | Christos Stavrakakis | out.write(line + "\n")
|
249 | b0e7f310 | Christos Stavrakakis | else:
|
250 | b0e7f310 | Christos Stavrakakis | raise ValueError("Unknown output format '%s'" % output_format) |