models.py
に次のものがあるとします:
class Company(models.Model):
name = ...
class Rate(models.Model):
company = models.ForeignKey(Company)
name = ...
class Client(models.Model):
name = ...
company = models.ForeignKey(Company)
base_rate = models.ForeignKey(Rate)
つまり複数のCompanies
があり、それぞれがRates
とClients
の範囲を持っています。各Client
には、別のCompany's Rates
ではなく、親のCompany's Rates
から選択されたベースRate
が必要です。
Client
を追加するためのフォームを作成するとき、Company
の選択肢(Company
ページの[Add Client]ボタンで既に選択されている)と制限を削除したいRate
の選択肢Company
も同様です。
Django 1.0でこれを行うにはどうすればよいですか?
私の現在のforms.py
ファイルは現時点では単なる定型です:
from models import *
from Django.forms import ModelForm
class ClientForm(ModelForm):
class Meta:
model = Client
また、views.py
も基本です。
from Django.shortcuts import render_to_response, get_object_or_404
from models import *
from forms import *
def addclient(request, company_id):
the_company = get_object_or_404(Company, id=company_id)
if request.POST:
form = ClientForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(the_company.get_clients_url())
else:
form = ClientForm()
return render_to_response('addclient.html', {'form': form, 'the_company':the_company})
Django 0.96では、テンプレートをレンダリングする前に次のようなことをすることでこれをハックすることができました。
manipulator.fields[0].choices = [(r.id,r.name) for r in Rate.objects.filter(company_id=the_company.id)]
ForeignKey.limit_choices_to
は有望に思えますが、the_company.id
を渡す方法がわかりません。とにかくそれが管理インターフェイスの外で機能するかどうかはわかりません。
ありがとう。 (これは非常に基本的な要求のように思えますが、何かを再設計する必要がある場合は、提案を受け入れます。)
ForeignKeyはDjango.forms.ModelChoiceFieldで表されます。これは選択肢がモデルQuerySetであるChoiceFieldです。 ModelChoiceField のリファレンスを参照してください。
そのため、フィールドのqueryset
属性にQuerySetを提供します。フォームの構築方法に依存します。明示的なフォームを作成する場合、フィールドには直接名前が付けられます。
form.rate.queryset = Rate.objects.filter(company_id=the_company.id)
デフォルトのModelFormオブジェクトを使用する場合、form.fields["rate"].queryset = ...
これは、ビューで明示的に行われます。ハッキングはありません。
S.Lottの回答に加えて、コメントでGuruとして言及されているように、ModelForm.__init__
関数をオーバーライドすることでquerysetフィルターを追加することが可能です。 (これは通常のフォームに簡単に適用できます)再利用を支援し、ビュー機能を整頓します。
class ClientForm(forms.ModelForm):
def __init__(self,company,*args,**kwargs):
super (ClientForm,self ).__init__(*args,**kwargs) # populates the post
self.fields['rate'].queryset = Rate.objects.filter(company=company)
self.fields['client'].queryset = Client.objects.filter(company=company)
class Meta:
model = Client
def addclient(request, company_id):
the_company = get_object_or_404(Company, id=company_id)
if request.POST:
form = ClientForm(the_company,request.POST) #<-- Note the extra arg
if form.is_valid():
form.save()
return HttpResponseRedirect(the_company.get_clients_url())
else:
form = ClientForm(the_company)
return render_to_response('addclient.html',
{'form': form, 'the_company':the_company})
これは、多くのモデルで一般的なフィルターが必要な場合(通常、抽象Formクラスを宣言する場合)に再利用するのに役立ちます。例えば。
class UberClientForm(ClientForm):
class Meta:
model = UberClient
def view(request):
...
form = UberClientForm(company)
...
#or even extend the existing custom init
class PITAClient(ClientForm):
def __init__(company, *args, **args):
super (PITAClient,self ).__init__(company,*args,**kwargs)
self.fields['support_staff'].queryset = User.objects.exclude(user='michael')
それ以外は、Djangoブログの資料を再掲しています。
これは簡単で、Django 1.4で動作します:
class ClientAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ClientAdminForm, self).__init__(*args, **kwargs)
# access object through self.instance...
self.fields['base_rate'].queryset = Rate.objects.filter(company=self.instance.company)
class ClientAdmin(admin.ModelAdmin):
form = ClientAdminForm
....
これをフォームクラスで指定する必要はありませんが、ModelAdminで直接行うことができます。DjangoにはModelAdminにこの組み込みメソッドが既に含まれています(ドキュメントから)。
ModelAdmin.formfield_for_foreignkey(self, db_field, request, **kwargs)¶
'''The formfield_for_foreignkey method on a ModelAdmin allows you to
override the default formfield for a foreign keys field. For example,
to return a subset of objects for this foreign key field based on the
user:'''
class MyModelAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "car":
kwargs["queryset"] = Car.objects.filter(owner=request.user)
return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
これを行うためのさらに便利な方法(たとえば、ユーザーがアクセスできるフロントエンド管理インターフェイスを作成する場合)は、ModelAdminをサブクラス化し、以下のメソッドを変更することです。最終的な結果は、ユーザー(スーパーユーザー)がすべてを表示できるようにしながら、ユーザーに関連するコンテンツのみを表示するユーザーインターフェイスです。
4つのメソッドをオーバーライドしました。最初の2つは、ユーザーが何も削除できないようにし、管理サイトから削除ボタンも削除します。
3番目のオーバーライドは、参照を含むクエリをフィルタリングします(例では「ユーザー」または「ヤマアラシ」)(例として)。
最後のオーバーライドは、モデル内の任意の外部キーフィールドをフィルタリングして、基本クエリセットと同じように利用可能な選択肢をフィルタリングします。
このようにして、ユーザーが自分のオブジェクトをいじることができる、管理が簡単な管理サイトを提示できます。また、上で説明した特定のModelAdminフィルターを入力する必要はありません。
class FrontEndAdmin(models.ModelAdmin):
def __init__(self, model, admin_site):
self.model = model
self.opts = model._meta
self.admin_site = admin_site
super(FrontEndAdmin, self).__init__(model, admin_site)
「削除」ボタンを削除します。
def get_actions(self, request):
actions = super(FrontEndAdmin, self).get_actions(request)
if 'delete_selected' in actions:
del actions['delete_selected']
return actions
削除許可を防ぎます
def has_delete_permission(self, request, obj=None):
return False
管理サイトで表示できるオブジェクトをフィルタリングします。
def get_queryset(self, request):
if request.user.is_superuser:
try:
qs = self.model.objects.all()
except AttributeError:
qs = self.model._default_manager.get_queryset()
return qs
else:
try:
qs = self.model.objects.all()
except AttributeError:
qs = self.model._default_manager.get_queryset()
if hasattr(self.model, ‘user’):
return qs.filter(user=request.user)
if hasattr(self.model, ‘porcupine’):
return qs.filter(porcupine=request.user.porcupine)
else:
return qs
管理サイトのすべての外部キーフィールドの選択肢をフィルタリングします。
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if request.employee.is_superuser:
return super(FrontEndAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
else:
if hasattr(db_field.rel.to, 'user'):
kwargs["queryset"] = db_field.rel.to.objects.filter(user=request.user)
if hasattr(db_field.rel.to, 'porcupine'):
kwargs["queryset"] = db_field.rel.to.objects.filter(porcupine=request.user.porcupine)
return super(ModelAdminFront, self).formfield_for_foreignkey(db_field, request, **kwargs)
CreateViewなどの汎用ビューでこれを行うには...
class AddPhotoToProject(CreateView):
"""
a view where a user can associate a photo with a project
"""
model = Connection
form_class = CreateConnectionForm
def get_context_data(self, **kwargs):
context = super(AddPhotoToProject, self).get_context_data(**kwargs)
context['photo'] = self.kwargs['pk']
context['form'].fields['project'].queryset = Project.objects.for_user(self.request.user)
return context
def form_valid(self, form):
pobj = Photo.objects.get(pk=self.kwargs['pk'])
obj = form.save(commit=False)
obj.photo = pobj
obj.save()
return_json = {'success': True}
if self.request.is_ajax():
final_response = json.dumps(return_json)
return HttpResponse(final_response)
else:
messages.success(self.request, 'photo was added to project!')
return HttpResponseRedirect(reverse('MyPhotos'))
その最も重要な部分...
context['form'].fields['project'].queryset = Project.objects.for_user(self.request.user)
フォームを作成しておらず、クエリセットを変更したい場合は、次を実行できます。
formmodel.base_fields['myfield'].queryset = MyModel.objects.filter(...)
これは、汎用ビューを使用している場合に非常に便利です!
それで、私は本当にこれを理解しようとしましたが、Djangoはまだこれを非常に簡単にしないようです。私はそんなに愚かなわけではありませんが、(少し)単純な解決策を見ることができません。
この種のことのために管理ビューをオーバーライドする必要があるのは、一般的にかなりfindいと思います。私が見つけたすべての例は、管理ビューに完全に適用されることはありません。
これは私が作ったモデルの一般的な状況であり、これに対する明白な解決策がないことは恐ろしいと思います...
これらのクラスがあります:
# models.py
class Company(models.Model):
# ...
class Contract(models.Model):
company = models.ForeignKey(Company)
locations = models.ManyToManyField('Location')
class Location(models.Model):
company = models.ForeignKey(Company)
会社の管理者を設定するときに問題が発生します。これは、契約と場所の両方にインラインがあり、場所の契約のm2mオプションが現在編集している会社に応じて適切にフィルタリングされないためです。
要するに、私はこのような何かをするためにいくつかの管理オプションが必要になるでしょう:
# admin.py
class LocationInline(admin.TabularInline):
model = Location
class ContractInline(admin.TabularInline):
model = Contract
class CompanyAdmin(admin.ModelAdmin):
inlines = (ContractInline, LocationInline)
inline_filter = dict(Location__company='self')
最終的に、フィルタリングプロセスがベースのCompanyAdminに配置されるか、ContractInlineに配置されるかは気にしません。 (インラインに配置するほうが理にかなっていますが、基本契約を「自己」として参照するのが難しくなります。)
このひどく必要なショートカットと同じくらい簡単なことを知っている人はいますか?このようなことのためにPHP管理者を作成したとき、これは基本的な機能と考えられていました!実際、それは常に自動であり、本当に必要ない場合は無効にする必要がありました!
より一般的な方法は、Adminクラスでget_formを呼び出すことです。また、データベース以外のフィールドでも機能します。たとえば、ここにはフォームに「_terminal_list」というフィールドがあり、特殊な場合にget_list(request)から複数の端末項目を選択し、request.userに基づいてフィルタリングできます。
class ChangeKeyValueForm(forms.ModelForm):
_terminal_list = forms.ModelMultipleChoiceField(
queryset=Terminal.objects.all() )
class Meta:
model = ChangeKeyValue
fields = ['_terminal_list', 'param_path', 'param_value', 'scheduled_time', ]
class ChangeKeyValueAdmin(admin.ModelAdmin):
form = ChangeKeyValueForm
list_display = ('terminal','task_list', 'plugin','last_update_time')
list_per_page =16
def get_form(self, request, obj = None, **kwargs):
form = super(ChangeKeyValueAdmin, self).get_form(request, **kwargs)
qs, filterargs = Terminal.get_list(request)
form.base_fields['_terminal_list'].queryset = qs
return form