28
loading...
This website collects cookies to deliver better user experience
./manage.py
, but why is this helpful or needed? Consider this script:# product_stat.py
from application.models import Product
print(Product.objects.count())
$ python product_stat.py
ImproperlyConfigured
exception. There are a couple of modifications that you could make to get the script to run.django.setup()
.DJANGO_SETTINGS_MODULE
.
# product_stat.py
import django
django.setup()
from application.models import Product
print(Product.objects.count())
django.setup()
must be before your Django related imports (like the Product
model in this example). Now the script can run if you supply where the settings are located too.$ DJANGO_SETTING_MODULE=project.settings python product_stat.py
./manage.py -h
.django-admin
script. We saw it all the way back in the first article where I provided a short set of setup instructions to get you started if you've never used Django before.manage.py
file, and the commands you've seen in most articles are in the form of:$ ./manage.py some_command
django-admin
and manage.py
? In truth, not much!django-admin
comes from Django's Python packaging. In Python packages, package developers can create scripts by defining an entry point in the packaging configuration. In Django, this configuration looks like:[options.entry_points]
console_scripts =
django-admin = django.core.management:execute_from_command_line
manage.py
of one of my projects is:#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
execute_from_command_line
function to run a command. The primary difference is that the latter script will attempt to set the DJANGO_SETTINGS_MODULE
environment variable automatically. Since Django needs to have DJANGO_SETTINGS_MODULE
defined for most commands (note: startproject
does not require that variable), manage.py
is a more convenient way to run commands.execute_from_command_line
is able to present what commands are available to a project, whether a command comes from Django itself, an installed app, or is a custom command that you created yourself. How are the commands discovered? The command system does discovery by following some packaging conventions.application
. Django can find the command if you have the following packaging structure.application
├── __init__.py
├── management
│ ├── __init__.py
│ └── commands
│ ├── __init__.py
│ └── custom_command.py
├── models.py
└── views.py
... Other typical Django app files
$ ./manage.py custom_command
<app>/management/commands/<command name>.py
.__init__.py
files! Django can only discover the commands if management
and commands
are proper Python package directories.custom_command
, but you can name your command with whatever valid Python module name that you want.custom_command.py
and assume that Django will know how to run it. Within the custom_command.py
module, Django needs to find a Command
class that subclasses a BaseCommand
class that is provided by the framework. Django requires this structure to give command authors a consistent way to access features of the command system.Command
class, you can add a help
class attribute. Adding help can gives users a description of what your command does when running ./manage.py custom_command -h
.Command
class will also help you with handling arguments. If your command needs to work with user input, you'll need to parse that data. Thankfully, the class integrates with Python's built-in argparse
module. By including an add_arguments
method, a command can parse the data and pass the results to the command's handler method in a structured way. If you've had to write Python scripts before, then you may understand how much time this kind of parsing can save you (and for those who haven't, the answer is "a lot of time!").Command
class too. Perhaps you only want your command to run if your project has satisified certain pre-conditions. Commands can use the requires_migration_checks
or requires_system_checks
to ensure that the system is in the correct state before running.Command
class is to help you with common actions that many commands will need to use. There is a small API to learn, but the system is a boon to making scripts quickly.expire_trials
command looks like in my app. I've simplified things a bit, so that you can ignore the details that are specific to my service.# application/management/commands/expire_trials.py
import datetime
from django.core.management.base import BaseCommand
from django.utils import timezone
from application.models import Account
class Command(BaseCommand):
help = "Expire any accounts that are TRIALING beyond the trial days limit"
def handle(self, *args, **options):
self.stdout.write("Search for old trial accounts...")
# Give an extra day to be gracious and avoid customer complaints.
cutoff_days = 61
trial_cutoff = timezone.now() - datetime.timedelta(days=cutoff_days)
expired_trials = Account.objects.filter(
status=Account.TRIALING, created__lt=trial_cutoff
)
count = expired_trials.update(status=Account.TRIAL_EXPIRED)
self.stdout.write(f"Expired {count} trial(s)")
python manage.py expire_trials
every day in the early morning. The command checks the current time and looks for Account
records in the trialing state that were created before the cutoff time. From that query set, the affected accounts are set to the expired account state.call_command
from django.core.management
. Since the example command doesn't require arguments, I didn't take that approach.handle
method directly. In my example above, you can see that the command uses self.stdout
instead of calling print
. Django does this so that you could check your output if desired.# application/tests/test_commands.py
from io import StringIO
from application.models import Account
from application.tests.factories import AccountFactory
def test_expires_trials():
"""Old trials are marked as expired."""
stdout = StringIO()
account = AccountFactory(
status=Account.TRIALING,
created=timezone.now() - datetime.timedelta(days=65),
)
command = Command(stdout=stdout)
command.handle()
account.refresh_from_db()
assert account.status == Account.TRIAL_EXPIRED
assert "Expired 1 trial(s)" in stdout.getvalue()
StringIO
instance is injected into the Command
constructor. By building the command this way, checking the output becomes a very achievable task via the getvalue
method.check
- Checks that your project is in good shape.collectstatic
- Collects static files into a single directory.createsuperuser
- Creates a super user record.makemigrations
- Makes new migration files based on model changes.migrate
- Runs any unapplied migrations to your database.runserver
- Runs a development web server to check your app.shell
- Starts a Django shell that allows you to use Django code on the command line.startapp
- Makes a new Django app from a template.startproject
- Makes a new Django project from a template.test
- Executes tests that checks the validity of your app.dbshell
command starts a different kind of shell. The shell is a database program that will connect to the same database that your Django app uses. This shell will vary based on your choice of database../manage.py dbshell
will start psql
. From this shell, you can execute SQL statements directly to inspect the state of your database. I don't reach for this command often, but I find it very useful to connect to my database without having to remember database credentials.showmigrations
command has a simple job. The command shows all the migrations for each Django app in your project. Next to each migration is an indicator of whether the migration is applied to your database.users
app from one of my Django projects:$ ./manage.py showmigrations users
users
[X] 0001_initial
[X] 0002_first_name_to_150_max
[ ] 0003_profile
showmigrations
is a good way to show the state of your database from Django's point of view.sqlmigrate
command is very handy. The command will show you what SQL statements Django would run for an individual migration file.AbstractUser
model so that the first_name
field could have a maximum length of 150 characters. Anyone using the AbstractUser
model (which includes me) had to generate a migration to apply that change.showmigrations
output above, you can see that the second migration of my users
app applied this particular framework change.$ ./manage.py sqlmigrate users 0002
BEGIN;
--
-- Alter field first_name on user
--
ALTER TABLE "users_user" ALTER COLUMN "first_name" TYPE varchar(150);
COMMIT;
ALTER COLUMN
DDL statement to modify the length of the first_name
field.squashmigrations
command is designed to let you tidy up an app's set of migration files.squashmigrations
, you can condense an app's migrations into a significantly smaller number. The reduced migrations can accurately represent your database schema, and make it easier to reason about what changes happened in the app's history. As a side benefit, migration squashing can make Django's migration handling faster because Django gets to process fewer files.shell_plus
command is like the regular shell, but the command will import all your models automatically. For the five extra characters of _plus
, you can save your fingers a lot of typing to import your models and get directly to whatever you needed the shell for.reverse
, settings,
timezone
, and more.graph_models
command, I can create an image of all my models and how those models relate to each other (using UML syntax). This is a great way to:shell_plus
and graph_models
, there are 20 other commands that you can use that may be very useful to you. You should definitely check out django-extensions.