Manager para diferentes conexões de banco no Django

Pra tirar um pouco as teias de aranha do blog, vou escrever sobre algo que fiz recentemente e achei bacana.

Eu estava trabalhando em um novo projeto (em Django, é claro), vinculado a um projeto principal e comecei a me sentir um pouco amarrado. Os dois projetos compartilhavam as mesmas aplicações mas depois de algumas definições no meio do caminho pode-se ver que eles precisariam compartilhar pouquíssimo conteúdo.

Há algum tempo, havia visto um manager que era capaz de se conectar a um banco diferente do padrão e achei que esta seria a solução ideal. Eu poderia separar o conteúdo dos dois projetos e, quando precisasse, usaria o banco de dados do outro projeto.

O manager que eu havia visto estava em algum destes projetos em Django que foram abertos ao público recentemente, mas não consegui mais encontrar. Então me vi obrigado a fazer um pouco de pesquisa e inventar o meu próprio.

Acabei encontrando uma solução do Eric Florenzano e outra do Kenneth Falck.

A do Erick parecia um tanto antiga e junto com a pesquisa esbarrei na changeset 10026, em que foi alterada a maneira de se conectar ao banco, ao invés de usar o módulo django.conf.settings o parâmetro passa a ser um dicionário com dados de conexão, e isso facilitou bastante.

Chega de papo furado e vamos ao código. Vou explicando por partes.

Primeiro foi preciso definir uma maneira fácil de usar conexões diferentes sem ficar repetindo no código as configurações de cada conexão. A solução copiada do Eric foi criar um dicionário de conexões nas configurações do projeto:

# Configurações padrão do banco
DATABASE_ENGINE = 'postgresql_psycopg2'
DATABASE_NAME = 'default_db_name'
DATABASE_USER = 'user'
DATABASE_PASSWORD = ''
DATABASE_HOST = ''
DATABASE_PORT = ''

# Configuração padrão + alternativas
DATABASES = {
    'default': {},
    'alternative': {
        'DATABASE_NAME': 'alternative_db_name',
    },
}

Como eu disse, quiz deixar fácil o uso de conexões diferentes, então fiz com que não fosse necessário repetir os dados de configuração de cada conexão, visto que é comum que se use o mesmo usuário, senha, engine, etc. Então se você pretende somente usar outro banco, basta mudar o DATABASE_NAME. Abaixo o código responsável por isto:

from django.conf import settings
from django.db import models, backend

# Dicionário de conexão padrão
db_settings = {
    'DATABASE_HOST': settings.DATABASE_HOST,
    'DATABASE_NAME': settings.DATABASE_NAME,
    'DATABASE_OPTIONS': settings.DATABASE_OPTIONS,
    'DATABASE_PASSWORD': settings.DATABASE_PASSWORD,
    'DATABASE_PORT': settings.DATABASE_PORT,
    'DATABASE_USER': settings.DATABASE_USER,
    'TIME_ZONE': settings.TIME_ZONE,
}

def prepare_db_settings(db_profile_name):
    """
    Recebe um dicionário de conexão alternativa e retorna um novo dicionário,
    completando propriedades ausentes com os valores padrão.
    """
    return dict(db_settings, **settings.DATABASES[db_profile_name])

Por fim, o manager se comporta normalmente, se você quiser conectar a um banco diferente é só usar o método use, informando o nome do perfil de conexão que deseja utilizar. O manager retorna a query padrão mas aponta para outro banco de dados. Bacana não?

class MultiDBManager(models.Manager):
    """
    A manager that can connect to different databases.
    """
    def use(self, db_profile_name):
    	"""
    	Return a queryset connected to a custom database.
    	"""
        # Get customized database settings to use in a new connection wrapper
        db_settings = prepare_db_settings(db_profile_name)

    	# Get the queryset and replace its connection
        qs = self.get_query_set()
        qs.query.connection = backend.DatabaseWrapper(db_settings)
        return qs

Mas nem tudo são flores, é tudo muito experimental e não foi testado a fundo. Aqui vão alguns problemas conhecidos:

  1. Você vai encontrar erros se tentar acessar models relacionados, o manager inicial vai usar a conexão alternativa, mas o model relacionado vai ser procurado no banco padrão. Para contornar isto use o método select_related(‘model_relacionado’), desta forma todos os models serão listados em apenas uma consulta ao mesmo banco.
  2. Não foi testado nenhum tipo de alteração de dados (inclusão, edição, exclusão). O post do Eric talvez possa ajudar a encontrar uma solução neste sentido.

