Dica 54 - django-extensions - mais comandos
Versões usadas no vídeo: Django 3.2, django-extensions 3.1, Python 3.9 e IPython 7.27.
Github: https://github.com/rg3915/dicas-de-django
Doc: https://django-extensions.readthedocs.io/en/latest/
O django-extensions é um pacote que acrescenta dezenas de comandos ao manage.py. Nesta dica veremos mais comandos do django-extensions, todos listados na seção Command Extensions da documentação: shell_plus, admin_generator, clean_pyc, create_command, create_template_tags, show_template_tags, generate_password, generate_secret_key, graph_models, list_model_info, list_signals, print_settings e show_urls.
Pré-requisitos
O vídeo usa o projeto do repositório dicas-de-django: o pacote myproject com as apps core, product, ecommerce, event, travel etc. Para acompanhar, basta qualquer projeto Django com pelo menos uma app e um modelo. Nos exemplos usamos a app product com o modelo Product:
# myproject/product/models.py
from django.db import models
class Product(models.Model):
title = models.CharField('título', max_length=100, unique=True)
price = models.DecimalField('preço', max_digits=7, decimal_places=2)
manufacturing_date = models.DateField('data de fabricação', null=True, blank=True) # noqa E501
due_date = models.DateField('data de vencimento', null=True, blank=True)
class Meta:
ordering = ('title',)
verbose_name = 'produto'
verbose_name_plural = 'produtos'
def __str__(self):
return self.title
Instalação
O django-extensions já estava instalado no projeto; no vídeo ele foi atualizado com -U:
pip install -U django-extensions
No requirements.txt do projeto, as versões ficaram assim:
Django==3.2.*
django-extensions==3.1.*
Depois acrescente django_extensions ao INSTALLED_APPS:
# myproject/settings.py
INSTALLED_APPS = [
'myproject.core',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
...
'django_extensions',
...
]
Para ver todos os comandos disponíveis, digite apenas:
python manage.py
Na lista aparece a seção [django_extensions] com os comandos novos:
[django_extensions]
admin_generator
clean_pyc
clear_cache
compile_pyc
create_command
create_jobs
create_template_tags
delete_squashed_migrations
describe_form
drop_test_database
dumpscript
export_emails
find_template
generate_password
generate_secret_key
graph_models
list_model_info
list_signals
mail_debug
merge_model_instances
notes
pipchecker
print_settings
print_user_for_session
reset_db
reset_schema
runjob
runjobs
runprofileserver
runscript
runserver_plus
...
shell_plus
Roda o shell do Django importando todos os pacotes essenciais. É o comando que eu mais uso.
python manage.py shell_plus
Ele importa automaticamente todos os modelos do projeto (inclusive os do Django, como User, Group e Permission) e os utilitários mais usados, como settings, transaction, timezone, reverse e as funções de agregação de django.db.models. No vídeo a saída foi esta (com o IPython instalado, o shell_plus usa o IPython):
# Shell Plus Model Imports
from django.contrib.admin.models import LogEntry
from django.contrib.auth.models import Group, Permission, User
from django.contrib.contenttypes.models import ContentType
from django.contrib.sessions.models import Session
from myproject.core.models import Article, Category, Person
from myproject.ecommerce.models import Order, OrderItems
from myproject.event.models import Room
from myproject.product.models import Product
from myproject.travel.models import Travel
# Shell Plus Django Imports
from django.core.cache import cache
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import transaction
from django.db.models import Avg, Case, Count, F, Max, Min, Prefetch, Q, Sum, When
from django.utils import timezone
from django.urls import reverse
from django.db.models import Exists, OuterRef, Subquery
Python 3.9.6 (default, Jul 5 2021, 08:48:45)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.27.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]:
Como Product já foi importado, dá para usá-lo direto:
In [1]: Product.objects.all()
Out[1]: <QuerySet [<Product: After lot hold discover stuff just manager.>, ...]>
Para sair, Ctrl+D.
admin_generator
Gera uma classe no Admin para a app selecionada, lendo os campos dos modelos dela.
python manage.py admin_generator product
Saída:
# -*- coding: utf-8 -*-
from django.contrib import admin
from .models import Product
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'price', 'manufacturing_date', 'due_date')
list_filter = ('manufacturing_date', 'due_date')
Repare que ele coloca todos os campos em list_display e os campos de data em list_filter. Para gravar o resultado direto no arquivo, redirecione a saída com >> (acrescenta no fim do arquivo):
python manage.py admin_generator product >> myproject/product/admin.py
Como o admin.py do vídeo já tinha um ProductAdmin, o código gerado ficou embaixo do existente, e o trecho repetido foi apagado. Num admin.py vazio, o resultado já fica pronto para usar.
clean_pyc
Remove todos os arquivos *.pyc do projeto (os bytecodes que o Python grava em __pycache__).
python manage.py clean_pyc
No vídeo, o tree antes do comando mostrava vários *.cpython-39.pyc; depois do clean_pyc não sobrou nenhum.
create_command
Cria um novo comando do Django (um management command) dentro de uma app.
python manage.py create_command core -n novocomando
O -n (ou --name) define o nome do comando. Ele cria as pastas management/commands com os __init__.py, se ainda não existirem, e o arquivo do comando com um esqueleto básico para você implementar:
# myproject/core/management/commands/novocomando.py
from django.core.management.base import BaseCommand
class Command(BaseCommand):
help = "My shiny new management command."
def add_arguments(self, parser):
parser.add_argument('sample', nargs='+')
def handle(self, *args, **options):
raise NotImplementedError()
Depois de implementar o handle, o comando roda com python manage.py novocomando.
create_template_tags
Cria um novo arquivo de template tags em uma app.
python manage.py create_template_tags core
Sem o nome, ele cria a pasta templatetags (se não existir) e um arquivo com o nome da app seguido de _tags, no caso core_tags.py:
# myproject/core/templatetags/core_tags.py
from django import template
register = template.Library()
Com -n você escolhe o nome do arquivo:
python manage.py create_template_tags core -n lorem_tags
Isso cria myproject/core/templatetags/lorem_tags.py, com o mesmo conteúdo. Para escrever as suas tags, veja a Dica 34 - Django custom template tags.
show_template_tags
Lista todos os template tags e filtros do projeto, app por app, com a docstring de cada um.
python manage.py show_template_tags
Ele mostra as bibliotecas que você carrega com {% load ... %} e, para cada tag ou filtro, a docstring. No vídeo, a app core aparece assim (trecho):
App: myproject.core
load: model_name_tags
Tag: model_name
Django template filter which returns the verbose name of a model.
Tag: model_name_plural
Django template filter which returns the plural verbose name of a model.
load: url_replace
Tag: url_replace
load: core_tags
load: usergroup_tags
Filter: name_group
Retorna o nome do grupo do usuário.
Usage:
{% load usergroup_tags %}
{{ user|name_group }}
Filter: has_group
Verifica se este usuário pertence a um grupo.
Usage:
{% load usergroup_tags %}
...
Repare que url_replace não tem docstring, então só o nome aparece; e core_tags, recém-criado, não tem nenhuma tag. As docstrings vêm do próprio código, por exemplo:
# myproject/core/templatetags/usergroup_tags.py
from django import template
register = template.Library()
@register.filter('name_group')
def name_group(user):
'''
Retorna o nome do grupo do usuário.
Usage:
{% load usergroup_tags %}
{{ user|name_group }}
'''
_groups = user.groups.first()
if _groups:
return _groups.name
return ''
@register.filter('has_group')
def has_group(user, group_name):
'''
Verifica se este usuário pertence a um grupo.
Usage:
{% load usergroup_tags %}
{% if user|has_group:"Nome do grupo" %}
{{ user|name_group }}
{% endif %}
'''
if user:
groups = user.groups.all().values_list('name', flat=True)
return True if group_name in groups else False
return False
Por isso vale a pena documentar as suas template tags: o show_template_tags vira uma documentação de graça.
generate_password
Gera uma senha aleatória.
python manage.py generate_password
R2ZcV7wdQW
Com --length você define o tamanho:
python manage.py generate_password --length 32
dA8C5Qyja3YUTSunpmfA335c8Zr48XTU
generate_secret_key
Gera uma nova chave secreta, para usar no SECRET_KEY do settings.py (ou no .env).
python manage.py generate_secret_key
_p07+-^01u1s%sfxo#+l@yqod-^ze*i2+g-$x)ug)%i9p-n6(f
A cada execução sai uma senha e uma chave diferentes.
graph_models
O graph_models gera o diagrama dos modelos do projeto. Ele tem um vídeo só para ele (link da descrição do vídeo):
O passo a passo está na Dica 36 - Visualizando seus modelos com graph_models.
list_model_info
Lista as informações de um modelo: os campos e os métodos públicos.
python manage.py list_model_info --model product.Product
product.Product
Fields:
product_items -
id -
title -
price -
manufacturing_date -
due_date -
Methods (non-private/internal):
Total Models Listed: 1
O product_items aparece porque, no projeto do vídeo, o modelo OrderItems da app ecommerce tem uma chave estrangeira para Product com related_name='product_items'. Com --field-class ele mostra também o tipo de cada campo:
python manage.py list_model_info --model product.Product --field-class
product.Product
Fields:
id - BigAutoField
title - CharField
price - DecimalField
manufacturing_date - DateField
due_date - DateField
Methods (non-private/internal):
Total Models Listed: 1
O id é BigAutoField porque o projeto tem DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' no settings.py.
list_signals
Lista os sinais (signals) registrados no projeto.
python manage.py list_signals
O projeto do vídeo não tinha nenhum sinal, por isso o comando não retornou nada.
print_settings
Mostra todas as configurações do projeto, incluindo os valores padrão do Django que você não alterou no settings.py.
python manage.py print_settings
ABSOLUTE_URL_OVERRIDES = {}
ADMINS = []
ALLOWED_HOSTS = []
APPEND_SLASH = True
AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.ModelBackend']
AUTH_USER_MODEL = 'auth.User'
...
show_urls
Lista todas as URLs do projeto: o caminho, a view que atende e o nome da rota.
python manage.py show_urls
Trecho da saída no vídeo:
/admin/travel/travel/ django.contrib.admin.options.changelist_view admin:travel_travel_changelist
/admin/travel/travel/<path:object_id>/ django.views.generic.base.RedirectView
/admin/travel/travel/<path:object_id>/change/ django.contrib.admin.options.change_view admin:travel_travel_change
/admin/travel/travel/<path:object_id>/delete/ django.contrib.admin.options.delete_view admin:travel_travel_delete
/admin/travel/travel/<path:object_id>/history/ django.contrib.admin.options.history_view admin:travel_travel_history
/admin/travel/travel/add/ django.contrib.admin.options.add_view admin:travel_travel_add
/articles/ myproject.core.views.article_list core:article_list
/articles/filter/ myproject.core.views.article_filter_list core:article_filter_list
/articles/json/ myproject.core.views.article_json core:article_json
/persons/ myproject.core.views.PersonListView core:person_list
/persons/create/ myproject.core.views.person_create core:person_create
/travel/ myproject.travel.views.TravelListView travel:travel_list
/travel/create/ myproject.travel.views.TravelCreateView travel:travel_create
É muito útil para descobrir o nome de uma rota para usar no {% url %} ou no reverse().
Conclusão
Com o django-extensions instalado, o manage.py ganha atalhos para tarefas do dia a dia: um shell com tudo importado, geração de admin, de comandos e de template tags, senhas e chaves secretas, e várias formas de inspecionar o projeto (modelos, sinais, settings e URLs). Na próxima dica veremos o runserver_plus, também do django-extensions.