# Copyright (c) 2009 Dennis Kaarsemaker <dennis@kaarsemaker.net>
# All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
# 
#     1. Redistributions of source code must retain the above copyright notice, 
#        this list of conditions and the following disclaimer.
#     
#     2. Redistributions in binary form must reproduce the above copyright 
#        notice, this list of conditions and the following disclaimer in the
#        documentation and/or other materials provided with the distribution.
# 
#     3. Neither the name of Django nor the names of its contributors may be used
#        to endorse or promote products derived from this software without
#        specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

from django.db.models import Q
from django.core.management import BaseCommand
from django.template import loader, Template, Context
from optparse import make_option
import os.path
import sys

usage="""The django ORM will be queried with the filters on the commandline. Records
will be separated with newlines, fields with the specified separator 
(the default is a comma). Alternatively, a template can be specified which 
will be passed the result of the query as the 'objects' variable

Query key/value pairs can be prefixed with a '!' to negate it, internally this uses
a Q object.

Examples:
 - Display name and assettag of all mc01 servers
   %prog query name__startswith=mc01 -f name,assettag
 - Get a list of name, ip, mac for all servers where the does not contain .82.
   %prog query -m Interface !ip_address__contains='.82.' -f server.name,ip_address,mac_address
 - Use a template to get the roles, depending on mac address
   %prog query interface__mac_address=00:17:A4:8D:E6:BC -t '{{ objects.0.role_set.all|join:"," }}'

/!\\ Warning /!\\
This script does not do much error checking. If you spell your query wrong, or
do something wrong with templates, you will get a python traceback and not a
nice error message."""

class Command(BaseCommand):
    option_list = BaseCommand.option_list + (
        make_option('-a', '--application', dest="application",
                    default=os.environ.get("DJANGO_QUERY_DEFAULT_APPLICATION", None),
                    help="Use this application", metavar="APP"),
        make_option('-m', '--model', dest="model",
                    default=os.environ.get("DJANGO_QUERY_DEFAULT_MODEL", None),
                    help="Query this model"),
        make_option('-f', '--fields', dest="fields", default=None,
                    help="Give these fields"),
        make_option('-o', '--order', dest="order", default=None,
                    help="Order by this field"),
        make_option('-s', '--separator', dest="separator", default=",",
                    help="Output separator"),
        make_option('-t', '--template', dest="template", default='',
                    help="Template in django syntax"),
        make_option('-T', '--template-file', dest="template_file", default=None,
                    help="File containing the template (abs/rel path or loader path)")
    )
    help = usage
    args = 'filter [filter ...]'

    def handle(self, *args, **options):
        if not options['application']:
            print "You must specify which application to use"
            sys.exit(1)
        if not options['model']:
            print "You must specify which model to use"
            sys.exit(1)
        if not options['fields'] and not options['template'] and not options['template_file']:
            print "You must specify a list of fields or a template"
            sys.exit(1)

        # Import the model
        models = options['application'] + '.models'
        __import__(models)
        models = sys.modules[models]
        model = getattr(models, options['model'])

        # Create queryset
        qargs = []
        for x in args:
            key, val = x.split('=',1)
            if key.startswith('!') or key.startswith('~'):
                qargs.append(~Q(**{key[1:]: val}))
            else:
                qargs.append(Q(**{key: val}))
        queryset = model.objects.filter(*qargs)
        if options['order']:
            queryset = queryset.order_by(options['order'])

        # Generate output
        if options['template'] or options['template_file']:
            template = Template(options['template'])
            tf = options['template_file']
            if tf == '-':
                template = Template(sys.stdin.read())
            elif tf and os.path.exists(tf):
                template = Template(open(tf).read())
            elif tf:
                template = loader.get_template(tf)
            print template.render(Context({'objects': queryset}))
        else:
            def getattr_r(obj, attr):
                if '.' in attr:
                    me, next = attr.split('.',1)
                    return getattr_r(getattr(obj, me), next)
                return getattr(obj, attr)
            fields = options['fields'].split(',')
            for record in queryset:
                print options['separator'].join([unicode(getattr_r(record, x)) for x in fields])

