create a custom BaseCommand for a Django app
Tags: django, command
Published: 2024-05-07 14:33:22

## **Introduction:**


## **Prerequisites:**
I assume you know how to create a Django application, otherwise check out [this article](https://dev.to/doridoro/create-a-simple-django-app-from-scratch-with-pycharm-ide-and-use-a-github-repository-1gd5).

The official Django documentation about [management commands](https://docs.djangoproject.com/en/5.0/howto/custom-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:

```python
# 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)

```python
# 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']`

<hr>
# Create a custom BaseCommand to inherit to a child BaseCommand

Let's say we create a simple Django app where we create an employee. 

```python
# 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_
```python
# 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()`: With `super().handle(*args, **options)` calls the handle method in parent Command (the BaseCommand handle method). And after it calls the `get_data()`, the `make_changes()` and `display_result()` method.


To be able to create an employee, we need an employee model. The employee model will look like:

```python
# 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

```python
# 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 value `CREATE` to the class attribute `action`. This will have an importance in the evolved version of the `CustomCommand`.

- `get_data()`: 
   1) We display with `self.stdout.write()` a message on the terminal. It is equivalent to the `print()` but in Django Management Commands we use the `stdout`.

   2) In `return` we prompt inputs for the user to enter the data.

- `make_changes()`: With `employee_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 attribute `self.object` and return this new created employee instance.

- `display_result()`: With `self.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. 

<hr>

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:

```python
# 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}"
```

```python
# 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
        ")
```

<hr>

## What do we have to change in CustomCommand?

Until now the `action` has no effect. We will change that.

```python
# 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. 

<hr>
**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.