Statistics
| Branch: | Tag: | Revision:

root / snf-webproject / synnefo / webproject / management / commands / __init__.py @ b0e7f310

History | View | Annotate | Download (10.4 kB)

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

46 b0e7f310 Christos Stavrakakis
    Management command to handle common tasks when implementing a -list
47 b0e7f310 Christos Stavrakakis
    management command. This class handles the following tasks:
48 b0e7f310 Christos Stavrakakis

49 b0e7f310 Christos Stavrakakis
    * Retrieving objects from database.
50 b0e7f310 Christos Stavrakakis

51 b0e7f310 Christos Stavrakakis
    The DB model class is declared in ``object_class`` class attribute. Also,
52 b0e7f310 Christos Stavrakakis
    results can be filter using either the ``filters`` and ``excludes``
53 b0e7f310 Christos Stavrakakis
    attribute or the "--filter-by" option.
54 b0e7f310 Christos Stavrakakis

55 b0e7f310 Christos Stavrakakis
    * Display specific fields of the database objects.
56 b0e7f310 Christos Stavrakakis

57 b0e7f310 Christos Stavrakakis
    List of available fields is defined in the ``FIELDS`` class attribute,
58 b0e7f310 Christos Stavrakakis
    which is a dictionary mapping from field names to tuples containing the
59 b0e7f310 Christos Stavrakakis
    way the field is retrieved and a text help message to display. The first
60 b0e7f310 Christos Stavrakakis
    field of the tuple is either a string containing a chain of attribute
61 b0e7f310 Christos Stavrakakis
    accesses (e.g. "machine.flavor.cpu") either a callable function, taking
62 b0e7f310 Christos Stavrakakis
    as argument the DB object and returning a single value.
63 b0e7f310 Christos Stavrakakis

64 b0e7f310 Christos Stavrakakis
    The fields that will be displayed be default is contained in the ``fields``
65 b0e7f310 Christos Stavrakakis
    class attribute. The user can specify different fields using the "--fields"
66 b0e7f310 Christos Stavrakakis
    option.
67 b0e7f310 Christos Stavrakakis

68 b0e7f310 Christos Stavrakakis
    * Handling of user UUIDs and names.
69 b0e7f310 Christos Stavrakakis

70 b0e7f310 Christos Stavrakakis
    If the ``user_uuid_field`` is declared, then "--user" and "--display-mails"
71 b0e7f310 Christos Stavrakakis
    options will become available. The first one allows filtering via either
72 b0e7f310 Christos Stavrakakis
    a user's UUID or display name. The "--displayname" option will append
73 b0e7f310 Christos Stavrakakis
    the displayname of ther user with "user_uuid_field" to the output.
74 b0e7f310 Christos Stavrakakis

75 b0e7f310 Christos Stavrakakis
    * Pretty printing output to a nice table.
76 b0e7f310 Christos Stavrakakis

