So umgehen Sie den Mangel an Unterstützung für Fremdschlüssel in verschiedenen Datenbanken in Django


77

Ich weiß, dass Django keine Fremdschlüssel in mehreren Datenbanken unterstützt (ursprünglich Django 1.3-Dokumente).

Aber ich suche nach einer Problemumgehung.

Was funktioniert nicht?

Ich habe jeweils zwei Modelle in einer separaten Datenbank.

routers.py:

class NewsRouter(object):
    def db_for_read(self, model, **hints):
        if model._meta.app_label == 'news_app':
            return 'news_db'
        return None

    def db_for_write(self, model, **hints):
        if model._meta.app_label == 'news_app':
            return 'news_db'
        return None

    def allow_relation(self, obj1, obj2, **hints):
        if obj1._meta.app_label == 'news_app' or obj2._meta.app_label == 'news_app':
            return True
        return None

    def allow_syncdb(self, db, model):
        if db == 'news_db':
            return model._meta.app_label == 'news_app'
        elif model._meta.app_label == 'news_app':
            return False
        return None

Modell 1 in obst_app / models.py:

from django.db import models

class Fruit(models.Model):
    name = models.CharField(max_length=20)

Modell 2 in news_app / models.py:

from django.db import models

class Article(models.Model):
    fruit = models.ForeignKey('fruit_app.Fruit')
    intro = models.TextField()

Der Versuch, dem Administrator einen "Artikel" hinzuzufügen, führt zu folgendem Fehler, da das FruitModell in der falschen Datenbank gesucht wird ( 'news_db'):

DatabaseError at /admin/news_app/article/add/

(1146, "Table 'fkad_news.fruit_app_fruit' doesn't exist")

Methode 1: Unterklasse IntegerField

Ich habe ein benutzerdefiniertes Feld erstellt, ForeignKeyAcrossDb, eine Unterklasse von IntegerField. Der Code befindet sich auf github unter: https://github.com/saltycrane/django-foreign-key-across-db-testproject/tree/integerfield_subclass

fields.py:

from django.db import models


class ForeignKeyAcrossDb(models.IntegerField):
    '''
    Exists because foreign keys do not work across databases
    '''
    def __init__(self, model_on_other_db, **kwargs):
        self.model_on_other_db = model_on_other_db
        super(ForeignKeyAcrossDb, self).__init__(**kwargs)

    def to_python(self, value):
        # TODO: this db lookup is duplicated in get_prep_lookup()
        if isinstance(value, self.model_on_other_db):
            return value
        else:
            return self.model_on_other_db._default_manager.get(pk=value)

    def get_prep_value(self, value):
        if isinstance(value, self.model_on_other_db):
            value = value.pk
        return super(ForeignKeyAcrossDb, self).get_prep_value(value)

    def get_prep_lookup(self, lookup_type, value):
        # TODO: this db lookup is duplicated in to_python()
        if not isinstance(value, self.model_on_other_db):
            value = self.model_on_other_db._default_manager.get(pk=value)

        return super(ForeignKeyAcrossDb, self).get_prep_lookup(lookup_type, value)

Und ich habe mein Artikelmodell geändert, um:

class Article(models.Model):
    fruit = ForeignKeyAcrossDb(Fruit)
    intro = models.TextField()

Das Problem ist, manchmal, wenn ich auf Article.fruit zugreife, ist es eine Ganzzahl, und manchmal ist es das Fruit-Objekt. Ich möchte, dass es immer ein Fruchtobjekt ist. Was muss ich tun, damit der Zugriff auf Article.fruit immer ein Fruit-Objekt zurückgibt?

Als Problemumgehung für meine Problemumgehung habe ich eine fruit_objEigenschaft hinzugefügt , die ich jedoch nach Möglichkeit entfernen möchte:

