在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()
Daniel Naab.. 399
正如在这个答案中指出的,Django 1.9添加了Field.disabled属性:
禁用的boolean参数设置为True时,将使用禁用的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 and instance.pk
为表示您正在编辑的其他条件.您也可以disabled
在输入字段上设置属性,而不是readonly
.
该clean_sku
函数将确保该readonly
值不会被a覆盖POST
.
否则,没有内置的Django表单字段,它将在拒绝绑定的输入数据时呈现值.如果这是你想要的,你应该创建一个单独的ModelForm
排除不可编辑的字段,然后在模板中打印它们.
正如在这个答案中指出的,Django 1.9添加了Field.disabled属性:
禁用的boolean参数设置为True时,将使用禁用的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 and instance.pk
为表示您正在编辑的其他条件.您也可以disabled
在输入字段上设置属性,而不是readonly
.
该clean_sku
函数将确保该readonly
值不会被a覆盖POST
.
否则,没有内置的Django表单字段,它将在拒绝绑定的输入数据时呈现值.如果这是你想要的,你应该创建一个单独的ModelForm
排除不可编辑的字段,然后在模板中打印它们.
Django 1.9添加了Field.disabled属性:https://docs.djangoproject.com/en/stable/ref/forms/fields/#disabled
禁用的boolean参数设置为True时,将使用禁用的HTML属性禁用表单字段,以便用户无法编辑它.即使用户篡改了提交给服务器的字段值,也会忽略该表单的初始数据中的值.
在窗口小部件上设置READONLY只会使浏览器中的输入为只读.添加返回instance.sku的clean_sku可确保字段值不会在表单级别上更改.
def clean_sku(self): if self.instance: return self.instance.sku else: return self.fields['sku']
这样您就可以使用模型(未修改的保存)和aviod获取字段所需的错误.
awalker的答案对我帮助很大!
我使用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
字段为读写,但在更改时,它将变为只读.
要使其适用于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.2+,您可以像这样覆盖该字段:
sku = forms.CharField(widget = forms.TextInput(attrs={'readonly':'readonly'}))
我创建了一个可以继承的MixIn类,以便能够添加一个read_only可迭代字段,该字段将禁用非安全字段上的字段并保护字段:
(基于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:http://docs.djangoproject.com/en/1.2/ref/contrib/admin/#modeladmin-methods
我刚刚为readonly字段创建了最简单的小部件 - 我真的不明白为什么表单没有这个:
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())
很简单 - 让我只输出.在一个带有一堆只读值的formset中得心应手.当然 - 你也可以更聪明一点,给它一个带有attrs的div,这样你就可以为它添加类.
一种简单的选择是只输入form.instance.fieldName
模板而不是form.fieldName
。
作为Humphrey帖子的有用补充,我在django-reversion中遇到了一些问题,因为它仍然将禁用的字段注册为"已更改".以下代码修复了该问题.
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: try: self.changed_data.remove('sku') except ValueError, e: pass return instance.sku else: return self.cleaned_data.get('sku', None)
由于我还不能评论(muhuk的解决方案),我会作为一个单独的答案作出回应.这是一个完整的代码示例,对我有用:
def clean_sku(self): if self.instance and self.instance.pk: return self.instance.sku else: return self.cleaned_data['sku']