In a Django form, how do I make a field readonly (or disabled) so that it cannot be edited?
在django表单中,如何将字段设置为只读(或禁用)?
当窗体用于创建新条目时,应启用所有字段-但当记录处于更新模式时,某些字段需要为只读。
例如,创建新的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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 |
类
1 2 3 4 5 6 | def update_item_view(request): if request.method == 'POST': form = ItemUpdateForm(request.POST) # Validate and save else: form = ItemUpdateForm() |
号
如本答案所指出的,Django 1.9添加了field.disabled属性:
The disabled boolean argument, when set to True, disables a form field using the disabled HTML attribute so that it won’t be editable by users. Even if a user tampers with the field’s value submitted to the server, it will be ignored in favor of the value from the form’s initial data.
号
对于django 1.8及更早版本,要禁用小部件上的条目并防止恶意的后黑客攻击,除了在表单字段上设置
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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'] |
或者,用另一个指示正在编辑的条件替换
否则,没有内置的django表单字段在拒绝绑定的输入数据时呈现值。如果这是您想要的,那么您应该创建一个单独的
django 1.9添加了field.disabled属性:https://docs.djangoproject.com/en/stable/ref/forms/fields/disabled
The disabled boolean argument, when set to True, disables a form field using the disabled HTML attribute so that it won’t be editable by users. Even if a user tampers with the field’s value submitted to the server, it will be ignored in favor of the value from the form’s initial data.
号
在widget上设置read only只会使浏览器中的输入为只读。添加一个干净的返回instance.sku的sku可以确保字段值不会在表单级别发生更改。
1 2 3 4 5 | def clean_sku(self): if self.instance: return self.instance.sku else: return self.fields['sku'] |
号
通过这种方式,您可以使用模型的(未修改的保存)和avoid获取字段所需的错误。
阿瓦克的回答帮助了我很多!
我已经将他的示例改为使用get_readonly_字段与Django 1.3一起使用。
通常您应该在
1 2 3 | class ItemAdmin(admin.ModelAdmin): ... readonly_fields = ('url',) |
。
我是这样适应的:
1 2 3 4 5 6 7 8 | # In the admin.py file class ItemAdmin(admin.ModelAdmin): ... def get_readonly_fields(self, request, obj=None): if obj: return ['url'] else: return [] |
而且效果很好。现在,如果添加一个项,
要使此项对
因此,对于外键,您需要执行以下操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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) |
这样,浏览器就不会让用户更改字段,并且总是保留空白的
对于django 1.2+,您可以这样覆盖字段:
1 | sku = forms.CharField(widget = forms.TextInput(attrs={'readonly':'readonly'})) |
。
我创建了一个mixin类,您可以继承它来添加一个只读的iterable字段,该字段将在非第一次编辑时禁用并保护字段:
(基于丹尼尔和穆胡克的答案)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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') |
我刚刚为只读字段创建了一个可能最简单的小部件-我真的不明白表单为什么还没有这样的小部件:
1 2 3 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 |
形式:
1 | my_read_only = CharField(widget=ReadOnlyWidget()) |
。
非常简单-我只需要输出。使用一组只读值的表单集很方便。当然-你也可以更聪明一点,给它一个带有属性的DIV,这样你就可以向它附加类了。
我遇到了一个类似的问题。看起来我可以通过在modeladmin类中定义一个"get-readonly-fields"方法来解决这个问题。
像这样:
1 2 3 4 5 6 7 8 9 | # In the admin.py file class ItemAdmin(admin.ModelAdmin): def get_readonly_display(self, request, obj=None): if obj: return ['sku'] else: return [] |
好的是,添加新项目时,
get_readonly_显示记录如下:http://docs.djangoproject.com/en/1.2/ref/contrib/admin/modeladmin方法
一个简单的选择是只在模板中键入
由于我还不能发表评论(Muhuk的解决方案),我将作为一个单独的答案回答。这是一个完整的代码示例,适用于我:
1 2 3 4 5 | def clean_sku(self): if self.instance and self.instance.pk: return self.instance.sku else: return self.cleaned_data['sku'] |
。
作为Humphrey文章的一个有用补充,我对django的恢复有一些问题,因为它仍然将禁用字段注册为"changed"。以下代码修复了问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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) |
。
我如何使用django 1.11:
1 2 3 4 5 6 7 8 9 10 11 | class ItemForm(ModelForm): disabled_fields = ('added_by',) class Meta: model = Item fields = '__all__' def __init__(self, *args, **kwargs): super(ItemForm, self).__init__(*args, **kwargs) for field in self.disabled_fields: self.fields[field].disabled = True |
号
再一次,我将提供另一个解决方案:)我使用的是汉弗莱的代码,所以这是基于这个。
然而,我遇到了一个问题,这个领域是一个模式选择领域。每件事都会按第一个请求进行。但是,如果表单集试图添加新项,但验证失败,则"现有"表单出现问题,其中所选选项被重置为默认的"--------"。
不管怎么说,我不知道怎么解决这个问题。因此,(我认为这实际上在形式上更清晰),我将字段设置为hiddeninputfield()。这意味着你需要在模板中做更多的工作。
所以对我来说,解决方法是简化表单:
1 2 3 4 5 6 7 | 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'].widget=HiddenInput() |
然后在模板中,您需要对表单集进行一些手动循环。
因此,在这种情况下,您可以在模板中执行如下操作:
1 2 | {{ form.instance.sku }} <!-- This prints the value --> {{ form }} <!-- Prints form normally, and makes the hidden input --> |
号
这对我来说有点好,而且对身体的操控也更少。
我也遇到了同样的问题,所以我创建了一个mixin,它似乎适用于我的用例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class ReadOnlyFieldsMixin(object): readonly_fields =() def __init__(self, *args, **kwargs): super(ReadOnlyFieldsMixin, self).__init__(*args, **kwargs) for field in (field for name, field in self.fields.iteritems() if name in self.readonly_fields): field.widget.attrs['disabled'] = 'true' field.required = False def clean(self): cleaned_data = super(ReadOnlyFieldsMixin,self).clean() for field in self.readonly_fields: cleaned_data[field] = getattr(self.instance, field) return cleaned_data |
。
用法,只需定义哪些必须是只读的:
1 2 | class MyFormWithReadOnlyFields(ReadOnlyFieldsMixin, MyForm): readonly_fields = ('field1', 'field2', 'fieldx') |
号
还有两种(类似的)方法,一个通用示例:
1)第一种方法-删除save()方法中的字段,例如(未测试;):
1 2 3 4 5 | 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)第二种方法-将字段重置为清洁方法中的初始值:
1 2 | def clean_<fieldname>(self): return self.initial[<fieldname>] # or getattr(self.instance, fieldname) |
基于第二种方法,我将其概括如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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) |
。
如果需要多个只读字段,可以使用下面给出的任何方法。
方法1
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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
继承方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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',) |
号
下面是一个稍微复杂一点的版本,基于Christophe31的答案。它不依赖于"readonly"属性。这使得它的问题,如选择框仍然是可变的,数据选择器仍然出现,消失。
相反,它将表单字段小部件包装在只读小部件中,从而使表单仍然有效。原始小部件的内容显示在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | 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) |
。
基于Yamikep的回答,我找到了一个更好且非常简单的解决方案,它也可以处理
从
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class ReadOnlyFieldsMixin(object): readonly_fields = () def __init__(self, *args, **kwargs): super(ReadOnlyFieldsMixin, self).__init__(*args, **kwargs) for field in (field for name, field in self.fields.iteritems() if name in self.readonly_fields): field.widget.attrs['disabled'] = 'true' field.required = False def clean(self): for f in self.readonly_fields: self.cleaned_data.pop(f, None) return super(ReadOnlyFieldsMixin, self).clean() |
号
用途:
1 2 | class MyFormWithReadOnlyFields(ReadOnlyFieldsMixin, MyForm): readonly_fields = ('field1', 'field2', 'fieldx') |
号
对于管理版本,如果您有多个字段,我认为这是一种更紧凑的方法:
1 2 3 4 5 6 7 | 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 |
这是最简单的方法吗?
就在这样的视图代码中:
1 2 3 4 5 6 7 8 | def resume_edit(request, r_id): ..... r = Resume.get.object(pk=r_id) resume = ResumeModelForm(instance=r) ..... resume.fields['email'].widget.attrs['readonly'] = True ..... return render(request, 'resumes/resume.html', context) |
它工作得很好!
对于Django 1.9+可以使用fields disabled参数来禁用字段。例如,在以下Forms.py文件中的代码段中,我已禁用了Employee_code字段
1 2 3 4 5 | class EmployeeForm(forms.ModelForm): employee_code = forms.CharField(disabled=True) class Meta: model = Employee fields = ('employee_code', 'designation', 'salary') |
参考https://docs.djangoproject.com/en/2.0/ref/forms/fields/已禁用
如果使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | def bound_data_readonly(_, initial): return initial def to_python_readonly(field): native_to_python = field.to_python def to_python_filed(_): return native_to_python(field.initial) return to_python_filed def disable_read_only_fields(init_method): def init_wrapper(*args, **kwargs): self = args[0] init_method(*args, **kwargs) for field in self.fields.values(): if field.widget.attrs.get('readonly', None): field.widget.attrs['disabled'] = True setattr(field, 'bound_data', bound_data_readonly) setattr(field, 'to_python', to_python_readonly(field)) return init_wrapper class YourForm(forms.ModelForm): @disable_read_only_fields def __init__(self, *args, **kwargs): ... |
主要的想法是,如果字段是
旁白:别忘了设置
如果您使用的是django admin,这里是最简单的解决方案。
1 2 3 4 5 6 7 8 9 | class ReadonlyFieldsMixin(object): def get_readonly_fields(self, request, obj=None): if obj: return super(ReadonlyFieldsMixin, self).get_readonly_fields(request, obj) else: return tuple() class MyAdmin(ReadonlyFieldsMixin, ModelAdmin): readonly_fields = ('sku',) |
号
我认为您最好的选择是将readonly属性包含在呈现在
中的模板中,而不是将其包含在只读形式中。
表单用于收集数据,而不是显示数据。也就是说,在
我这样解决了这个问题:
1 2 3 4 5 | class UploadFileForm(forms.ModelForm): class Meta: model = FileStorage fields = '__all__' widgets = {'patient': forms.HiddenInput()} |
号
在视图中:
1 | form = UploadFileForm(request.POST, request.FILES, instance=patient, initial={'patient': patient}) |
号
就这些。