class Article(models.Model):
    fruit = ForeignKeyAcrossDb(Fruit)
    intro = models.TextField()

    # TODO: shouldn't need fruit_obj if ForeignKeyAcrossDb field worked properly
    @property
    def fruit_obj(self):
        if not hasattr(self, '_fruit_obj'):
            # TODO: why is it sometimes an int and sometimes a Fruit object?
            if isinstance(self.fruit, int) or isinstance(self.fruit, long):
                print 'self.fruit IS a number'
                self._fruit_obj = Fruit.objects.get(pk=self.fruit)
            else:
                print 'self.fruit IS NOT a number'
                self._fruit_obj = self.fruit
        return self._fruit_obj

    def fruit_name(self):
        return self.fruit_obj.name

Methode 2: Unterklasse ForeignKey-Feld

Als zweiten Versuch habe ich versucht, das ForeignKey-Feld zu unterordnen. Ich habe geändert ReverseSingleRelatedObjectDescriptor, um die Datenbank zu verwenden, die forced_usingim Modellmanager von angegeben ist Fruit. Ich habe auch die validate()Methode in der ForeignKeyUnterklasse entfernt. Diese Methode hatte nicht das gleiche Problem wie Methode 1. Code auf github unter: https://github.com/saltycrane/django-foreign-key-across-db-testproject/tree/foreignkey_subclass

fields.py:

from django.db import models
from django.db import router
from django.db.models.query import QuerySet


class ReverseSingleRelatedObjectDescriptor(object):
    # This class provides the functionality that makes the related-object
    # managers available as attributes on a model class, for fields that have
    # a single "remote" value, on the class that defines the related field.
    # In the example "choice.poll", the poll attribute is a
    # ReverseSingleRelatedObjectDescriptor instance.
    def __init__(self, field_with_rel):
        self.field = field_with_rel

    def __get__(self, instance, instance_type=None):
        if instance is None:
            return self

        cache_name = self.field.get_cache_name()
        try:
            return getattr(instance, cache_name)
        except AttributeError:
            val = getattr(instance, self.field.attname)
            if val is None:
                # If NULL is an allowed value, return it.
                if self.field.null:
                    return None
                raise self.field.rel.to.DoesNotExist
            other_field = self.field.rel.get_related_field()
            if other_field.rel:
                params = {'%s__pk' % self.field.rel.field_name: val}
            else:
                params = {'%s__exact' % self.field.rel.field_name: val}

            # If the related manager indicates that it should be used for
            # related fields, respect that.
            rel_mgr = self.field.rel.to._default_manager
            db = router.db_for_read(self.field.rel.to, instance=instance)
            if getattr(rel_mgr, 'forced_using', False):
                db = rel_mgr.forced_using
                rel_obj = rel_mgr.using(db).get(**params)
            elif getattr(rel_mgr, 'use_for_related_fields', False):
                rel_obj = rel_mgr.using(db).get(**params)
            else:
                rel_obj = QuerySet(self.field.rel.to).using(db).get(**params)
            setattr(instance, cache_name, rel_obj)
            return rel_obj

    def __set__(self, instance, value):
        raise NotImplementedError()

class ForeignKeyAcrossDb(models.ForeignKey):

    def contribute_to_class(self, cls, name):
        models.ForeignKey.contribute_to_class(self, cls, name)
        setattr(cls, self.name, ReverseSingleRelatedObjectDescriptor(self))
        if isinstance(self.rel.to, basestring):
            target = self.rel.to
        else:
            target = self.rel.to._meta.db_table
        cls._meta.duplicate_targets[self.column] = (target, "o2m")

    def validate(self, value, model_instance):
        pass

ruit_app / models.py:

from django.db import models


class FruitManager(models.Manager):
    forced_using = 'default'


class Fruit(models.Model):
    name = models.CharField(max_length=20)

    objects = FruitManager()

news_app / models.py:

from django.db import models

from foreign_key_across_db_testproject.fields import ForeignKeyAcrossDb
from foreign_key_across_db_testproject.fruit_app.models import Fruit


