2009-07-30 30 views
22

次の管理者設定を使用して、ユーザーとそのプロフィールを同時に追加/編集することができます。Django管理者はどのようにインライン化する必要がありますか?

class ProfileInline(admin.StackedInline): 
    """ 
    Allows profile to be added when creating user 
    """ 
    model = Profile 


class UserProfileAdmin(admin.ModelAdmin): 
    """ 
    Options for the admin interface 
    """ 
    inlines = [ProfileInline] 
    list_display = ['edit_obj', 'name', 'username', 'email', 'is_active', 
     'last_login', 'delete_obj'] 
    list_display_links = ['username'] 
    list_filter = ['is_active'] 
    fieldsets = (
     (None, { 
      'fields': ('first_name', 'last_name', 'email', 'username', 
       'is_active', 'is_superuser')}), 
     ) 
    ordering = ['last_name', 'first_name'] 
    search_fields = ['first_name', 'last_name'] 

admin.site.register(User, UserProfileAdmin) 

問題は、ユーザーを追加するときにプロファイルインラインフォームの2つのフィールドが必要になることです。入力がない限り、インラインフォームは検証されません。それは空白のままにすることができないように、インラインが必要なので、とにかくありますか?

答えて

29

私はカールのアドバイスを取って、私は彼の答えに私のコメントで述べたハックっぽい1その後、はるかに優れた実装を行いました。私forms.pyから

from django.forms.models import BaseInlineFormSet 


class RequiredInlineFormSet(BaseInlineFormSet): 
    """ 
    Generates an inline formset that is required 
    """ 

    def _construct_form(self, i, **kwargs): 
     """ 
     Override the method to change the form attribute empty_permitted 
     """ 
     form = super(RequiredInlineFormSet, self)._construct_form(i, **kwargs) 
     form.empty_permitted = False 
     return form 

そしてadmin.py

class ProfileInline(admin.StackedInline): 
    """ 
    Allows profile to be added when creating user 
    """ 
    model = Profile 
    extra = 1 
    max_num = 1 
    formset = RequiredInlineFormSet 


class UserProfileAdmin(admin.ModelAdmin): 
    """ 
    Options for the admin interface 
    """ 
    inlines = [ProfileInline] 
    list_display = ['edit_obj', 'name', 'username', 'email', 'is_active', 
     'last_login', 'delete_obj'] 
    list_display_links = ['username'] 
    list_filter = ['is_active'] 
    fieldsets = (
     (None, { 
      'fields': ('first_name', 'last_name', 'email', 'username', 
       'is_active', 'is_superuser')}), 
     (('Groups'), {'fields': ('groups',)}), 
    ) 
    ordering = ['last_name', 'first_name'] 
    search_fields = ['first_name', 'last_name'] 


admin.site.register(User, UserProfileAdmin) 

これはまさに私が欲しいもの、それはプロファイルのインラインフォームセットの検証を行いんここに私のソリューションです。したがって、必須の情報がインラインフォームに入力されていない場合は、プロファイルフォームに必須のフィールドがあるため、検証して失敗します。

+1

'GenericInlineModelAdmin'を使用している場合は、' BaseInlineFormSet'を 'BaseGenericInlineFormSet'に置き換えてください。 – L42y

+1

ありがとう!ただし、フォームに必須フィールドがない場合は、フィールドが空の場合は検証および保存されません。空でも保存するには、form.has_changed = lambda:Trueを使用してフォームをモンキーパッチします。 – bouke

9

あなたはおそらくこれを行うことができますが、フォームセット/インラインコードで手が汚れて取得する必要があります。

まず、この場合はformsetに1つのフォームしか存在しないようにしたいと思います.= 1とextra = 1をProfileInlineに設定するとよいでしょう。

、コアの問題は、フォームセット内のそれぞれの「余分な」(すなわち、空)を形成するBaseFormSet._construct_form passes empty_permitted=Trueことです。このパラメータは、フォームが変更されていない場合、検証をバイパスするようにフォームに指示します。フォームに対してempty_permitted = Falseを設定する方法を見つけるだけで済みます。

あなたはuse your own BaseInlineFormset subclassあなたのインラインで、そのためには役立つかもしれないことができます。 _construct_formが** kwargsを受け取り、それが個々のFormインスタンスに渡されたkwargsをオーバーライドできるようにすることで、Formsetサブクラスの_construct_formsをオーバーライドし、_construct_formの各呼び出しでempty_permitted = Falseを渡すことができます。欠点は、内部APIに依存していることです(また、_construct_formsを書き直す必要があります)。

また、あなたはあなたのProfileInlineにget_formsetメソッドをオーバーライドしてみてください、と親のget_formsetを呼び出した後、手動で返さフォームセット内のフォームで突くことができます:

def get_formset(self, request, obj=None, **kwargs): 
    formset = super(ProfileInline, self).get_formset(request, obj, **kwargs) 
    formset.forms[0].empty_permitted = False 
    return formset 

は周りの再生とあなたが仕事を作ることができるかを見ます!

+0

感謝を参照してください。私は解決策を思いついたが、それは非常にハック・イッシュであり、私はそれを特に誇りに思いません。 ModelAdminのadd_viewをオーバーライドし、デフォルトのビューからすべてのコードをコピーして、formset値を変更しました。あなたの提案を見て、よりクリーンな方法で実装できるかどうかを見ていきます。リードしてくれてありがとう! –

7

それを行うための最も簡単で自然な方法は、fomset clean()経由です:

class RequireOneFormSet(forms.models.BaseInlineFormSet): 
    def clean(self): 
     super().clean() 
     if not self.is_valid(): 
      return 
     if not self.forms or not self.forms[0].cleaned_data: 
      raise ValidationError('At least one {} required' 
            .format(self.model._meta.verbose_name)) 

class ProfileInline(admin.StackedInline): 
    model = Profile 
    formset = RequireOneFormSet 

(ジャンゴ1.11および2.0で動作するようにテストされ、this Matthew Flanagan's snippet以下Mitarさんのコメントに触発さ)。

+2

パーフェクト!私は少し変更し、 'self.errors'を手作業ではなくself.is_valid():'を使用し、 'self.model._meta.verbose_name'を使用しました。 – Mitar

16

今Djangoの1.7とは、パラメータmin_numを使用することができます。あなたはもうクラスRequiredInlineFormSetは必要ありません。

は、情報のためhttps://docs.djangoproject.com/en/1.8/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.min_num

class ProfileInline(admin.StackedInline): 
    """ 
    Allows profile to be added when creating user 
    """ 
    model = Profile 
    extra = 1 
    max_num = 1 
    min_num = 1 # new in Django 1.7 


class UserProfileAdmin(admin.ModelAdmin): 
    """ 
    Options for the admin interface 
    """ 
    inlines = [ProfileInline] 
    ... 


admin.site.register(User, UserProfileAdmin) 
+0

これは、表示されるインラインの数だけを制御します。 – spookylukey

+5

実際にインラインに必要なフィールドの1つがある場合は、インライン自体が埋め込まれていることも制御します。 – quick

+1

作業中のことを確認しました。 – Geotob

関連する問題