multiple databases mysql django django-models foreign-keys

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 ()

Ver boleto

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 )