Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

There is a way to add features to an existing django command?

Tags:

python

django

I want to run a command just before the a django command is started.

For example:

$ python manage.py runserver
Validating models...

0 errors found
Django version 1.3, using settings 'creat1va.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
(started some command in the background)
[10/Jul/2011 21:50:26] "GET / HTTP/1.1" 200 1611
[10/Jul/2011 21:50:26] "GET /assets/css/master.css HTTP/1.1" 404 1783
[10/Jul/2011 21:50:26] "GET /assets/images/misc/logo.png HTTP/1.1" 404 1801
[10/Jul/2011 21:50:26] "GET /assets/images/icons/bo.gif HTTP/1.1" 404 1798
[10/Jul/2011 21:50:28] (My background process) "Some nice Feedback"

The main idea is to start a background process, and output the logging.

Is there a way to achieve this, without hacking the django sources?

like image 568
Mario César Avatar asked Jul 11 '11 01:07

Mario César


People also ask

What is a Django management command?

Django management commands are included as part of Django apps and are designed to fulfill repetitive or complex tasks through a one keyword command line instruction. Every Django management command is backed by a script that contains the step-by-step Python logic to fulfill its duties.

What is Runserver in Django?

The runserver command is a built-in subcommand of Django's manage.py file that will start up a development server for this specific Django project.

What does the Django command manage py shell do?

When you run python manage.py shell you run a python (or IPython) interpreter but inside it load all your Django project configurations so you can execute commands against the database or any other resources that are available in your Django project.

What is app config in Django?

The AppConfig class used to configure the application has a path class attribute, which is the absolute directory path Django will use as the single base path for the application.


2 Answers

Just realize that you can override the commands just easily as making an app with a command with the same name.

So I create an app and create a file with the same name as runserver, and later on that extend the runserver base class to add a new feature before it runs.

For example, I want to run the command $ compass watch, just before runserver starts and keep it running along runserver execution.

"""
Start $compass watch, command when you do $python manage.py runserver

file: main/management/commands/runserver.py

Add ´main´ app to the last of the installed apps
"""

from optparse import make_option
import os
import subprocess

from django.core.management.base import BaseCommand, CommandError
from django.core.management.commands.runserver import BaseRunserverCommand
from django.conf import settings

class Command(BaseRunserverCommand):
    option_list = BaseRunserverCommand.option_list + (
        make_option('--adminmedia', dest='admin_media_path', default='',
            help='Specifies the directory from which to serve admin media.'),
        make_option('--watch', dest='compass_project_path', default=settings.MEDIA_ROOT,
            help='Specifies the project directory for compass.'),
    )

    def inner_run(self, *args, **options):
        self.compass_project_path = options.get('compass_project_path', settings.MEDIA_ROOT)

        self.stdout.write("Starting the compass watch command for %r\n" % self.compass_project_path)
        self.compass_pid = subprocess.Popen(["compass watch %s" % self.compass_project_path],
            shell=True,
            stdin=subprocess.PIPE,
            stdout=self.stdout,
            stderr=self.stderr)
        self.stdout.write("Compas watch process on %r\n" % self.compass_pid.pid)

        super(Command, self).inner_run(*args, **options)

This works just fine.

Look at https://docs.djangoproject.com/en/dev/howto/custom-management-commands/ for more details about django commands

Hope someone find this helpful

like image 130
Mario César Avatar answered Oct 07 '22 19:10

Mario César


To further expand on @Mario César's excellent answer, I would like to provide a modern version of his initial 2011 code adapted for Django 1.8+:

Before Django 1.8, management commands were based on the optparse module [...] Now that management commands use argparse for argument parsing, all arguments are passed in **options by default [...]

Source

Additionally, I would like to point out that the specific command runserver that was chosen in the question has a slight complication making it both a good and bad example.

Bad example, because the complication is that the command is overridden by Django itself as well. Indeed, Django uses the same method as proposed by Mario: Django overrides it in the staticfiles app (see Django code on github) in order to offer the additional static files options.

Therefore, it is better to override the staticfiles app command rather than the core command, if one uses static. This answers to @ts_pati's comment too on why there is problem. The Django code of staticfiles is the good example on how to override it, but this time importing the staticfiles in order not to lose that functionality:

from django.contrib.staticfiles.management.commands.runserver import Command as StaticfilesRunserverCommand


class Command(StaticfilesRunserverCommand):
    help = "Starts a lightweight Web server for development, serves static files and does some custom fancy stuff."

    def add_arguments(self, parser):
        super(Command, self).add_arguments(parser)
        parser.add_argument('--my-custom-argument', action="...", dest='my_custom_handler', default=True, help='do some stuff in fancy ways')

    def get_handler(self, *args, **options):
        """
        My fancy stuff function.
        """
        handler = super(Command, self).get_handler(*args, **options)
        my_custom_handler = options.get('my_custom_handler', True)
        # do stuff here
        return handler

EDIT: I would also like to add the order of this in INSTALLED_APPS is apparently important and it has to be before django.contrib.staticfiles.

like image 29
Wtower Avatar answered Oct 07 '22 17:10

Wtower