带有多对多的modleform表单保存到另一个数据库遇到的问题
带有多对多的modleform表单保存到另一个数据库遇到的问题
环境介绍
django版本1.11,数据库做了主从同步,读写分离。
# 涉及到读写django在save时会using不同数据库配置
class Router:
def db_for_write(self, model, **kwargs):
return 'google'
def db_for_read(self, model, **kwargs):
return 'default'
问题介绍
modelform是Model类的复用体现,从modleform生成的表单字段,是读的过程,生成的对象保存了一些数据库来源信息,带有数据的form对象有个方法,可以直接save(),不用获取出各个字段值再保存到modle的对象,再用modle的对象去save,但是如果写入另一个数据库会报错,代码片段如下:
class Customer(models.Model):
# ...
class_list = models.ManyToManyField('ClassList',)
class CustomerForm(forms.ModelForm):
class Meta:
model = models.Customer
fields = "__all__"
if request.method == 'POST':
form_obj = CustomerForm(data=request.POST, instance=customer)
if form_obj.is_valid():
customer_form=form_obj.save()
next = request.GET.get('next')
if next:
return redirect(next)
return redirect(reverse('customer_list'))
报错如下
ValueError: Cannot add "<ClassList: Linux3>": instance is on database "google", value is on database "default"
普通字段没关系,如果是外键或多对多关系,其中包含了一个关系管理对象,关系管理对象中保存的对象来源于读取的数据库。
解决方法
queryset,其中包含的是modle对象,将其转换为pk值,再去保存就会脱开原数据库。
if request.method == 'POST':
form_obj = CustomerForm(data=request.POST, instance=customer)
if form_obj.is_valid():
val = [i.pk for i in form_obj.cleaned_data['class_list'].all()]
form_obj.cleaned_data['class_list'] = val
form_obj.save()
next = request.GET.get('next')
if next:
return redirect(next)
return redirect(reverse('customer_list'))
可以先把form对象转换为model对象,再处理多对多关系,同样是要将queryset的对象转换成pk列表,更加麻烦,做法参考后面知识点。
附:官方文档save()方法解释
每个 ModelForm
还有一个 save()
方法。此方法根据绑定到表单的数据创建并保存数据库对象。 ModelForm
的子类可接受一个现有的模型实例作为关键字参数 instance
;如果提供了,则 save()
会更新这个实例。如果没有,则 save()
会创建一个对应模型的新实例。
>>> from myapp.models import Article
>>> from myapp.forms import ArticleForm
# Create a form instance from POST data.
>>> f = ArticleForm(request.POST)
# Save a new Article object from the form's data.
>>> new_article = f.save()
# Create a form to edit an existing Article, but use
# POST data to populate the form.
>>> a = Article.objects.get(pk=1)
#
>>> f = ArticleForm(request.POST, instance=a)
>>> f.save()
请注意,如果表单尚未验证,调用 save()
将通过检查 form.errors
来实现验证。如果表单验证不过,则会引发 ValueError
—— 比如,如果 form.errors
返回 True
。
save()
方法接受一个可选参数 commit
,它的值是 True
或者 False
。如果调用 save()
的时候使用 commit=False
,那么它会返回一个尚未保存到数据库的对象。在这种情况下,需要您自己在生成的模型实例上调用 save()
。如果要在保存对象之前对对象执行自定义操作,或者要使用其中一个专用的 model save options,这很有用。 commit
的值默认为 True
。
另一个使用 commit=False
的作用,您可以在模型与另一个模型有多对多关系的时候看到。如果您的模型具有多对多关系,并且在保存表单时指定了 commit=False
,Django无法立即保存多对多关系的表单数据。这是因为实例的多对多数据只有实例在数据库中存在时才能保存。
要解决这个问题,Django会在您每次使用 commit=False
保存表单时,向 ModelForm
子类添加一个 save_m2m()
方法。在您手动保存表单生成的实例后,可以调用 save_m2m()
来保存多对多的表单数据。例如:
# 用POST数据创建一个表单实例
>>> f = AuthorForm(request.POST)
# 返回一个尚未保存的实例对象
>>> new_author = f.save(commit=False)
# 修改一些数据
>>> new_author.some_field = 'some_value'
# 保存这个新的实例
>>> new_author.save()
# 调用save_m2m(),保存具有 多对多关系 的数据
>>> f.save_m2m()
只有在您使用 save(commit=False)
的时候才需要调用 save_m2m()
。当您在表单上使用普通的 save()
时,无需调用其他方法,所有数据(包括多对多数据)都会被保存。例如:
# 用POST数据创建表单实例
>>> a = Author()
>>> f = AuthorForm(request.POST, instance=a)
# 创建并保存这个新实例,不需要在做其他事
>>> new_author = f.save()
除了 save()
和 save_m2m()
方法之外,ModelForm
与普通的表单工作方式一样。例如,用 is_valid()
方法来检查合法性,用 is_multipart()
方法来确定表单是否需要multipart文件上传(之后是否必须将 request.FILES
传递给表单),等等。更多相关信息,请参阅 Binding uploaded files to a form 。