Create a custom manage.py command in Django
Tags: python, django, tutorial
Published: 2022-08-26 10:04:06


If you ever used Django you have probably used its manage.py commands to execute different things like:

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

There are lots of useful built-in commands available that you can use if you look in the documentation ([django-admin and manage.py](https://docs.djangoproject.com/en/3.0/ref/django-admin/)). For example:

- [dumpdata](https://docs.djangoproject.com/en/4.0/ref/django-admin/#dumpdata) - export data from an app to JSON or some other format
- [loaddata](https://docs.djangoproject.com/en/4.0/ref/django-admin/#loaddata) - import data to the database
- [migrate](https://docs.djangoproject.com/en/4.0/ref/django-admin/#migrate) - sync the database with the current set of models and migrations


You can also build your own command for administrative tasks you might have. For example if you want to have a scheduled maintenance done, you can build such a command and run it as a scheduled job (for example via [celery](https://docs.celeryq.dev/en/latest/index.html)). 

In this article I’ll show you how you can build your own custom manage.py command to fill data from an API request into your database.

The full source code for this sample Django app can be found at [GitHub](https://github.com/Joeriksson/Beers)


## Scenario
Let’s say that we for some reason want to fill a database model we have with some beer data from an API. If you’re interested in beer, you might recall that Brewdog had a series of beers named "Hello, my name is...".

We will get information about those beers from a free API at [https://punkapi.com](https://punkapi.com) 

![Screenshot of beer app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ncizor50tx3unyxo2ooh.jpg)
<small><i>
_Preview of the beer data_
</i></small>

## Django Model
In a Django app build the model for the data:

```python
# models.py
from django.db import models


class Beer(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.CharField(max_length=200)
    first_brewed = models.CharField(max_length=20)
    description = models.TextField()
    added = models.DateTimeField(auto_now_add=True)
    edited = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f'Beer: {self.name}'

```

Migrate the model into the database.

```bash
$ python manage.py makemigrations beers

$ python manage.py migrate
```

## Building the custom command
To add a new command create a folder structure under the apps folder in the project.

Quote from the [Django documentation](https://docs.djangoproject.com/en/3.0/howto/custom-management-commands/)
> To do this, add a management/commands directory to the application. Django will register a manage.py command for each Python module in that directory whose name doesn’t begin with an underscore

So in this case, create an `update_beers.py` in the following folder:

```
beers/
    __init__.py
    models.py
    management/
			__init__.py
			commands/
            update_beers.py 

```


### The command code

The command code should include a Command class that sub-classes Djangos BaseCommand. The BaseCommand has one method that needs to be implemented, and that’s the `handle` method. It will contain the logic of our command.

The basic structure of the code needed can be seen below.

```python
from django.core.management import BaseCommand
from beers.models import Beer

class Command(BaseCommand):
    def __init__(self, *args, **kwargs):
        super(Command, self).__init__(*args, **kwargs)

    help = "Update Beer table from punkapi.com"

    def handle(self, *args, **options):
			# Implement the logic here
			pass

```

In this case, we want to make requests to an API for data, that then will be written to the database. We will also use the [requests](https://requests.readthedocs.io/en/master/) library. 

The full code for the command could look like this. *(It is just for demo purposes. In a real world scenario we would probably not use the name of the beer to query if the beer already exists in the database.)* 

```python
from django.core.management import BaseCommand

import requests

from beers.models import Beer


class Command(BaseCommand):
    def __init__(self, *args, **kwargs):
        super(Command, self).__init__(*args, **kwargs)

    help = "Update Beer table from punkapi.com"

    def handle(self, *args, **options):
        # Request data from the beer API
        response = requests.get('https://api.punkapi.com/v2/beers/?beer_name=my_name_is')

        # Loop through the response
        for beer in response.json():
            try:
                beer_row = Beer.objects.get(name=beer['name'])
            except Beer.DoesNotExist:
                beer_row = None

            if beer_row:
                # Beer already exists - do nothing
                self.stdout.write(f'{beer_row.name} already exists.')
                continue
            else:
                # Add beer to db
                self.stdout.write('Create new row')
                beer_row = Beer()
                beer_row.name = beer['name']
                beer_row.tagline = beer['tagline']
                beer_row.first_brewed = beer['first_brewed']
                beer_row.description = beer['description']
                beer_row.save()

        self.stdout.write('#########################')
        self.stdout.write('Updated Beer list')
        self.stdout.write('#########################')

```

Here we check if the beer already exists in the database. If not we add it to the database.

### Execute the custom command
Execute the custom command by running:

```bash
python manage.py update_beers
```

Which will show the output:

![Command output of running the command](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/62l3rfmgpnxeil2gk3d3.jpg)
<small><i>Output of running the command</i></small>

If all goes well, the API will be requested for data about the beers, and the data will be written to the database.

#### Another way to do it

What if we want to get any updates to an existing beer from the api?

Rewrite the handle method like so.

```python
def handle(self, *args, **options):
        response = requests.get('https://api.punkapi.com/v2/beers/?beer_name=my_name_is')

        for beer in response.json():

            object, created = Beer.objects.update_or_create(
                name=beer['name'],
                defaults={
                    'tagline': beer['tagline'],
                    'first_brewed': beer['first_brewed'],
                    'description': beer['description']
                }
            )

            object.save()

            if created:
                self.stdout.write('New beer added')
            else:
                self.stdout.write(f'{beer["name"]} updated')
```

Here we use Djangos [`update_or_create`](https://docs.djangoproject.com/en/4.1/ref/models/querysets/#update-or-create) method which returns a tuple, where object is the created or updated object and created is a boolean specifying whether a new object was created.

## Conclusion
In this article I have demonstrated how you can build your own custom manage.py command in Django. The article shows how you can build a custom command to get data from an API via the `requests` library and store that in the database. 

If you want to set up a custom command as a scheduled task, have a look at the last resource link below.

## Resources

- [Writing custom django-admin commands (Django documentation)](https://docs.djangoproject.com/en/3.0/howto/custom-management-commands/)
- [The full source code of this Beer app at GitHub](https://github.com/Joeriksson/Beers)
- [Handling Periodic Tasks in Django with Celery and Docker](https://testdriven.io/blog/django-celery-periodic-tasks/)