我有两个不同数据库的2个模型:
数据库是手动创建的,但它不会改变任何内容。
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
在以下代码之后出现错误
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"
为什么我不能为不同的数据库使用外键和模型?
答案 0 :(得分:11)
Django目前不支持跨越多个数据库的外键或多对多关系。如果您已使用路由器将模型分区到不同的数据库,则由这些模型定义的任何外键和多对多关系必须位于单个数据库的内部。
Django - limitations-of-multiple-databases
同样的麻烦。 ForeignKey()类中的错误。
在validate()方法中。
v1.2,v1.3,v1.4rc1
中存在Bug尝试this patch来解决它。
答案 1 :(得分:7)
*注意:这是Vitaly Fadeev的答案
的延伸由于希望保持参照完整性,Django不允许跨越多个数据库的外键:https://docs.djangoproject.com/en/dev//topics/db/multi-db/#limitations-of-multiple-databases。尽管99%的应用程序都需要这样做,但在某些情况下,即使您无法确保参照完整性,也可以在ORM中创建这样的关联。
我创建了一个Gist,它使用here提出的解决方案Vitaly Fadeev包装为Django ForeignKey字段的子类。此解决方案不需要您修改Django Core文件,而是在您需要的情况下使用此字段类型。
# 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')
要点:https://gist.github.com/gcko/de1383080e9f8fb7d208
为方便访问,请在此处复制:
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
)
答案 2 :(得分:4)
作为替代方案(虽然有点hackish),你可以继承ForeignKey来检查右边db中的实例存在:
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})
然后勉强:
class NewsModel(models.Model): # in default database
…
link = models.CrossDbForeignKey(LinkModel)
请注意,它或多或少与维塔利提到的the patch相对应,但这种方式不需要修补django源代码。
答案 3 :(得分:1)
几天后我的脑袋里,我设法把我的外键放在同一个银行!
可以对FORM进行更改,以便在另一家银行寻找外键!
首先,在函数 _ init _
中直接(破解)我的表单添加一个FIELDS的RECHARGEapp.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()
从视图中调用表单
app.view.py
form = forms.FormNewHelpDesk(request.POST or None, using=banco)
现在,源代码DJANGO的变化
只有ForeignKey,ManyToManyField和OneToOneField类型的字段可以使用'using',所以添加了一个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
改变
# line 717
qs = model_class._default_manager.filter(**lookup_kwargs)
的
# line 717
qs = model_class._default_manager.using(getattr(self, 'db', None)).filter(**lookup_kwargs)
准备好了:D
答案 4 :(得分:-1)
使用多个数据库会让事情变得更加困难。
对于像这样工作的东西,据我所知,你必须使用链接中描述的数据库路由器。 我从来没有在它们之间使用带有外键的多数据库设置,但这就是我要开始的地方。