在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()
对于Admin版本,如果你有多个字段,我认为这是一个更紧凑的方式:
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
我刚刚为一个只读字段创建了一个最简单的小部件——我真的不明白为什么表单还没有这个:
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,这样你就可以在上面添加类了。
对于Admin版本,如果你有多个字段,我认为这是一个更紧凑的方式:
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
我做了一个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')
从禁用字段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>
您将获得与更新视图中相同的表单,但禁用了所有字段。
为了使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方法,将字段的值设置为实例中的原始值。