我的博客帖子模型标签有多对多字段:
tags = models.ManyToManyField(PostTag)
但是编辑它并不舒服,我修改了我的模型:
def _get_tagging(self): # Returns comma separated list of tags
tagging = []
for tag in self.tags.all():
tagging.append(tag.name)
return ", ".join(tagging)
def _set_tagging (self, tagging): # Saves tags from comma separated list
tagging = tagging.split(", ")
self.tags.clear()
for tag in tagging:
if len(tag) < 1:
continue
try:
self.tags.add(PostTag.objects.get(name=tag))
except ObjectDoesNotExist:
self.tags.create(name=tag)
tagging = property(_get_tagging, _set_tagging)
然后我修改了我的admin.py
:
class BlogAdminForm (forms.ModelForm):
tagging = forms.CharField(required=False, label="Tags", max_length=200,
widget=forms.TextInput(attrs={'class':'vTextField'}))
class Meta:
model = BlogPost
def __init__(self, *args, **kwargs):
super(BlogAdminForm, self).__init__(*args, **kwargs)
if kwargs.has_key('instance'):
instance = kwargs['instance']
self.initial['tagging'] = instance.tagging
def save(self, commit=True):
model = super(BlogAdminForm, self).save(commit=False)
model.tagging = self.cleaned_data["tagging"]
if commit:
model.save()
return model
这很好用,但仅适用于编辑对象。我尝试创建一个新对象时遇到错误。为什么?因为多对多关系可以与尚未在数据库中的对象一起使用并且没有主键('BlogPost'实例需要在多对多关系之前具有主键值使用)。我尝试通过以这种方式编辑save方法来解决它:
def save(self, commit=True):
model = super(BlogAdminForm, self).save(commit=False)
try:
model.tagging = self.cleaned_data["tagging"]
except ValueError:
model.save()
model.tagging = self.cleaned_data["tagging"]
if commit:
model.save()
这解决了原来的问题。但是现在model.save()
没有调用我的管理模型的save_model
方法:
class BlogAdmin (admin.ModelAdmin):
# ...
form = BlogAdminForm
def save_model(self, request, obj, form, change):
obj.author = request.user
obj.save()
由于这个原因,我收到了一个新错误:null value in column "author_id" violates not-null constraint.
我做错了什么?我可以手动调用此方法吗?
答案 0 :(得分:3)
保存实例后,您将不得不保存标记,这意味着您需要在save_model
函数中执行此操作。这与您的代码操作代码无关:如果您查看documentation for the Form.save
method,则说明:
当您的模型与另一个模型具有多对多关系时,可以看到使用
commit=False
的另一个副作用。如果模型具有多对多关系,并且在保存表单时指定commit=False
,则Django无法立即保存多对多关系的表单数据。这是因为在实例存在于数据库中之前,无法为实例保存多对多数据。要解决此问题,每次使用
commit=False
保存表单时,Django都会向save_m2m()
子类添加ModelForm
方法。手动保存表单生成的实例后,可以调用save_m2m()
来保存多对多表单数据。
有几种方法可以解决您的问题。您可以编写一个widget,在标记ID和逗号分隔的标记名称列表之间来回转换,然后在form.save_m2m()
方法中调用save_model
。但是这种方法的缺点是,在解码窗口小部件中的值时,您必须创建新标记,即使表单未保存(可能是由于表单中其他地方的验证错误)。
所以我认为在这种情况下更好的方法是将自己的save_tags
方法添加到表单中:
class BlogAdminForm(forms.ModelForm):
tagging = forms.CharField(required=False, label="Tags", max_length=200,
widget=forms.TextInput(attrs={'class':'vTextField'}))
class Meta:
model = Post
def __init__(self, *args, **kwargs):
super(BlogAdminForm, self).__init__(*args, **kwargs)
if 'instance' in kwargs:
tags = (t.name for t in kwargs['instance'].tags.all())
self.initial['tagging'] = ', '.join(tags)
def save_tags(self, obj):
obj.tags = (Tag.objects.get_or_create(name = tag.strip())[0]
for tag in self.cleaned_data['tagging'].split(','))
class BlogPostAdmin(admin.ModelAdmin):
form = BlogAdminForm
def save_model(self, request, obj, form, change):
obj.author = request.user
obj.save()
form.save_tags(obj)
请注意,我将标记操作代码移动到表单中:我认为它属于此处,而不是模型中,因为它完全是关于用户输入的。我还做了几个风格上的改进:
'instance' in kwargs
比kwargs.has_key('instance')
简单。
生成器表达式(t.name for t in kwargs['instance'].tags.all())
比在for
循环中构建列表更简单。
get_or_create
method是一个方便的快捷方式,无需try: ... except ObjectDoesNotExist: ...
您可以直接分配到ManyToMany
字段,而不是调用clear
,然后调用add
(同样,当代码不更改时效率更高)。