| | 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})) |