2009-12-01 8 views
61

Die Logik des Modells ist:Grenze Fremdschlüssel Entscheidungen in ausgewählten in einer Inline-Form in Admin

  • A Building hat viele Rooms
  • A Room innerhalb eines anderen Room (ein Schrank sein kann, zum Beispiel --ForeignKey auf ‚selbst‘)
  • A Room kann nur im gleichen Gebäude innerhalb eines anderen Room sein (dies ist der schwierige Teil)
Hier 10

ist der Code, ich habe:

#spaces/models.py 
from django.db import models  

class Building(models.Model): 
    name=models.CharField(max_length=32) 
    def __unicode__(self): 
     return self.name 

class Room(models.Model): 
    number=models.CharField(max_length=8) 
    building=models.ForeignKey(Building) 
    inside_room=models.ForeignKey('self',blank=True,null=True) 
    def __unicode__(self): 
     return self.number 

und:

#spaces/admin.py 
from ex.spaces.models import Building, Room 
from django.contrib import admin 

class RoomAdmin(admin.ModelAdmin): 
    pass 

class RoomInline(admin.TabularInline): 
    model = Room 
    extra = 2 

class BuildingAdmin(admin.ModelAdmin): 
    inlines=[RoomInline] 

admin.site.register(Building, BuildingAdmin) 
admin.site.register(Room) 

Die Inline nur Zimmer im aktuellen Gebäude angezeigt wird (das ist, was ich will). Das Problem besteht jedoch darin, dass für das Drop-Down-Menü inside_room alle Räume in der Rooms-Tabelle (einschließlich der in anderen Gebäuden) angezeigt werden.

Im inline von rooms, ich brauche die inside_room Entscheidungen nur rooms zu begrenzen, die in dem aktuellen building (das Gebäude Datensatz derzeit von der Haupt BuildingAdmin Form verändert zu werden) sind.

Ich kann einen Weg weder mit einem limit_choices_to in dem Modell, noch kann ich herausfinden, wie genau die Inline-Formularsatz des Admins richtig überschreiben (ich fühle mich wie ich irgendwie ein benutzerdefiniertes Inline-Formular erstellen sollte) Übergeben Sie die building_id des Hauptformulars an die benutzerdefinierte Inline-Datei, und begrenzen Sie dann das Abfrage-Set für die Auswahlmöglichkeiten des Felds auf dieser Grundlage - aber ich kann einfach nicht sagen, wie es geht.

Vielleicht ist das zu komplex für die Admin-Seite, aber es scheint wie etwas, das allgemein nützlich wäre ...

Antwort

2

Wenn Daniel, nachdem Ihre Frage bearbeiten, nicht beantwortet hat - ich glaube nicht, dass ich wird viel helfen ... :-)

Ich werde vorschlagen, dass Sie versuchen, passen in den Django-Admin einige Logik, die besser als Ihre eigene Gruppe von Ansichten, Formen und Vorlagen umgesetzt werden würde.

Ich glaube nicht, dass es möglich ist, diese Art der Filterung auf InlineModelAdmin anzuwenden.

1

Ich muss zugeben, ich habe nicht genau das verfolgt, was Sie versuchen zu tun, aber ich denke, es ist komplex genug, dass Sie in Betracht ziehen sollten, Ihre Website nicht vom Admin zu trennen.

Ich habe einmal eine Site erstellt, die mit der einfachen Admin-Oberfläche begann, aber schließlich so angepasst wurde, dass es sehr schwierig wurde, mit den Einschränkungen des Administrators zu arbeiten. Ich wäre besser dran gewesen, wenn ich gerade von vorne angefangen hätte - mehr Arbeit am Anfang, aber viel mehr Flexibilität und weniger Schmerzen am Ende. Meine Faustregel wäre, wenn das, was du versuchst zu tun, nicht dokumentiert ist (d. H. Das Überschreiben von Admin-Methoden, das Hineinschauen in den Admin-Quellcode usw.), dann ist es wahrscheinlich besser, den Admin nicht zu benutzen. Nur ich zwei Cent. :)

4

This question and answer is very similar, and works for a regular admin form

Innerhalb eines Inline - und das ist, wo es auseinander fällt ...Ich kann einfach nicht auf die Daten des Hauptformulars zugreifen, um den Fremdschlüsselwert zu erhalten, den ich in meinem Limit benötige (oder zu einem der Inline-Datensätze, um den Wert zu erfassen).

