2009-01-14 271 views
38

我正在爲一個樂隊製作出席報名表。我的想法是有表格的一部分輸入表演或彩排的事件信息。下面是事件表型號:預先填充內聯FormSet?

class Event(models.Model): 
    event_id = models.AutoField(primary_key=True) 
    date = models.DateField() 
    event_type = models.ForeignKey(EventType) 
    description = models.TextField() 

然後我想有一個內聯表單集鏈接樂隊成員的事件和記錄他們是否存在,不存在,或原諒:

class Attendance(models.Model): 
    attendance_id = models.AutoField(primary_key=True) 
    event_id = models.ForeignKey(Event) 
    member_id = models.ForeignKey(Member) 
    attendance_type = models.ForeignKey(AttendanceType) 
    comment = models.TextField(blank=True) 

現在,我想要做的就是預先填充這個內聯FormSet的所有當前成員的條目,並默認它們存在(大約60個成員)。遺憾的是,Django doesn't allow initial values in this case.

有什麼建議嗎?

+4

這個問題已經有幾年了。現在在Django 1.3中有更簡單的解決方案嗎? – monkut 2011-08-25 13:43:05

+1

是的,它解決了。只需在參數中傳遞相關實例,並將其預填充它。 – 2012-01-04 19:21:54

+12

有誰知道如何實現上面提到的「只需在參數中傳遞相關實例,並且它會預填充它」的任何地方的參考? – i3enhamin 2012-02-16 00:01:09

回答

28

所以,你不會喜歡這個答案,部分原因是我還沒有完成編寫代碼,部分原因是它有很多工作。

你需要做什麼,因爲我發現,當我碰到了這個自己,就是:

  1. 花了大量的時間,通過該formset和模型表單集讀碼,以獲得這一切是如何的感覺工作(沒有幫助的事實,一些功能住在formset類,其中一些生活在工廠功能,吐出它們)。您將在後面的步驟中需要這些知識。
  2. 寫你自己的formset類,它從BaseInlineFormSet繼承而來,接受initial。這裏的真正棘手的一點是,你必須覆蓋__init__(),你必須確保它調用多達BaseFormSet.__init__(),而不是使用直接的父母或祖父母__init__()(因爲這些是BaseInlineFormSetBaseModelFormSet,分別與他們都不可以處理初始數據)。
  3. 編寫適當的管理內聯類的自己的子類(在我的情況下它是TabularInline),並覆蓋它的get_formset方法,使用您的自定義formset類返回inlineformset_factory()的結果。
  4. 實際ModelAdmin子類的直列型號,覆蓋add_viewchange_view,並複製大部分代碼,但是有一個大的變化:建立你的表單集需要的初始數據,並將其傳遞給您的自定義表單集(這將由您的ModelAdminget_formsets()方法返回)。

我和Brian和Joseph談了很多關於改進Django版本的聊天記錄;目前,模型框架的工作方式只會讓它比通常值得的更麻煩,但通過一些API清理,我認爲它可以變得非常簡單。

+4

哎呀。我想我會通過分兩種形式來避免這個問題。謝謝! – 2009-01-15 06:06:13

+1

很好的答案。這實際上是django中非常混亂,複雜且記錄不準的特性。 – Rich 2010-10-31 23:33:23

+1

這是什麼狀態?有我可以訂閱的票嗎?你需要幫助嗎? – Thomas 2014-07-10 13:38:19

3

我遇到了同樣的問題。

你可以通過JavaScript來做到這一點,做一個簡單的JS,使所有的樂隊memebers ajax調用,並填充表單。

該解決方案缺乏DRY原則,因爲您需要爲每個內聯表單編寫此代碼。

0

這是我如何解決問題。 有一個位創建和刪除記錄的權衡,但代碼是乾淨的......

def manage_event(request, event_id): 
    """ 
    Add a boolean field 'record_saved' (default to False) to the Event model 
    Edit an existing Event record or, if the record does not exist: 
    - create and save a new Event record 
    - create and save Attendance records for each Member 
    Clean up any unsaved records each time you're using this view 
    """ 
    # delete any "unsaved" Event records (cascading into Attendance records) 
    Event.objects.filter(record_saved=False).delete() 
    try: 
     my_event = Event.objects.get(pk=int(event_id)) 
    except Event.DoesNotExist: 
     # create a new Event record 
     my_event = Event.objects.create() 
     # create an Attendance object for each Member with the currect Event id 
     for m in Members.objects.get.all(): 
      Attendance.objects.create(event_id=my_event.id, member_id=m.id) 
    AttendanceFormSet = inlineformset_factory(Event, Attendance, 
             can_delete=False, 
             extra=0, 
             form=AttendanceForm) 
    if request.method == "POST": 
     form = EventForm(request.POST, request.FILES, instance=my_event) 
     formset = AttendanceFormSet(request.POST, request.FILES, 
             instance=my_event) 
     if formset.is_valid() and form.is_valid(): 
      # set record_saved to True before saving 
      e = form.save(commit=False) 
      e.record_saved=True 
      e.save() 
      formset.save() 
      return HttpResponseRedirect('/') 
    else: 
     form = EventForm(instance=my_event) 
     formset = OptieFormSet(instance=my_event) 
    return render_to_response("edit_event.html", { 
          "form":form, 
          "formset": formset, 
          }, 
          context_instance=RequestContext(request)) 
17

我花了很多時間相當試圖拿出我能重新的解決方案跨站點使用。詹姆斯的帖子包含了延伸BaseInlineFormSet但策略性地調用BaseFormSet的呼叫的關鍵部分。

下面的解決方案分爲兩部分:一個AdminInline和一個BaseInlineFormSet

  1. InlineAdmin根據暴露的請求對象動態生成一個初始值。
  2. 它使用currying通過傳遞給構造函數的關鍵字參數將初始值公開到自定義BaseInlineFormSet
  3. 構造函數BaseInlineFormSet將關鍵字參數和構造的列表中的初始值從通常情況下彈出。
  4. 最後一塊是通過改變形式的最大總數,並使用BaseFormSet._construct_formBaseFormSet._construct_forms方法

下面是使用OP的類中的某些具體的片段覆蓋表單施工過程。我已經對Django 1.2.3進行了測試。我強烈建議在開發時保留formsetadmin文檔。

admin.py

from django.utils.functional import curry 
from django.contrib import admin 
from example_app.forms import * 
from example_app.models import * 

class AttendanceInline(admin.TabularInline): 
    model   = Attendance 
    formset   = AttendanceFormSet 
    extra   = 5 

    def get_formset(self, request, obj=None, **kwargs): 
     """ 
     Pre-populating formset using GET params 
     """ 
     initial = [] 
     if request.method == "GET": 
      # 
      # Populate initial based on request 
      # 
      initial.append({ 
       'foo': 'bar', 
      }) 
     formset = super(AttendanceInline, self).get_formset(request, obj, **kwargs) 
     formset.__init__ = curry(formset.__init__, initial=initial) 
     return formset 

forms.py

from django.forms import formsets 
from django.forms.models import BaseInlineFormSet 

class BaseAttendanceFormSet(BaseInlineFormSet): 
    def __init__(self, *args, **kwargs): 
     """ 
     Grabs the curried initial values and stores them into a 'private' 
     variable. Note: the use of self.__initial is important, using 
     self.initial or self._initial will be erased by a parent class 
     """ 
     self.__initial = kwargs.pop('initial', []) 
     super(BaseAttendanceFormSet, self).__init__(*args, **kwargs) 

    def total_form_count(self): 
     return len(self.__initial) + self.extra 

    def _construct_forms(self): 
     return formsets.BaseFormSet._construct_forms(self) 

    def _construct_form(self, i, **kwargs): 
     if self.__initial: 
      try: 
       kwargs['initial'] = self.__initial[i] 
      except IndexError: 
       pass 
     return formsets.BaseFormSet._construct_form(self, i, **kwargs) 

AttendanceFormSet = formsets.formset_factory(AttendanceForm, formset=BaseAttendanceFormSet) 
14

Django的1.4和更高的支撐providing initial values

在原來的問題方面,以下將工作:

