在Django表单中,如何将字段设置为只读(或禁用)以便没法对其进行编辑?

在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类吗? 在ItemFormItem模型类中须要进行哪些更改? 我是否须要编写另外一个类“ ItemUpdateForm ”来更新项目? 测试

def update_item_view(request):
    if request.method == 'POST':
        form = ItemUpdateForm(request.POST)
        # Validate and save
    else:
        form = ItemUpdateForm()

#1楼

对于管理员版本,若是您有多个字段,我认为这是一种更紧凑的方法: 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

#2楼

根据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)

#3楼

为了使此功能适用于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


#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

形式:

my_read_only = CharField(widget=ReadOnlyWidget())

很是简单-并让我输出。 在带有一堆只读值的表单集中很方便。 固然-您也能够更聪明一些,并给它一个attrs的div,以便您能够向其添加类。


#5楼

另两种(相似)方法,其中有一个通用示例:

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)
相关文章
相关标签/搜索