Post on 19-Jun-2015
Implementação de Acesso a múltiplos Bancos de dados no Django.
Rômulo Jales - rjales@fitec.org.br
Agenda
● O que é Django?● Um rápido Tutorial
● A necessidade● A solução
O que é Django?
● Um framework web de alto nível● Escrito em Python● Estimula o desenvolvimento rápido e limpo● Implementa o conceito DRY
“Um framework para perfeccionistas com deadlines”
O que é django?
Um rápido tutorial
● Crie um projeto:● django-admin startproject NOME_PRJ● São criados 4 arquivos:
– __init__.py– settings.py– manage.py– urls.py
Um rápido tutorial
● settings.py● Armazena as configurações do serviço● O banco de dados é configurado aqui!
● urls.py● Realiza o roteamento das urls para os métodos e
recursos
● manager.py● Gerenciador local do django.
Um rápido tutorial
● ./manager startapp NOME_DA_APP● São criados 4 arquivos
– init.py – models.py– view.py– tests.py
Um rápido tutorial
● models.py → acesso aos dados● views.py → Tratamento das requisições da
aplicação.
Um rápido tutorial
● Código para models.py
from django.db import models
class Artigo(models.Model):
titulo = models.CharField(max_length=100)
conteudo = models.TextField()
publicacao = models.DateTimeField()
Um rápido tutorial
● Crie um arquivo chamado admin.py
from django.contrib import admin
from models import Artigo
admin.site.register(Artigo)
● Edite a urls.py
(r'^$', 'django.views.generic.date_based.archive_index',
{'queryset': Artigo.objects.all(), 'date_field': 'publicacao'}),
(r'^admin/(.*)', admin.site.root),
Um rápido tutorial
Um rápido tutorial
● Sincronize o banco.● ./manager syncdb
Um rápido tutorial
Quem usa o django?
● globo.com● washingtonpost.com● E outros 3575 cadastrados no
http://www.djangosites.org/!
A necessidade
● Acessar vários banco de dados usando a versão em produção 1.1 do cliente.
A necessidade
● Solução de acesso atual (na view.py)
mssql = _mssql.connect('IP_DO_SERVER','linux','SENHA')
query = "SELECT OP FROM CONSULTA_OP WHERE NUMEROSERIE = '"+serial+"'"
mssql.query(query)
A necessidade
● Solução quebra a arquitetura!!!!!!● Não é DRY!● Não é simples!● Se o IP do banco mudar?● Se o backend do banco mudar?
A solução
● Premissas e restrições:● Não pode mudar o que já funciona!● Usar o conceito de DRY● Funcionar com todos os backends nativos do
Django (mysql, postgres, sqlite3) e o **SQL SERVER**
A solução - análise
connection = backend.DatabaseWrapper({
‘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,
})
A solução - análise
● Models● Tem um atributo _default_manager (Manager)
● Manager● Tem um método central get_query_set (QuerySet).
Método que acessa o backend
● QuerySet● Tem um atributo query do tipo Query, que é
instancia de DataBaseWrapper, que é uma classe abstrata definida pela parametrização contida em settings.py
A solução
● Permitir indexação dos parâmetros DATABASE_*● Modificar a classe QuerySet!!
A soluçãofrom django.db.models import sqlfrom django.db.models.sql.where import WhereNodefrom utils import getConnection
class MultiBdQuery(sql.Query): def __init__(self, model, banco): self.banco = banco self.connection = getConnection(self.banco) super(MultiBdQuery, self).__init__(model, self.connection, WhereNode) def __setstate__(self, obj_dict): obj_dict['select_fields'] = [ name is not None and obj_dict['model']._meta.get_field(name) or None for name in obj_dict['select_fields'] ] self.__dict__.update(obj_dict) self.connection = getConnection(self.banco)
A soluçãofrom django.conf import settingsfrom django.db import load_backend
def getConnection(banco): engine = settings.SECONDARY_DB[banco]['DATABASE_ENGINE'] if engine == "sql_server.pyodbc": backend = __import__(engine+'.base', {}, {}, ['base']) else: backend = load_backend(engine) return backend.DatabaseWrapper(settings.SECONDARY_DB[banco])
A soluçãofrom django.db.models import sqlfrom django.db.models.manager import Managerfrom django.db.models.query import QuerySetfrom query import MultiBdQuery
class MultiBdManager(Manager):
use_for_related_fields = True def __init__(self, banco, *args, **kwargs): super(MultiBdManager, self).__init__(*args, **kwargs) self.banco = banco def get_query_set(self): #Obtem um novo query a partir das configuracoes de banco query = MultiBdQuery(self.model, self.banco) return QuerySet(self.model, query)
A solução – visão do usuário
● Adicionar no settings.py um dicionário chamadoSECONDARY_DB contendo as configurações dos outros bancos
● Novos modelos● Modificar o Manager padrão, passando o nome do
banco de qual a classe está associada.
Correção da gambiarra - antes
def get_op_from_scf(serial): import _mssql
mssql = _mssql.connect('IP_DO_SERVER','linux','SENHA')
query = "SELECT OP FROM CONSULTA_OP WHERE NUMEROSERIE = '"+serial+"'"
mssql.query(query) ret = mssql.fetch_array()
If ret[0][1] == 0: raise Exception("Nao existe nenhuma OP para o numero de serie informado: " + str(serial))
op = ret[0][2][0][0]
return op.strip()
Correção da gambiarra - depois
class OPLEGADO(Model): class Meta: db_table = "CONSULTA_OP" managed = False _default_manager = MultiBdManager("scf") op = CharField(max_length=13,primary_key=True) numeroserie = CharField(max_length=26)
def get_op_from_scf(serial):op = OPLEGADO.objects.get(numeroserie=serial)If op:
return opelse:
raise Exception(“"Nao existe nenhuma OP para o numero de serie informado: " + str(serial))
Limitações
● Não sincroniza todos os bancos simultaneamente!
● Inicialização mais demorada● Funciona apenas para versões >= 1.1 do
Django.