在Django表单中,我如何使字段只读(或禁用)?
当使用表单创建新条目时,应该启用所有字段——但当记录处于更新模式时,某些字段需要为只读。
例如,当创建一个新的Item模型时,所有字段都必须是可编辑的,但是在更新记录时,是否有一种方法禁用sku字段,使其可见,但不能编辑?
class Item(models.Model):
sku = models.CharField(max_length=50)
description = models.CharField(max_length=200)
added_by = models.ForeignKey(User)
class ItemForm(ModelForm):
class Meta:
model = Item
exclude = ('added_by')
def new_item_view(request):
if request.method == 'POST':
form = ItemForm(request.POST)
# Validate and save
else:
form = ItemForm()
# Render the view
ItemForm类可以重用吗?ItemForm或Item模型类需要做哪些更改?我是否需要编写另一个类“ItemUpdateForm”来更新项目?
def update_item_view(request):
if request.method == 'POST':
form = ItemUpdateForm(request.POST)
# Validate and save
else:
form = ItemUpdateForm()
我做了一个MixIn类,你可以继承能够添加一个read_only iterable字段,将禁用和安全的字段在非第一次编辑:
(根据Daniel和Muhuk的回答)
from django import forms
from django.db.models.manager import Manager
# I used this instead of lambda expression after scope problems
def _get_cleaner(form, field):
def clean_field():
value = getattr(form.instance, field, None)
if issubclass(type(value), Manager):
value = value.all()
return value
return clean_field
class ROFormMixin(forms.BaseForm):
def __init__(self, *args, **kwargs):
super(ROFormMixin, self).__init__(*args, **kwargs)
if hasattr(self, "read_only"):
if self.instance and self.instance.pk:
for field in self.read_only:
self.fields[field].widget.attrs['readonly'] = "readonly"
setattr(self, "clean_" + field, _get_cleaner(self, field))
# Basic usage
class TestForm(AModelForm, ROFormMixin):
read_only = ('sku', 'an_other_field')
我也遇到过类似的问题。
看起来我可以通过在ModelAdmin类中定义get_readonly_fields方法来解决这个问题。
就像这样:
# In the admin.py file
class ItemAdmin(admin.ModelAdmin):
def get_readonly_display(self, request, obj=None):
if obj:
return ['sku']
else:
return []
好的事情是,当你添加一个新的项目时,obj将是None,或者当你改变一个现有的项目时,它将是正在编辑的对象。
Get_readonly_display记录在这里。
再一次,我将提供另一个解决方案:)我正在使用Humphrey的代码,所以这是基于它。
但是,我遇到了一个ModelChoiceField字段的问题。对于第一个请求,一切都会正常工作。但是,如果表单集试图添加一个新项,但验证失败,那么“现有”表单就出了问题,其中SELECTED选项被重置为默认的---------。
总之,我不知道怎么解决这个问题。所以相反,(我认为这实际上是更干净的形式),我使字段HiddenInputField()。这只是意味着您必须在模板中多做一些工作。
所以我的解决方案是简化表单:
class ItemForm(ModelForm):
def __init__(self, *args, **kwargs):
super(ItemForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.id:
self.fields['sku'].widget=HiddenInput()
然后在模板中,您需要对该格式集进行一些手动循环。
所以,在这种情况下,你会在模板中做这样的事情:
<div>
{{ form.instance.sku }} <!-- This prints the value -->
{{ form }} <!-- Prints form normally, and makes the hidden input -->
</div>
这对我来说工作得更好一点,而且形式操作更少。
基于yamiack的回答,我找到了一个更好的、非常简单的解决方案,它也可以处理ModelMultipleChoiceField字段。
从表单中删除字段。Cleaned_data禁止保存字段:
class ReadOnlyFieldsMixin(object):
readonly_fields = ()
def __init__(self, *args, **kwargs):
super(ReadOnlyFieldsMixin, self).__init__(*args, **kwargs)
for field in (field for name, field in self.fields.iteritems() if
name in self.readonly_fields):
field.widget.attrs['disabled'] = 'true'
field.required = False
def clean(self):
for f in self.readonly_fields:
self.cleaned_data.pop(f, None)
return super(ReadOnlyFieldsMixin, self).clean()
用法:
class MyFormWithReadOnlyFields(ReadOnlyFieldsMixin, MyForm):
readonly_fields = ('field1', 'field2', 'fieldx')
从禁用字段mixin开始:
class ModelAllDisabledFormMixin(forms.ModelForm):
def __init__(self, *args, **kwargs):
'''
This mixin to ModelForm disables all fields. Useful to have detail view based on model
'''
super().__init__(*args, **kwargs)
form_fields = self.fields
for key in form_fields.keys():
form_fields[key].disabled = True
然后:
class MyModelAllDisabledForm(ModelAllDisabledFormMixin, forms.ModelForm):
class Meta:
model = MyModel
fields = '__all__'
准备视图:
class MyModelDetailView(LoginRequiredMixin, UpdateView):
model = MyModel
template_name = 'my_model_detail.html'
form_class = MyModelAllDisabledForm
把它放在my_model_detail.html模板中:
<div class="form">
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form | crispy }}
</form>
</div>
您将获得与更新视图中相同的表单,但禁用了所有字段。
正如在这个回答中指出的,Django 1.9添加了Field。禁用属性:
当disabled布尔参数设置为True时,使用disabled HTML属性禁用表单字段,以便用户不能编辑它。即使用户篡改了提交给服务器的字段值,它也会被忽略,而由表单初始数据的值代替。
在Django 1.8及更早的版本中,为了禁用小部件上的条目并防止恶意的POST攻击,除了在表单字段上设置readonly属性外,你还必须清除输入:
class ItemForm(ModelForm):
def __init__(self, *args, **kwargs):
super(ItemForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.pk:
self.fields['sku'].widget.attrs['readonly'] = True
def clean_sku(self):
instance = getattr(self, 'instance', None)
if instance and instance.pk:
return instance.sku
else:
return self.cleaned_data['sku']
或者,替换if instance和instance。Pk,另一个条件表明你正在编辑。您还可以将输入字段的属性设置为disabled,而不是readonly。
clean_sku函数将确保只读值不会被POST重写。
否则,Django没有内置的表单字段可以在拒绝绑定输入数据时呈现一个值。如果这是您想要的,那么您应该创建一个单独的ModelForm来排除不可编辑的字段,并在模板中打印它们。