Hier ist meine admin.py. Ich denke, ich suche nach der Magie, um das ???? mit - wenn ich in einem fest codierten Wert Stecker (sagen wir, 1), es funktioniert gut und begrenzt richtig, die die zur Verfügung stehenden Möglichkeiten in der Inline ...

#spaces/admin.py 
from demo.spaces.models import Building, Room 
from django.contrib import admin 
from django.forms import ModelForm 


class RoomInlineForm(ModelForm): 
    def __init__(self, *args, **kwargs): 
    super(RoomInlineForm, self).__init__(*args, **kwargs) 
    self.fields['inside_room'].queryset = Room.objects.filter(
           building__exact=????)      # <------ 

class RoomInline(admin.TabularInline): 
    form = RoomInlineForm 
    model=Room 

class BuildingAdmin(admin.ModelAdmin): 
    inlines=[RoomInline] 

admin.site.register(Building, BuildingAdmin) 
admin.site.register(Room) 
4

fand ich eine fairly elegant solution, die gut für die Inline-Formen arbeitet.

auf mein Modell angewandt, wo ich das inside_room Feld bin Filterung nur Zimmer zurück, die im selben Gebäude sind:

#spaces/admin.py 
class RoomInlineForm(ModelForm): 
    def __init__(self, *args, **kwargs): 
    super(RoomInlineForm, self).__init__(*args, **kwargs) #On init... 
    if 'instance' in kwargs: 
    building = kwargs['instance'].building 
    else: 
    building_id = tuple(i[0] for i in self.fields['building'].widget.choices)[1] 
    building = Building.objects.get(id=building_id) 
    self.fields['inside_room'].queryset = Room.objects.filter(building__exact=building) 

Grundsätzlich, wenn ein ‚instance‘ Schlüsselwort in das Formular übergeben wird, ist es ein vorhandener Datensatz wird inline angezeigt, und so kann ich einfach das Gebäude aus der Instanz holen. Wenn es sich nicht um eine Instanz handelt, handelt es sich um eine der leeren "Extra" -Zeilen im Inline-Code. Sie durchläuft also die versteckten Formularfelder von Inline, die die implizite Relation zurück auf die Hauptseite speichern, und nimmt den ID-Wert daraus. Dann greift es das Gebäudeobjekt basierend auf dieser Gebäudeidentifikation. Schließlich können wir jetzt, da wir das Gebäude haben, das Abfrage-Set der Dropdown-Elemente so einstellen, dass nur die relevanten Objekte angezeigt werden.

Eleganter als meine ursprüngliche Lösung, die als Inline abgestürzt und gebrannt hat (aber funktioniert - gut, wenn es Ihnen nichts ausmacht, das Formular zu speichern, um die Dropdowns für die einzelnen Formulare ausfüllen zu lassen):

Für Nicht-Inline-Verbindungen funktionierte es, wenn der Raum bereits existierte. Wenn nicht, würde es einen Fehler (DoesNotExist) auslösen, also würde ich es fangen und dann das Feld verstecken (da es vom Admin keine Möglichkeit gab, es auf das richtige Gebäude zu beschränken, da der gesamte Raumdatensatz neu war, und kein Gebäude wurde noch gesetzt!) ... sobald Sie sparen getroffen, es spart das Gebäude und auf reload könnte es die Auswahl begrenzen ...

ich muss nur einen Weg finden, um die Fremdschlüssel Filter von einem kaskadieren Feld zu einem anderen in einem neuen Datensatz - dh neuer Datensatz, wählen Sie ein Gebäude, und es begrenzt automatisch die Auswahlmöglichkeiten in der Auswahlbox inside_room - bevor der Datensatz gespeichert wird. Aber das ist für einen anderen Tag ...

90

Gebrauchte Anfrage Instanz als temporärer Container für Obj. Überlagerte Inline-Methode formfield_for_foreignkey zum Ändern des Abfragesatzes. Dies funktioniert zumindest auf Django 1.2.3.

+1

Das hier hat mir eine Menge Ärger erspart. Ich musste Auswahlmöglichkeiten filtern, aber nach einer Sitzungsvariablen. Diese Antwort lässt mich mit 5 Zeilen Code machen. Vielen Dank. –

