预填充内联FormSet?
我正在为一个乐队的出勤logging工作。 我的想法是有表格的一个部分input表演或彩排的事件信息。 以下是事件表的模型:
class Event(models.Model): event_id = models.AutoField(primary_key=True) date = models.DateField() event_type = models.ForeignKey(EventType) description = models.TextField()
然后,我希望有一个内联FormSet,将乐队成员链接到该事件,并logging他们是否在场,缺席或原谅:
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 不允许初始值。
有什么build议么?
所以,你不会喜欢这个答案,部分是因为我还没有完成代码的编写工作,部分是因为工作很多。
当我遇到这个问题时,你需要做的是:
- 花费大量的时间阅读formset和model-formset代码,以了解它是如何工作的(这并不是因为某些function驻留在formset类上,而有些function驻留在吐出的工厂函数中他们出)。 您将在后面的步骤中需要这些知识。
- 编写自己的formset类从
BaseInlineFormSet
子类并接受initial
。 这里真正棘手的是你必须重写__init__()
,你必须确保它调用BaseFormSet.__init__()
而不是直接的父或祖父__init__()
(因为它们分别是BaseInlineFormSet
和BaseModelFormSet
,而且他们都不能处理初始数据)。 - 编写适当的pipe理内联类的自己的子类(在我的情况下,它是
TabularInline
),并重写它的inlineformset_factory()
方法返回inlineformset_factory()
使用您的自定义formset类的结果。 - 在模型的实际
ModelAdmin
子类中,使用内联,覆盖add_view
和change_view
,并复制大部分代码,但有一个很大的改变:构buildformset将需要的初始数据,并将其传递给您的自定义formset(这将是由您的ModelAdmin
get_formsets()
方法返回)。
我与Brian和Joseph进行了一些有益的讨论,关于改进Django的未来版本。 目前,模型框架的工作方式使得这比通常的价值更麻烦,但是通过一些API清理,我认为它可以变得非常简单。
我花了相当长的时间试图提出一个解决scheme,我可以重复使用跨网站。 James的post包含了扩展BaseInlineFormSet
的关键部分,但策略性地调用BaseFormSet
。
下面的解决scheme分成两部分:一个AdminInline
和一个BaseInlineFormSet
。
-
InlineAdmin
根据公开的请求对象dynamic生成一个初始值。 - 它使用currying将初始值通过传递给构造函数的关键字参数展示给一个自定义的
BaseInlineFormSet
。 -
BaseInlineFormSet
构造函数通常将关键字参数和构造的列表中的初始值popup。 - 最后一部分是通过改变表单的最大总数并使用
BaseFormSet._construct_form
和BaseFormSet._construct_forms
方法来覆盖表单构build过程
这里是一些使用OP的类的具体片段。 我已经testing了这个对Django 1.2.3。 我强烈build议在开发过程中保留formset和admin文档。
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)
Django 1.4及更高版本支持提供初始值 。
就原来的问题而言,以下是可行的:
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的forms查找它:
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
我遇到了同样的问题。
你可以通过JavaScript来做到这一点,做一个简单的JS,为所有的乐队成员做一个Ajax调用,然后填充表单。
这个解决scheme缺乏DRY的原则,因为你需要为每一个你所拥有的内联表单写这个。
使用django 1.7我们遇到了一些问题,创build一个内联表单,并在模型中添加额外的上下文(而不仅仅是要传入的模型的一个实例)。
我想出了一个不同的解决scheme,将数据注入到传递给表单集的ModelForm中。 因为在Python中你可以dynamic地创build类,而不是直接通过表单的构造函数传递数据,所以类可以通过任何你想传入的参数的方法来构build。然后,当类被实例化时,它可以访问方法参数。
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
然后调用内联formset工厂看起来像这样:
inlineformset_factory(ParentModel, MyModel, form=build_my_model_form(extra_data))
6年后我遇到了这个问题 – 现在我们正在使用Django 1.8。
仍然没有完全干净,简短的回答这个问题。
问题在于ModelAdmin._create_formsets() github ; 我的解决scheme是重写它,并注入我想要在github链接中突出显示的行周围的初始数据。
我还必须重写InlineModelAdmin.get_extra(),以便为提供的初始数据“有空间”。 默认情况下,它将只显示3个初始数据
我相信在即将到来的版本中应该有一个更清晰的答案
您可以重写formset上的empty_form getter。 下面是我如何处理这个与Django的pipe理结合的例子:
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
这是我如何解决这个问题。 在创build和删除logging方面有一点折衷,但代码是干净的…
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))
我有同样的问题。 我使用的是Django 1.9,我尝试了由Simanas提出的解决scheme,重写属性“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
如果我们find一种方法来使其工作时有额外的forms,这个解决scheme将是一个很好的解决方法。
只要重写“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
- Django的pipe理员 – 特定的用户(pipe理员)的内容
- Django Forms和Bootstrap – CSS类和<divs>
- 与WSGIDaemonProcess的django apacheconfiguration不工作
- 按属性过滤
- 将额外的parameter passing给Django Rest Framework中的Serializer类
- Django的auto_now和auto_now_add
- Django ManyToMany filter()
- 我如何使用pip和需求文件升级特定的软件包?
- Django的设置:psycopg2.OperationalError:FATAL:用户“indivo”的对等身份validation失败