Implementing custom management commands

Django allows your applications to register custom management commands for the manage.py utility. For example, we used the management commands makemessages and compilemessages in Chapter 9Extending Your Shop to create and compile translation files.

A management command consists of a Python module containing a Command class that inherits from django.core.management.base.BaseCommand or one of its subclasses. You can create simple commands or make them take positional and optional arguments as input.

Django looks for management commands in the management/commands/ directory for each active application in the INSTALLED_APPS setting. Each module found is registered as a management command named after it.

You can learn more about custom management commands at https://docs.djangoproject.com/en/2.0/howto/custom-management-commands/.

We are going to create a custom management command to remind students to enroll at least in one course. The command will send an email reminder to users that have been registered for longer than a specified period that aren't enrolled in any course yet.

Create the following file structure inside the students application directory:

management/
__init__.py
commands/
__init__.py
enroll_reminder.py

Edit the enroll_reminder.py file and add the following code to it:

import datetime
from django.conf import settings
from django.core.management.base import BaseCommand
from django.core.mail import send_mass_mail
from django.contrib.auth.models import User
from django.db.models import Count

class Command(BaseCommand):
help = 'Sends an e-mail reminder to users registered more
than N days that are not enrolled into any courses yet'

def add_arguments(self, parser):
parser.add_argument('--days', dest='days', type=int)

def handle(self, *args, **options):
emails = []
subject = 'Enroll in a course'
date_joined = datetime.date.today() -
datetime.timedelta(days=options['days'])
users = User.objects.annotate(course_count=Count('courses_joined'))
.filter(course_count=0, date_joined__lte=date_joined)
for user in users:
message = 'Dear {}, We noticed that you didn't
enroll in any courses yet. What are you waiting
for?'.format(user.first_name)
emails.append((subject,
message,
settings.DEFAULT_FROM_EMAIL,
[user.email]))
send_mass_mail(emails)
self.stdout.write('Sent {} reminders'.format(len(emails)))

This is our enroll_reminder command. The preceding code is as follows:

  • The Command class inherits from BaseCommand.
  • We include a help attribute. This attribute provides a short description of the command that is printed if you run the command python manage.py help enroll_reminder.
  • We use the add_arguments() method to add the --days named argument. This argument is used to specify the minimum number of days a user has to be registered, without having enrolled in any course, in order to receive the reminder.
  • The handle() command contains the actual command. We get the days attribute parsed from the command line. We retrieve the users that have been registered for more than the specified days, which are not enrolled in any courses yet. We achieve this by annotating the QuerySet with the total number of courses each user is enrolled in. We generate the reminder email for each user and append it to the emails list. Finally, we send the emails using the send_mass_mail() function, which is optimized to open a single SMTP connection for sending all emails, instead of opening one connection per email sent.

You have created your first management command. Open the shell and run your command:

python manage.py enroll_reminder --days=20

If you don't have a local SMTP server running, you can take a look at Chapter 2Enhancing Your Blog with Advanced Features where we configured SMTP settings for our first Django project. Alternatively, you can add the following setting to the settings.py file to make Django output emails to the standard output during development:

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

Let's schedule our management command so that the server runs it every day at 8 a.m. If you are using a UNIX-based system such as Linux or macOS X, open the shell and run crontab -e to edit your crontab. Add the following line to it:

0 8 * * * python /path/to/educa/manage.py enroll_reminder --days=20 --settings=educa.settings.pro

If you are not familiar with cron you can find an introduction to cron at  http://www.unixgeeks.org/security/newbie/unix/cron-1.html.

If you are using Windows, you can schedule tasks using the Task Scheduler. You can find more information about it at https://msdn.microsoft.com/en-us/library/windows/desktop/aa383614(v=vs.85).aspx.

Another option for executing actions periodically is to create tasks and schedule them with Celery. Remember that we used Celery in Chapter 7Building an Online Shop to execute asynchronous tasks. Instead of creating management commands and scheduling them with cron, you can create asynchronous tasks and execute them with the Celery beat scheduler. You can learn more about scheduling periodic tasks with Celery at https://celery.readthedocs.io/en/latest/userguide/periodic-tasks.html.

Use management commands for standalone scripts that you want to schedule with cron or the Windows scheduler control panel.

Django also includes a utility to call management commands using Python. You can run management commands from your code as follows:

from django.core import management
management.call_command('enroll_reminder', days=20)

Congratulations! You can now create custom management commands for your applications and schedule them when needed.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset