在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开始:
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>
您将获得与更新视图中相同的表单,但禁用了所有字段。
沃克的回答对我帮助很大!
我用get_readonly_fields修改了他的例子,让它在Django 1.3中工作。
通常你应该在app/admin.py中声明这样的东西:
class ItemAdmin(admin.ModelAdmin):
...
readonly_fields = ('url',)
我是这样适应的:
# In the admin.py file
class ItemAdmin(admin.ModelAdmin):
...
def get_readonly_fields(self, request, obj=None):
if obj:
return ['url']
else:
return []
它工作得很好。现在,如果您添加一个Item, url字段是读写的,但在更改时它变成只读的。
下面是一个稍微复杂一点的版本,基于christophe31的回答。它不依赖于“readonly”属性。这使得它的问题,如选择框仍然是可变的,数据提取器仍然弹出,消失了。
相反,它将表单字段小部件包装在只读小部件中,从而使表单仍然有效。原始小部件的内容显示在<span class="hidden"></span>标记中。如果小部件具有render_readonly()方法,它将其用作可见文本,否则它将解析原始小部件的HTML并尝试猜测最佳表示。
import django.forms.widgets as f
import xml.etree.ElementTree as etree
from django.utils.safestring import mark_safe
def make_readonly(form):
"""
Makes all fields on the form readonly and prevents it from POST hacks.
"""
def _get_cleaner(_form, field):
def clean_field():
return getattr(_form.instance, field, None)
return clean_field
for field_name in form.fields.keys():
form.fields[field_name].widget = ReadOnlyWidget(
initial_widget=form.fields[field_name].widget)
setattr(form, "clean_" + field_name,
_get_cleaner(form, field_name))
form.is_readonly = True
class ReadOnlyWidget(f.Select):
"""
Renders the content of the initial widget in a hidden <span>. If the
initial widget has a ``render_readonly()`` method it uses that as display
text, otherwise it tries to guess by parsing the html of the initial widget.
"""
def __init__(self, initial_widget, *args, **kwargs):
self.initial_widget = initial_widget
super(ReadOnlyWidget, self).__init__(*args, **kwargs)
def render(self, *args, **kwargs):
def guess_readonly_text(original_content):
root = etree.fromstring("<span>%s</span>" % original_content)
for element in root:
if element.tag == 'input':
return element.get('value')
if element.tag == 'select':
for option in element:
if option.get('selected'):
return option.text
if element.tag == 'textarea':
return element.text
return "N/A"
original_content = self.initial_widget.render(*args, **kwargs)
try:
readonly_text = self.initial_widget.render_readonly(*args, **kwargs)
except AttributeError:
readonly_text = guess_readonly_text(original_content)
return mark_safe("""<span class="hidden">%s</span>%s""" % (
original_content, readonly_text))
# Usage example 1.
self.fields['my_field'].widget = ReadOnlyWidget(self.fields['my_field'].widget)
# Usage example 2.
form = MyForm()
make_readonly(form)
如果您需要多个只读字段。您可以使用下面给出的任何方法
方法1
class ItemForm(ModelForm):
readonly = ('sku',)
def __init__(self, *arg, **kwrg):
super(ItemForm, self).__init__(*arg, **kwrg)
for x in self.readonly:
self.fields[x].widget.attrs['disabled'] = 'disabled'
def clean(self):
data = super(ItemForm, self).clean()
for x in self.readonly:
data[x] = getattr(self.instance, x)
return data
方法2
继承的方法
class AdvancedModelForm(ModelForm):
def __init__(self, *arg, **kwrg):
super(AdvancedModelForm, self).__init__(*arg, **kwrg)
if hasattr(self, 'readonly'):
for x in self.readonly:
self.fields[x].widget.attrs['disabled'] = 'disabled'
def clean(self):
data = super(AdvancedModelForm, self).clean()
if hasattr(self, 'readonly'):
for x in self.readonly:
data[x] = getattr(self.instance, x)
return data
class ItemForm(AdvancedModelForm):
readonly = ('sku',)
为了使ForeignKey字段能够正常工作,需要做一些更改。首先,SELECT HTML标记没有readonly属性。我们需要使用disabled="disabled"来代替。但是,浏览器不会为该字段返回任何表单数据。因此,我们需要将该字段设置为非必需,以便该字段能够正确验证。然后,我们需要将值重置为以前的值,这样它就不会被设置为空白。
所以对于外键,你需要做如下的事情:
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'].required = False
self.fields['sku'].widget.attrs['disabled'] = 'disabled'
def clean_sku(self):
# As shown in the above answer.
instance = getattr(self, 'instance', None)
if instance:
return instance.sku
else:
return self.cleaned_data.get('sku', None)
通过这种方式,浏览器不会让用户更改字段,并且总是POST,因为它是空的。然后重写clean方法,将字段的值设置为实例中的原始值。
正如在这个回答中指出的,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来排除不可编辑的字段,并在模板中打印它们。