| 1 | from django.core.exceptions import FieldError |
| 2 | from django.core.management import BaseCommand |
| 3 | from django.db.models import Q |
| 4 | from django.template import loader, Template, Context, TemplateSyntaxError, TemplateDoesNotExist |
| 5 | from optparse import make_option |
| 6 | import os.path |
| 7 | import sys |
| 8 | |
| 9 | usage="""The django ORM will be queried with the filters on the commandline. |
| 10 | Records will be separated with newlines, fields with the specified separator |
| 11 | (the default is a comma). Alternatively, a template can be specified which will |
| 12 | be passed the result of the query as the 'objects' variable |
| 13 | |
| 14 | Query key/value pairs can be prefixed with a '!' or ~ to negate the query, |
| 15 | internally this uses a Q object. |
| 16 | |
| 17 | Examples: |
| 18 | - Display username and email for all users |
| 19 | %prog query -a django.contrib.auth -m User -f username |
| 20 | - Show who can delete sites |
| 21 | %prog query -a django.contrib.auth -m User -f username groups__permissions__codename=delete_site |
| 22 | - Use comma-separated values for __in lookups |
| 23 | %prog query -a django.contrib.auth -m User -f username groups__permissions__codename__in=delete_site,change_site |
| 24 | - Use a template to format the information |
| 25 | %prog query -a django.contrib.auth -m User -t '{% for o in objects %}{{ o.get_full_name }} has joined on {{ o.date_joined }} |
| 26 | {% endfor %}' |
| 27 | """ |
| 28 | |
| 29 | class Command(BaseCommand): |
| 30 | option_list = BaseCommand.option_list + ( |
| 31 | make_option('-a', '--application', dest="application", |
| 32 | default=os.environ.get("DJANGO_QUERY_DEFAULT_APPLICATION", None), |
| 33 | help="Use this application", metavar="APP"), |
| 34 | make_option('-m', '--model', dest="model", |
| 35 | default=os.environ.get("DJANGO_QUERY_DEFAULT_MODEL", None), |
| 36 | help="Query this model"), |
| 37 | make_option('-o', '--order', dest="order", default=None, |
| 38 | help="Order by this field"), |
| 39 | make_option('-f', '--fields', dest="fields", default=None, |
| 40 | help="Give these fields"), |
| 41 | make_option('-s', '--separator', dest="separator", default=",", |
| 42 | help="Output separator"), |
| 43 | make_option('-t', '--template', dest="template", default='', |
| 44 | help="Inline template in django syntax"), |
| 45 | make_option('-T', '--template-file', dest="template_file", default=None, |
| 46 | help="File containing the template (abs/rel path or loader path)") |
| 47 | ) |
| 48 | help = usage |
| 49 | args = 'filter [filter ...]' |
| 50 | |
| 51 | def handle(self, *args, **options): |
| 52 | if not options['application']: |
| 53 | print "You must specify which application to use" |
| 54 | sys.exit(1) |
| 55 | if not options['model']: |
| 56 | print "You must specify which model to use" |
| 57 | sys.exit(1) |
| 58 | if not options['fields'] and not options['template'] and not options['template_file']: |
| 59 | print "You must specify a list of fields or a template" |
| 60 | sys.exit(1) |
| 61 | |
| 62 | # Import the model |
| 63 | models = options['application'] + '.models' |
| 64 | __import__(models) |
| 65 | models = sys.modules[models] |
| 66 | model = getattr(models, options['model']) |
| 67 | |
| 68 | # Create queryset |
| 69 | qargs = [] |
| 70 | for x in args: |
| 71 | if '=' not in args: |
| 72 | print "Invalid filter '%s' - should be in attribute=value format" % x |
| 73 | sys.exit(1) |
| 74 | key, val = x.split('=',1) |
| 75 | # Accecpt comma-separated values for __in lookups |
| 76 | if key.endswith('__in'): |
| 77 | val = val.split(',') |
| 78 | if key.startswith('!') or key.startswith('~'): |
| 79 | qargs.append(~Q(**{key[1:]: val})) |
| 80 | else: |
| 81 | qargs.append(Q(**{key: val})) |
| 82 | try: |
| 83 | queryset = model.objects.filter(*qargs).distinct() |
| 84 | if options['order']: |
| 85 | queryset = queryset.order_by(options['order']) |
| 86 | # Force coercion into list to trap illegal values for options['order'] |
| 87 | queryset = list(queryset) |
| 88 | except FieldError, e: |
| 89 | print e |
| 90 | sys.exit(1) |
| 91 | |
| 92 | # Generate output |
| 93 | if options['template']: |
| 94 | # Inline template |
| 95 | template = options['template'] |
| 96 | elif options['template_file']: |
| 97 | # Template file, 3 options: stdin, existing path, or loader |
| 98 | tf = options['template_file'] |
| 99 | if tf == '-': |
| 100 | template = sys.stdin.read() |
| 101 | elif tf and os.path.exists(tf): |
| 102 | template = open(tf).read() |
| 103 | elif tf: |
| 104 | try: |
| 105 | template = loader.get_template(tf) |
| 106 | except TemplateDoesNotExist: |
| 107 | print "Template %s does not exist" % tf |
| 108 | sys.exit(1) |
| 109 | else: |
| 110 | # Build a (c)sv template |
| 111 | template = "{% for obj in objects %}" |
| 112 | for field in options['fields'].split(','): |
| 113 | template += '{{ obj.' + field + ' }}' + options['separator'] |
| 114 | template = template[:-len(options['separator'])] + "\n{% endfor %}" |
| 115 | try: |
| 116 | template = Template(template) |
| 117 | except TemplateSyntaxError, e: |
| 118 | print "Template syntax error: " + str(e) |
| 119 | sys.exit(1) |
| 120 | print template.render(Context({'objects': queryset})) |