mysql - django multiple databases
¿Cómo usar los modelos django con claves foráneas en diferentes DB? (5)
Tengo 2 modelos para 2 bases de datos diferentes:
Las bases de datos se crearon manualmente pero no deberían cambiar nada.
class LinkModel(models.Model): # in ''urls'' database
id = models.AutoField(primary_key=True)
host_id = models.IntegerField()
path = models.CharField(max_length=255)
class Meta:
db_table = ''links''
app_label = ''testapp''
def __unicode__(self):
return self.path
class NewsModel(models.Model): # in default database
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=50)
link = models.ForeignKey(LinkModel)
class Meta:
db_table = ''news''
app_label = ''test''
def __unicode__(self):
return self.title
Después del siguiente código, aumenta el error
newsItem, created = NewsModel.objects.get_or_create( title="test" )
link = LinkModel.objects.using(''urls'').get( id=1 )
newsItem.link = link # error!
Cannot assign "<LinkModel: />": instance is on database "default", value is on database "urls"
¿Por qué no puedo usar una clave externa y un modelo para una base de datos diferente?
Usar múltiples bases de datos hace que las cosas sean más difíciles.
Leer: MultipleDB Django
Para que cosas así funcionen, tienes que usar enrutadores de base de datos como se describe en el enlace, hasta donde yo sé. Nunca utilicé una configuración de base de datos múltiple con claves externas entre ellos, pero ahí es donde comenzaría.
Limitaciones de bases de datos cruzadas
Actualmente, Django no brinda ningún soporte para llaves foráneas o relaciones de muchos a muchos que abarcan múltiples bases de datos. Si ha utilizado un enrutador para dividir los modelos en diferentes bases de datos, cualquier clave externa y las relaciones de muchos a muchos definidas por esos modelos deben ser internas de una única base de datos.
Django - limitaciones de las bases de datos múltiples
Problema
Mismo problema. Error en la clase ForeignKey ().
En el método validate ()
Existe error en v1.2, v1.3, v1.4rc1
Solución
Prueba este parche para resolverlo.
Como alternativa (aunque un poco hackosa), podría subclase ForeignKey para verificar, por ejemplo, existencia dentro del db correcto:
class CrossDbForeignKey(models.ForeignKey):
def validate(self, value, model_instance):
if self.rel.parent_link:
return
super(models.ForeignKey, self).validate(value, model_instance)
if value is None:
return
# Here is the trick, get db relating to fk, not to root model
using = router.db_for_read(self.rel.to, instance=model_instance)
qs = self.rel.to._default_manager.using(using).filter(
**{self.rel.field_name: value}
)
qs = qs.complex_filter(self.rel.limit_choices_to)
if not qs.exists():
raise exceptions.ValidationError(self.error_messages[''invalid''] % {
''model'': self.rel.to._meta.verbose_name, ''pk'': value})
entonces apenas
class NewsModel(models.Model): # in default database
…
link = models.CrossDbForeignKey(LinkModel)
Tenga en cuenta que corresponde más o menos al parche que Vitaly menciona, pero de esa manera no es necesario parchear código fuente de django.
¡Después de romperme la cabeza algunos días, logré obtener mi Foreign Key EN EL MISMO BANCO!
¡Se puede hacer un cambio sobre el FORMULARIO para buscar una LLAVE EXTRAÑA en un banco diferente!
Primero, agregue una RECARGA de CAMPOS, ambos directamente (crack) mi formulario, en la función _ init _
app.form.py
# -*- coding: utf-8 -*-
from django import forms
import datetime
from app_ti_helpdesk import models as mdp
#classe para formulario de Novo HelpDesk
class FormNewHelpDesk(forms.ModelForm):
class Meta:
model = mdp.TblHelpDesk
fields = (
"problema_alegado",
"cod_direcionacao",
"data_prevista",
"hora_prevista",
"atendimento_relacionado_a",
"status",
"cod_usuario",
)
def __init__(self, *args, **kwargs):
#-------------------------------------
# using remove of kwargs
#-------------------------------------
db = kwargs.pop("using", None)
# CASE use Unique Key`s
self.Meta.model.db = db
super(FormNewHelpDesk, self).__init__(*args,**kwargs)
#-------------------------------------
# recreates the fields manually
from copy import deepcopy
self.fields = deepcopy( forms.fields_for_model( self.Meta.model, self.Meta.fields, using=db ) )
#
#-------------------------------------
#### follows the standard template customization, if necessary
self.fields[''problema_alegado''].widget.attrs[''rows''] = 3
self.fields[''problema_alegado''].widget.attrs[''cols''] = 22
self.fields[''problema_alegado''].required = True
self.fields[''problema_alegado''].error_messages={''required'': ''Necessário informar o motivo da solicitação de ajuda!''}
self.fields[''data_prevista''].widget.attrs[''class''] = ''calendario''
self.fields[''data_prevista''].initial = (datetime.timedelta(4)+datetime.datetime.now().date()).strftime("%Y-%m-%d")
self.fields[''hora_prevista''].widget.attrs[''class''] = ''hora''
self.fields[''hora_prevista''].initial =datetime.datetime.now().time().strftime("%H:%M")
self.fields[''status''].initial = ''0'' #aberto
self.fields[''status''].widget.attrs[''disabled''] = True
self.fields[''atendimento_relacionado_a''].initial = ''07''
self.fields[''cod_direcionacao''].required = True
self.fields[''cod_direcionacao''].label = "Direcionado a"
self.fields[''cod_direcionacao''].initial = ''2''
self.fields[''cod_direcionacao''].error_messages={''required'': ''Necessário informar para quem é direcionado a ajuda!''}
self.fields[''cod_usuario''].widget = forms.HiddenInput()
llamando al formulario desde la vista
app.view.py
form = forms.FormNewHelpDesk(request.POST or None, using=banco)
Ahora, el cambio en el código fuente DJANGO
Solo los campos de tipo ForeignKey, ManyToManyField y OneToOneField pueden usar el ''uso'', por lo que se agrega un IF ...
django.forms.models.py
# line - 133: add using=None
def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_callback=None, using=None):
# line - 159
if formfield_callback is None:
#----------------------------------------------------
from django.db.models.fields.related import (ForeignKey, ManyToManyField, OneToOneField)
if type(f) in (ForeignKey, ManyToManyField, OneToOneField):
kwargs[''using''] = using
formfield = f.formfield(**kwargs)
#----------------------------------------------------
elif not callable(formfield_callback):
raise TypeError(''formfield_callback must be a function or callable'')
else:
formfield = formfield_callback(f, **kwargs)
ALTER FOLLOW FILE
django.db.models.base.py
alterar
# line 717
qs = model_class._default_manager.filter(**lookup_kwargs)
para
# line 717
qs = model_class._default_manager.using(getattr(self, ''db'', None)).filter(**lookup_kwargs)
Listo: D
El problema
* Nota: esta es una extensión de la respuesta de Vitaly Fadeev
Debido al deseo de mantener la integridad referencial, Django no permite claves externas que abarcan múltiples bases de datos: https://docs.djangoproject.com/en/dev//topics/db/multi-db/#limitations-of-multiple -databases . Aunque esto se desea en el 99% de todas las aplicaciones, en algunos casos es útil poder crear una asociación de este tipo en el ORM, incluso si no se puede garantizar la integridad referencial.
Una solución
He creado un Gist que utiliza la solución propuesta aquí por Vitaly Fadeev envuelto como una subclase del campo ForeignKey de Django. Esta solución no requiere que modifique los archivos Django Core sino que utiliza este tipo de campo en su lugar en los casos que lo necesite.
Ejemplo de uso
# app1/models.py
from django.db import models
class ClientModel(models.Model)
name = models.CharField()
class Meta:
app_label = ''app1''
# app2/models.py
from django.db import models
from some_location.related import SpanningForeignKey
class WidgetModel(models.Model):
client = SpanningForeignKey(''app1.ClientModel'', default=None, null=True,
blank=True, verbose_name=''Client'')
La esencia
La esencia está disponible aquí: https://gist.github.com/gcko/de1383080e9f8fb7d208
Copiado aquí para facilitar el acceso:
from django.core import exceptions
from django.db.models.fields.related import ForeignKey
from django.db.utils import ConnectionHandler, ConnectionRouter
connections = ConnectionHandler()
router = ConnectionRouter()
class SpanningForeignKey(ForeignKey):
def validate(self, value, model_instance):
if self.rel.parent_link:
return
# Call the grandparent rather than the parent to skip validation
super(ForeignKey, self).validate(value, model_instance)
if value is None:
return
using = router.db_for_read(self.rel.to, instance=model_instance)
qs = self.rel.to._default_manager.using(using).filter(
**{self.rel.field_name: value}
)
qs = qs.complex_filter(self.get_limit_choices_to())
if not qs.exists():
raise exceptions.ValidationError(
self.error_messages[''invalid''],
code=''invalid'',
params={
''model'': self.rel.to._meta.verbose_name, ''pk'': value,
''field'': self.rel.field_name, ''value'': value,
}, # ''pk'' is included for backwards compatibility
)