django-forms - Django ModelForm 没有正确摄取派生的表单数据
问题描述
作为我一直在开发的 Wagtail CMS 应用程序的一部分,我无法让文件上传/处理过程正常工作。在过去的六周里,我只在站点上上下班工作,但到目前为止,我在设置多级页面模型结构、流域中的新块类型以及从外部云中摄取图像方面取得了相当大的成功环境进入 Wagtail Image 商店。
我的目标是建立一个模型,列出一系列路径(即纬度/经度序列),其信息是从通过 Wagtail 管理界面上传的文件中提取的。到目前为止,我采用的方法是围绕 Wagtail ModelAdmin 构建它,以便我可以维护路径列表(列表、编辑、删除)并覆盖添加/创建功能,以便可以通过拖放上传路径文件 -降低; 代码来源于 Wagtail 的图像和文档管理应用程序。
具体来说,我发现使用模型表单时未设置模型中字段的值。这些值来自上传文件的内容,而不是直接来自 POSTed 表单。因为没有设置模型值,所以保存模型和后续编辑都会受到影响。
以下是简化的代码片段:
模型.py:
from django.db import models
class AbstractPath(models.Model):
name = models.CharField(max_length=100, blank=False, null=False)
start_location = models.CharField(max_length=120, blank=False, null=False)
start_timestamp = models.DateTimeField(blank=False, null=False)
stop_location = models.CharField(max_length=120, blank=False, null=False)
stop_timestamp = models.DateTimeField(blank=False, null=False)
class Meta:
abstract = True
def __str__(self):
return self.name
class RealPath(AbstractPath):
average_speed = models.FloatField(blank=False, null=False)
max_speed = models.FloatField(blank=False, null=False)
表格.py:
from wagtail.admin.forms import WagtailAdminModelForm
from wagtail.admin.edit_handlers import BaseFormEditHandler
class PathForm(WagtailAdminModelForm):
permission_policy = paths_permission_policy
class Meta:
model = RealPath
fields = '__all__'
class PathFormEditHandler(BaseFormEditHandler):
base_form_class = PathForm
管理员.py:
from wagtail.contrib.modeladmin.options import ModelAdmin, modeladmin_register
from wagtail.contrib.modeladmin.views import CreateView, EditView
from django.template.loader import render_to_string
from django.http import HttpResponseBadRequest, JsonResponse
from django.utils.encoding import force_str
class RealPathCreateView(CreateView):
def get_template_names(self):
return ['RealPath_create.html']
def post(self, request, *args, **kwargs):
if not request.is_ajax():
return HttpResponseBadRequest("Cannot POST to this view without AJAX")
if not request.FILES:
return HttpResponseBadRequest("Must upload a file")
# Get dict with data derived from the file - keys align with model fields
path_form_data = pathfile_process(request.FILES['files[]'])
# Build a form for validation
RealPathForm = self.get_form_class()
form = RealPathForm(path_form_data, { 'file': request.FILES['files[]'] })
if form.is_valid():
path = form.save(commit=False)
# Temporary workaround to load path model with derived data - shouldn't be necessary?
for f, v in path_form_data.items():
setattr(path, f, v)
path.save()
return JsonResponse({
'success': True,
'path_id': int(path.id),
'form': render_to_string('RealPath_edit.html', {
'path': path,
'form': RealPathForm(
instance=path, prefix='path-%d' % path.id
),
}, request=request),
})
else:
# Validation error
return JsonResponse({
'success': False,
'error_message': '\n'.join(['\n'.join([force_str(i) for i in v]) for k, v in form.errors.items()]),
})
def get_edit_handler(self):
edit_handler = self.model_admin.get_edit_handler(
instance=self.get_instance(), request=self.request
)
return edit_handler.bind_to(model=self.model_admin.model)
class RealPathAdmin(ModelAdmin):
model = RealPath
menu_label = "Real Path"
menu_icon = "arrow-right"
menu_order = 320
add_to_settings_menu = False
exclude_from_explorer = False
list_display = ("name", "start_timestamp", "start_location", "stop_timestamp", "stop_location")
form_fields_exclude = ["start_timestamp", "stop_timestamp", "average_speed"]
create_view_class = RealPathCreateView
def get_edit_handler(self, instance, request):
return PathFormEditHandler(())
modeladmin_register(RealPathAdmin)
路径文件上传正常,可在 RealPathCreateView.post() 方法中使用。该文件被处理以提取相关数据,并放入path_form_data
. 我的期望是,当使用创建表单时RealPathForm
,它将创建RealPath
模型的一个实例并用该数据填充其中的字段。我发现没有填充数据值;如果此时我尝试使用 保存form.save(commit=True)
,则会引发异常django.db.utils.IntegrityError: NOT NULL constraint failed: paths_realpath.start_timestamp
。
对该问题的深入调查显示该form
对象具有一个空fields
属性,这意味着RealPath
从未设置模型字段,然后表单被“验证”并且保存失败,因为传递给数据库层的大部分数据都是None
or 0.0
。完整的字段列表在 django 的方法中生成,ModelFormMetaclass.__new__()
基于模型的内容,但从未传递给模型本身。
我最终实现了手动设置模型字段的解决方法(如上面的代码中所示),但后来我发现后续的表单渲染也被破坏了,因为需要迭代表单字段 - 也没有填充。显然,我应该解决第一个问题,因为它将解决第二个问题(可能还有其他问题),但是我看不到 django 代码中这种转移发生的位置,因此我需要对我的代码进行哪些更改。
感谢你的协助。
注意:您会注意到RealPath
模型是基于AbstractPath
模型的——原因是我将有几种类型的实数Path
,其数据来自不同的来源。我把那个结构留在那里,以防它是我遇到问题的原因。
解决方案
我最终得出的结论是,如果要从派生文件数据中设置模型字段,并且如果我希望 ModelAdmin 和 django 表单功能正常工作,我将不得不删除其中的form_fields_exclude
属性RealPathAdmin
- 它本质上与 NOT NULL 不兼容模型中的约束RealPath
。随后进行编辑时,这会生成一个包含所有数据的表单,包括我不想编辑的字段。然后,我可以通过使用属性、指定小部件并使用一些 CSS将这些不可编辑的字段完全隐藏在用户面前。ModelAdmin.panels
HiddenInput
这也意味着接受所有数据将流入和流出浏览器,从而提供一个技术机会来更改应该只来自原始文件的数据。在我的部署方案中,这是低风险的,但可以在编辑视图中使用更多逻辑关闭打开。
我修改后的要点代码如下:
models.py:(没有变化)
from django.db import models
class AbstractPath(models.Model):
name = models.CharField(max_length=100, blank=False, null=False)
start_location = models.CharField(max_length=120, blank=False, null=False)
start_timestamp = models.DateTimeField(blank=False, null=False)
stop_location = models.CharField(max_length=120, blank=False, null=False)
stop_timestamp = models.DateTimeField(blank=False, null=False)
class Meta:
abstract = True
def __str__(self):
return self.name
class RealPath(AbstractPath):
average_speed = models.FloatField(blank=False, null=False)
max_speed = models.FloatField(blank=False, null=False)
forms.py:(已删除 - 不需要编辑处理程序)
管理员.py:
from wagtail.contrib.modeladmin.options import ModelAdmin, modeladmin_register
from .models import RealPath
from .views import RealPathCreateView
class RealPathAdmin(ModelAdmin):
model = RealPath
menu_label = "Real Path"
menu_icon = "arrow-right"
menu_order = 320
add_to_settings_menu = False
exclude_from_explorer = False
list_display = ("name", "start_timestamp", "start_location", "stop_timestamp", "stop_location")
create_view_class = RealPathCreateView
panels = [
MultiFieldPanel([
FieldPanel('name'),
FieldPanel('start_location'),
FieldPanel('stop_location'),
], heading="Real Path Name"),
MultiFieldPanel([
FieldPanel('start_timestamp', classname="realpath_admin_hidden", widget=HiddenInput),
FieldPanel('stop_timestamp', classname="realpath_admin_hidden", widget=HiddenInput),
FieldPanel('average_speed', classname="realpath_admin_hidden", widget=HiddenInput),
FieldPanel('max_speed', classname="realpath_admin_hidden", widget=HiddenInput),
])
]
modeladmin_register(RealPathAdmin)
views.py:(视图类从 admin.py 移动)
from wagtail.contrib.modeladmin.views import CreateView
from django.template.loader import render_to_string
from django.http import HttpResponseBadRequest, JsonResponse
from django.utils.encoding import force_str
class RealPathCreateView(CreateView):
def get_template_names(self):
return ['RealPath_create.html']
def post(self, request, *args, **kwargs):
if not request.is_ajax():
return HttpResponseBadRequest("Cannot POST to this view without AJAX")
if not request.FILES:
return HttpResponseBadRequest("Must upload a file")
# Get dict with data derived from the file - keys align with model fields
path_form_data = pathfile_process(request.FILES['files[]'])
# Build a form for validation
RealPathForm = self.get_form_class()
form = RealPathForm(path_form_data, { })
if form.is_valid():
path = form.save()
return JsonResponse({
'success': True,
'path_id': int(path.id),
'form': render_to_string('RealPath_edit.html', {
'path': path,
'form': RealPathForm(
instance=path, prefix='path-%d' % path.id
),
}, request=request),
})
else:
# Validation error
return JsonResponse({
'success': False,
'error_message': '\n'.join(['\n'.join([force_str(i) for i in v]) for k, v in form.errors.items()]),
})
推荐阅读
- r - R中的无损JPEG
- hadoop - 什么是 Hadoop 中的作业历史服务器,为什么在 Map Reduce 模式下启动 Pig 之前必须启动历史服务器?
- power-automate - MS Flow,带有 XML 有效负载的 HTTP Webhook
- azure - 将 Umbraco 更新到 Azure 应用服务上的最新版本
- c# - 在 SignalR 方法中添加额外参数而不破坏更改
- android - 可展开的 ListView 不会在单击时展开/折叠
- go - 如何在 Golang 中删除大文件的前 N 个字节?
- node.js - 如何重构具有子查询的查询有点不同
- excel - 根据条件相乘的多个变量会产生错误
- android - 如何为您的适配器选择最佳构造函数?