ほんじゃらねっと

ダイエット中プログラマのブログ

Admin管理画面でForeignKeyをプルダウン以外で選択できるようにする

DjangoのAdmin管理画面でForeignKeyを使用したモデルを編集する場合、
プルダウンを生成して参照先のオブジェクトを簡単に選択できるようにしてくれる。


しかし、プルダウン生成時に全てのオブジェクトを取得してきてしまうので、
参照先のデータが増えてきて数万件単位になると、タイムアウトするか、
負荷がかかりすぎてエラー終了してしまう。


ModelAdminクラスには raw_id_fields というプロパティがあり、
これを使うとプルダウンではなくテキストフィールドに参照先オブジェクトの
IDを指定できるようにはなっている。

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


でもIDは管理画面のURLには含まれてるけど画面上には表示されていないので
普通のユーザーには分からない。


そこで、もう少し分かりやすく、
参照先のユニークなコード(ユーザーコードとか)で指定できる方法がないかをを調べたところ、
ModelAdminのformプロパティとsave_modelメソッドを上書きすることで実装できるみたい。


以下サンプル


会社モデルと業界モデル、そして会社業界マップモデルがあるとする。
会社が大量に登録されてるイメージ。
会社業界マップを作成・編集する時に会社コードで会社を指定できるようにする。


project/company/models.py (モデル定義)

# -*- coding: utf-8 -*-
from django.db import models
class Category(models.Model):
name = models.CharField("業界", max_length=255)
class Company(models.Model):
code = models.SlugField("会社コード", max_length=100, unique=True)
name = models.CharField("会社名", max_length=255)
class CompanyCategoryMap(models.Model):
category = models.ForeignKey(Industry, verbose_name="業界")
company = models.ForeignKey(Company, verbose_name="会社")


project/company/adminforms.py (管理画面用カスタムフォーム)

# -*- coding: utf-8 -*-
from django import forms
from django.core.exceptions import ObjectDoesNotExist
from project.company.models import *
class CompanySelectBaseAdminForm(forms.ModelForm): # 使いまわせるように別フォームに定義
company_code = forms.CharField(label="会社コード")
def __init__(self, *args, **kwargs): # 編集画面を開いた時に会社データから会社コードを取得して表示
if "instance" in kwargs and kwargs["instance"] is not None:
if "initial" in kwargs:
kwargs["initial"]["company_code"] = kwargs["instance"].company.code
else:
kwargs["initial"] = {"company_code": kwargs["instance"].company.code}
super(forms.ModelForm, self).__init__(*args, **kwargs)
def clean_company_code(self): # validation処理定義。入力された会社コードが存在することを確認。
company_code = self.cleaned_data["company_code"]
try:
Company.objects.get(code=company_code)
except ObjectDoesNotExist:
raise forms.ValidationError("会社コードが存在しません")
return company_code
class CompanyCategoryMapAdminForm(CompanySelectBaseAdminForm):
class Meta:
model = CompanyCategoryMap
fields = ["company_code", "category",]


project/company/admin.py (管理機能定義)

# -*- coding: utf-8 -*-
from django.contrib import admin
from project.company.models import *
from project.company.adminforms import *
class CompanyAdmin(admin.ModelAdmin):
list_display = ("name",)
search_fields = ['name',]
admin.site.register(Company, CompanyAdmin)
class CompanyCategoryMapAdmin(admin.ModelAdmin):
list_display = ("company", "category")
form = CompanyCategoryMapAdminForm # カスタムフォーム指定
def save_model(self, request, obj, form, change): # save 処理を上書き
form_data = form.cleaned_data
obj.company = Company.objects.get(code=form_data["company_code"])
obj.save()
admin.site.register(CompanyCategoryMap, CompanyCategoryMapAdmin)