Statistics
| Branch: | Tag: | Revision:

root / snf-django-lib / snf_django / management / utils.py @ 2be50766

History | View | Annotate | Download (7.7 kB)

1
# Copyright 2012 - 2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
import json
35
import csv
36
import functools
37
import operator
38
from datetime import datetime
39
from django.utils.timesince import timesince, timeuntil
40
from django.db.models.query import QuerySet
41

    
42
from synnefo.util.text import uenc, udec
43

    
44

    
45
def parse_bool(value, strict=True):
46
    """Convert a string to boolen value.
47

48
    If strict is True, then ValueError will be raised, if the string can not be
49
    converted to boolean. Otherwise the string will be returned as is.
50

51
    """
52
    if isinstance(value, bool):
53
        return value
54

    
55
    if value.lower() in ("yes", "true", "t", "1"):
56
        return True
57
    if value.lower() in ("no", "false", "f", "0"):
58
        return False
59

    
60
    if strict:
61
        raise ValueError("Cannot convert '%s' to boolean value" % value)
62
    else:
63
        return value
64

    
65

    
66
def format_bool(b):
67
    """Convert a boolean value to YES or NO."""
68
    return "YES" if b else "NO"
69

    
70

    
71
def format_date(d):
72
    if not d:
73
        return ""
74

    
75
    if d < datetime.now():
76
        return timesince(d) + " ago"
77
    else:
78
        return "in " + timeuntil(d)
79

    
80

    
81
def filter_results(results, filters):
82
    if isinstance(results, QuerySet):
83
        return filter_queryset_results(results, filters)
84
    elif isinstance(results, list):
85
        return filter_object_results(results, filters)
86
    else:
87
        raise ValueError("Invalid type for results argument: %s", results)
88

    
89

    
90
def parse_queryset_filters(filters):
91
    """Parse a string into lookup parameters for QuerySet.filter(**kwargs).
92

93
    This functions converts a string of comma-separated key 'cond' val triples
94
    to two dictionaries, containing lookup parameters to be used for filter
95
    and exclude functions of QuerySet.
96

97
    e.g. filter_by="foo>=2, baz!=4" -> ({"foo__gte": "2"}, {"baz": "4"})
98

99
    """
100
    OP_MAP = [
101
        (">=", "__gte"),
102
        ("=>", "__gte"),
103
        (">",  "__gt"),
104
        ("<=", "__lte"),
105
        ("=<", "__lte"),
106
        ("<", "__lt"),
107
        ("=", ""),
108
        ]
109

    
110
    filter_dict = {}
111
    exclude_dict = {}
112
    for filter_str in filters.split(","):
113
        if "!=" in filter_str:
114
            key, val = filter_str.split("!=")
115
            exclude_dict[key] = parse_bool(val, strict=False)
116
            continue
117
        for op, new_op in OP_MAP:
118
            if op in filter_str:
119
                key, val = filter_str.split(op)
120
                filter_dict[key + new_op] = parse_bool(val, strict=False)
121
                break
122
        else:
123
            raise ValueError("Unknown filter expression: %s" % filter_str)
124

    
125
    return (filter_dict, exclude_dict)
126

    
127

    
128
def filter_queryset_results(results, filters):
129
    filter_dict, exclude_dict = parse_queryset_filters(filters)
130
    return results.exclude(**exclude_dict).filter(**filter_dict)
131

    
132

    
133
def parse_object_filters(filters):
134
    OP_MAP = [
135
        (">=", operator.ge),
136
        ("=>", operator.ge),
137
        (">",  operator.gt),
138
        ("<=", operator.le),
139
        ("=<", operator.le),
140
        ("<", operator.lt),
141
        ("!=", operator.ne),
142
        ("=", operator.eq),
143
    ]
144
    filters = []
145
    for filter_str in filters.split(","):
146
        for op, op_func in OP_MAP:
147
            if op in filter_str:
148
                key, val = filter_str.split(op)
149
                filters.append((key.strip(), op_func, val.strip()))
150
                break
151
        else:
152
            raise ValueError("Unknown filter expression: %s" % filter_str)
153
    return filters
154

    
155

    
156
def filter_object_results(results, filters):
157
    results = list(results)
158
    if results is []:
159
        return results
160
    zero_result = results[0]
161
    for key, op_func, val in parse_object_filters(filters):
162
        val_type = type(getattr(zero_result, key))
163
        results = filter(lambda x: op_func(getattr(x, key), val_type(val)),
164
                         results)
165
    return results
166

    
167

    
168
def pprint_table(out, table, headers=None, output_format='pretty',
169
                 separator=None, vertical=False, title=None):
170
    """Print a pretty, aligned string representation of table.
171

172
    Works by finding out the max width of each column and padding to data
173
    to this value.
174
    """
175

    
176
    assert(isinstance(table, (list, tuple))), "Invalid table type"
177
    if headers:
178
        assert(isinstance(headers, (list, tuple))), "Invalid headers type"
179

    
180
    sep = separator if separator else "  "
181

    
182
    def stringnify(obj):
183
        if isinstance(obj, (unicode, str)):
184
            return udec(obj)
185
        else:
186
            return str(obj)
187

    
188
    if headers:
189
        headers = map(stringnify, headers)
190
    table = [map(stringnify, row) for row in table]
191

    
192
    if output_format == "json":
193
        assert(headers is not None), "json output format requires headers"
194
        table = [dict(zip(headers, row)) for row in table]
195
        out.write(json.dumps(table, indent=4))
196
        out.write("\n")
197
    elif output_format == "csv":
198
        cw = csv.writer(out)
199
        if headers:
200
            table.insert(0, headers)
201
        table = map(functools.partial(map, uenc), table)
202
        cw.writerows(table)
203
    elif output_format == "pretty":
204
        if vertical:
205
            assert(len(table) == 1)
206
            row = table[0]
207
            max_key = max(map(len, headers))
208
            for row in table:
209
                for (k, v) in zip(headers, row):
210
                    k = uenc(k.ljust(max_key))
211
                    v = uenc(v)
212
                    out.write("%s: %s\n" % (k, v))
213
        else:
214
            # Find out the max width of each column
215
            columns = [headers] + table if headers else table
216
            widths = [max(map(len, col)) for col in zip(*(columns))]
217

    
218
            t_length = sum(widths) + len(sep) * (len(widths) - 1)
219
            if title is not None:
220
                t_length = max(t_length, len(title))
221
                out.write("-" * t_length + "\n")
222
                out.write(title.center(t_length) + "\n")
223
                out.write("-" * t_length + "\n")
224
            if headers:
225
                # pretty print the headers
226
                line = sep.join(uenc(v.rjust(w))
227
                                for v, w in zip(headers, widths))
228
                out.write(line + "\n")
229
                out.write("-" * t_length + "\n")
230

    
231
            # print the rest table
232
            for row in table:
233
                line = sep.join(uenc(v.rjust(w)) for v, w in zip(row, widths))
234
                out.write(line + "\n")
235
    else:
236
        raise ValueError("Unknown output format '%s'" % output_format)