在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)第一种方法-删除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)第二种方法-在clean方法中将字段重置为初始值:
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)
我也遇到过类似的问题。
看起来我可以通过在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记录在这里。
为了使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方法,将字段的值设置为实例中的原始值。
你可以这样做:
检查请求是否更新或保存一个新对象。
如果请求更新,则禁用字段sku。
如果请求是添加一个新对象,那么您必须在不禁用字段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):
def disable_sku_field(self):
elf.fields['sku'].widget.attrs['readonly'] = True
class Meta:
model = Item
exclude = ('added_by')
def new_item_view(request):
if request.method == 'POST':
form = ItemForm(request.POST)
# Just create an object or instance of the form.
# Validate and save
else:
form = ItemForm()
# Render the view
def update_item_view(request):
if request.method == 'POST':
form = ItemForm(request.POST)
# Just create an object or instance of the form.
# Validate and save
else:
form = ItemForm()
form.disable_sku_field() # call the method that will disable field.
# Render the view with the form that will have the `sku` field disabled on it.
如果您需要多个只读字段。您可以使用下面给出的任何方法
方法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',)