class Article(models.Model):
    fruit = ForeignKeyAcrossDb(Fruit)
    intro = models.TextField()

    def fruit_name(self):
        return self.fruit.name

Methode 2a: Fügen Sie einen Router fürruit_app hinzu

Diese Lösung verwendet einen zusätzlichen Router für fruit_app. Für diese Lösung sind keine Änderungen ForeignKeyerforderlich, die in Methode 2 erforderlich waren. Nachdem django.db.utils.ConnectionRouterwir uns das Standard-Routing-Verhalten von Django in angesehen hatten , stellten wir fest, dass der Hinweis, der für die Suche nach Fremdschlüsseln übergeben wurde, aktiviert wurde, obwohl wir erwartet hatten fruit_app, dass er sich 'default'standardmäßig in der Datenbank befindet die Datenbank. Wir haben einen zweiten Router hinzugefügt, um sicherzustellen, dass Modelle immer aus der Datenbank gelesen wurden . Eine Unterklasse wird nur verwendet, um die Methode zu "reparieren" . (Wenn Django Fremdschlüssel datenbankübergreifend unterstützen wollte, würde ich sagen, dass dies ein Django-Fehler ist.) Code befindet sich auf github unter: https://github.com/saltycrane/django-foreign-key-across-db-testprojectinstancedb_for_read'news_db'fruit_app'default'ForeignKeyForeignKey.validate()

routers.py:

class NewsRouter(object):
    def db_for_read(self, model, **hints):
        if model._meta.app_label == 'news_app':
            return 'news_db'
        return None

    def db_for_write(self, model, **hints):
        if model._meta.app_label == 'news_app':
            return 'news_db'
        return None

    def allow_relation(self, obj1, obj2, **hints):
        if obj1._meta.app_label == 'news_app' or obj2._meta.app_label == 'news_app':
            return True
        return None

    def allow_syncdb(self, db, model):
        if db == 'news_db':
            return model._meta.app_label == 'news_app'
        elif model._meta.app_label == 'news_app':
            return False
        return None


class FruitRouter(object):
    def db_for_read(self, model, **hints):
        if model._meta.app_label == 'fruit_app':
            return 'default'
        return None

    def db_for_write(self, model, **hints):
        if model._meta.app_label == 'fruit_app':
            return 'default'
        return None

    def allow_relation(self, obj1, obj2, **hints):
        if obj1._meta.app_label == 'fruit_app' or obj2._meta.app_label == 'fruit_app':
            return True
        return None

    def allow_syncdb(self, db, model):
        if db == 'default':
            return model._meta.app_label == 'fruit_app'
        elif model._meta.app_label == 'fruit_app':
            return False
        return None

ruit_app / models.py:

from django.db import models


class Fruit(models.Model):
    name = models.CharField(max_length=20)

news_app / models.py:

from django.db import models

from foreign_key_across_db_testproject.fields import ForeignKeyAcrossDb
from foreign_key_across_db_testproject.fruit_app.models import Fruit


class Article(models.Model):
    fruit = ForeignKeyAcrossDb(Fruit)
    intro = models.TextField()

    def fruit_name(self):
        return self.fruit.name

fields.py:

from django.core import exceptions
from django.db import models
from django.db import router


class ForeignKeyAcrossDb(models.ForeignKey):

    def validate(self, value, model_instance):
        if self.rel.parent_link:
            return
        models.Field.validate(self, value, model_instance)
        if value is None:
            return

        using = router.db_for_read(self.rel.to, instance=model_instance)  # is this more correct than Django's 1.2.5 version?
        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})

Zusätzliche Information

Aktualisieren

Wir haben die letzte Methode implementiert, nachdem wir unsere Router weiter optimiert haben. Die gesamte Implementierung war ziemlich schmerzhaft, was uns denken lässt, dass wir es falsch machen müssen. Auf der TODO-Liste stehen dazu Unit-Tests.


13
Ich hasse es, wenn es keine Antwort auf eine Frage gibt, aber verdammt noch mal, wer das beantworten kann
Bastardo

