If you update a relatively old Django project from 1.9 to 1.10 or later and you have added your own custom management commands that take positional arguments, it's possible that those commands will abruptly stop working (well, stop accepting positional arguments). Also, although it is not explicitly documented, the
*args
parameter of your Command's
handle()
function is now often completely meaningless and you won't see anything passed in it (although when this happens is fairly obscure).
If you were still using the
optparse
based approach to argument parsing in your custom management commands, this is sort of expected; the 1.10 release notes mention, in the large 'features removed in 1.10' section, that 'support for
optparse
is dropped for custom management commands' (and you'll have been getting deprecation warnings about that in 1.9). However this happens even if you had already switched over to using
argparse
based argument parsing (ie, your custom management command class has an
add_arguments
function). Your code worked fine in Django 1.9 and failed in Django 1.10, despite no deprecation warning.
So, here is what is going on. In Django 1.9 and earlier, code in django.core.management.base's BaseCommand class hierarchy silently introspected your command class to see if it had an
args
member. If it did and you were already using
argparse
, BaseCommand silently added an extra
args
argument to your argument parser that swept up all positional arguments:
if not self.use_argparse:
... old optparse code ...
else:
....
if self.args:
# Keep compatibility and always accept
# positional arguments, like optparse when
# args is set
parser.add_argument('args', nargs='*')
Later, other code in BaseCommand would silently take the value of the
args
argument from
argparse
's results and turn it into the
args
parameter for your
handle()
function (removing it from the argparse results in the process):
# Move positional args out of options to
# mimic legacy optparse
args = cmd_options.pop('args', ())
Note that this specifically happened when you had switched over to using
argparse
. That this catch-all argument was added (without real documentation) meant that in Django 1.9, if you still had an
args
member, you could not add your own argument to collect some or all of the positional arguments because it would clash with the automatically added argument here. And really confusing things might happen if you called some
argparse
argument 'args', because it would be stolen as the
args
parameter for your
handle()
function.
In Django 1.10, the first chunk of this code was changed so that an extra
args
argument is no longer automatically added to your argument parser if you still had an
args
member in your command class. Now such custom management commands could not get their extra positional arguments (and I believe would fail with a 'cannot parse command line arguments' error if you tried to supply some). However, watch out, because the second chunk of code is still there. If you have an argparse argument called '
args
', Django will silently remove it from the argparse result and pass it to your
handle()
command as the
args
parameter.
(Of course this is kind of a feature. As it stands now, you can just add your own copy of the old Django 1.9
parser.add_argument
call and nothing else in your code has to change. But it smells like a hack to me and I wouldn't be surprised if Django made this magic behavior disappear at some point.)
In my opinion, this automatic addition of an
args
argument should have been explicitly deprecated, meaning both a deprecation warning in Django 1.9 and an explicit mention in Django 1.10's documentation. Probably the magic behavior of converting an
args
argparse argument in the
handle()
args
parameter should have been deprecated at the same time, but I don't have strong opinions.
(This is the kind of entry I write because we stumbled over this and then I went and dug the details out of the Django revision history, so I'm certainly not going to waste all that work.)