+3

Danke eine Million! Eine Alternative ist die Zuweisung von kwargs ['queryset'] vor dem Aufruf von super wie in Dokument: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_for_foreignkey – powlo

+0

Code hat mir auch viel Zeit gespart. Vielen Dank für das Posten dieses – fangsterr

15

Nachdem ich diesen Beitrag durchgelesen und viel experimentiert habe, denke ich, dass ich eine ziemlich definitive Antwort auf diese Frage gefunden habe. Da dies ein Designmuster ist, das häufig verwendet wird, habe ich eine Mixin for the Django admin geschrieben, um davon Gebrauch zu machen.

(Dynamisch) Begrenzen der Abfrage für ForeignKey-Felder ist jetzt so einfach wie Unterklassen LimitedAdminMixin und Definieren einer get_filters(obj) Methode, die relevanten Filter zurückgeben. Alternativ kann eine filters-Eigenschaft für den Administrator festgelegt werden, wenn keine dynamische Filterung erforderlich ist.

Beispiel Nutzung:

class MyInline(LimitedAdminInlineMixin, admin.TabularInline): 
    def get_filters(self, obj): 
     return (('<field_name>', dict(<filters>)),) 

Hier <field_name> ist der Name des FK Feld gefiltert werden und <filters> ist eine Liste von Parametern wie Sie sie immer in der filter() Methode der querysets angeben würden.

+1

Danke, funktioniert super! Viel sauberer. (Und übrigens, Sie haben einige Logging-Anweisungen in Ihrem Code hinterlassen, die nirgendwohin gehen) – Dave

8

Sie können einige benutzerdefinierte Klassen erstellen, die dann einen Verweis auf die übergeordnete Instanz an das Formular übergeben.

from django.forms.models import BaseInlineFormSet 
from django.forms import ModelForm 

class ParentInstInlineFormSet(BaseInlineFormSet): 
    def _construct_forms(self): 
     # instantiate all the forms and put them in self.forms 
     self.forms = [] 
     for i in xrange(self.total_form_count()): 
      self.forms.append(self._construct_form(i, parent_instance=self.instance)) 

    def _get_empty_form(self, **kwargs): 
     return super(ParentInstInlineFormSet, self)._get_empty_form(parent_instance=self.instance) 
    empty_form = property(_get_empty_form) 


class ParentInlineModelForm(ModelForm): 
    def __init__(self, *args, **kwargs): 
     self.parent_instance = kwargs.pop('parent_instance', None) 
     super(ParentInlineModelForm, self).__init__(*args, **kwargs) 

in Klasse RoomInline nur hinzufügen:

class RoomInline(admin.TabularInline): 
     formset = ParentInstInlineFormset 
     form = RoomInlineForm #(or something) 

In Ihrem Formular jetzt Sie den Zugriff in der init-Methode müssen self.parent_instance! parent_instance kann nun verwendet werden, Entscheidungen zu filtern und so weiter

so etwas wie:

class RoomInlineForm(ParentInlineModelForm): 
    def __init__(self, *args, **kwargs): 
     super(RoomInlineForm, self).__init__(*args, **kwargs) 
     building = self.parent_instance 
     #Filtering and stuff 
+0

Vielen Dank dafür! Es ist die erste Version, die für meine Anwendung funktioniert hat und auch schön und klar ist. – Justin

12

Es gibt limit_choices_to ForeignKey Option, die die verfügbaren Server-Betreiber Entscheidungen für das Objekt

+2

Das hilft nicht, da die Abfrage, die in der limit_choices_to ausgeführt wird, keinen Verweis auf die "Elternklasse" hat. Dh, wenn ein Modell A einen Fremdschlüssel zu B und auch zu C hat und C einen Fremdschlüssel zu B hat und wir sicherstellen wollen, dass ein A nur auf ein C verweist, das sich auf dasselbe B wie A bezieht Die Abfrage muss über A-> B wissen, was nicht der Fall ist. –

2

In django 1.6 begrenzen kann:

form = SpettacoloForm(instance = spettacolo) 
form.fields['teatro'].queryset = Teatro.objects.filter(utente = request.user).order_by("nome").all() 
+1

Könnten Sie bitte die Lösung an die in der Frage vorhandenen Modelle anpassen? – raratiru

Verwandte Themen