4
Sieht aus wie diese Frage ein Kopfgeld braucht
Adam Nelson

Warum nicht einfach beide Modelle in einer Datenbank speichern? Was ist der besondere Grund, warum Sie sich gegen diese (offensichtliche) Problemumgehung entschieden haben?
Sampablokuper

3
Die Tatsache, dass Ihre Tabellen auf mehrere Datenbanken verteilt sind, bedeutet, dass sie sich in separaten Domänen befinden. In diesem Fall liegt es immer an Ihrem Anwendungscode, die "Integritätsprüfung" durchzuführen. Dies geht in Ihre Geschäftsschicht. Der Grund, warum Ihre Lösung so "schmerzhaft" ist, ist, dass Sie versuchen, dies in Ihrer Datenschicht zu tun. Wenn sich die Tabellen nicht in separaten Domänen befinden, kann ich den Kommentar von sampablokuper wiederholen: Warum befinden sie sich nicht in derselben Datenbank?
Frans

Es würde mich interessieren, ob Sie noch 2a verwenden: Es funktioniert nicht gut. Ich musste ein paar leere Tabellen für fruitDinge in der newsDatenbank erstellen , damit es funktioniert. Wenn Sie im Admin-Tool eine DB-übergreifende Referenz erstellen , wird diese unterbrochen, es sei denn, Sie verwenden die Methodenumgehung, die Ihrer fruit_nameMethode in GitHub ähnelt . Auch southwird schrecklich mit Migrationen verwechselt.
Aidan Fitzpatrick

Antworten:


2

Sie können eine Ansicht in der Datenbank erstellen, in der sich die datenbankübergreifende Abfrage befindet, und dann das Modell für die Ansicht in einer separaten Datei definieren, damit die Synchronisierung weiterhin funktioniert.

Viel Spaß beim Programmieren. :) :)


1
Dies funktioniert nur, wenn beide Datenbanken vom gleichen Typ sind und auf demselben Datenbankserver ausgeführt werden ...
Cerin

2

Ich weiß, dass Djano-nosql Schlüssel und dergleichen unterstützt, obwohl etwas Magie von http://www.allbuttonspressed.com/projects/django-dbindexer . Vielleicht könnte etwas davon helfen.

Aus der Beschreibung:

"Sie können dem Datenbankindexer einfach mitteilen, welche Modelle und Felder diese Abfragen unterstützen sollen, und er sorgt dafür, dass die erforderlichen Indizes für Sie verwaltet werden."

-Kerry


2

Könnten ForeignKeyAcrossDbSie nicht einige Anpassungen an Ihrer Klasse vornehmen __init__? Überprüfen Sie, ob das entsprechende Feld vorhanden ist Integer, laden Sie es aus der Datenbank oder führen Sie andere erforderliche Schritte aus. Python __class__es kann zur Laufzeit ohne große Probleme geändert werden.


2

Nachdem ich mir einige Tage den Kopf gebrochen hatte, gelang es mir, meinen Fremdschlüssel auf die gleiche Bank zu bringen!

Kann über das FORMULAR geändert werden, um einen AUSLÄNDISCHEN SCHLÜSSEL in einer anderen Bank zu suchen!

Fügen Sie zunächst in Funktion ____init____ eine RECHARGE of FIELDS hinzu, beide direkt (knacken) meine Form

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

Aufrufen des Formulars aus der Ansicht

app.view.py

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

Nun die Änderung im Quellcode DJANGO

Nur Felder vom Typ ForeignKey, ManyToManyField und OneToOneField können das 'using' verwenden. Fügen Sie daher eine IF hinzu ...

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

ändern

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

zum

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

Bereit: D.


1

Ein Fremdschlüsselfeld impliziert, dass Sie - die Beziehung abfragen können, indem Sie z. B. Fruchtname beitreten - die referenzielle Integrität überprüfen - die referenzielle Integrität beim Löschen sicherstellen - die Funktion zum Nachschlagen der Raw-ID des Administrators - (einige weitere ...)

