quinta-feira, 28 de novembro de 2013

Criando alias para cada virtualenv

Quem programa usando Linux ou Mac, deve conhecer o comando alias que cria um atalho para um comando.

Para quem desenvolve com Django deve estar acostumado a digitar: python manage.py runserver

Se nós usarmos o recurso de alias nós economizaremos muito tempo, dedo e teclado pois ao invés de digitar aquele comando todo agente pode digitar apenas mr.

Legal, e como eu faço isso?

Simples, basta digitar esse comando no terminal:

alias mr='python manage.py runserver'

E a partir de agora iremos chamar o comando apenas digitando mr. Ou qualquer outro apelido que for mais conveniente para você.

Simples não?

Mas toda vez que abrir um terminal vou ter que definir um alias?

Por padrão quando queremos que os alias fiquem sempre disponíveis, definimos eles em um deste arquivos:

  • ~/.bashrc - só funciona para o usuário atual;
  • /etc/bash.bashrc - funciona para todos os usuários;
Uma vez definidos os alias dentro de um destes arquivos você pode carregá-los usando uma destas alternativas:
  • Fechando o terminal e abrindo novamente;
  • Digitando bash
  • Digitando source ~/.bashrc ou source /etc/bash.bashrc dependendo de em qual deles você definiu seus alias
Agora vamos supor que temos diversos projetos Django e cada um tem seu próprio virtualenv, e cada um tem suas peculiaridades e atalhos específicos, e se você criar todos os alias em um dos arquivos acima citados você eventualmente pode encontrar algum problema ou simplesmente vai encher seu shell de atalhos inúteis. Então o que posso fazer?

Simples, neste caso nós vamos criar os alias dependentes de projetos dentro do arquivo activate do virtualenv do projeto.

Ficou confuso?

Vamos entender.

Nós temos 4 virtualenvs distintos para cada projeto:
  • /home/exemplo/virtualenv_a
  • /home/exemplo/virtualenv_b
  • /home/exemplo/virtualenv_c
  • /home/exemplo/virtualenv_d


Dentro de cada virtualenv nós temos o diretório bin e dentro dele o arquivo activate, certo?

Pois bem, o arquivo activate nada mais é que um shell script, assim como o ~/.bashrc e o /etc/bash.bashrc, então agente simplesmente coloca os alias dentro do activate de cada virtualenv, assim cada um terá sua própria configuração de forma isolada. Afinal de contas essa é a premissa do virtualenv, que "cada macaco fique no seu galho". 

OBS: Isso funciona com qualquer comando de terminal.

