Writing Django custom command
Tags: django, python, webapplication
Published: 2019-09-26 10:01:43

---
title: Writing Django custom command
published: true
description: writing custom command for Django admin
tags: django, python, web application
---

When developing application using Django framework, we extensively use various Django commands.
Few common Django commands we use regularly are:

- `python manage.py runserver`
- `python manage.py makemigrations`
- `python manage.py migrate`

These commands are built in and lies within Django itself.

We can also write custom Django admin command. Today I will show an example of wrinting custom Django admin command.


These custom admin commands can be invoked using `manage.py COMMAND_NAME`.

To add custom Django admin command, firstly we need to add a directory `management/commands` to any of the apps folder.

Suppose, we need to write a custom Django admin command to insert some data from a CSV file to existing model.

Let's name the command as `insert_upazila_office_reports.py`.

We have following CSV files:

- `acland_offices_rank.csv`
- `uno_offices_rank.csv`

After placing the files in respective directories, directory structure may look like this:
```bash
APPLICATION_ROOT
├── manage.py
├── PROJECT_ROOT
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── APP1
│   ├── admin.py
│   ├── apps.py
│   ├── fixtures
│   │   ├── fixture_1.json
│   │   └── fixture_2.json
│   ├── __init__.py
│   ├── management
│   │   └── commands
│   │       ├── insert_upazila_office_reports.py
│   │       ├── acland_offices_rank.csv
│   │       └── uno_offices_rank.csv
│   ├── migrations
│   │   ├── 0001_initial.py
│   │   └── __init__.py
│   ├── models.py
│   ├── templates
│   │   ├── base_generic.html
│   │   └── index.html
│   ├── tests.py
│   ├── urls.py
│   └── views.py
└── requirements.txt

```

The name of the CSV file will be passed as argument to the Django command.

The desired functionality of our command is to insert data from passed CSV files to existing model.

This command will insert data from CSV file to `UNOOfficeReport` model assuming the CSV file name is passed.
Additionally, it will insert data to `ACLandOfficeReport` model if `--acland` optional argument is passed.

Let's create the `insert_upazila_office_reports.py`.

```python
import csv
import os
from django.apps import apps
from django.core.management.base import BaseCommand, CommandError
from reports.models import UNOOfficeReport, ACLandOfficeReport


class Command(BaseCommand):
    help = "Insert Upazila office reports from a CSV file. " \
           "CSV file name(s) should be passed. " \
           "If no optional argument (e.g.: --acland) is passed, " \
           "this command will insert UNO office reports."

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.model_name = UNOOfficeReport

    def insert_upazila_report_to_db(self, data):
        try:
            self.model_name.objects.create(
                upazila=data["upazila"],
                rank=data["rank"],
                office_name=data["office_name"]
            )
        except Exception as e:
            raise CommandError("Error in inserting {}: {}".format(
                self.model_name, str(e)))

    def get_current_app_path(self):
        return apps.get_app_config('reports').path

    def get_csv_file(self, filename):
        app_path = self.get_current_app_path()
        file_path = os.path.join(app_path, "management",
                                 "commands", filename)
        return file_path

    def add_arguments(self, parser):
        parser.add_argument('filenames',
                            nargs='+',
                            type=str,
                            help="Inserts Upazila Office reports from CSV file")
        # Named (optional) arguments
        parser.add_argument(
            '--acland',
            action='store_true',
            help='Insert AC land office reports rather than UNO office',
        )

    def handle(self, *args, **options):
        if options['acland']:
            self.model_name = ACLandOfficeReport

        for filename in options['filenames']:
            self.stdout.write(self.style.SUCCESS('Reading:{}'.format(filename)))
            file_path = self.get_csv_file(filename)
            try:
                with open(file_path) as csv_file:
                    csv_reader = csv.reader(csv_file, delimiter=',')
                    for row in csv_reader:
                        if row != "":
                            words = [word.strip() for word in row]
                            upazila_name = words[0]
                            office_name = words[1]
                            rank = int(words[2])
                            data = {}
                            data["upazila"] = upazila_name
                            data["office_name"] = office_name
                            data["rank"] = rank
                            self.insert_upazila_report_to_db(data)
                            self.stdout.write(
                                self.style.SUCCESS('{}_{}: {}'.format(
                                        upazila_name, office_name,                                        
                                        rank
                                        )
                                )
                            )


            except FileNotFoundError:
                raise CommandError("File {} does not exist".format(
                    file_path))
```


We can invoke the command like:
```python
python manage.py insert_upazila_office_reports uno_offices_rank.csv
```

or

```python
python manage.py insert_upazila_office_reports --acland acland_offices_rank.csv
```


### Important fact about writing custom Django admin command

- The command should be a Python class extending `BaseCommand` class from `django.core.management.base`
- The command file should be placed in `management/commands` directory.
- If you implement `__init__` in your subclass of `BaseCommand`, you must call `BaseCommand`’s `__init__`:
  ```python
  class Command(BaseCommand):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # ...
  ```

Figure to get details of our custom Django admin command:

```bash
(venv) ➜  nothi git:(master) python manage.py insert_upazila_office_reports --help
usage: manage.py insert_upazila_office_reports [-h] [--acland] [--version]
                                               [-v {0,1,2,3}]
                                               [--settings SETTINGS]
                                               [--pythonpath PYTHONPATH]
                                               [--traceback] [--no-color]
                                               [--force-color]
                                               filenames [filenames ...]

Insert Upazila office reports from a CSV file. CSV file name(s) should be
passed. If no optional argument (e.g.: --acland) is passed, this command will
insert UNO office reports.

positional arguments:
  filenames             Inserts Upazila Office reports from CSV file

optional arguments:
  -h, --help            show this help message and exit
  --acland              Insert AC land office reports rather than UNO office
  --version             show program's version number and exit
  -v {0,1,2,3}, --verbosity {0,1,2,3}
                        Verbosity level; 0=minimal output, 1=normal output,
                        2=verbose output, 3=very verbose output
  --settings SETTINGS   The Python path to a settings module, e.g.
                        "myproject.settings.main". If this isn't provided, the
                        DJANGO_SETTINGS_MODULE environment variable will be
                        used.
  --pythonpath PYTHONPATH
                        A directory to add to the Python path, e.g.
                        "/home/djangoprojects/myproject".
  --traceback           Raise on CommandError exceptions
  --no-color            Don't colorize the command output.
  --force-color         Force colorization of the command output.
```


### Reference:
  - [Django documentation on Writing custom django-admin commands](https://docs.djangoproject.com/en/2.2/howto/custom-management-commands/)



This post is first published in [https://arshovon.com/blog/django-custom-command/](https://arshovon.com/blog/django-custom-command/) 

**Update**

Added Django Custom Command to *Django Docs Example Polls Application* in this Github code repository: [https://github.com/arsho/django-custom-command](https://github.com/arsho/django-custom-command)