Как использовать модели django с внешними ключами в разных БД? - PullRequest
16 голосов
/ 26 июля 2011

У меня есть 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"

Почему нельзя использовать внешний ключ и модель для другой базы данных?

Ответы [ 5 ]

11 голосов
/ 13 марта 2012

Ограничения между базами данных

В настоящее время Django не поддерживает внешние ключи и отношения «многие ко многим», охватывающие несколько баз данных.Если вы использовали маршрутизатор для разделения моделей на разные базы данных, любые внешние ключи и отношения «многие ко многим», определенные этими моделями, должны быть внутренними для одной базы данных.

Django - ограничения-ограничениянесколько баз данных

Проблема

Та же проблема.Ошибка в классе ForeignKey ().

В методе validate ().

См. Тикет

Ошибка существует в v1.2, v1.3, v1.4rc1

Решение

Попробуйте этот патч , чтобы решить его.

7 голосов
/ 18 августа 2015

Проблема

* Примечание: это продолжение ответа Виталия Фадеева

Из-за желания сохранить ссылочную целостность Django не допускает использование внешних ключей, охватывающих несколько баз данных: https://docs.djangoproject.com/en/dev//topics/db/multi-db/#limitations-of-multiple-databases. Хотя это желательно в 99% всех приложений, в некоторых случаях полезно иметь возможность создать такую ​​связь в ORM, даже если вы не можете обеспечить ссылочную целостность.

Решение

Я создал Gist, в котором используется решение, предложенное здесь Виталием Фадеевым , заключенное в подкласс поля 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
            )
4 голосов
/ 14 февраля 2013

В качестве альтернативы (хотя и немного хакерской), вы можете создать подкласс ForeignKey для проверки существования экземпляра внутри правой базы данных:

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)

Обратите внимание, что он более или менее соответствует патчу , о котором упоминает Виталий, но для этого не требуется исправлять исходный код django.

1 голос
/ 07 марта 2013

После того, как я несколько дней сломал голову, мне удалось получить свой внешний ключ в том же банке!

Можно внести изменения в ФОРМУ, чтобы искать ИНОСТРАННЫЙ КЛЮЧ в другом банке!

Во-первых, добавьте RECHARGE of FIELDS, оба напрямую (взломайте) мою форму, в функции _ 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()

вызов формы из представления

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

0 голосов
/ 26 июля 2011

Использование нескольких баз данных делает вещи более «сложными».

Чтение: MultipleDB Django

Для того, чтобы подобные вещи работали, вы должны использовать Маршрутизаторы баз данных, как описано по ссылке, насколько я знаю. Я никогда не использовал настройку нескольких БД с внешними ключами между ними, но именно с этого я и начал.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...