2016-04-18 11 views
5

Titel sagt eigentlich alles, aber ich dies derzeit haben, aber es funktioniert nicht:Ist es möglich, Unterparser in einem Django-Management-Befehl zu erstellen?

class Command(BaseCommand): 
    help = ("Functions related to downloading, parsing, and indexing the " 
      "content") 

    def add_arguments(self, parser): 
     subparsers = parser.add_subparsers() 

     download_parser = subparsers.add_parser(
      'download', 
      help='Using a local CSV, download the XML data for content. ' 
       'Output is sent to the log.' 
     ) 
     download_parser.add_argument(
      '--start_line', 
      type=int, 
      default=0, 
      help='The line in the file where you wish to start processing.' 
     ) 

     # Add an argparse parser for parsing the content. Yes, this is 
     # a bit confusing. 
     content_parser_parser = subparsers.add_parser(
      'parse', 
      help="Look at the file system and parse everything you see so that " 
       "we have content in the databse." 
     ) 
     content_parser_parser.add_argument(
      '--start_item', 
      type=int, 
      default=0, 
      help="Assuming the content is sorted by file name, this item is " 
       "the one to start on." 
     ) 

Meine konkrete Idee ist, einen Befehl zu erstellen, die Unterbefehle für das Herunterladen von XML-Inhalten oder zum Parsen in die Datenbank hat .

+0

Ohne zu wissen, was bereits in der 'parser' ist, oder was' django' tut es mit ihm später, kann ich nicht sagen, . Aus der Hand sehen Ihre Subparser-Definitionen gut aus.Wie Sie jedoch aus anderen SO-Fragen sehen können, kann die Erstellung von Subparsern mit anderen Argumenten, Positions- und/oder Optionals, schwierig sein. Fügen Sie zu Beginn Ihrer Funktion 'print parser._actions' hinzu. – hpaulj

+0

http://stackoverflow.com/questions/31919101/djangos-call-command-fails-with-missing-required-arguments, ist eine vorherige Frage, die sowohl argparse als auch django betrifft. Sieht so aus, als hätte Django Optparse benutzt, aber kürzlich die Alternative argparse hinzugefügt. – hpaulj

Antwort

7

Es ist möglich, aber es erfordert ein wenig Arbeit:

from django.core.management.base import BaseCommand, CommandParser 

class Command(BaseCommand): 

    [...] 

    def add_arguments(self, parser): 
     cmd = self 

     class SubParser(CommandParser): 

      def __init__(self, **kwargs): 
       super(SubParser, self).__init__(cmd, **kwargs) 

     subparsers = parser.add_subparsers(title="subcommands", 
              parser_class=SubParser) 

Wenn Sie add_subparsers standardmäßig rufen argparse einen neuen Parser erzeugt, die als Parser der gleichen Klasse ist, auf dem Sie add_subparser genannt. Es kommt vor, dass der Parser, den Sie in parser erhalten, eine CommandParser Instanz ist (definiert in django.core.management.base). Die CommandParser Klasse erfordert ein cmd Argument vor dem **kwargs (während der Standard-Parser-Klasse von argparse bereitgestellt nur **kwargs nimmt):

def __init__(self, cmd, **kwargs): 

Also, wenn Sie versuchen, die subparser hinzufügen, es schlägt fehl, da der Konstruktor nur genannt wird, mit **kwargs und das cmd Argument fehlt.

Der obige Code behebt das Problem, indem in parser_class Argument eine Klasse übergeben wird, die den fehlenden Parameter hinzufügt.

Dinge zu beachten: oben

  1. Im Code erstelle ich eine neue Klasse, weil der Name, parser_class schlägt vor, was übergeben werden soll es eine echte Klasse. Allerdings ist dies auch funktioniert:

    def add_arguments(self, parser): 
        cmd = self 
        subparsers = parser.add_subparsers(
         title="subcommands", 
         parser_class=lambda **kw: CommandParser(cmd, **kw)) 
    

    Im Moment habe ich nicht in Probleme laufen, aber es ist möglich, dass eine künftige Änderung argparse scheitern könnte eine Lambda eher als eine echte Klasse machen mit. Da das Argument parser_class heißt und nicht etwa wie parser_maker oder parser_manufacture, würde ich eine solche Änderung für faires Spiel halten.

  2. Können wir nicht einfach eine der argparse Klassen übergeben, statt eine benutzerdefinierte Klasse in parser_class zu übergeben? Es würde kein sofortiges Problem geben, aber es würde unbeabsichtigte Konsequenzen geben. Die Kommentare in CommandParser zeigen, dass das Verhalten des Stick-Parsers argparse für Django-Befehle unerwünscht ist. Insbesondere die docstring für die Klasse lautet:

    """ 
    Customized ArgumentParser class to improve some error messages and prevent 
    SystemExit in several occasions, as SystemExit is unacceptable when a 
    command is called programmatically. 
    """ 
    

    Dies ist ein Problem, dass Jerzyk's answer leidet. Die Lösung hier vermeidet dieses Problem, indem sie von CommandParser ableitet und somit das korrekte Verhalten bereitstellt, das von Django benötigt wird.

1

Sie können es hinzufügen, und es war ziemlich einfach:

class Command(BaseCommand): 
    help = 'dump/restore/diff' 

    def add_arguments(self, parser): 
     parser.add_argument('-s', '--server', metavar='server', type=str, 
          help='server address') 
     parser.add_argument('-d', '--debug', help='Print lots of debugging') 

     subparsers = parser.add_subparsers(metavar='command', 
              dest='command', 
              help='sub-command help') 
     subparsers.required = True 

     parent_parser = argparse.ArgumentParser(add_help=False) 
     parent_parser.add_argument('machine', metavar='device', type=str) 
     parent_parser.add_argument('-e', '--errors', action='store_true') 

     parser_dump = subparsers.add_parser('dump', parents=[parent_parser], 
              cmd=self) 
     parser_dump.add_argument('-i', '--indent', metavar='indent', type=int,         
            default=None, help='file indentation') 

     parser_restore = subparsers.add_parser('restore',    
               parents=[parent_parser], 
               cmd=self) 
     parser_restore.add_argument('infile', nargs='?', 
            type=argparse.FileType('r'), 
            default=sys.stdin) 

     parser_diff = subparsers.add_parser('diff', parents=[parent_parser], 
              cmd=self) 
     parser_diff.add_argument('infile', nargs='?', 
           type=argparse.FileType('r'), 
           default=sys.stdin) 
+0

Sie verwenden hier die Vanille-Argument, die System beenden wird. Dies ist unerwünscht, deshalb erbt Django die Klasse und beseitigt dieses Verhalten. Das ist also keine empfohlene Methode, wenn Sie daran interessiert sind, die gleichen Standards wie das Django-Projekt einzuhalten – Joakim