Noa*_*itz 7 python django django-forms
在提交表单时,我很难弄清楚如何为ForeignKey字段自动创建模型实例.这是一个简单的玩具网站,说明了这个问题:
我有两个型号,Model1和Model2.Model2包含一个到Model1的ForeignKey.我希望用户能够通过专门选择要存储在ForeignKey中的Model1实例,或者将该值留空并让自动生成Model1的实例来创建Model2的实例.
这就是我觉得代码看起来像的样子.我的models.py代码非常简单:
# models.py
from django.db import models
from django.core.validators import MinValueValidator
class Model1(models.Model):
# Note this field cannot be negative
my_field1 = models.IntegerField(validators=[MinValueValidator(0)])
class Model2(models.Model):
# blank = True will make key_to_model1 not required on the form,
# but since null = False, I will still require the ForeignKey
# to be set in the database.
related_model1 = models.ForeignKey(Model1, blank=True)
# Note this field cannot be negative
my_field2 = models.IntegerField(validators=[MinValueValidator(0)])
Run Code Online (Sandbox Code Playgroud)
forms.py有点牵扯,但是发生的事情非常简单.如果Model2Form没有收到Model1的实例,它会尝试在clean方法中自动创建一个,验证它,如果它有效,它会保存它.如果它无效,则会引发异常.
#forms.py
from django import forms
from django.forms.models import model_to_dict
from .models import Model1, Model2
# A ModelForm used for validation purposes only.
class Model1Form(forms.ModelForm):
class Meta:
model = Model1
class Model2Form(forms.ModelForm):
class Meta:
model = Model2
def clean(self):
cleaned_data = super(Model2Form, self).clean()
if not cleaned_data.get('related_model1', None):
# Don't instantiate field2 if it doesn't exist.
val = cleaned_data.get('my_field2', None)
if not val:
raise forms.ValidationError("My field must exist")
# Generate a new instance of Model1 based on Model2's data
new_model1 = Model1(my_field1=val)
# validate the Model1 instance with a form form
validation_form_data = model_to_dict(new_model1)
validation_form = Model1Form(validation_form_data)
if not validation_form.is_valid():
raise forms.ValidationError("Could not create a proper instance of Model1.")
# set the model1 instance to the related model and save it to the database.
new_model1.save()
cleaned_data['related_model1'] = new_model1
return cleaned_data
Run Code Online (Sandbox Code Playgroud)
但是,这种方法不起作用.如果我在表单中输入有效数据,它可以正常工作.但是,如果我没有为ForeignKey输入任何内容并为整数设置负值,我会得到一个ValueError.
回溯:文件"/Library/Python/2.7/site-packages/django/core/handlers/base.py"在get_response 111中.response = callback(request,*callback_args,**callback_kwargs)File"/Library/Python/2.7 /site-packages/django/views/generic/base.py"在视图48中.返回self.dispatch(request,*args,**kwargs)文件"/Library/Python/2.7/site-packages/django/views/发送69中的generic/base.py".返回处理程序(request,*args,**kwargs)文件"/Library/Python/2.7/site-packages/django/views/generic/edit.py",位于172后. super(BaseCreateView,self).post(request,*args,**kwargs)文件"/Library/Python/2.7/site-packages/django/views/generic/edit.py"在帖子137中.如果form.is_valid( ):文件"/Library/Python/2.7/site-packages/django/forms/forms.py"在is_valid 124. return self.is_bound而不是bool(self.errors)文件"/Library/Python/2.7/site- package/django/forms/forms.py"在_get_errors 115. self.full_clean()文件"/Library/Python/2.7/site-packages/django/forms/forms.py"in full_clean 272. self._post_clean ()_post_clean 309中的文件"/Library/Python/2.7/site-packages/django/forms/models.py".self.instance = construct_instance(self,self.instance,opts.fields,opts.exclude)文件"/在construct_instance 51中的Library/Python/2.7/site-packages/django/forms/models.py".f.save_form_data(instance,cleaning_data [f.name])File"/Library/Python/2.7/site-packages/django/ db/models/fields/init .py"在save_form_data中454.setattr(instance,self.name,data)文件"/Library/Python/2.7/site-packages/django/db/models/fields/related.py"in 设置 362.(instance._meta.object_name,self.field.name))
异常类型:ValueError at/add/Exception值:不能赋值None:"Model2.related_model1"不允许空值.
那么,什么是情况是,Django是抓我ValidationError,仍然创造即使验证失败,模型2的一个实例.
我可以通过覆盖_post_clean方法来修复此问题,以便在出现错误时不创建Model2的实例.但是,这个解决方案很难看.特别是,_post_clean的行为通常非常有用 - 在更复杂的项目中,我需要_post_clean来运行其他原因.
我也可以允许ForeignKey为null但在实践中从不将其设置为null.但是,再次,这似乎是一个坏主意.
我甚至可以设置一个虚拟的Model1,只要对尝试的新Model1进行验证失败,我就会使用它,但这似乎也是hackish.
一般来说,我可以想到很多黑客来解决这个问题,但我不知道如何以一种相当干净,pythonic的方式解决这个问题.
基于 karthikr 在评论中的讨论,我找到了一个我认为可能可以接受的解决方案。我绝对仍然对其他选择持开放态度。
这个想法是使用视图中的逻辑在两种形式之间进行选择来进行验证:一种形式是标准模型形式,一种是不带ForeignKey字段的模型形式。
所以,我的 models.py 是相同的。
我的 forms.py 有两种 Model2 形式...一种非常简单,另一种没有foreignkey字段,并且具有新的逻辑来为foreignkey动态生成model1的新实例。新表单的干净逻辑就是我用来放入 Model2Form 中的干净逻辑:
#forms.py
from django import forms
from django.forms.models import model_to_dict
from .models import Model1, Model2
# A ModelForm used for validation purposes only.
class Model1Form(forms.ModelForm):
class Meta:
model = Model1
class Model2Form(forms.ModelForm):
class Meta:
model = Model2
# This inherits from Model2Form so that any additional logic that I put in Model2Form
# will apply to it.
class Model2FormPrime(Model2Form):
class Meta:
model = Model2
exclude = ('related_model1',)
def clean(self):
cleaned_data = super(Model2Form, self).clean()
if cleaned_data.get('related_model1', None):
raise Exception('Huh? This should not happen...')
# Don't instantiate field2 if it doesn't exist.
val = cleaned_data.get('my_field2', None)
if not val:
raise forms.ValidationError("My field must exist")
# Generate a new instance of Model1 based on Model2's data
new_model1 = Model1(my_field1=val)
# validate the Model1 instance with a form form
validation_form_data = model_to_dict(new_model1)
validation_form = Model1Form(validation_form_data)
if not validation_form.is_valid():
raise forms.ValidationError("Could not create a proper instance of Model1.")
# set the Model1 instance to the related model and save it to the database.
cleaned_data['related_model1'] = new_model1
return cleaned_data
def save(self, commit=True):
# Best to wait til save is called to save the instance of Model1
# so that instances aren't created when the Model2Form is invalid
self.cleaned_data['related_model1'].save()
# Need to handle saving this way because otherwise related_model1 is excluded
# from the save due to Meta.excludes
instance = super(Model2FormPrime, self).save(False)
instance.related_model1 = self.cleaned_data['related_model1']
instance.save()
return instance
Run Code Online (Sandbox Code Playgroud)
然后我的视图逻辑根据发布数据使用两种形式之一进行验证。如果它使用 Model2FormPrime 并且验证失败,它会将数据和错误移动到常规 Model2Form 以向用户显示:
# Create your views here.
from django.views.generic.edit import CreateView
from django.http import HttpResponseRedirect
from .forms import Model2Form, Model2FormPrime
class Model2CreateView(CreateView):
form_class = Model2Form
template_name = 'form_template.html'
success_url = '/add/'
def post(self, request, *args, **kwargs):
if request.POST.get('related_model', None):
# Complete data can just be sent to the standard CreateView form
return super(Model2CreateView, self).post(request, *args, **kwargs)
else:
# super does this, and I won't be calling super.
self.object = None
# use Model2FormPrime to validate the post data without the related model.
validation_form = Model2FormPrime(request.POST)
if validation_form.is_valid():
return self.form_valid(validation_form)
else:
# Create a normal instance of Model2Form to be displayed to the user
# Insantiate it with post data and validation_form's errors
form = Model2Form(request.POST)
form._errors = validation_form._errors
return self.form_invalid(form)
Run Code Online (Sandbox Code Playgroud)
这个解决方案很有效,而且非常灵活。我可以向我的模型和基本 Model2Form 添加逻辑,而不必太担心破坏它或违反 DRY。
不过,它有点难看,因为它要求我使用两种表单来完成一种表单的工作,即在表单之间传递错误。因此,如果有人能提出任何建议,我绝对愿意接受替代解决方案。