DoriDoro
Posted on May 7, 2024
Introduction:
Prerequisites:
I assume you know how to create a Django application, otherwise check out this article.
The official Django documentation about management commands.
In your Django project you will create in one app two python directories (management/commands/) and inside the commands folder, you create your commands-files, like:
# project tree
app_name/
__init__.py
management/
__init__.py
commands/
__init__.py
_private_command.py
command_name.py
The command-file will look like: (example of Django website, updated)
# command_name.py
from django.core.management.base import BaseCommand, CommandError
from polls.models import Question as Poll
class CustomCommand(BaseCommand):
help = "Closes the specified poll for voting"
def add_arguments(self, parser):
parser.add_argument("poll_ids", nargs="+", type=int)
def handle(self, *args, **options):
for poll_id in options["poll_ids"]:
try:
poll = Poll.objects.get(pk=poll_id)
except Poll.DoesNotExist:
raise CommandError('Poll "%s" does not exist' % poll_id)
poll.opened = False
poll.save()
self.stdout.write(
self.style.SUCCESS('Successfully closed poll "%s"' % poll_id)
)
You create a new class and inherit from BaseCommand.
The 'help =' will display the help text when calling
python manage.py help
or more precise for this custom command:python manage.py help command_name
.def add_arguments(self, parser):
will specify the arguments that your custom command should accept. It is part of the setup process and is optional. Your custom command does not need any arguments or you can create optional arguments.def handle(self, *args, **options):
This method is where the actual logic of the command is implemented. It is the entry point for the execution of the command, and it is automatically called when the command is run.
additional information to the parser.add_argument()
:
What it does:
- It defines the arguments that the command will accept.
- It specifies the type of each argument.
- It can set default values for optional arguments.
- It provides help text for each argument, which is displayed when the
--help
option is used.
parser.add_argument("poll_ids", nargs="+", type=int)
in our example:
-
"poll_ids"
is the name of the argument. -
nargs="+"
is only necessary when the provided arguments value can be one or more values. (nargs="+"
: This indicates that the argument can take one or more values. The + means that one or more arguments are expected.) -
type=int
defines the accepted type, in or case an int/number.
You can add positional arguments or optional arguments:
parser.add_argument('positional_arg', type=int, help='This is a positional argument')
parser.add_argument('-o', '--optional', type=str, help='This is an optional argument')
additional information to the handle()
:
To access the arguments you will use options.
- to access positional arguments, you assign options['positional_arg'] to a variable like:
pos_arg_value = options['positional_arg']
- to access an optional argument, you do the same, like:
opt_arg_value = options['optional']
Create a custom BaseCommand to inherit to a child BaseCommand
Let's say we create a simple Django app where we create an employee.
# our project tree:
employee/
__init__.py
management/
__init__.py
commands/
__init__.py
employee_create.py # the employee command
models.py
utils/
__init__.py
management/
__init__.py
commands/
__init__.py
custom_command.py # the custom command
Create the parent custom BaseCommand in file: custom_command.py
Note: this file will evolve
# utils/management/commands/custom_command.py
class CustomCommand(BaseCommand):
help = "Custom BaseCommand"
action = None
def __init__(self, *args, **options):
super().__init__(*args, **options)
self.object = None
self.fields = []
self.fields_to_update = []
self.update_table = []
def get_data(self):
return dict()
def make_changes(self, data):
return None
def display_result(self, data):
pass
def handle(self, *args, **options):
super().handle(*args, **options)
validated_data = self.get_data()
self.make_changes(validated_data)
self.display_result(validated_data)
What does these methods of the custom BaseCommand do?
get_data()
: Prompts the user for data/information and returns a dictionary, which gets the user's input.make_changes(data)
: Verifies if the user's input from 'get_data' exists. Makes queries to verify the user's input from the database and makes the changes. It takes a dictionary with the data (user's input) as argument.display_result(data)
: Prints the created employee or can print a success message, etc.handle()
: Withsuper().handle(*args, **options)
calls the handle method in parent Command (the BaseCommand handle method). And after it calls theget_data()
, themake_changes()
anddisplay_result()
method.
To be able to create an employee, we need an employee model. The employee model will look like:
# employee/models.py
from django.db import models
from django.utils.translation import gettext_lazy as _
class Employee(models.Model):
email = models.EmailField(_("email address"), unique=True, verbose_name=_("first name"))
first_name = models.CharField(max_length=100, verbose_name=_("first name"))
last_name = models.CharField(max_length=100, verbose_name=_("last name"))
Create the child Command to create an employee
# employee/management/commands/employee_create.py
from employee.models import Employee
from utils.custom_command import CustomCommand
class Command(CustomCommand):
help = "Prompts to create a new employee."
action = "CREATE"
def get_data(self):
self.stdout.write("Enter details to create an employee:")
return {
"email": input("Email address: "),
"password": input("Password: "),
"first_name": input("First name: "),
"last_name": input("Last name: "),
}
def make_changes(self, data):
employee_exists = Employee.objects.filter(email=data.pop("email", None))
).first()
if employee_exists:
call_command()
self.object = Employee.objects.create(**data)
return self.object
def display_result(self, data):
self.stdout.write("You have successfully created this employee:")
self.stdout.write("
Name: f'{self.object.first_name}' '{self.object.last_name}'
Email: self.object.email
")
action = "CREATE"
: we assign the valueCREATE
to the class attributeaction
. This will have an importance in the evolved version of theCustomCommand
.get_data()
:
1) We display withself.stdout.write()
a message on the terminal. It is equivalent to theprint()
but in Django Management Commands we use thestdout
.
2) In return
we prompt inputs for the user to enter the data.
make_changes()
: Withemployee_exists
we verify inside the database if this employee already exists. If that employee does not exists already, we create a new employee instance, save this employee instance in the instance attributeself.object
and return this new created employee instance.display_result()
: Withself.stdout.write()
we print a message for the user, that the creation of the employee was successful and we display the full name and the email address of the new created employee.
We can improve here, and simplify the display of the full name. We can create a property in the models.py
file and use this property to display the full name of the employee.
we would update the models.py
file like this:
# employee/models.py
from django.db import models
from django.utils.translation import gettext_lazy as _
class Employee(models.Model):
email = models.EmailField(_("email address"), unique=True, verbose_name=_("first name"))
first_name = models.CharField(max_length=100, verbose_name=_("first name"))
last_name = models.CharField(max_length=100, verbose_name=_("last name"))
# NEW: add this property:
@property
def get_full_name(self):
return f"{self.first_name} {self.last_name}"
# employee/management/commands/employee_create.py
def display_result(self, data):
self.stdout.write("You have successfully created this employee:")
self.stdout.write("
Name: self.object.full_name # simplify this
Email: self.object.email
")
What do we have to change in CustomCommand?
Until now the action
has no effect. We will change that.
# utils/management/commands/custom_command.py
class CustomCommand(BaseCommand):
help = "Custom BaseCommand"
action = None
def __init__(self, *args, **options):
super().__init__(*args, **options)
self.object = None
self.fields = []
self.fields_to_update = []
self.update_table = []
def get_data(self):
return dict()
def make_changes(self, data):
return None
def display_result(self, data):
pass
def create(self):
validated_data = self.get_data()
self.make_changes(validated_data)
self.display_result(validated_data)
def handle(self, *args, **options):
super().handle(*args, **options)
if self.action == 'CREATE':
self.create()
Now every Management Command inherit from CustomCommand with action = 'CREATE'
will trigger the create method of the CustomCommand to create an instance.
Advice: If you ever have to call a Management Command, keep in mind if you want to use
call_command()
, the name of the command you are about to call, has to be Command()
otherwise it will throw an error.
Posted on May 7, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.