モデルのロジックは次のとおりです。
Building
には多くのRooms
がありますRoom
は、別のRoom
(たとえば、クローゼット-ForeignKey on 'self')の中にある場合がありますRoom
は、同じ建物内の別のRoom
内にのみ配置できます(これは注意が必要な部分です)ここに私が持っているコードがあります:
#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
そして:
#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)
インラインには、現在の建物内の部屋のみが表示されます(これが必要です)。ただし、問題はinside_room
ドロップダウンの場合、Roomsテーブルのすべての部屋(他の建物の部屋を含む)が表示されることです。
rooms
のインラインで、inside_room
の選択肢を、現在のrooms
(現在変更されているビルディングレコードにあるbuilding
のみに制限する必要があります。メインBuildingAdmin
フォーム)。
モデル内のlimit_choices_to
でそれを行う方法がわからず、管理者のインラインフォームセットを適切にオーバーライドする方法を正確に把握することもできません(何らかの形でカスタムインラインフォームを作成する必要があるように感じます、メインフォームのbuilding_idをカスタムインラインに渡し、それに基づいてフィールドの選択肢のクエリセットを制限します。
これは管理サイトにとっては複雑すぎるかもしれませんが、一般的に有用なもののようです...
Objの一時コンテナとしてリクエストインスタンスを使用しました。クエリセットを変更するためのインラインメソッドformfield_for_foreignkeyをオーバーライドしました。これは、少なくともDjango 1.2.3。
class 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':
if request._obj_ is not None:
field.queryset = field.queryset.filter(building__exact = request._obj_)
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)
この投稿を読んで多くのことを試した後、この質問に対するかなり決定的な答えを見つけたと思います。これはよく使用されるデザインパターンなので、 Django admin の混合を使用して)を作成しました。
(動的)ForeignKeyフィールドのクエリセットの制限は、LimitedAdminMixin
をサブクラス化し、get_filters(obj)
メソッドを定義して関連するフィルターを返すのと同じくらい簡単になりました。または、動的フィルタリングが不要な場合は、filters
プロパティを管理者に設定できます。
使用例:
_class MyInline(LimitedAdminInlineMixin, admin.TabularInline):
def get_filters(self, obj):
return (('<field_name>', dict(<filters>)),)
_
ここで、_<field_name>
_はフィルタリングされるFKフィールドの名前であり、_<filters>
_はクエリセットのfilter()
メソッドで通常指定するパラメーターのリストです。
limit_choices_to ForeignKeyオプションがあり、オブジェクトの利用可能な管理者の選択肢を制限できます。
いくつかのカスタムクラスを作成して、親インスタンスへの参照をフォームに渡すことができます。
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)
クラスRoomInlineに次を追加します。
class RoomInline(admin.TabularInline):
formset = ParentInstInlineFormset
form = RoomInlineForm #(or something)
フォームでは、initメソッドでself.parent_instanceにアクセスできます! parent_instanceを使用して、選択項目などをフィルタリングできるようになりました
何かのようなもの:
class RoomInlineForm(ParentInlineModelForm):
def __init__(self, *args, **kwargs):
super(RoomInlineForm, self).__init__(*args, **kwargs)
building = self.parent_instance
#Filtering and stuff
この質問と回答は非常に似ており、通常の管理フォームで機能します
インラインの内部-そして、それはバラバラになります...私は、メインフォームのデータにアクセスして、制限内で(または値を取得するためにインラインのレコードの1つに)必要な外部キー値を取得できません。 。
これが私のadmin.pyです。私は????を置き換える魔法を探していると思いますwith--ハードコードされた値(たとえば、1)をプラグインすると、正常に機能し、インラインで使用可能な選択肢が適切に制限されます...
#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)
かなりエレガントなソリューション が見つかりました。これはインラインフォームに適しています。
同じ建物内にある部屋のみを返すようにinside_roomフィールドをフィルタリングするモデルに適用されます。
#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)
基本的に、「インスタンス」キーワードがフォームに渡されると、インラインに表示される既存のレコードであるため、インスタンスから建物を取得するだけです。インスタンスではない場合、それはインラインの空の「余分な」行の1つであるため、暗黙的なリレーションをメインページに保存するインラインの非表示フォームフィールドを通過し、そこからid値を取得します。次に、そのbuilding_idに基づいて建物オブジェクトを取得します。最後に、建物ができたので、関連するアイテムのみを表示するようにドロップダウンのクエリセットを設定できます。
クラッシュしてインラインとして焼かれた元のソリューションよりもエレガントです(ただし、個々のフォームでドロップダウンを埋めるために途中でフォームを保存してもかまいません)。
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)')
非インラインの場合、部屋がすでに存在していれば機能していました。そうでない場合、エラー(DoesNotExist)がスローされるので、キャッチしてフィールドを非表示にします(管理者からは、部屋のレコード全体が新しいため、正しい建物に制限する方法がなかったため、建物はまだ設定されていません!)...保存を押すと、建物が保存され、リロードすると選択肢が制限される可能性があります...
新しいレコードの1つのフィールドから別のフィールドに外部キーフィルターをカスケードする方法を見つける必要があります(つまり、新しいレコード、建物を選択すると、レコードが取得される前に、inside_room選択ボックスの選択肢が自動的に制限されます)保存しました。しかし、それは別の日です...
@nogus回答の問題は、ポップアップ/?_to_field=id&_popup=1
ユーザーがポップアップで間違ったアイテムを選択できるようにする
最終的に機能させるには、field.widget.rel.limit_choices_to
dict
class 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)
あなたの質問を編集した後、ダニエルが答えていない場合-私は私が多くの助けになるとは思わない... :-)
Django admin独自のビュー、フォーム、テンプレートのグループとして実装したほうがよいロジックを管理するように強制することをお勧めします。
そのようなフィルタリングをInlineModelAdminに適用することは不可能だと思います。
In Django 1.6:
form = SpettacoloForm( instance = spettacolo )
form.fields['teatro'].queryset = Teatro.objects.filter( utente = request.user ).order_by( "nome" ).all()
私はあなたがやろうとしていることを正確には守らなかったと認めざるを得ませんが、あなたのサイトを管理者から切り離さないことを検討したいほど複雑だと思います。
一度シンプルな管理インターフェースから始めたサイトを構築しましたが、最終的には非常にカスタマイズされたため、管理者の制約内で作業することは非常に困難になりました。ゼロから始めたばかりであれば、最初はより多くの作業が必要でしたが、柔軟性が増し、最後に苦痛は減りました。私の経験則は、あなたがやろうとしていることが文書化されていない場合(つまり、管理メソッドのオーバーライド、管理ソースコードの覗き込みなど)、管理者を使用しない方が良いでしょう。ちょうど私は2セント。 :)