在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()
#1楼
对于管理员版本,如果您有多个字段,我认为这是一种更紧凑的方法:
def get_readonly_fields(self, request, obj=None):
skips = ('sku', 'other_field')
fields = super(ItemAdmin, self).get_readonly_fields(request, obj)
if not obj:
return [field for field in fields if not field in skips]
return fields
#2楼
根据christophe31的回答 ,这是一个涉及程度稍高的版本。 它不依赖于“只读”属性。 这样就产生了问题,例如选择框仍然可以更改,数据选择器仍然弹出。
而是将表单字段小部件包装在只读小部件中,从而使表单仍然有效。 原始窗口小部件的内容显示在<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)
#3楼
为了使此功能适用于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
方法,以将字段的值设置为实例中的原始值。
#4楼
我刚刚为一个只读字段创建了最简单的窗口小部件-我真的不明白为什么表单还没有这个:
class ReadOnlyWidget(widgets.Widget):
"""Some of these values are read only - just a bit of text..."""
def render(self, _, value, attrs=None):
return value
形式:
my_read_only = CharField(widget=ReadOnlyWidget())
非常简单-并让我输出。 在带有一堆只读值的表单集中很方便。 当然-您也可以更聪明一些,并给它一个attrs的div,以便您可以向其添加类。
#5楼
另两种(类似)方法,其中有一个通用示例:
1)第一种方法-删除save()方法中的字段,例如(未测试;)):
def save(self, *args, **kwargs):
for fname in self.readonly_fields:
if fname in self.cleaned_data:
del self.cleaned_data[fname]
return super(<form-name>, self).save(*args,**kwargs)
2)第二种方法-在清除方法中将字段重置为初始值:
def clean_<fieldname>(self):
return self.initial[<fieldname>] # or getattr(self.instance, fieldname)
基于第二种方法,我将其概括如下:
from functools import partial
class <Form-name>(...):
def __init__(self, ...):
...
super(<Form-name>, self).__init__(*args, **kwargs)
...
for i, (fname, field) in enumerate(self.fields.iteritems()):
if fname in self.readonly_fields:
field.widget.attrs['readonly'] = "readonly"
field.required = False
# set clean method to reset value back
clean_method_name = "clean_%s" % fname
assert clean_method_name not in dir(self)
setattr(self, clean_method_name, partial(self._clean_for_readonly_field, fname=fname))
def _clean_for_readonly_field(self, fname):
""" will reset value to initial - nothing will be changed
needs to be added dynamically - partial, see init_fields
"""
return self.initial[fname] # or getattr(self.instance, fieldname)
来源:oschina
链接:https://my.oschina.net/u/3797416/blog/3164004