Skip to main content
 首页 » 编程设计

python之特定数据库中的 Django 模型形式 is_valid()

2024年08月23日29powertoolsteam

我在 settings.py 中定义了两个不同的数据库连接('default' 和 'banco1')。

然后在某些 View 中,我收到一个 POST,其中包含一些我想根据我创建的 ModelForm 进行验证的数据:

 product_form = ProductForm(request.POST) 
 if product_form.is_valid(): 

产品型号:

class Product(models.Model): 
 
    name = models.CharField(max_length=125, blank=False, null=False) 
    description = models.CharField(max_length=255, blank=True) 
    abbreviation = models.CharField(max_length=15, blank=True) 
    category = models.ForeignKey('inventory.Category', 
        on_delete=models.SET_NULL, null=True, blank=True) 
    metric = models.CharField(max_length=15, blank=True, null=True) 

表格:

class ProductForm(ModelForm): 
    class Meta: 
        model = Product 
        fields = ['name', 'description', 'abbreviation', 'category', 'metric'] 

View :

 def create(request): 
        if request.method != 'POST': 
            return JsonResponse('Operação inválida', safe=False) 
 
        product_form = ProductForm(request.POST) 
        if product_form.is_valid(): 
            f = product_form.save(commit = False) 
            f.save(using=request.session['database']) 
            return HttpResponse("Novo product salvo com sucesso") 
        else: 
            return HttpResponse("Impossível salvar product. Verifique os campos") 

Product 模型应该始终保存在“banco1”数据库中,因此应该从“banco1”数据库中查询 FK 验证,但每当我尝试使用 product_form.is_valid() Django 尝试查询“默认”数据库并抛出验证错误,因为在“默认”数据库中无法满足 FK 约束。

我如何设置模型表单以再次验证手动选择的数据库?

编辑

有人建议我使用 DB ROUTER 模式来选择正确的数据库配置,效果很好。我的问题是这个选择应该基于一些标准而不仅仅是模型。如何将一些变量从 View 传递到我的数据库路由器类中的 db_for_read 方法?

EDIT2

新的 ProductForm 类

class ProductForm(ModelForm): 
    class Meta: 
        model = Product 
        fields = ['name', 'description', 'abbreviation', 'category', 'metric'] 
 
    def __init__(self,database): 
        category = forms.ModelChoiceField(queryset=Category.objects.using(database).all()) 

尝试像这样在我的 View 中实例化表单:

product_form = ProductForm(request.POST, request.session['database']) 

这给我一个错误:

init() takes 2 positional arguments but 3 were given

我知道我重写 __init__ 方法的方式是无效的,但我在 python 中是菜鸟才能意识到这一点。

编辑 3

最后的错误:

Traceback:

File "C:\Users\Pavarine\Documents\Visual Studio 2015\Projects\gims\gims\env\lib\site-packages\django\core\handlers\base.py" in get_response 149. response = self.process_exception_by_middleware(e, request)

File "C:\Users\Pavarine\Documents\Visual Studio 2015\Projects\gims\gims\env\lib\site-packages\django\core\handlers\base.py" in get_response 147. response = wrapped_callback(request, *callback_args, **callback_kwargs)

File "C:\Users\Pavarine\Documents\Visual Studio 2015\Projects\gims\gims\gims\apps\inventory\views.py" in create 247. if product_form.is_valid():

File "C:\Users\Pavarine\Documents\Visual Studio 2015\Projects\gims\gims\env\lib\site-packages\django\forms\forms.py" in is_valid 161. return self.is_bound and not self.errors

File "C:\Users\Pavarine\Documents\Visual Studio 2015\Projects\gims\gims\env\lib\site-packages\django\forms\forms.py" in errors 153. self.full_clean()

File "C:\Users\Pavarine\Documents\Visual Studio 2015\Projects\gims\gims\env\lib\site-packages\django\forms\forms.py" in full_clean 362. self._clean_fields()

File "C:\Users\Pavarine\Documents\Visual Studio 2015\Projects\gims\gims\env\lib\site-packages\django\forms\forms.py" in _clean_fields 374. value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))

File "C:\Users\Pavarine\Documents\Visual Studio 2015\Projects\gims\gims\env\lib\site-packages\django\forms\widgets.py" in value_from_datadict 231. return data.get(name)

Exception Type: AttributeError at /inventory/product/create Exception Value: 'tuple' object has no attribute 'get'

请您参考如下方法:

要启用路由器,只需将它添加到 setting.py 中的 DATABASE_ROUTERS 即可,详情请见此处:https://docs.djangoproject.com/en/1.9/topics/db/multi-db/#using-routers

每个路由器方法都得到hints 字典,它应该包含代表正在使用的模型实例的instance 键。根据您希望从 View 中获取的信息,您可能会很好地处理来自实例属性的信息。看这里:https://docs.djangoproject.com/en/1.9/topics/db/multi-db/#topics-db-multi-db-hints .

即:

def db_for_read(self, model, **hints): 
    model_instance = hints.get('instance')    
    if model_instance is not None: 
        pass 
        #perform actions with model_instance attributes 
    return None 

除此之外,我认为没有其他直接的方法可以将信息从事件 View 传递到路由器,因为路由器应该使用模型并根据所使用的模型做出决策。

数据库路由器用于模型层的自动数据库选择,但模型方法允许手动选择数据库。请参阅此处的示例:https://docs.djangoproject.com/en/1.9/topics/db/multi-db/#manually-selecting-a-database

因此,另一种解决方案是重新定义表单类的方法,即 save()(请参阅 https://docs.djangoproject.com/en/1.9/topics/forms/modelforms/#the-save-method),并手动保存表单数据,并指定要与 一起使用的数据库 参数。即:

class ProductForm(ModelForm): 
    class Meta: 
        ... 
 
    def save(self, commit=True): 
        product_to_save=super(ProductForm, self).save(commit=False) 
        #the above creates product_to_save instance, but doesn't saves it to DB 
        product_to_save.save(using=='banco1') 
        return product_to_save 

在评论中,我建议将需要针对其他数据库进行验证的字段子类化,但可能还有更简单的方法......因为它是 FK 字段,它可能是 ModelChoiceField 的实例,它接受构造函数中的 queryset 参数,因此您可以提供它,即:

class ProductForm(ModelForm): 
   category_field = forms.ModelMultipleChoiceField(queryset=Category.objects.using('banco1')) 
    ... 

参见:https://docs.djangoproject.com/en/1.9/ref/forms/fields/#fields-which-handle-relationships

对 EDIT2 的回答

您重新定义构造函数的方式,您的 database 参数获取 request.POST 并且 request.session['database'] 未映射到任何东西,这就是您收到错误的原因。

您应该考虑其他参数并调用父类(super class)构造函数,否则您将破坏 MRO,所以像这样的事情应该可以完成这项工作:

class ProductForm(ModelForm): 
    ... 
 
    def __init__(self, *args, db_to_use='default', **kwargs): 
        super(ProductForm, self).__init__(*args, **kwargs) 
        #category = forms.ModelChoiceField(queryset=Category.objects.using(db_to_use).all()) 
        #the below line suggested as improvement by Alasdair and confirmed as working by Pavarine, so I updated the answer here 
        self.fields['category'].queryset = Category.objects.using(database).all() 

然后像往常一样,但使用命名参数:

product_form = ProductForm(request.POST, db_to_use=request.session['database'])