class AttendanceFormSet(models.BaseInlineFormSet): 
    def __init__(self, *args, **kwargs): 
     super(AttendanceFormSet, self).__init__(*args, **kwargs) 
     # Check that the data doesn't already exist 
     if not kwargs['instance'].member_id_set.filter(# some criteria): 
      initial = [] 
      initial.append({}) # Fill in with some data 
      self.initial = initial 
      # Make enough extra formsets to hold initial forms 
      self.extra += len(initial) 

如果您發現該形式被填充,但沒有被保存,那麼你可能需要自定義模型形式。一個簡單的方法是通過標籤的初始數據,並在表格的init尋找它:

class AttendanceForm(forms.ModelForm): 
    def __init__(self, *args, **kwargs): 
     super(AttendanceForm, self).__init__(*args, **kwargs) 
     # If the form was prepopulated from default data (and has the 
     # appropriate tag set), then manually set the changed data 
     # so later model saving code is activated when calling 
     # has_changed(). 
     initial = kwargs.get('initial') 
     if initial: 
      self._changed_data = initial.copy() 

    class Meta: 
     model = Attendance 
-1

就越權「save_new」的方法,它在Django 1.5.5工作對我來說:

class ModelAAdminFormset(forms.models.BaseInlineFormSet): 
    def save_new(self, form, commit=True): 
     result = super(ModelAAdminFormset, self).save_new(form, commit=False) 
     # modify "result" here 
     if commit: 
      result.save() 
     return result 
3

使用django 1.7我們遇到了一些問題,創建了一個內聯表單,並在模型中添加了額外的上下文(而不僅僅是要傳入的模型的一個實例)。

我想出了一種不同的解決方案,用於將數據注入到傳遞給表單集的ModelForm中。因爲在Python中你可以動態地創建類,而不是直接通過表單的構造函數傳遞數據,所以類可以通過一個方法來創建,並且你需要傳入任何參數。然後,當類被實例化時,它可以訪問方法的參數。

def build_my_model_form(extra_data): 
    return class MyModelForm(forms.ModelForm): 
     def __init__(self, *args, **kwargs): 
      super(MyModelForm, self).__init__(args, kwargs) 
      # perform any setup requiring extra_data here 

     class Meta: 
      model = MyModel 
      # define widgets here 

然後調用內聯表單集工廠應該是這樣的:

inlineformset_factory(ParentModel, 
         MyModel, 
         form=build_my_model_form(extra_data)) 
2

我就遇到了這個問題 - 6年later-,我們對Django的1.8現在。

仍然沒有完全乾淨,簡短的問題答案。

問題出在ModelAdmin._create_formsets()github; 我的解決方案是覆蓋它,並注入我想要在github鏈接中突出顯示的行周圍的初始數據。

我還必須重寫InlineModelAdmin.get_extra(),以便爲提供的初始數據「有空間」。左默認我認爲應該在即將到來的版本

1

更清潔的回答,您可以在一個formset覆蓋empty_form吸氣,將只顯示3初始數據

的。以下是我如何與django admin一起處理此問題的示例:

class MyFormSet(forms.models.BaseInlineFormSet): 
    model = MyModel 

    @property 
    def empty_form(self): 
     initial = {} 
     if self.parent_obj: 
      initial['name'] = self.parent_obj.default_child_name 
     form = self.form(
      auto_id=self.auto_id, 
      prefix=self.add_prefix('__prefix__'), 
      empty_permitted=True, initial=initial 
     ) 
     self.add_fields(form, None) 
     return form  

class MyModelInline(admin.StackedInline): 
    model = MyModel 
    formset = MyFormSet 

    def get_formset(self, request, obj=None, **kwargs):  
     formset = super(HostsSpaceInline, self).get_formset(request, obj, **kwargs) 
     formset.parent_obj = obj 
     return formset 
0

我遇到同樣的問題。我使用的是Django 1.9,並且我嘗試了由Simanas提出的解決方案,覆蓋屬性「empty_form」,在首字母加上一些默認值。這有效,但在我的情況下,我有4個額外的內聯表格,共5個,並且只有五個表格中的一個填充了初始數據。

我修改這樣的代碼(見初始字典):

class MyFormSet(forms.models.BaseInlineFormSet): 
    model = MyModel 

    @property 
    def empty_form(self): 
     initial = {'model_attr_name':'population_value'} 
     if self.parent_obj: 
      initial['name'] = self.parent_obj.default_child_name 
     form = self.form(
      auto_id=self.auto_id, 
      prefix=self.add_prefix('__prefix__'), 
      empty_permitted=True, initial=initial 
     ) 
     self.add_fields(form, None) 
     return form  

class MyModelInline(admin.StackedInline): 
    model = MyModel 
    formset = MyFormSet 

    def get_formset(self, request, obj=None, **kwargs):  
     formset = super(HostsSpaceInline, self).get_formset(request, obj, **kwargs) 
     formset.parent_obj = obj 
     return formset 

如果我們找到一種方法,使工作有額外的形式時,該解決方案將是一個很好的解決方法。