Der erste Anwendungsfall wäre immer problematisch. Wahrscheinlich gibt es einige andere Fremdschlüssel-Sonderfälle in der Codebasis, die ebenfalls nicht funktionieren würden.

Ich betreibe eine ziemlich große Django-Site und wir verwenden derzeit ein einfaches Ganzzahlfeld. Im Moment würde ich denken, dass es am einfachsten wäre, das Integer-Feld zu unterklassifizieren und die ID zur Objektkonvertierung hinzuzufügen (in 1.2, in der einige Django-Bits gepatcht werden mussten, hoffe, dass sich dies inzwischen verbessert hat). Lassen Sie Sie wissen, welche Lösung wir finden.


1

Es trat ein ähnliches Problem auf, (meistens) statische Daten über mehrere (5) Datenbanken hinweg referenzieren zu müssen. Der ReversedSingleRelatedObjectDescriptor wurde geringfügig aktualisiert, um das zugehörige Modell festlegen zu können. Die umgekehrte Beziehung atm wird nicht implementiert.

class ReverseSingleRelatedObjectDescriptor(object):
"""
This class provides the functionality that makes the related-object managers available as attributes on a model
class, for fields that have a single "remote" value, on the class that defines the related field. Used with
LinkedField.
"""
def __init__(self, field_with_rel):
    self.field = field_with_rel
    self.cache_name = self.field.get_cache_name()

def __get__(self, instance, instance_type=None):
    if instance is None:
        return self

    try:
        return getattr(instance, self.cache_name)
    except AttributeError:
        val = getattr(instance, self.field.attname)
        if val is None:
            # If NULL is an allowed value, return it
            if self.field.null:
                return None
            raise self.field.rel.to.DoesNotExist
        other_field = self.field.rel.get_related_field()
        if other_field.rel:
            params = {'%s__pk' % self.field.rel.field_name: val}
        else:
            params = {'%s__exact' % self.field.rel.field_name: val}

        # If the related manager indicates that it should be used for related fields, respect that.
        rel_mgr = self.field.rel.to._default_manager
        db = router.db_for_read(self.field.rel.to, instance=instance)
        if getattr(rel_mgr, 'forced_using', False):
            db = rel_mgr.forced_using
            rel_obj = rel_mgr.using(db).get(**params)
        elif getattr(rel_mgr, 'use_for_related_fields', False):
            rel_obj = rel_mgr.using(db).get(**params)
        else:
            rel_obj = QuerySet(self.field.rel.to).using(db).get(**params)
        setattr(instance, self.cache_name, rel_obj)
        return rel_obj

def __set__(self, instance, value):
    if instance is None:
        raise AttributeError("%s must be accessed via instance" % self.field.name)

    # If null=True, we can assign null here, but otherwise the value needs to be an instance of the related class.
    if value is None and self.field.null is False:
        raise ValueError('Cannot assign None: "%s.%s" does not allow null values.' %
                         (instance._meta.object_name, self.field.names))
    elif value is not None and not isinstance(value, self.field.rel.to):
        raise ValueError('Cannot assign "%r": "%s.%s" must be a "%s" instance.' %
                         (value, instance._meta.object_name, self.field.name, self.field.rel.to._meta.object_name))
    elif value is not None:
        # Only check the instance state db, LinkedField implies that the value is on a different database
        if instance._state.db is None:
            instance._state.db = router.db_for_write(instance.__class__, instance=value)

    # Is not used by OneToOneField, no extra measures to take here

    # Set the value of the related field
    try:
        val = getattr(value, self.field.rel.get_related_field().attname)
    except AttributeError:
        val = None
    setattr(instance, self.field.attname, val)

    # Since we already know what the related object is, seed the related object caches now, too. This avoids another
    # db hit if you get the object you just set
    setattr(instance, self.cache_name, value)
    if value is not None and not self.field.rel.multiple:
        setattr(value, self.field.related.get_cache_name(), instance)

