2009-07-30 106 views
22

我有以下管理員設置,以便我可以同時添加/編輯用戶及其個人資料。如何在Django Admin中需要內聯?

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) 

問題是我需要添加用戶時需要配置文件內聯表單中的兩個字段。除非輸入內容,否則內聯表單不會生效。無論如何需要內聯,以便它不能留空?

回答

29

我接受了卡爾的建議,並做了一個更好的實施,然後我在他的回答中提到了這個黑客問題。這裏是我的解決方案:

從我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 2012-02-24 03:39:27

+1

謝謝!但是,如果您的表單沒有必填字段,則在空白時仍不會進行驗證和保存。使用`form.has_changed = lambda:True`對錶單進行修補,儘管它是空的,但可以保存它。 – bouke 2012-05-13 10:16:51

9

你可以這樣做,但你必須在formset/inline代碼中弄髒你的手。

首先,我認爲你希望在這種情況下總是有一個formset的形式,並且不會超過一個,所以你需要在ProfileInline中設置max_num = 1和extra = 1。

您的核心問題是BaseFormSet._construct_form passes empty_permitted=True到formset中的每個「額外」(即空)形式。該參數告訴表單如果未更改,則繞過驗證。你只需要找到一種方法來爲表單設置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值。我會看看你的建議,看看我能否以更清晰的方式實施它。感謝您的線索! – 2009-07-31 16:07:20

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 

(由this Matthew Flanagan's snippet啓發及以下米塔爾的評論,測試在Django 1.11和2.0的工作)。

+2

完美!我改變了一下,使用`如果不是self.is_valid():`而不是手動通過`self.errors`並使用`self.model._meta.verbose_name`。 – Mitar 2012-06-13 20:37:45

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) 
相關問題