Statistics
| Branch: | Tag: | Revision:

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

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 value.lower() in ("yes", "true", "t", "1"):
53
        return True
54
    if value.lower() in ("no", "false", "f", "0"):
55
        return False
56

    
57
    if strict:
58
        raise ValueError("Cannot convert '%s' to boolean value" % value)
59
    else:
60
        return value
61

    
62

    
63
def format_bool(b):
64
    """Convert a boolean value to YES or NO."""
65
    return "YES" if b else "NO"
66

    
67

    
68
def format_date(d):
69
    if not d:
70
        return ""
71

    
72
    if d < datetime.now():
73
        return timesince(d) + " ago"
74
    else:
75
        return "in " + timeuntil(d)
76

    
77

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

    
86

    
87
def parse_queryset_filters(filters):
88
    """Parse a string into lookup parameters for QuerySet.filter(**kwargs).
89

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

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

96
    """
97
    OP_MAP = [
98
        (">=", "__gte"),
99
        ("=>", "__gte"),
100
        (">",  "__gt"),
101
        ("<=", "__lte"),
102
        ("=<", "__lte"),
103
        ("<", "__lt"),
104
        ("=", ""),
105
        ]
106

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

    
122
    return (filter_dict, exclude_dict)
123

    
124

    
125
def filter_queryset_results(results, filters):
126
    filter_dict, exclude_dict = parse_queryset_filters(filters)
127
    return results.exclude(**exclude_dict).filter(**filter_dict)
128

    
129

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

    
152

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

    
164

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

169
    Works by finding out the max width of each column and padding to data
170
    to this value.
171
    """
172

    
173
    assert(isinstance(table, (list, tuple))), "Invalid table type"
174
    if headers:
175
        assert(isinstance(headers, (list, tuple))), "Invalid headers type"
176

    
177
    sep = separator if separator else "  "
178

    
179
    def stringnify(obj):
180
        if isinstance(obj, (unicode, str)):
181
            return udec(obj)
182
        else:
183
            return str(obj)
184

    
185
    if headers:
186
        headers = map(stringnify, headers)
187
    table = [map(stringnify, row) for row in table]
188

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

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

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