Abraço e até a próxima!

    quarta-feira, 13 de novembro de 2013

    Compartilhando arquivos via HTTP.

    Você já precisou compartilhar algum arquivo e sempre teve algum problema, precisou instalar algo, seguiu o passo a passo e na hora H ocorreu algum problema?

    Livre desse mal de forma simples e rápida, como? Assim:

    Abra um terminal CTRL+ALT+T vá até o diretório onde estão os arquivos e digite:

    python -m SimpleHTTPServer 8000

    Após digitar o comando você verá algo como:

    Serving HTTP on 0.0.0.0 port 8000 ...

    Agora basta abrir o navegador de acessar o IP_DO_COMPUTADOR:8000.

    Pronto!
    Simples fácil e rápido como só Python sabe.

    Agradeço ao amigo Diogo Alves pela dica.

    ;)

    quinta-feira, 7 de novembro de 2013

    Usando regex para remover espaços no fim da linha

    Quem programa com Python e tenta seguir ao máximo o PEP8, pode se deparar vez ou outra com aquelas linhas em branco no fim do arquivo, principalmente quando agente pega código de programadores que não estão acostumados com Python ou que simplesmente não ligam para padronização.

    Existe uma forma simples, rápida e eficaz de consertar utilizando os comandos find e sed. Veja:

    find . -name *.py -type f -exec sed -i 's/ \+$//g' {} \;


    Vamos "esquartejar" o comando e estudar cada parte:


    1. find .  - O ponto diz ao find para pesquisa no diretório atual. Altere conforme sua necessidade.
    2. -name *.py - Diz ao find que só queremos arquivos com a extensão .py - essa nem precisava explicar, tá na cara né;
    3. -type f - Diz ao find que só queremos que ele procure arquivos. Mas na linha anterior você já não disse que era para encontrar somente a extensão .py? Pra que dizer que é só arquivo agora? Bem, para mostrar que você tem várias alternativas de uso;)
    4. -exec - Diz ao find que queremos executar um comando para cada arquivo encontrado, neste caso será o sed.
    5. sed -i - O parâmetro -i diz ao sed para fazer um edit in place. Caso você seja um cara muito cismado, ou não esteja usando controle de versão(que é uma péssima idéia), você pode passar para -i um sufixo, assim ele criará uma cópia do arquivo original colocando o sufixo informado.
    6. 's/ \+$//g' - Precisamos "esquartejar" mais um pouco:
      1. s/ - Diz ao sed que estamos fazendo uma operação de find and replace, logo após essa barra informamos o que estamos procurando;
      2. " \+$" Vamos "esquartejar" mais um pouco?:
        1. " "- Coloquei as "aspas" apenas para que você consiga ver que existe um espaço, que é o que estamos procurando;
        2. \+ O + em uma regex diz que o padrão anterior, o espaço, tem que aparecer no mínimo uma vez. E a barra invertida tem que estar lá ;);
        3. $ - O cifrão indica que estamos procurando este padrão, um ou vários espaços em branco, apenas no fim da linha.
      3. / - Indica que aqui acaba o padrão que estamos procurando e começa o conteúdo que irá substituir as ocorrências;
      4. / - Não se confunda, veja que antes do g que estão no fim existem duas barras invertidas, essa é a que está antes do g. Ela indica o fim do conteúdo que irá substituir o padrão encontrado. Neste caso como temos // vamos substituir os espaços em branco por NADA.
      5. g - Aplique o find and replace a TODAS as ocorrências e não somente a primeira;
    7.  {} \; - Bem, esse padrão precisar estar no final senão o exec do find simplesmente não funciona;

    É isso ai pessoal, espero que seja útil para alguém.

    terça-feira, 5 de novembro de 2013

    Operações com data de forma fácil

    Quando se trata de operações com data, só existem dois tipos de programados, os que já enfrentaram problemas e os que ainda vão enfrentar.

    Quem já precisou somar ou subtrair um dia, um mês ou ano em uma data?
    Com certeza você já deve ter encontrado diversas soluções, das mais simples as mais mirabolantes.

    A solução mais simples que encontrei até hoje foi utilizando a biblioteca relativedelta. Para manter a simplicidade dos posts não vou entrar em muitos detalhes, um código vale mais que mil palavras.

    # Importamos as libs necessárias
    >>> import datetime
    >>> from dateutil.relativedelta import *
    >>> import calendar
    
    # Gravamos a data atual na variável hoje
    >>> hoje=datetime.datetime.today()
    >>> print(hoje)
    2013-11-05 12:03:07.014243
    
    # Adicionar um dia a hoje.
    >>> print(hoje + relativedelta(days=1))
    2013-11-06 12:03:07.014243
    
    # Adicionar um mês a hoje.
    >>> print(hoje + relativedelta(months=1))
    2013-12-05 12:03:07.014243
    
    # Adicionar um ano a hoje.
    >>> print(hoje + relativedelta(years=1))
    2014-11-05 12:03:07.014243
    
    # Quanto tempo falta para o fim do ano?
    >>> fim_do_ano=datetime.date(2013,12,31)
    >>> print(relativedelta(fim_do_ano, hoje))
    relativedelta(months=+1, days=+25, hours=+11, minutes=+56, seconds=+52, microseconds=+985757)
    
    # Qual a data da próxima sexta feira?
    >>> print(hoje+relativedelta(weekday=calendar.FRIDAY))
    2013-11-08 12:03:07.014243
    # Ou assim:
    >>> print(hoje+relativedelta(weekday=FR))
    2013-11-08 12:03:07.014243
    
    # Qual a data da segunda sexta feira, a partir de hoje?
    >>> print(hoje+relativedelta(weekday=FR(+2)))
    2013-11-15 12:03:07.014243
    
    # Descobrir a quanto tempo alguém está vivo com base na sua data de nascimento
    >>> nascimento=datetime.date(2000,1,1)
    >>> print(relativedelta(hoje, nascimento))
    relativedelta(years=+13, months=+10, days=+4, hours=+12, minutes=+3, seconds=+7, microseconds=+14243)
    
    É isso ai pessoal, tem alguns outros exemplos na documentação, mas com isso já dá para perceber o potencial desta lib.
    Abraço a todos e até a próxima!

    sexta-feira, 1 de novembro de 2013

    Boas práticas no Django

    Achei este vídeo do Henrique Bastos que é muito bom e dá dicas excelentes para escrever apps de ótima qualidade.


    Fechar sessão do usuário quando o browser for fechado.

    Por padrão o Django mantém a sessão do usuário aberta(via cookie) por um período padrão de 2 semanas, com isso o usuário terá a comodidade de não ter que se logar sempre que abrir o browser, mas em sistemas que requerem um nível de segurança um pouco mais elevado isso é um problema grave. 

    Você tem duas formas de controlar usando ajustes no settings.py.

    1. Fechar a sessão sempre que o browser for fechado. Basta atribuir a variável SESSION_EXPIRE_AT_BROWSER_CLOSE o valor True.
    2. Se você quiser manter este comportamento mas apenas alterar o tempo de vida do cookie que controla a sessão, basta informar quantos segundos de vida ele terá usando a variável SESSION_COOKIE_AGE.


    Mais detalhes na documentação oficial

    quarta-feira, 30 de outubro de 2013

    Overload com ForeignKey no Django

    No Django os campos do tipo ForeignKey são preenchidos por dados que vem de uma QuerySet, até ai nenhuma novidade, mas você sabia que as QuerySets são implementadas utilizando o recurso de avaliação preguiçosa?

    Mas o que é avaliação preguiçosa e o que isso tem a ver com o tema?

    Quando você executa algo como ClasseX.objects.all(), você recebe um QuerySet com todos os registros, nenhuma novidade. Mas você sabia que somente quando você tentar acessar algum atributo é que um comando SQL será enviado para o banco para recuperar o valor deste atributo? É por isso que é chamada de avaliação preguiçosa, pois o ORM só vai enviar um comando SQL quando realmente for necessário. E isso poupa bastante recurso e tempo.

    Mas cuidado... esse comportamento é uma faca de dois gumes.

    Imagine que você tem uma ForeignKey que aponta para uma tabela com 2000 registros, na melhor das hipóteses você terá 2000 consultas ao banco, e se a classe para qual aponta o ForeignKey usar herança, a situação fica ainda pior, pois não teremos uma consulta simples, mas um join com outra(s) tabela(s), que aumentará ainda mais o tempo de resposta.

    Então como faço para contornar este "problema"?

    Eu consigo contornar este problema usando um Form, na verdade um ModelForm:

    class ClienteForm(forms.ModelForm):
        choices = PessoaBase.objects.values_list("id", "nome").all()
        """ Precisamos executar a etapa abaixo, senão o admin vai
        ficar pegando o primeiro registro, e não queremos isso.
    
        Aqui eu usei -------- para seguir o que o Django adota, mas
        você pode colocar qualquer mensagem.
    
        Se você conseguir aperfeiçoar este código, compartilhe conosco.
    
        ;)
        """
        choices = [('','--------')] + [i for i in choices]
    
        pessoa_base = forms.ChoiceField(choices=choices)
        def __init__(self, *args, **kwargs):
            super(ClienteForm, self).__init__(*args, **kwargs)
    
        class Meta:
            model = Cliente
    
        def clean(self):
            """ É preciso sobrescrever o método clean porque o Django
            vai estar esperando um objeto do tipo PessoaBase, e caso 
            não façamos isso ele vai receber um inteiro e levantar uma
            exceção
            """
            cleaned_data = self.cleaned_data
            pessoa_base_id = cleaned_data.get('pessoa_base')
            try:
                pessoa_base = PessoaBase.objects.get(id=pessoa_base_id)
            except PessoaBase.DoesNotExist:
                raise forms.ValidationError('Escolha inválida!')
    
            cleaned_data["pessoa_base"] = pessoa_base
            return cleaned_data

    Para finalizar só precisamos vincular nosso form ao admin(caso você esteja usando):

    class ClienteAdmin(admin.ModelAdmin):
        model = Cliente
        form = ClienteForm
    

    Apenas para vocês terem uma noção da diferença que essa técnica simples causa no desempenho, vejam os prints que fiz antes e depois de usar código acima(observem os campos hora e SQL):