77 b0e7f310 Christos Stavrakakis
    """
78 b0e7f310 Christos Stavrakakis
79 b0e7f310 Christos Stavrakakis
    # The following fields must be handled in the ListCommand subclasses!
80 b0e7f310 Christos Stavrakakis
81 b0e7f310 Christos Stavrakakis
    # The django DB model
82 b0e7f310 Christos Stavrakakis
    object_class = None
83 b0e7f310 Christos Stavrakakis
    # The name of the field containg the user ID of the user, if any.
84 b0e7f310 Christos Stavrakakis
    user_uuid_field = None
85 b0e7f310 Christos Stavrakakis
    # The name of the field containg the deleted flag, if any.
86 b0e7f310 Christos Stavrakakis
    deleted_field = None
87 b0e7f310 Christos Stavrakakis
    # Dictionary with all available fields
88 b0e7f310 Christos Stavrakakis
    FIELDS = {}
89 b0e7f310 Christos Stavrakakis
    # List of fields to display by default
90 b0e7f310 Christos Stavrakakis
    fields = []
91 b0e7f310 Christos Stavrakakis
    # Default filters and excludes
92 b0e7f310 Christos Stavrakakis
    filters = {}
93 b0e7f310 Christos Stavrakakis
    excludes = {}
94 b0e7f310 Christos Stavrakakis
95 b0e7f310 Christos Stavrakakis
    help = "Generic List Command"
96 b0e7f310 Christos Stavrakakis
    option_list = BaseCommand.option_list + (
97 b0e7f310 Christos Stavrakakis
        make_option(
98 b0e7f310 Christos Stavrakakis
            "-o", "--output",
99 b0e7f310 Christos Stavrakakis
            dest="fields",
100 b0e7f310 Christos Stavrakakis
            help="Comma-separated list of output fields"),
101 b0e7f310 Christos Stavrakakis
        make_option(
102 b0e7f310 Christos Stavrakakis
            "--list-fields",
103 b0e7f310 Christos Stavrakakis
            dest="list_fields",
104 b0e7f310 Christos Stavrakakis
            action="store_true",
105 b0e7f310 Christos Stavrakakis
            default=False,
106 b0e7f310 Christos Stavrakakis
            help="List available output fields"),
107 b0e7f310 Christos Stavrakakis
        make_option(
108 b0e7f310 Christos Stavrakakis
            "--filter-by",
109 b0e7f310 Christos Stavrakakis
            dest="filter_by",
110 b0e7f310 Christos Stavrakakis
            metavar="FILTERS",
111 b0e7f310 Christos Stavrakakis
            help="Filter results. Comma separated list of key `cond` val pairs"
112 b0e7f310 Christos Stavrakakis
                 " that displayed entries must satisfy. e.g."
113 b0e7f310 Christos Stavrakakis
                 " --filter-by \"deleted=False,id>=22\"."),
114 b0e7f310 Christos Stavrakakis
        make_option(
115 b0e7f310 Christos Stavrakakis
            "--list-filters",
116 b0e7f310 Christos Stavrakakis
            dest="list_filters",
117 b0e7f310 Christos Stavrakakis
            action="store_true",
118 b0e7f310 Christos Stavrakakis
            default=False,
119 b0e7f310 Christos Stavrakakis
            help="List available filters"),
120 b0e7f310 Christos Stavrakakis
        make_option(
121 b0e7f310 Christos Stavrakakis
            "--no-headers",
122 b0e7f310 Christos Stavrakakis
            dest="headers",
123 b0e7f310 Christos Stavrakakis
            action="store_false",
124 b0e7f310 Christos Stavrakakis
            default=True,
125 b0e7f310 Christos Stavrakakis
            help="Do not display headers"),
126 b0e7f310 Christos Stavrakakis
        make_option(
127 b0e7f310 Christos Stavrakakis
            "--output-format",
128 b0e7f310 Christos Stavrakakis
            dest="output_format",
129 b0e7f310 Christos Stavrakakis
            metavar="[pretty, csv, json]",
130 b0e7f310 Christos Stavrakakis
            default="pretty",
131 b0e7f310 Christos Stavrakakis
            choices=["pretty", "csv", "json"],
132 b0e7f310 Christos Stavrakakis
            help="Select the output format: pretty [the default], tabs"
133 b0e7f310 Christos Stavrakakis
                 " [tab-separated output], csv [comma-separated output]"),
134 b0e7f310 Christos Stavrakakis
    )
135 b0e7f310 Christos Stavrakakis
136 b0e7f310 Christos Stavrakakis
    def __init__(self, *args, **kwargs):
137 b0e7f310 Christos Stavrakakis
        if self.user_uuid_field:
138 b0e7f310 Christos Stavrakakis
            self.option_list += (
139 b0e7f310 Christos Stavrakakis
                make_option(
140 b0e7f310 Christos Stavrakakis
                    "-u", "--user",
141 b0e7f310 Christos Stavrakakis
                    dest="user",
142 b0e7f310 Christos Stavrakakis
                    metavar="USER",
143 b0e7f310 Christos Stavrakakis
                    help="List items only for this user."
144 b0e7f310 Christos Stavrakakis
                         " 'USER' can be either a user UUID or a display"
145 b0e7f310 Christos Stavrakakis
                         " name"),
146 b0e7f310 Christos Stavrakakis
                make_option(
147 b0e7f310 Christos Stavrakakis
                    "--display-mails",
148 b0e7f310 Christos Stavrakakis
                    dest="display_mails",
149 b0e7f310 Christos Stavrakakis
                    action="store_true",
150 b0e7f310 Christos Stavrakakis
                    default=False,
151 b0e7f310 Christos Stavrakakis
                    help="Include the user's email"),
152 b0e7f310 Christos Stavrakakis
            )
153 b0e7f310 Christos Stavrakakis
154 b0e7f310 Christos Stavrakakis
        if self.deleted_field:
155 b0e7f310 Christos Stavrakakis
            self.option_list += (
156 b0e7f310 Christos Stavrakakis
                make_option(
157 b0e7f310 Christos Stavrakakis
                    "-d", "--deleted",
158 b0e7f310 Christos Stavrakakis
                    dest="deleted",
159 b0e7f310 Christos Stavrakakis
                    action="store_true",
160 b0e7f310 Christos Stavrakakis
                    help="Display only deleted items"),
161 b0e7f310 Christos Stavrakakis
            )
162 b0e7f310 Christos Stavrakakis
        super(ListCommand, self).__init__(*args, **kwargs)
163 b0e7f310 Christos Stavrakakis
164 b0e7f310 Christos Stavrakakis
    def handle(self, *args, **options):
165 b0e7f310 Christos Stavrakakis
        if len(args) > 0:
166 b0e7f310 Christos Stavrakakis
            raise CommandError("List commands do not accept any argument")
167 b0e7f310 Christos Stavrakakis
168 b0e7f310 Christos Stavrakakis
        assert(self.object_class), "object_class variable must be declared"
169 b0e7f310 Christos Stavrakakis
170 b0e7f310 Christos Stavrakakis
        if options["list_fields"]:
171 b0e7f310 Christos Stavrakakis
            self.display_fields()
172 b0e7f310 Christos Stavrakakis
            return
173 b0e7f310 Christos Stavrakakis
174 b0e7f310 Christos Stavrakakis
        if options["list_filters"]:
175 b0e7f310 Christos Stavrakakis
            self.display_filters()
176 b0e7f310 Christos Stavrakakis
            return
177 b0e7f310 Christos Stavrakakis
178 b0e7f310 Christos Stavrakakis
        # --output option
179 b0e7f310 Christos Stavrakakis
        if options["fields"]:
180 b0e7f310 Christos Stavrakakis
            fields = options["fields"]
181 b0e7f310 Christos Stavrakakis
            fields = fields.split(",")
182 b0e7f310 Christos Stavrakakis
            self.validate_fields(fields)
183 b0e7f310 Christos Stavrakakis
            self.fields = options["fields"].split(",")
184 b0e7f310 Christos Stavrakakis
185 b0e7f310 Christos Stavrakakis
        # --filter-by option
186 b0e7f310 Christos Stavrakakis
        if options["filter_by"]:
187 b0e7f310 Christos Stavrakakis
            filters, excludes = util.parse_filters(options["filter_by"])
188 b0e7f310 Christos Stavrakakis
        else:
189 b0e7f310 Christos Stavrakakis
            filters, excludes = ({}, {})
190 b0e7f310 Christos Stavrakakis
191 b0e7f310 Christos Stavrakakis
        self.filters.update(filters)
192 b0e7f310 Christos Stavrakakis
        self.excludes.update(excludes)
193 b0e7f310 Christos Stavrakakis
194 b0e7f310 Christos Stavrakakis
        # --user option
195 b0e7f310 Christos Stavrakakis
        user = options.get("user")
196 b0e7f310 Christos Stavrakakis
        if user:
197 b0e7f310 Christos Stavrakakis
            if "@" in user:
198 b0e7f310 Christos Stavrakakis
                user = UserCache().get_uuid(user)
199 b0e7f310 Christos Stavrakakis
            self.filters[self.user_uuid_field] = user
200 b0e7f310 Christos Stavrakakis
201 b0e7f310 Christos Stavrakakis
        # --deleted option
202 b0e7f310 Christos Stavrakakis
        if self.deleted_field:
203 b0e7f310 Christos Stavrakakis
            deleted = options.get("deleted")
204 b0e7f310 Christos Stavrakakis
            if deleted:
205 b0e7f310 Christos Stavrakakis
                self.filters[self.deleted_field] = True
206 b0e7f310 Christos Stavrakakis
            else:
207 b0e7f310 Christos Stavrakakis
                self.filters[self.deleted_field] = False
208 b0e7f310 Christos Stavrakakis
209 b0e7f310 Christos Stavrakakis
        # Special handling of arguments
210 b0e7f310 Christos Stavrakakis
        self.handle_args(self, *args, **options)
211 b0e7f310 Christos Stavrakakis
212 b0e7f310 Christos Stavrakakis
        objects = self.object_class.objects
213 b0e7f310 Christos Stavrakakis
        try:
214 b0e7f310 Christos Stavrakakis
            objects = objects.filter(**self.filters)
215 b0e7f310 Christos Stavrakakis
            objects = objects.exclude(**self.excludes)
216 b0e7f310 Christos Stavrakakis
        except FieldError as e:
217 b0e7f310 Christos Stavrakakis
            raise CommandError(e)
218 b0e7f310 Christos Stavrakakis
        except Exception as e:
219 b0e7f310 Christos Stavrakakis
            raise CommandError("Can not filter results: %s" % e)
220 b0e7f310 Christos Stavrakakis
221 b0e7f310 Christos Stavrakakis
        # --display-mails option
222 b0e7f310 Christos Stavrakakis
        display_mails = options.get("display_mails")
223 b0e7f310 Christos Stavrakakis
        if display_mails:
224 b0e7f310 Christos Stavrakakis
            if 'user_mail' in self.object_class._meta.get_all_field_names():
225 b0e7f310 Christos Stavrakakis
                raise RuntimeError("%s has already a 'user_mail' attribute")
226 b0e7f310 Christos Stavrakakis
227 b0e7f310 Christos Stavrakakis
            self.fields.append("user.email")
228 b0e7f310 Christos Stavrakakis
            self.FIELDS["user.email"] =\
229 b0e7f310 Christos Stavrakakis
                ("user_email", "The email of the owner.")
230 b0e7f310 Christos Stavrakakis
            uuids = [getattr(obj, self.user_uuid_field) for obj in objects]
231 b0e7f310 Christos Stavrakakis
            ucache = UserCache()
232 b0e7f310 Christos Stavrakakis
            ucache.fetch_names(list(set(uuids)))
233 b0e7f310 Christos Stavrakakis
            for obj in objects:
234 b0e7f310 Christos Stavrakakis
                uuid = getattr(obj, self.user_uuid_field)
235 b0e7f310 Christos Stavrakakis
                obj.user_email = ucache.get_name(uuid)
236 b0e7f310 Christos Stavrakakis
237 b0e7f310 Christos Stavrakakis
        # Special handling of DB results
238 b0e7f310 Christos Stavrakakis
        self.handle_db_objects(objects)
239 b0e7f310 Christos Stavrakakis
240 b0e7f310 Christos Stavrakakis
        headers = self.fields
241 b0e7f310 Christos Stavrakakis
        columns = [self.FIELDS[key][0] for key in headers]
242 b0e7f310 Christos Stavrakakis
243 b0e7f310 Christos Stavrakakis
        table = []
244 b0e7f310 Christos Stavrakakis
        for obj in objects:
245 b0e7f310 Christos Stavrakakis
            row = []
246 b0e7f310 Christos Stavrakakis
            for attr in columns:
247 b0e7f310 Christos Stavrakakis
                if callable(attr):
248 b0e7f310 Christos Stavrakakis
                    row.append(attr(obj))
249 b0e7f310 Christos Stavrakakis
                else:
250 b0e7f310 Christos Stavrakakis
                    item = obj
251 b0e7f310 Christos Stavrakakis
                    attrs = attr.split(".")
252 b0e7f310 Christos Stavrakakis
                    for attr in attrs:
253 b0e7f310 Christos Stavrakakis
                        item = getattr(item, attr)
254 b0e7f310 Christos Stavrakakis
                    row.append(item)
255 b0e7f310 Christos Stavrakakis
            table.append(row)
256 b0e7f310 Christos Stavrakakis
257 b0e7f310 Christos Stavrakakis
        # Special handle of output
258 b0e7f310 Christos Stavrakakis
        self.handle_output(table, headers)
259 b0e7f310 Christos Stavrakakis
260 b0e7f310 Christos Stavrakakis
        # Print output
261 b0e7f310 Christos Stavrakakis
        output_format = options["output_format"]
262 b0e7f310 Christos Stavrakakis
        headers = headers if options["headers"] else None
263 b0e7f310 Christos Stavrakakis
        util.pprint_table(self.stdout, table, headers, output_format)
264 b0e7f310 Christos Stavrakakis
265 b0e7f310 Christos Stavrakakis
    def handle_args(self, *args, **kwargs):
266 b0e7f310 Christos Stavrakakis
        pass
267 b0e7f310 Christos Stavrakakis
268 b0e7f310 Christos Stavrakakis
    def handle_db_objects(self, objects):
269 b0e7f310 Christos Stavrakakis
        pass
270 b0e7f310 Christos Stavrakakis
271 b0e7f310 Christos Stavrakakis
    def handle_output(self, table, headers):
272 b0e7f310 Christos Stavrakakis
        pass
273 b0e7f310 Christos Stavrakakis
274 b0e7f310 Christos Stavrakakis
    def display_fields(self):
275 b0e7f310 Christos Stavrakakis
        headers = ["Field", "Description"]
276 b0e7f310 Christos Stavrakakis
        table = []
277 b0e7f310 Christos Stavrakakis
        for field, (_, help_msg) in self.FIELDS.items():
278 b0e7f310 Christos Stavrakakis
            table.append((field, help_msg))
279 b0e7f310 Christos Stavrakakis
        util.pprint_table(self.stdout, table, headers)
280 b0e7f310 Christos Stavrakakis
281 b0e7f310 Christos Stavrakakis
    def validate_fields(self, fields):
282 b0e7f310 Christos Stavrakakis
        for f in fields:
283 b0e7f310 Christos Stavrakakis
            if f not in self.FIELDS.keys():
284 b0e7f310 Christos Stavrakakis
                raise CommandError("Unknown field '%s'. 'Use --list-fields"
285 b0e7f310 Christos Stavrakakis
                                   " option to find out available fields."
286 b0e7f310 Christos Stavrakakis
                                   % f)
287 b0e7f310 Christos Stavrakakis
288 b0e7f310 Christos Stavrakakis
    def display_filters(self):
289 b0e7f310 Christos Stavrakakis
        headers = ["Filter", "Description", "Help"]
290 b0e7f310 Christos Stavrakakis
        table = []
291 b0e7f310 Christos Stavrakakis
        for field in self.object_class._meta.fields:
292 b0e7f310 Christos Stavrakakis
            table.append((field.name, field.verbose_name, field.help_text))
293 b0e7f310 Christos Stavrakakis
        util.pprint_table(self.stdout, table, headers)