Veja aqui como criar o seu próprio comando para ser usado com o django-admin ou manage.py do Django.
O django-admin ou manage.py já tem um bocado de comandos interessantes, os mais utilizados são:
- startproject - cria novos projetos.
- startapp - cria novas apps.
- makemigrations - cria novas migrações baseadas nas mudanças detectadas nos modelos Django.
- migrate - sincroniza o banco de dados com as novas migrações.
- createsuperuser - cria novos usuários.
- test - roda os testes da aplicação.
- loaddata - carrega dados iniciais a partir de um json, por exemplo,
python manage.py loaddata fixtures.json
- shell - inicializa um interpretador Python interativo.
- dbshell - acessa o banco de dados através da linha de comando, ou seja, você pode executar comandos sql do banco, por exemplo, diretamente no terminal.
- inspectdb - retorna todos os modelos Django que geraram as tabelas do banco de dados.
- runserver - roda o servidor local do projeto Django.
Mas de repente você precisa criar um comando personalizado conforme a sua necessidade. A palavra chave é BaseCommand
ou Writing custom django-admin commands.
Começando do começo
Importante: estamos usando Django 1.8.12 e Python 3.
Criando o projeto
Eu usei esta sequência de comandos para criar o projeto.
# Criando djangoproject.
mkdir djangoproject
cd djangoproject
# Criando virtualenv
virtualenv -p python3 .venv
# Ativando o .venv.
source .venv/bin/activate
# Diminuindo o caminho do prompt (opcional)
PS1="(`basename \"$VIRTUAL_ENV\"`)\e[1;34m:/\W\033[00m$ "
# Instalando o Django
pip install django==1.8.12
pip freeze > requirements.txt
# Criando o projeto myproject ...
django-admin.py startproject myproject .
cd myproject
# Criando a app 'core' ...
python ../manage.py startapp core
cd ..
# Editando settings.py"
sed -i "/django.contrib.staticfiles/a\ 'myproject.core'," myproject/settings.py
Pronto! Agora nós já temos um projetinho Django funcionando. Note que o nome da app é core.
Criando as pastas
Para criarmos um novo comando precisamos das seguintes pastas:
core
├── management
│ ├── __init__.py
│ ├── commands
│ │ ├── __init__.py
│ │ ├── novocomando.py
No nosso caso, teremos 3 novos comandos, então digite, estando na pasta djangoproject
mkdir -p core/management/commands
touch core/management/__init__.py
touch core/management/commands/{__init__.py,hello.py,initdata.py,search.py}
Sintaxe do novo comando
Importante: estamos usando Django 1.8.12 e Python 3.
O Django 1.8 usa o argparse
como parser de argumentos do command
, mais informações em module-argparse.
from django.core.management.base import BaseCommand, CommandError
from optparse import make_option
class Command(BaseCommand):
help = 'Texto de ajuda aqui.'
option_list = BaseCommand.option_list + (
make_option('--awards', '-a',
action="store_true",
help='Ajuda da opção aqui.'),
)
def handle(self, **options):
self.stdout.write('Hello world.')
if options['awards']:
self.stdout.write('Awards')
Entendeu? Basicamente o handle
é a função que executa o comando principal, no caso o self.stdout.write('Hello world.')
, ou seja, se você digitar o comando a seguir ele imprime a mensagem na tela.
$ python manage.py hello
Hello World
--awards
é um argumento opcional, você também pode digitar -a
.
$ python manage.py hello -a
Hello World
Awards
action="store_true"
significa que ele armazena um valor verdadeiro.
Obs: A partir do Django 1.8 os comandos de argumentos opcionais são baseados em **options
.
Veja uma outra forma de escrever
from django.core.management.base import BaseCommand, CommandError
class Command(BaseCommand):
def add_arguments(self, parser):
# Argumento nomeado (opcional)
parser.add_argument('--awards', '-a',
action='store_true',
help='Ajuda da opção aqui.')
def handle(self, *args, **options):
self.stdout.write('Hello world.')
if options['awards']:
self.stdout.write('Awards')
A diferença é que aqui usamos parser.add_argument
ao invés de make_option
.
hello.py
from django.core.management.base import BaseCommand, CommandError
# minimalista
class Command(BaseCommand):
help = 'Print hello world'
def handle(self, **options):
self.stdout.write('Hello World')
Uso
$ python manage.py hello
initdata.py
Objetivo: Obter alguns filmes de uma api e salvar os dados no banco.
api: omdbapi.com
models.py
from django.db import models
class Movie(models.Model):
title = models.CharField(u'título', max_length=100)
year = models.PositiveIntegerField('ano', null=True, blank=True)
released = models.CharField(u'lançamento', max_length=100, default='', blank=True)
director = models.CharField('diretor', max_length=100, default='', blank=True)
actors = models.CharField('atores', max_length=100, default='', blank=True)
poster = models.URLField('poster', null=True, blank=True)
imdbRating = models.DecimalField(max_digits=6, decimal_places=2, null=True, blank=True)
imdbID = models.CharField(max_length=50, default='', blank=True)
class Meta:
ordering = ['title']
verbose_name = 'filme'
verbose_name_plural = 'filmes'
def __str__(self):
return self.title
Não se esqueça de fazer
python manage.py makemigrations
python manage.py migrate
admin.py
Vamos visualizar pelo admin.
from django.contrib import admin
from core.models import Movie
admin.site.register(Movie)
Instale o requests
pip install requests
initdata.py
O código a seguir é longo, mas basicamente temos
print_red(name)
função que imprime um texto em vermelho (opcional)get_html(year)
função que lê os dados da api usando requests, e depois escolhe um filme randomicamente a partir de 2 letrasget_movie(year)
se o dicionário conter{'Response': 'True', ...}
então retorna um dicionário do filme localizadosave()
salva os dados no bancohandle(movies, year)
este é o comando principal. Busca os filmes várias vezes, conforme definido pela variávelmovies
, e salva os n filmes.
# -*- coding: utf-8 -*- #
import random
import string
import requests
from django.core.management.base import BaseCommand, CommandError
from django.core.exceptions import ValidationError
from optparse import make_option
from core.models import Movie
class Command(BaseCommand):
help = """Faz o crawler numa api de filmes e retorna os dados.
Uso: python manage.py initdata
ou: python manage.py initdata -m 20
ou: python manage.py initdata -m 20 -y 2015"""
option_list = BaseCommand.option_list + (
make_option('--movies', '-m',
dest='movies',
default=10,
help='Define a quantidade de filmes a ser inserido.'),
make_option('--year', '-y',
dest='year',
action='store',
default=None,
help='Define o ano de lançamento do filme.'),
)
def print_red(self, name):
"""imprime em vermelho"""
print("\033[91m {}\033[00m".format(name))
def get_html(self, year):
"""
Le os dados na api http://www.omdbapi.com/ de forma aleatoria
e escolhe um filme buscando por 2 letras
"""
# Escolhe duas letras aleatoriamente
letters = ''.join(random.choice(string.ascii_lowercase) for _ in range(2))
# Se não for definido o ano, então escolhe um randomicamente
if year is None:
year = str(random.randint(1950, 2015))
url = 'http://www.omdbapi.com/?t={letters}*&y={year}&plot=short&r=json'.format(letters=letters, year=str(year))
return requests.get(url).json()
def get_movie(self, year, **kwargs):
""" Retorna um dicionário do filme """
movie = self.get_html(year)
j = 1 # contador
# Faz a validação de Response. Se a resposta for falsa, então busca outro filme.
while movie['Response'] == 'False' and j < 100:
movie = self.get_html(year)
self.print_red('Tentanto %d vezes\n' % j)
j += 1
return movie
def save(self, **kwargs):
"""SALVA os dados"""
try:
Movie.objects.create(**kwargs)
except ValidationError as e:
self.print_red(e.messages)
self.print_red('O objeto não foi salvo.\n')
def handle(self, movies, year, **options):
""" se "movies" não for nulo, transforma em inteiro """
self.verbosity = int(options.get('verbosity'))
if movies is not None:
movies = int(movies)
# busca os filmes n vezes, a partir da variavel "movies"
for i in range(movies):
# verifica as validações
m = self.get_movie(year)
if m['imdbRating'] == "N/A":
m['imdbRating'] = 0.0
# Transforma "year" em inteiro
if "–" in m['Year']:
m['Year'] = year
data = {
"title": m['Title'],
"year": m['Year'],
"released": m['Released'],
"director": m['Director'],
"actors": m['Actors'],
"poster": m['Poster'],
"imdbRating": m['imdbRating'],
"imdbID": m['imdbID'],
}
self.save(**data)
if self.verbosity > 0:
self.stdout.write('\n {0} {1} {2}'.format(i + 1, data['year'], data['title']))
if self.verbosity > 0:
self.stdout.write('\nForam salvos %d filmes' % movies)
Uso
Usage: python manage.py initdata [options]
Faz o crawler numa api de filmes e retorna os dados.
Uso: python manage.py initdata
ou: python manage.py initdata -m 20
ou: python manage.py initdata -m 20 -y 2015
search.py
Objetivo: Localizar o filme pelo título ou ano de lançamento.
from django.core.management.base import BaseCommand, CommandError
from optparse import make_option
from core.models import Movie
class Command(BaseCommand):
help = """Localiza um filme pelo título ou ano de lançamento.
Uso: python manage.py search -t 'Ted 2'
ou: python manage.py search -y 2015
ou: python manage.py search -t 'a' -y 2015"""
option_list = BaseCommand.option_list + (
make_option('--title', '-t',
dest='title',
default=None,
help='Localiza um filme pelo título.'),
make_option('--year', '-y',
dest='year',
default=None,
help='Localiza um filme pelo ano de lançamento.'),
)
def handle(self, title=None, year=None, **options):
""" dicionário de filtros """
self.verbosity = int(options.get('verbosity'))
filters = {
'title__istartswith': title,
'year': year
}
filter_by = {key: value for key, value in filters.items() if value is not None}
queryset = Movie.objects.filter(**filter_by)
if self.verbosity > 0:
for movie in queryset:
self.stdout.write("{0} {1}".format(movie.year, movie.title))
self.stdout.write('\n{0} filmes localizados.'.format(queryset.count()))
Uso
Usage: python manage.py search [options]
Localiza um filme pelo título ou ano de lançamento.
Uso: python manage.py search -t 'Ted 2'
ou: python manage.py search -y 2015
ou: python manage.py search -t 'a' -y 2015
Aqui tem um exemplo legal que eu usei como ideia pra fazer este post.
Mais algumas referências:
Writing custom django-admin commands
Zachary Voase: Fixing Django Management Commands
Adding Custom Commands to manage.py and django-admin.py by dave
"Criando novos comandos no django-admin" de "Regis da Silva" está licenciado com uma Licença Creative Commons - Atribuição-NãoComercial-SemDerivações 4.0 Internacional.