Loading

带有多对多的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

【官方链接】

posted @ 2020-09-16 21:24  陌路麒麟  阅读(365)  评论(0编辑  收藏  举报