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][18] - 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 e Python 3.
Criando o projeto
Eu usei este Makefile para criar o projeto.
wget --output-document=Makefile https://goo.gl/UMTpZ1
make setup
Ele vai criar um virtualenv e pedir pra você executar os seguintes comandos:
source venv/bin/activate
cd djangoproject
make install
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 e Python 3.
O Django 1.8 usa o argparse como parser de argumentos do command, mais informações em [module-argparse][19].
fromdjango.core.management.baseimportBaseCommand,CommandErrorfromoptparseimportmake_optionclassCommand(BaseCommand):help='Texto de ajuda aqui.'option_list=BaseCommand.option_list+(make_option('--awards','-a',action="store_true",help='Ajuda da opção aqui.'),)defhandle(self,**options):self.stdout.write('Hello world.')ifoptions['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
fromdjango.core.management.baseimportBaseCommand,CommandErrorclassCommand(BaseCommand):defadd_arguments(self,parser):# Argumento nomeado (opcional)parser.add_argument('--awards','-a',action='store_true',help='Ajuda da opção aqui.')defhandle(self,*args,**options):self.stdout.write('Hello world.')ifoptions['awards']:self.stdout.write('Awards')A diferença é que aqui usamos parser.add_argument ao invés de make_option.
hello.py
fromdjango.core.management.baseimportBaseCommand,CommandError# minimalistaclassCommand(BaseCommand):help='Print hello world'defhandle(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
fromdjango.dbimportmodelsclassMovie(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)classMeta:ordering=['title']verbose_name='filme'verbose_name_plural='filmes'def__str__(self):returnself.titleNão se esqueça de fazer
python manage.py makemigrations
python manage.py migrate
admin.py
Vamos visualizar pelo admin.
fromdjango.contribimportadminfromcore.modelsimportMovieadmin.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][20], 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 -*- #importrandomimportstringimportrequestsfromdjango.core.management.baseimportBaseCommand,CommandErrorfromdjango.core.exceptionsimportValidationErrorfromoptparseimportmake_optionfromcore.modelsimportMovieclassCommand(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.'),)defprint_red(self,name):"""imprime em vermelho"""print("\033[91m {}\033[00m".format(name))defget_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 aleatoriamenteletters=''.join(random.choice(string.ascii_lowercase)for_inrange(2))# Se não for definido o ano, então escolhe um randomicamenteifyearisNone: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))returnrequests.get(url).json()defget_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.whilemovie['Response']=='False'andj<100:movie=self.get_html(year)self.print_red('Tentanto %d vezes\n'%j)j+=1returnmoviedefsave(self,**kwargs):"""SALVA os dados"""try:Movie.objects.create(**kwargs)exceptValidationErrorase:self.print_red(e.messages)self.print_red('O objeto não foi salvo.\n')defhandle(self,movies,year,**options):""" se "movies" não for nulo, transforma em inteiro """self.verbosity=int(options.get('verbosity'))ifmoviesisnotNone:movies=int(movies)# busca os filmes n vezes, a partir da variavel "movies"foriinrange(movies):# verifica as validaçõesm=self.get_movie(year)ifm['imdbRating']=="N/A":m['imdbRating']=0.0# Transforma "year" em inteiroif"–"inm['Year']:m['Year']=yeardata={"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)ifself.verbosity>0:self.stdout.write('\n {0} {1} {2}'.format(i+1,data['year'],data['title']))ifself.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.
fromdjango.core.management.baseimportBaseCommand,CommandErrorfromoptparseimportmake_optionfromcore.modelsimportMovieclassCommand(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.'),)defhandle(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:valueforkey,valueinfilters.items()ifvalueisnotNone}queryset=Movie.objects.filter(**filter_by)ifself.verbosity>0:formovieinqueryset: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