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:
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
.
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 manage.py insert_upazila_office_reports uno_offices_rank.csv
or
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 fromdjango.core.management.base
- The command file should be placed in
management/commands
directory. - If you implement
__init__
in your subclass ofBaseCommand
, you must callBaseCommand
’s__init__
:
class Command(BaseCommand):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# ...
Figure to get details of our custom Django admin command:
(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:
This post is first published in 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
Top comments (4)
Great post! But I have a doubt. Why do you just put the filename directly? Currently you call the app config path name, management and command folders append to filename.
Thanks.
@marlysson , I have kept the files in the same directory of where I put the custom command. I suppose it is good practice to import data files in this way. Have you tried using the filename directly?
Thank you.
Shovon
Yes, I do.
Was because instead get current directory and join with filename, you got the app name and join with many folders and after that join with filename to build the complete path to file.
I just got this doubt, but this article was very useful to me.
@marlysson , glad the tutorial helped you. FYI, I have added Django Custom Command in Django Docs Example Polls Application in this Github code repository: github.com/arsho/django-custom-com...
Stay safe! :)