und

class LinkedField(models.ForeignKey):
"""
Field class used to link models across databases. Does not ensure referrential integraty like ForeignKey
"""
def _description(self):
    return "Linked Field (type determined by related field)"

def contribute_to_class(self, cls, name):
    models.ForeignKey.contribute_to_class(self, cls, name)
    setattr(cls, self.name, ReverseSingleRelatedObjectDescriptor(self))
    if isinstance(self.rel.to, basestring):
        target = self.rel.to
    else:
        target = self.rel.to._meta.db_table
    cls._meta.duplicate_targets[self.column] = (target, "o2m")

def validate(self, value, model_instance):
    pass

1

Diese Lösung wurde ursprünglich für eine verwaltete Datenbank mit Migrationen und eine oder mehrere Legacy-Datenbanken mit Meta-Modellen geschrieben, managed=Falsedie auf Datenbankebene mit derselben Datenbank verbunden sind. Wenn eine db_tableOption , um einen Datenbanknamen und Tabellennamen enthält zitiert korrekt durch ‚`‘ (MySQL) oder durch ‚"‘ (andere db), zum Beispiel db_table = '"DB2"."table_b"', dann ist es nicht mehr von Django zitiert. Abfragen werden korrekt von Django ORM zusammengestellt, auch mit 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

Abfragesatz:

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

Das wird von allen DB-Backends in Django unterstützt.

(Es scheint, dass ich eine Prämie für eine doppelte neue Frage gestartet habe , bei der meine Antwort fortgesetzt wird.)


0

Inspiriert von @Frans 'Kommentar. Meine Problemumgehung besteht darin, dies in der Geschäftsschicht zu tun. Im Beispiel dieser Frage. Ich würde Früchte auf ein Set IntegerFieldauf Article, als „nicht Integritätsprüfung in Datenschicht zu tun“.

class Fruit(models.Model):
    name = models.CharField()

class Article(models.Model):
    fruit = models.IntegerField()
    intro = models.TextField()

Beachten Sie dann die Referenzbeziehung im Anwendungscode (Business Layer). Nehmen wir zum Beispiel den Django-Administrator. Um Obst als Auswahl auf der Seite zum Hinzufügen von Artikeln anzuzeigen, füllen Sie manuell eine Liste mit Auswahlmöglichkeiten für Obst aus.

# admin.py in App article
class ArticleAdmin(admin.ModelAdmin):
    class ArticleForm(forms.ModelForm):
        fields = ['fruit', 'intro']

        # populate choices for fruit
        choices = [(obj.id, obj.name) for obj in Fruit.objects.all()]
        widgets = {
            'fruit': forms.Select(choices=choices)}

    form = ArticleForm
    list_diaplay = ['fruit', 'intro']

Natürlich müssen Sie sich möglicherweise um die Validierung der Formularfelder (Integritätsprüfung) kümmern.


0

Ich habe eine neue Lösung für Django v1.10. Es gibt zwei Teile. Es funktioniert mit django.admin und django.rest-framework.

  1. Erben Sie die ForeignKeyKlasse und erstellen ForeignKeyAcrossDbund überschreiben Sie die validate()Funktion basierend auf diesem Ticket und diesem Beitrag .

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. Verwenden Sie db_constraint=Falsein der Felddeklaration beispielsweise

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


TypeError: __init__() got an unexpected keyword argument 'db_constraints'
Hossein Jabbari

1
@HosseinJabbari Ich entschuldige mich. das ist ein Tippfehler, benutze db_constraint. Entfernen Sie die nachgestellten 's'.
Diansheng

Es wird als gute Art angesehen, Ihre Quellen zu benennen, wenn Sie Code kopieren: stackoverflow.com/a/32078727/7933618
masterfloda

@ masterfloda, ist dein Link nicht derselbe Link, den ich bereits in meiner Antwort hinzugefügt habe?
Diansheng
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.