Django 管理界面未及时调用对象的保存方法
我在Django中有两个应用,其中一个应用的模型(ScopeItem
)在创建实例时必须同时创建另一个应用的模型实例(Workflow
);也就是说,ScopeItem
包含它的工作流程。
在命令行中测试时,这个功能运行得很好。创建一个新的ScopeItem
会同时创建一个Workflow
并将其存储在ScopeItem
中。但是在管理后台,我遇到了一个错误,提示workflow
属性是必需的。这个属性没有被填入,而模型定义要求它必须设置。不过,我重写的save
方法是可以做到这一点的。因此,我的问题是,如何在管理后台检查之前调用save
方法?
如果我在管理后台选择一个已有的Workflow
实例并保存(成功保存后),我可以看到我的save
方法会在之后被调用,这时会创建一个新的Workflow
并附加到ScopeItem
实例上。只是这个调用的时机太晚了。
我知道我可以允许ScopeItem
中的workflow
属性为空,或者将ScopeItem
和Workflow
类合并,以避免在管理后台出现这个问题。不过这两种方法以后都会带来麻烦,我想避免这种“黑科技”。
另外,我不想在save_item
中重复代码。仅仅从那里调用save
显然是不够的。
这是来自scopeitems/models.py
的代码:
class ScopeItem(models.Model):
title = models.CharField(max_length=64)
description = models.CharField(max_length=4000, null=True)
workflow = models.ForeignKey(Workflow)
def save(self, *args, **kwargs):
if not self.id:
workflow = Workflow(
description='ScopeItem %s workflow' % self.title,
status=Workflow.PENDING)
workflow.save()
self.workflow = workflow
super(ScopeItem, self).save(*args, **kwargs)
还有workflow/models.py
:
from django.utils.timezone import now
class Workflow(models.Model):
PENDING = 0
APPROVED = 1
CANCELLED = 2
STATUS_CHOICES = (
(PENDING, 'Pending'),
(APPROVED, 'Done'),
(CANCELLED, 'Cancelled'),
)
description = models.CharField(max_length=4000)
status = models.IntegerField(choices=STATUS_CHOICES)
approval_date = models.DateTimeField('date approved', null=True)
creation_date = models.DateTimeField('date created')
update_date = models.DateTimeField('date updated')
def save(self, *args, **kwargs):
if not self.id:
self.creation_date = now()
self.update_date = now()
super(Workflow, self).save(*args, **kwargs)
在scopeitems/admin.py
中,我有:
from django.contrib import admin
from .models import ScopeItem
from workflow.models import Workflow
class ScopeItemAdmin(admin.ModelAdmin):
list_display = ('title', 'description', 'status')
list_filter = ('workflow__status', )
search_fields = ['title', 'description']
def save_model(self, request, obj, form, change):
obj.save()
def status(self, obj):
return Workflow.STATUS_CHOICES[obj.workflow.status][1]
admin.site.register(ScopeItem, ScopeItemAdmin)
4 个回答
@Daniel Roseman的回答是对的,只要你在后台管理界面不需要修改工作流字段。如果你需要修改它,那么你就得在管理表单上写一个自定义的clean()
方法。
forms.py
class ScopeItemAdminForm(forms.ModelForm):
class Meta:
model = ScopeItem
def clean(self):
cleaned_data = super(ScopeItemAdminForm, self).clean()
if 'pk' not in self.instance:
workflow = Workflow(
description='ScopeItem %s workflow' % self.title,
status=Workflow.PENDING)
workflow.save()
self.workflow = workflow
return cleaned_data
admin.py
class ScopeItemAdmin(admin.ModelAdmin):
form = ScopeItemAdminForm
...
admin.site.register(ScopeItem, ScopeItemAdmin)
你可以在 workflow
字段上设置 blank=True
。
你提到不想在 ScopeItem
中允许“空的 workflow
属性”。设置 blank=True
只是为了验证的需要。因此,在后台 workflow
仍然会是 NOT NULL
。根据 Django 的文档:
如果一个字段设置了
blank=True
,表单验证会允许输入空值。
根据你的例子,你应该可以使用:
workflow = models.ForeignKey(Workflow, blank=True)
你需要把这个字段从管理员使用的表单中去掉,这样它就不会被验证了。
class ScopeItemForm(forms.ModelForm):
class Meta:
exclude = ('workflow',)
model = ScopeItem
class ScopeItemAdmin(admin.ModelAdmin):
form = ScopeItemForm
...
admin.site.register(ScopeItem, ScopeItemAdmin)
我来回答我自己的问题:
正如@pcoronel所建议的,ScopeItem
中的workflow
属性必须设置blank=True
,这样才能在表单中正常使用。
按照@hellsgate的建议,重写表单的clean
方法也是必要的,这样才能创建并保存新的Workflow
。
为了避免代码重复,我在workflow/models.py
中添加了一个函数:
def create_workflow(title="N/A"):
workflow = Workflow(
description='ScopeItem %s workflow' % title,
status=Workflow.PENDING)
workflow.save()
return workflow
这使得ScopeItemAdminForm
看起来像这样:
class ScopeItemAdminForm(forms.ModelForm):
class Meta:
model = ScopeItem
def clean(self):
cleaned_data = super(ScopeItemAdminForm, self).clean()
cleaned_data['workflow'] = create_workflow(cleaned_data['title'])
return cleaned_data
另外,我还在scopeitems/models.py
中修改了save
方法为:
def save(self, *args, **kwargs):
if not self.id:
if not self.workflow:
self.workflow = create_workflow(self.title)
super(ScopeItem, self).save(*args, **kwargs)