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:
- 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.
- 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.