Die Logik des Modells ist:
- A
Building
hat vieleRooms
- A
Room
kann sich in einem anderen befindenRoom
(zum Beispiel in einem Schrank - ForeignKey on 'self') - A
Room
kann nur in einem anderenRoom
im selben Gebäude sein (dies ist der schwierige Teil)
Hier ist der Code, den 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)
In der Inline werden nur Räume im aktuellen Gebäude angezeigt (was ich möchte). Das Problem ist jedoch, dass für das inside_room
Dropdown-Menü alle Räume in der Zimmertabelle angezeigt werden (einschließlich der Räume in anderen Gebäuden).
In der Zeile von rooms
muss ich die inside_room
Auswahl auf nur rooms
die beschränken, die im aktuellen Formular enthalten sind building
(der Gebäudedatensatz wird derzeit durch das Hauptformular geändert BuildingAdmin
).
Ich kann weder mit a limit_choices_to
im Modell einen Weg finden , noch kann ich herausfinden, wie genau das Inline-Formset des Administrators richtig überschrieben werden soll (ich denke, ich sollte irgendwie ein benutzerdefiniertes Inline-Formular erstellen und die building_id von übergeben das Hauptformular auf die benutzerdefinierte Inline, dann beschränken Sie das Abfrageset für die Auswahl des Feldes basierend darauf - aber ich kann mich einfach nicht darum kümmern, wie es geht).
Vielleicht ist dies zu komplex für die Admin-Site, aber es scheint etwas zu sein, das allgemein nützlich wäre ...
Room
im Popup immer noch eine falsche Auswahl treffen. Siehe stackoverflow.com/a/50298577/2207154 für die LösungNachdem ich diesen Beitrag gelesen und viel experimentiert habe, denke ich, dass ich eine ziemlich endgültige Antwort auf diese Frage gefunden habe. Da dies ein Designmuster ist, das oft verwendet wird, habe ich ein Mixin für den Django-Administrator geschrieben , um es zu verwenden.
Das (dynamische) Einschränken des Abfragesatzes für ForeignKey-Felder ist jetzt so einfach wie das Unterklassifizieren
LimitedAdminMixin
und Definieren einerget_filters(obj)
Methode zum Zurückgeben der relevanten Filter. Alternativ kann einefilters
Eigenschaft für den Administrator festgelegt werden, wenn keine dynamische Filterung erforderlich ist.Anwendungsbeispiel:
class MyInline(LimitedAdminInlineMixin, admin.TabularInline): def get_filters(self, obj): return (('<field_name>', dict(<filters>)),)
Hier
<field_name>
ist der Name des zu filternden FK-Feldes und<filters>
eine Liste von Parametern, wie Sie sie normalerweise in derfilter()
Methode von Abfragesätzen angeben würden.quelle
Es gibt die Option limit_choices_to ForeignKey, mit der die verfügbaren Administratoroptionen für das Objekt eingeschränkt werden können
quelle
Sie können einige benutzerdefinierte Klassen erstellen, die dann einen Verweis auf die übergeordnete Instanz an das Formular weiterleiten.
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 der Klasse RoomInline einfach hinzufügen:
class RoomInline(admin.TabularInline): formset = ParentInstInlineFormset form = RoomInlineForm #(or something)
In Ihrem Formular haben Sie jetzt in der init-Methode Zugriff auf self.parent_instance! parent_instance kann jetzt verwendet werden, um Auswahlmöglichkeiten und so weiter zu filtern
etwas wie:
class RoomInlineForm(ParentInlineModelForm): def __init__(self, *args, **kwargs): super(RoomInlineForm, self).__init__(*args, **kwargs) building = self.parent_instance #Filtering and stuff
quelle
Das Problem in @nogus Antwort gibt es immer noch falsche URL im Popup
/?_to_field=id&_popup=1
die es dem Benutzer ermöglichen, ein falsches Element im Popup auszuwählen
Damit es endlich funktioniert, musste ich das
field.widget.rel.limit_choices_to
Diktat ändernclass RoomInline(admin.TabularInline): model = Room def formfield_for_foreignkey(self, db_field, request=None, **kwargs): field = super(RoomInline, self).formfield_for_foreignkey( db_field, request, **kwargs) if db_field.name == 'inside_room': building = request._obj_ if building is not None: field.queryset = field.queryset.filter( building__exact=building) # widget changed to filter by building field.widget.rel.limit_choices_to = {'building_id': building.id} else: field.queryset = field.queryset.none() return field class BuildingAdmin(admin.ModelAdmin): inlines = (RoomInline,) def get_form(self, request, obj=None, **kwargs): # just save obj reference for future processing in Inline request._obj_ = obj return super(BuildingAdmin, self).get_form(request, obj, **kwargs)
quelle
field.widget.rel.limit_choices_to = {'building_id': building.id}
Diese Frage und Antwort ist sehr ähnlich und funktioniert für ein reguläres Administrationsformular
Innerhalb einer Inline - und dort fällt es auseinander ... Ich kann einfach nicht auf die Daten des Hauptformulars zugreifen, um den Fremdschlüsselwert zu erhalten, den ich in meinem Limit benötige (oder auf einen der Inline-Datensätze, um den Wert zu ermitteln). .
Hier ist meine admin.py. Ich schätze, ich suche die Magie, um die ???? mit - wenn ich einen fest codierten Wert (z. B. 1) einstecke, funktioniert dies einwandfrei und schränkt die verfügbaren Auswahlmöglichkeiten in der Inline ordnungsgemäß ein ...
#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)
quelle
Ich habe eine ziemlich elegante Lösung gefunden , die sich gut für Inline-Formulare eignet.
Auf mein Modell angewendet, bei dem ich das Feld inside_room filtere, um nur Räume zurückzugeben, die sich im selben Gebäude befinden:
#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)
Wenn ein 'Instanz'-Schlüsselwort an das Formular übergeben wird, handelt es sich im Grunde genommen um einen vorhandenen Datensatz, der in der Inline angezeigt wird, sodass ich das Gebäude einfach aus der Instanz abrufen kann. Wenn es sich nicht um eine Instanz handelt, handelt es sich um eine der leeren "zusätzlichen" Zeilen in der Inline. Daher werden die ausgeblendeten Formularfelder der Inline durchlaufen, in denen die implizite Beziehung zur Hauptseite gespeichert ist, und der ID-Wert wird daraus abgerufen. Dann wird das Gebäudeobjekt basierend auf dieser building_id erfasst. Nachdem wir nun das Gebäude haben, können wir den Abfragesatz der Dropdowns so einstellen, dass nur die relevanten Elemente angezeigt werden.
Eleganter als meine ursprüngliche Lösung, die als Inline abgestürzt und gebrannt hat (aber funktioniert hat - nun, wenn es Ihnen nichts ausmacht, das Formular teilweise zu speichern, damit die Dropdowns ausgefüllt werden - für die einzelnen Formulare):
class RoomForm(forms.ModelForm): # For the individual rooms class Meta: mode = Room def __init__(self, *args, **kwargs): # Limits inside_room choices to same building only super(RoomForm, self).__init__(*args, **kwargs) #On init... try: self.fields['inside_room'].queryset = Room.objects.filter( building__exact=self.instance.building) # rooms with the same building as this room except: #and hide this field (why can't I exclude?) self.fields['inside_room']=forms.CharField( #Add room throws DoesNotExist error widget=forms.HiddenInput, required=False, label='Inside Room (save room first)')
Bei Nicht-Inlines funktionierte es, wenn der Raum bereits vorhanden war. Wenn nicht, würde es einen Fehler auslösen (DoesNotExist), also würde ich ihn abfangen und dann das Feld ausblenden (da es vom Administrator keine Möglichkeit gab, es auf das richtige Gebäude zu beschränken, da der gesamte Raumdatensatz neu war). und es wurde noch kein Gebäude festgelegt!) ... Sobald Sie auf Speichern klicken, wird das Gebäude gespeichert und beim erneuten Laden können die Auswahlmöglichkeiten eingeschränkt werden ...
Ich muss nur einen Weg finden, um die Fremdschlüsselfilter in einem neuen Datensatz von einem Feld in ein anderes zu kaskadieren - dh einen neuen Datensatz, ein Gebäude auswählen und die Auswahlmöglichkeiten im Auswahlfeld inside_room automatisch einschränken -, bevor der Datensatz abgerufen wird Gerettet. Aber das ist für einen anderen Tag ...
quelle
Wenn Daniel nach der Bearbeitung Ihrer Frage nicht geantwortet hat - ich glaube nicht, dass ich viel helfen werde ... :-)
Ich werde vorschlagen, dass Sie versuchen, eine Anpassung in den Django-Administrator zu erzwingen, die besser als Ihre eigene Gruppe von Ansichten, Formularen und Vorlagen implementiert werden kann.
Ich denke nicht, dass es möglich ist, diese Art der Filterung auf InlineModelAdmin anzuwenden.
quelle
In Django 1.6:
form = SpettacoloForm( instance = spettacolo ) form.fields['teatro'].queryset = Teatro.objects.filter( utente = request.user ).order_by( "nome" ).all()
quelle
Ich muss zugeben, ich habe nicht genau verfolgt, was Sie versuchen, aber ich denke, es ist komplex genug, dass Sie in Betracht ziehen sollten, Ihre Website nicht auf den Administrator zu stützen.
Ich habe einmal eine Site erstellt, die mit der einfachen Administrationsoberflä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 Sie versuchen, nicht dokumentiert ist (dh das Überschreiben von Admin-Methoden, das Durchsuchen des Admin-Quellcodes usw.), ist es wahrscheinlich besser, den Admin nicht zu verwenden. Nur ich zwei Cent. :) :)
quelle