Django:带有条件的ModelForm
Posted
技术标签:
【中文标题】Django:带有条件的ModelForm【英文标题】:Django : ModelForm with conditions 【发布时间】:2017-08-17 12:07:55 【问题描述】:我正在尝试创建一个表单变量。由于默认玩家的级别为 0,他可以更改名称。等到他1级的时候,就可以改名字和头像了。 3级时,他可以改变的是名字、头像和工作。等等……
Models.py:
class Player(models.Model):
level = models.SmallIntegerField(default=0)
name = models.CharField(max_length=50)
avatar = models.URLField(default='http://default-picture.com/01.png')
job = models.TextField(null=True)
Fomrs.py:
class ProfileForm(forms.ModelForm):
class Meta:
model = Player
fields = ['name', 'avatar', 'job']
widgets =
'name': forms.TextInput(),
'avatar': forms.TextInput(),
'job': forms.Textarea(),
Views.py:
def game(request, id):
user = get_object_or_404(Player, id=id)
if request.method == 'POST':
form = ProfileForm(request.POST, instance=user)
if form.is_valid():
form.save()
return HttpResponse('Success')
else:
form = ProfileForm(instance=user)
return render(request, "page/template.html",
'form': form)
模板.html:
form
可以在将表单发送到渲染引擎之前为表单的渲染添加条件吗?或者我需要在我的模板中使用条件?
我只是何时允许实例化对象根据这些参数之一具有或多或少的可能性(在示例中是玩家的级别)。
【问题讨论】:
【参考方案1】:您可以覆盖表单的__init__
方法以有条件地删除或禁用字段:
class ProfileForm(forms.ModelForm):
...
def __init__(self, *args, **kwargs):
super(ProfileForm, self).__init__(*args, **kwargs)
if self.instance and self.instance.level < 3:
self.fields['job'].disabled = True # still displays the field in the template
# del self.fields['job'] # removes field from form and template
if self.instance and self.instance.level < 1:
self.fields['avatar'].disabled = True
【讨论】:
当你说“仍然在模板中显示该字段”时,该字段没有出现,或者如果我编辑我的 html 代码,我可以删除禁用属性,输入一个新值并发布? 来自文档:“禁用的布尔参数,当设置为 True 时,使用禁用的 HTML 属性禁用表单字段,以便用户无法编辑它。即使用户篡改提交给服务器的字段值,它将被忽略,取而代之的是表单初始数据中的值。” docs.djangoproject.com/en/1.10/ref/forms/fields/#disabled 是的 Django 会忽略禁用的字段,但是如果您生成具有属性disable
的表单并且用户有脑子,他可以删除 html 代码中的disable
并发布表单。 Django不会忽略该字段,因为他没有属性disable
在服务器接收到它时不存在。
# del self.fields['job'] # removes field from form and template
抱歉,这正是我想要你写的!
对于任何在 self.fields 上循环使用 self.fields.copy() 的人的快速说明,以避免出现“迭代期间字典更改大小”错误。【参考方案2】:
Django pre_save signal 可以用来解决这个问题。
在保存模型之前,将调用pre_save
上的hook
。您可以在其中编写条件以检查是否允许用户更改字段。
您还需要存储模型的副本以比较pre_save
中的状态,这可以通过post_init 挂钩来完成。
from django.dispatch import receiver , Signal
from django.db.models.signals import post_init , pre_save
@receiver(pre_init , sender = Player)
def cache(sender , instance , **kwargs):
instance.__original_name = instance.name
instance.__original_avatar = instance.avatar
instance.__original_job = instance.job
@receiver(pre_save , sender= Player)
def check_update(sender , instance , **kwargs):
if instance.level == 1:
#Revert changes to avatar and job, and keep changes in the name
if instance.level == 2:
#Revert changes to job , and keep changes in the name and avatar
if instance.level == 3:
#Keep all changes
这样您可以跟踪所有更新字段。
【讨论】:
我看到信号是一种解决方案,信号和模型__init__
中的工作有什么区别?而这部分代码写在views.py中?
在我看来,这是一个非常优雅的解决方案,因为它将管理代码与模型定义分开。它不仅使您的代码更具可读性和自明性,而且当您进行大量操作时,通过这种方式跟踪所有钩子也很容易。
您的解决方案非常有趣,因为我认为信号是一个很好的解决方案,因此,我不只是在 html 中添加一个属性,如“只读”或“禁用”。我会试试的,谢谢。
如果是几个型号,我更喜欢写在models.py
的底部。否则我更喜欢将它们写在一个单独的文件中。
何井信号写在models.py
我不是sur。我去学习文档。【参考方案3】:
您可以在其 init 方法中自定义表单来实现此目的
对于之前描述的情况,你可以这样做:
class ProfileForm(forms.ModelForm):
class Meta:
model = Player
fields = ['name', 'avatar', 'job']
widgets =
'name': forms.TextInput(),
'avatar': forms.TextInput(),
'job': forms.Textarea(),
def __init__(self, *args, **kwargs):
super(ProfileForm, self).__init__(*args, **kwargs)
# checking if an instance exist
if self.instance.id:
# then at this point you can set as read only the fields about each case
if self.instance.level < 1:
self.fields["avatar"].widget.attrs["readonly"] = True
self.fields["job"].widget.attrs["readonly"] = True
elif self.instance.level >= 1 and self.instance.level < 3:
self.fields["job"].widget.attrs["readonly"] = True
【讨论】:
感谢您的回答,但它只是写了一个属性 html :'readonly' 不安全。如果我的用户有头脑并编辑了 html,他可以编辑值并将表单发布到它上面。但是你解决了我的另一个问题。谢谢。以上是关于Django:带有条件的ModelForm的主要内容,如果未能解决你的问题,请参考以下文章
Django - 带有 ModelForm 的属性和重新定义的字段?
带有 ModelForm 和 ModelFormSet 的 Django WizardView(这个不渲染)
带有 Select Widget 的 Django ModelForm - 使用 object.uid 作为默认选项值而不是 object.id