【问题标题】:How to use django models with foreign keys in different DBs?如何在不同的数据库中使用带有外键的 django 模型?
【发布时间】:2011-10-13 10:04:44
【问题描述】:

我有 2 个模型用于 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"

为什么我不能为不同的数据库使用外键和模型?

【问题讨论】:

    标签: mysql django django-models foreign-keys


    【解决方案1】:

    跨数据库限制

    Django 目前不支持跨多个数据库的外键或多对多关系。如果您使用路由器将模型分区到不同的数据库,则这些模型定义的任何外键和多对多关系都必须位于单个数据库的内部。

    Django - limitations-of-multiple-databases

    麻烦

    同样的麻烦。 ForeignKey() 类中的错误。

    在 validate() 方法中。

    See ticket

    Bug 存在于 v1.2、v1.3、v1.4rc1

    解决方案

    尝试this patch解决。

    【讨论】:

    • 好,但他们不会像我理解的那样更改这部分代码
    • @Vitaly Fadeev,感谢您的这篇文章!您作为解决方案发布的链接建议进行单行更改。我只是想知道在实施此更改后如何指定每个模型所属的数据库?是self.rel.to._meta.db_table吗?
    • @vitaly 我围绕您的解决方案创建了一个要点,将这些更改合并为 ForeignKey 的子类:gist.github.com/gcko/de1383080e9f8fb7d208
    • 这适用于 v1.7。当我升级到 v1.10 时,./manage.py migrate 开始出现问题。 django 正在尝试运行 sql 向数据库添加外键,这是不允许的。
    【解决方案2】:

    问题

    *注意:这是 Vitaly Fadeev 回答的延伸

    由于希望保持参照完整性,Django 不允许跨多个数据库的外键:https://docs.djangoproject.com/en/dev//topics/db/multi-db/#limitations-of-multiple-databases。尽管 99% 的应用程序都希望这样做,但在某些情况下,即使您无法确保参照完整性,也能够在 ORM 中创建这样的关联是有帮助的。

    解决方案

    我创建了一个 Gist,它使用 Vitaly Fadeev 提出的解决方案 here 包装为 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
                )
    

    【讨论】:

    • 你能提供更多关于你的要点的信息吗?
    • @DustinHolden 您还想知道什么?正如我所提到的,这是基于Vitaly Fadeev's 的答案,基本上跳过了 Django 的引用完整性检查。这意味着级联删除不会捕获这些外键所做的引用,如果不处理可能会导致异常。在我们的例子中,与我们相关的外键永远不会被删除(实际上会在调用链中比这里更早地导致问题),所以这不是问题。这有帮助吗?
    • 为什么这个答案在这里?检查并为我工作
    • 不确定这是针对哪个版本的 Django 编写的,但在 3.x 中,您将收到 'SpanningForeignKey' object has no attribute 'rel' 错误。尝试浏览以前的版本,看看我们可能需要进行哪些调整才能使其正常工作。
    • @MichaelThompson,我对你的调查结果很感兴趣。我刚遇到同样的问题。
    【解决方案3】:

    作为替代方案(虽然有点骇人听闻),您可以将 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)
    

    请注意,它或多或少对应于 Vitaly 提到的 the patch,但这种方式不需要修补 django 源代码。

    【讨论】:

    • 这是否也会填充反向关系,以便您可以从目标表加双下划线返回到具有外键的表?
    【解决方案4】:

    几天后,我设法让我的外键在同一家银行!

    可以对FORM进行更改以在不同的银行寻找FOREIGN KEY!

    首先,在函数 _init_

    中添加一个 RECHARGE 的 FIELDS,直接(破解)我的表单

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

    从视图中调用表单

    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)
    

    更改关注文件

    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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-01-17
      • 2017-06-03
      • 2023-03-29
      • 2014-08-22
      • 2012-03-31
      • 2016-02-20
      • 1970-01-01
      相关资源
      最近更新 更多