Wie beschränke ich Fremdschlüssel Entscheidungen zu verwandten Objekten nur in django

StackOverflow https://stackoverflow.com/questions/232435

  •  04-07-2019
  •  | 
  •  

Frage

Ich habe einen Zweiweg ausländische Beziehung ähnlich den folgenden

class Parent(models.Model):
  name = models.CharField(max_length=255)
  favoritechild = models.ForeignKey("Child", blank=True, null=True)

class Child(models.Model):
  name = models.CharField(max_length=255)
  myparent = models.ForeignKey(Parent)

Wie beschränke ich die Entscheidungen für Parent.favoritechild nur Kinder, deren Eltern selbst? Ich habe versucht,

class Parent(models.Model):
  name = models.CharField(max_length=255)
  favoritechild = models.ForeignKey("Child", blank=True, null=True, limit_choices_to = {"myparent": "self"})

aber das bewirkt, dass die Administrationsoberfläche keine Kinder Liste.

War es hilfreich?

Lösung

Ich kam gerade über ForeignKey.limit_choices_to in den Django docs. Noch nicht sicher, wie das funktioniert, aber es könnte genau das Richtige hier sein.

Update: ForeignKey.limit_choices_to erlaubt zu spezifizieren entweder eine Konstante, eine aufrufbare oder ein Q-Objekt die zulässigen Auswahlmöglichkeiten für den Schlüssel zu beschränken. Eine Konstante offensichtlich nicht von Nutzen ist hier, da es nichts über die beteiligten Objekte kennt.

eine aufrufbare (Funktion oder Klassenmethode oder jedes aufrufbare Objekt) Mit scheint vielversprechend. Doch das Problem, wie die notwendigen Informationen aus dem Httprequest Objekt zuzugreifen bleibt. lokalen Speicher fädeln eine Lösung sein kann.

2. Update: Hier ist was für mich gearbeitet hat:

habe ich eine Middleware wie in dem Link oben beschrieben. Es extrahiert ein oder mehrere Argumente aus der GET Teil der Anforderung, wie zum Beispiel „product = 1“ und speichert diese Informationen in den Thread Einheimischen.

Als nächstes gibt es eine Klassenmethode in dem Modell, das den Faden lokalen Variable liest und gibt eine Liste von Ids die Wahl eines Fremdschlüsselfeldes zu begrenzen.

@classmethod
def _product_list(cls):
    """
    return a list containing the one product_id contained in the request URL,
    or a query containing all valid product_ids if not id present in URL

    used to limit the choice of foreign key object to those related to the current product
    """
    id = threadlocals.get_current_product()
    if id is not None:
        return [id]
    else:
        return Product.objects.all().values('pk').query

Es ist wichtig, eine Abfrage, die alle möglichen IDs zurück, wenn keine wurde so gewählt, dass die normalen Admin-Seiten ok arbeiten.

Das Fremdschlüsselfeld wird dann erklärt, wie:

product = models.ForeignKey(
    Product,
    limit_choices_to={
        id__in=BaseModel._product_list,
    },
)

Der Haken ist, dass Sie die Informationen zur Verfügung stellen müssen die Entscheidungen über den Antrag zu beschränken. Ich sehe keinen Weg „Selbst“ zu gelangen hier.

Andere Tipps

Der ‚richtige‘ Weg, es zu tun, ist ein benutzerdefiniertes Formular zu verwenden. Von dort können Sie zugreifen self.instance, die das aktuelle Objekt. Beispiel -

from django import forms
from django.contrib import admin 
from models import *

class SupplierAdminForm(forms.ModelForm):
    class Meta:
        model = Supplier
        fields = "__all__" # for Django 1.8+


    def __init__(self, *args, **kwargs):
        super(SupplierAdminForm, self).__init__(*args, **kwargs)
        if self.instance:
            self.fields['cat'].queryset = Cat.objects.filter(supplier=self.instance)

class SupplierAdmin(admin.ModelAdmin):
    form = SupplierAdminForm

Der neue "richtige" Weg, dies zu tun, zumindest seit Django 1.1 ist durch Überschreiben der AdminModel.formfield_for_foreignkey (self, db_field, Anfrage, ** kwargs).

Siehe http: // docs.djangoproject.com/en/1.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_for_foreignkey

Für diejenigen, die nicht wollen, den Link unten ist ein Beispiel der Funktion folgen, die für die oben genannten Fragen Modelle nahe ist.

class MyModelAdmin(admin.ModelAdmin):
    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if db_field.name == "favoritechild":
            kwargs["queryset"] = Child.objects.filter(myparent=request.object_id)
        return super(MyModelAdmin, self).formfield_for_manytomany(db_field, request, **kwargs)

Ich bin nur nicht sicher, wie das aktuelle Objekt zu erhalten, die bearbeitet wird. Ich erwarte, dass es eigentlich irgendwo auf der Selbst ist, aber ich bin mir nicht sicher.

Das ist nicht, wie django funktioniert. Sie würden die Beziehung geht nur in eine Richtung erstellen.

class Parent(models.Model):
  name = models.CharField(max_length=255)

class Child(models.Model):
  name = models.CharField(max_length=255)
  myparent = models.ForeignKey(Parent)

Und wenn man die Kinder aus der Mutter zuzugreifen versuchen Sie tun würden, parent_object.child_set.all(). Wenn Sie eine related_name im myparent Feld gesetzt ist, dann ist das, was Sie es als verweisen würde. Ex: related_name='children', dann würden Sie tun parent_object.children.all()

