在Django表單中,如何將字段設爲只讀(或禁用)? html
當使用表單建立新條目時,應啓用全部字段-可是,當記錄處於更新模式時,某些字段必須是隻讀的。 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()
對於管理員版本,若是您有多個字段,我認爲這是一種更緊湊的方法: ui
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
根據christophe31的回答 ,這是一個涉及程度稍高的版本。 它不依賴於「只讀」屬性。 這樣就產生了問題,例如選擇框仍然能夠更改,數據選擇器仍然彈出。 spa
而是將表單字段小部件包裝在只讀小部件中,從而使表單仍然有效。 原始窗口小部件的內容顯示在<span class="hidden"></span>
標記內。 若是窗口小部件具備render_readonly()
方法,它將使用該方法做爲可見文本,不然它將解析原始窗口小部件的HTML並嘗試猜想最佳表示形式。 code
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)
爲了使此功能適用於ForeignKey
字段,須要進行一些更改。 首先, SELECT HTML
標記不具備readonly屬性。 咱們須要改用disabled="disabled"
。 可是,而後瀏覽器不會將該字段的任何表單數據發送回。 所以,咱們須要將該字段設置爲不須要,以便該字段正確驗證。 而後,咱們須要將值重置爲之前的值,這樣就不會將其設置爲空。 orm
所以,對於外鍵,您將須要執行如下操做: xml
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
方法,以將字段的值設置爲實例中的原始值。 htm
我剛剛爲一個只讀字段建立了最簡單的窗口小部件-我真的不明白爲何表單尚未這個:
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,以便您能夠向其添加類。
另兩種(相似)方法,其中有一個通用示例:
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)