How to work around lack of support for foreign keys across databases in Django

后端 未结 9 1357
生来不讨喜
生来不讨喜 2020-12-07 09:59

I know Django does not support foreign keys across multiple databases (originally Django 1.3 docs)

But I\'m looking for a workaround.

What doesn\'t work

相关标签:
9条回答
  • 2020-12-07 10:01

    A foreign key field implies that you can - query on the relationship by joining ie fruit__name - check referential integrity - ensure referential integrity upon deletes - admin raw id lookup functionality - (some more...)

    The first use case would always be problematic. Probably there are some other foreign key special cases in the codebase which also wouldn't work.

    I run a rather large django site and we are currently using a plain integerfield. For now i would think subclassing the integerfield and adding the id to object conversion would be easiest (in 1.2 that required patching some bits of django, hope that improved by now) Will let you know what solution we find.

    0 讨论(0)
  • 2020-12-07 10:01

    This solution is originally written for one managed database with migrations and one or more legacy databases with models Meta managed=False connected at database level to the same database. If a db_table option contains a database name plus table name quoted correctly by ' ` ' (MySQL) or by ' " ' (other db), e.g. db_table = '"DB2"."table_b"', then it is not quoted any more by Django. Queries are compiled by Django ORM correctly, even with JOINs:

    class TableB(models.Model):
        ....
        class Meta:    
            db_table = '`DB2`.`table_b`'    # for MySQL
            # db_table = '"DB2"."table_b"'  # for all other backends
            managed = False
    

    Query set:

    >>> qs = TableB.objects.all()
    >>> str(qs.query)
    'SELECT "DB2"."table_b"."id" FROM DB2"."table_b"'
    

    That is supported by all db backends in Django.

    (It seems that I started a bounty on a duplicate new question where my answer continues.)

    0 讨论(0)
  • 2020-12-07 10:02

    I know that Djano-nosql has support for keys and such though some magic from http://www.allbuttonspressed.com/projects/django-dbindexer. Maybe some of that could help.

    From the description:

    "you can just tell the dbindexer which models and fields should support these queries and it'll take care of maintaining the required indexes for you."

    -Kerry

    0 讨论(0)
  • 2020-12-07 10:02

    I have a new solution for django v1.10. There are two parts. It works with django.admin and django.rest-framework.

    1. Inherit the ForeignKey class and create ForeignKeyAcrossDb, and override the validate() function, based on this ticket and this post.

    class ForeignKeyAcrossDb(models.ForeignKey):
            def validate(self, value, model_instance):
                if self.remote_field.parent_link:
                    return
                super(models.ForeignKey, self).validate(value, model_instance)
                if value is None:
                    return
                using = router.db_for_read(self.remote_field.model, instance=model_instance)
                qs = self.remote_field.model._default_manager.using(using).filter(
                    **{self.remote_field.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.remote_field.model._meta.verbose_name, 'pk': value,
                            'field': self.remote_field.field_name, 'value': value,
                        },  # 'pk' is included for backwards compatibility
                    )
    
    1. In field declaration, use db_constraint=False, for example,

    album=ForeignKeyAcrossDb(Singer, db_constraint=False, on_delete=models.DO_NOTHING)

    0 讨论(0)
  • 2020-12-07 10:04

    You could make a view in the database that has the cross database query in it, then define the model for the view in a separate file to keep syncdb working.

    Happy programming. :)

    0 讨论(0)
  • 2020-12-07 10:16

    After breaking my head some days, I managed to get my Foreign Key ON THE SAME BANK!

    Can be made ​​a change over the FORM to seek a FOREIGN KEY in a different bank!

    First, add a RECHARGE of FIELDS, both directly (crack) my form, in function ____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 Keys
            self.Meta.model.db = db
    
            super(FormNewHelpDesk, self).__init__(*args,**kwargs)
    
            #-------------------------------------
            #   recreates the fields manually
            from copy import deepcopy
            self.fields.update(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()
    

    calling the Form from the View

    app.view.py

    form = forms.FormNewHelpDesk(request.POST or None, using=banco)
    

    Now, the change in the source Code DJANGO

    Only fields of type ForeignKey, ManyToManyField and OneToOneField can use the 'using', so added an 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

    alter

    # line 717
    qs = model_class._default_manager.filter(**lookup_kwargs)
    

    for

    # line 717
    qs = model_class._default_manager.using(getattr(self, 'db', None)).filter(**lookup_kwargs)
    

    Ready :D

    0 讨论(0)
提交回复
热议问题