Lesen Sie die docs http://docs.djangoproject.com/ en / dev / Themen / db / models / # many-to-one-Beziehungen mehr.

Wenn Sie nur die Einschränkungen in dem Django Admin-Interface benötigen, könnte dies funktionieren. Ich basiert es auf diese Antwort aus einem anderen Forum - obwohl es für ManyToMany Beziehungen ist, sollten Sie in der Lage sein formfield_for_foreignkey zu ersetzen, damit es funktioniert. In admin.py:

class ParentAdmin(admin.ModelAdmin):
    def get_form(self, request, obj=None, **kwargs):
        self.instance = obj
        return super(ParentAdmin, self).get_form(request, obj=obj, **kwargs)

    def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
        if db_field.name == 'favoritechild' and self.instance:       
            kwargs['queryset'] = Child.objects.filter(myparent=self.instance.pk)
        return super(ChildAdmin, self).formfield_for_foreignkey(db_field, request=request, **kwargs)

Möchten Sie die Möglichkeiten zur Verfügung, in dem Admin-Interface einschränken mögen beim Erstellen / Bearbeiten einer Modellinstanz?

Eine Möglichkeit, dies zu tun, ist die Validierung des Modells. Auf diese Weise können Sie einen Fehler in dem Admin-Interface erhöhen, wenn das ausländische Feld nicht die richtige Wahl ist.

Natürlich Erics Antwort ist richtig. Sie brauchen nur einen Fremdschlüssel wirklich, vom Kind zum Elternteil hier

@Ber: Ich habe Validierung zum Modell ähnlich wie diese hinzugefügt

class Parent(models.Model):
  name = models.CharField(max_length=255)
  favoritechild = models.ForeignKey("Child", blank=True, null=True)
  def save(self, force_insert=False, force_update=False):
    if self.favoritechild is not None and self.favoritechild.myparent.id != self.id:
      raise Exception("You must select one of your own children as your favorite")
    super(Parent, self).save(force_insert, force_update)

das funktioniert genau, wie ich will, aber es wäre wirklich schön, wenn diese Validierung Auswahl in der Dropdown-Liste im Admin-Interface, anstatt die Validierung nach der Wahl einschränken könnte.

Ich versuche, etwas ähnliches zu tun. Es scheint wie jeder sagen: ‚Sie nur einen Fremdschlüssel eine Möglichkeit haben sollte,‘ vielleicht falsch verstanden hat, was Sie tun versuchen.

Es ist eine Schande, die limit_choices_to = { „myparent“: „Selbst“} Sie tun wollten nicht funktioniert ... das wäre sauber und einfach gewesen. Leider ist die ‚Selbst‘ nicht ausgewertet bekommen und geht als einfache Schnur durch.

Ich dachte, vielleicht könnte ich tun:

class MyModel(models.Model):
    def _get_self_pk(self):
        return self.pk
    favourite = models.ForeignKey(limit_choices_to={'myparent__pk':_get_self_pk})

Aber leider, dass ein Fehler gibt, weil die Funktion nicht selbst arg bekommt übergeben: (

Es scheint, wie der einzige Weg, um die Logik in allen Formen zu setzen ist, die dieses Modell verwenden (dh übergeben Sie einen queryset um Ihre Auswahl für die Formularfeld). Welches ist leicht getan, aber es wäre mehr DRY sein, dies auf Modellebene zu haben. Sie das Verfahren des Modells speichern zwingende scheint eine gute Möglichkeit bekommen durch ungültige Entscheidungen zu verhindern.

Aktualisieren
Siehe meine Antwort später für eine andere Art und Weise https://stackoverflow.com/a/3753916/202168

Ein alternativer Ansatz wäre nicht zu haben, ‚favouritechild‘ fk als ein Feld auf dem übergeordnete Modell.

Stattdessen könnten Sie ein is_favourite boolean Feld auf das Kind haben.

Dies kann helfen: https://github.com/anentropic/django-exclusivebooleanfield

So können Sie das ganze Problem der Gewährleistung Kinder umgehen würde konnte nur der Liebling der Eltern gemacht werden, sie gehören.

Der Code Ansicht wäre etwas anders, aber die Filterlogik wäre einfach.

Im Admin könnte man sogar einen Inline für Kindermodelle hat, die die is_favourite Checkbox ausgesetzt (wenn Sie nur ein paar Kinder pro Elternteil), ansonsten der Admin von der Seite des Kindes getan werden müßte.

from django.contrib import admin
from sopin.menus.models import Restaurant, DishType

class ObjInline(admin.TabularInline):
    def __init__(self, parent_model, admin_site, obj=None):
        self.obj = obj
        super(ObjInline, self).__init__(parent_model, admin_site)

class ObjAdmin(admin.ModelAdmin):

    def get_inline_instances(self, request, obj=None):
        inline_instances = []
        for inline_class in self.inlines:
            inline = inline_class(self.model, self.admin_site, obj)
            if request:
                if not (inline.has_add_permission(request) or
                        inline.has_change_permission(request, obj) or
                        inline.has_delete_permission(request, obj)):
                    continue
                if not inline.has_add_permission(request):
                    inline.max_num = 0
            inline_instances.append(inline)

        return inline_instances



class DishTypeInline(ObjInline):
    model = DishType

    def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
        field = super(DishTypeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
        if db_field.name == 'dishtype':
            if self.obj is not None:
                field.queryset = field.queryset.filter(restaurant__exact = self.obj)  
            else:
                field.queryset = field.queryset.none()

        return field

class RestaurantAdmin(ObjAdmin):
    inlines = [
        DishTypeInline
    ]
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top