De qualquer forma, tem sido bastante útil para integrar projetos sem fazer muita bagunça.

E você? O que achou? Deixe sua opinião nos comentários!

* Disponibilizei o código no Django Snippets também: Manager for multiple database connections.

About these ads

Sobre Rico
Web developer e surfista de verão.

10 Responses to Manager para diferentes conexões de banco no Django

  1. Bom artigo! Futuramente vou precisar de algo do tipo.
    Gostei do seu blog, ja add no bookmarks!
    Parabéns!

    • Rico disse:

      Valeu Gustavo!
      Tomara que te ajude, depois conta como foi!
      Legal, teu blog já tá no meu Google Reader também! =)
      Abraço.

  2. felipe disse:

    virou djangueiro entao ‘e … phyton ‘e muito melhor que essa zona de php :P – t’a no caminho certo….

    • Rico disse:

      Pois é Felipe, já faz um bom tempo que estou mexendo com Django, dá pavor de pensar em PHP hoje.
      Parece a mesma diferença do tableless pro tablemore. :)
      E aí na NZ? O Django é popular?
      Abraço.

  3. Edgard disse:

    Ótimo artigo Enrico.
    Mas como eu digo para uma classe que ela deve usar esse Manager?
    Tentei o _default_manager mas não funcionou.
    Você teria como mostrar a estrutura dos arquivos e a chamada da execução de uma query na outra base?

    Obrigado.

    • Rico disse:

      Valeu Edgard!

      Dá uma olhada na documentação do Django.

      Você pode colar o código do manager no seu “models.py” ou colocar em algum lugar disponível no teu path (mais recomendado).

      Eu criei um diretório “multidb” e coloquei o manager em um arquivo “__init__.py”.

      Aí você precisa associar o manager ao teu model:

      from multidb import MultiDBManager
      
      class Blog(models.Model):
          # ... seus campos
          objects = MultiDBManager()
      

      Abraço.

  4. Valeu Enrico,
    Mas eu não entendi 1 coisa:
    1 – Onde fica o “prepare_db_settings”, no settings.py??

    …e estou com problema em outra coisa.
    Seguindo o modelo proposto pelo Eric Florenzano. Estou com o problema de não fechar conexão. Vc consegiu resolver isso ?

    • Rico disse:

      Fala Leonardo!
      O “prepare_db_settings” tá no segundo bloco de código deste post.
      Não fui a fundo para ver se está fechando a conexão do banco, se você encontrar uma solução eu também quero saber. :)
      Abraço.

  5. Edgard disse:

    Obrigado Enrico! Consegui fazer funcionar.
    Só estou com o seguinte problema. Eu quero acessar uma tabela no meu segundo banco de dados que não tem um nome no padrão do django.
    Então usei:
    db_table = ‘usuarios’

    No meu model. Mas quando vou acessar o admin, o seguinte erro é gerado:
    (1146, “Table ‘proj.usuarios’ doesn’t exist”)
    Sendo que “proj” é o nome do meu primeiro database e não do meu segundo.

    Tentei usar db_tablespace = ‘proj2′ no model mas também não funcionou.

    Você já passou por isso?

    Obrigado!

    • Rico disse:

      Olá Edgard!

      Que bom que está no caminho!

      Nunca passei por isso, mas imagino que o Django deveria respeitar a definição do nome da tabela que você definiu no Meta do seu model.

      Talvez o problema seja que este manager não usa um banco secundário como padrão. Você tem que usar o método “use(banco)” pra usar um banco diferente.

      Pra isso, acho que a melhor alternativa é colocar algo assim nas opções do teu admin:

      class BlogOptions(admin.ModelAdmin):
          def queryset(self, request):
              return super(BlogOptions, self).queryset(request).use('alternative_db').select_related()
      

      Não sei se entendi bem a tua dúvida, qualquer coisa grita.

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

Seguir

Obtenha todo post novo entregue na sua caixa de entrada.

%d blogueiros gostam disto: