我有一个多对多关系的模型。在pre_save
信号中,我想预处理实例数据:
@receiver(pre_save, sender=Book)
def pre_save_tags(sender, instance, **kwargs):
# tags is a many-to-many relation
print instance.tags
tags
设置在SelectMultiple
字段中。因此,它有多个值。
ValueError: 'Book' instance needs to have a primary key value before a many-to-many relationship can be used.
我不仅限于pre_save
。如果有更好的解决方案,我会全力以赴。
答案 0 :(得分:0)
这是因为对象是新的,并且尚未保存。因为它没有主键。如果保存之前保存的对象,则不应发生此错误。
为什么不使用post_save。在post_save中,对象已保存,因此具有主键。如果必须使用pre_save,则可以检查instance.id是否存在。如果它不存在,则不能有任何标记(因为对象必须具有ManyToMany-fields的主要ID才能工作(因此例外))
要测试主键,您可以执行以下操作:
@receiver(pre_save, sender=Book)
def pre_save_tags(sender, instance, **kwargs):
# tags is a many-to-many relation
if instance.id:
print instance.tags
答案 1 :(得分:0)
知道了。我相信有更优雅的方式。如果您知道如何改进代码,请给我一些指示或提供更好的解决方案:
class ManyToManyTagField(ManyToManyField):
def formfield(self, **kwargs):
defaults = {
'form_class': TagField,
}
return super(ManyToManyTagField, self).formfield(**defaults)
def save_form_data(self, instance, data):
tags = []
for tag_id in data:
autocompletedTag = tag_id.find('___')
if autocompletedTag > 0:
parts = tag_id.split('___')
tags.append(parts[0])
else:
newTag = Tag(name=tag_id, category=TagCategory.objects.get(name='New'))
newTag.save()
tags.append(newTag.id)
return super(ManyToManyTagField, self).save_form_data(instance, tags)
相关部分是方法save_form_data
。
以下是其他课程,仅为了完整起见。
TagWidget:
class TagWidget(SelectMultiple):
"""
Widget for Tag input fields.
"""
queryset = []
def render(self, name, value, attrs=None):
final_attrs = self.build_attrs(attrs, name=name)
final_attrs['class'] = 'tag-input'
selectedTags = []
if value:
for tag_id in value:
if isinstance(tag_id, (int, long)) or tag_id.isdigit():
tag_name = Tag.objects.get(pk=tag_id).name
selectedTags.append({ 'value': str(tag_id) + '___' + tag_name, 'label': tag_name })
else:
autocompletedTag = tag_id.find('___')
if autocompletedTag > 0:
parts = tag_id.split('___')
selectedTags.append({ 'value': parts[0] + '___' + parts[1], 'label': parts[1] })
else:
selectedTags.append({ 'value': tag_id, 'label': tag_id })
allTags = []
for tag in self.queryset:
allTags.append({ 'value': str(tag.id) + '___' + str(tag.name), 'label': str(tag.name) });
allTagsJson = simplejson.dumps(allTags)
return render_to_string('form/tagwidget.html',
{'attrs': flatatt(final_attrs), 'selectedTags': selectedTags, 'allTags': allTagsJson})
TagField:
class TagField(ModelMultipleChoiceField):
widget = TagWidget
def __init__(self, queryset, *args, **kwargs):
self.widget.queryset = queryset
super(TagField, self).__init__(queryset, *args, **kwargs)
def clean(self, value):
if self.required and not value:
raise ValidationError(self.error_messages['required'])
elif not self.required and not value:
return []
if not isinstance(value, (list, tuple)):
raise ValidationError(self.error_messages['list'])
# Since this overrides the inherited ModelChoiceField.clean
# we run custom validators here
self.run_validators(value)
return value
tagwidget.html:
{% autoescape off %}
<ul{{ attrs }}>
{% for tag in selectedTags %}
<li tagValue="{{ tag.value }}">{{ tag.label }}</li>
{% endfor %}
</ul>
<script type="text/javascript">
var autocompletionData = {{ allTags }};
</script>
{% endautoescape %}