在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

其他回答

正如在这个回答中指出的,Django 1.9添加了Field。禁用属性:

当disabled布尔参数设置为True时,使用disabled 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和instance。Pk,另一个条件表明你正在编辑。您还可以将输入字段的属性设置为disabled,而不是readonly。

clean_sku函数将确保只读值不会被POST重写。

否则,Django没有内置的表单字段可以在拒绝绑定输入数据时呈现一个值。如果这是您想要的,那么您应该创建一个单独的ModelForm来排除不可编辑的字段,并在模板中打印它们。

再一次,我将提供另一个解决方案:)我正在使用Humphrey的代码,所以这是基于它。

但是,我遇到了一个ModelChoiceField字段的问题。对于第一个请求,一切都会正常工作。但是,如果表单集试图添加一个新项,但验证失败,那么“现有”表单就出了问题,其中SELECTED选项被重置为默认的---------。

总之,我不知道怎么解决这个问题。所以相反,(我认为这实际上是更干净的形式),我使字段HiddenInputField()。这只是意味着您必须在模板中多做一些工作。

所以我的解决方案是简化表单:

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()

然后在模板中,您需要对该格式集进行一些手动循环。

所以,在这种情况下,你会在模板中做这样的事情:

<div>
    {{ form.instance.sku }} <!-- This prints the value -->
    {{ form }} <!-- Prints form normally, and makes the hidden input -->
</div>

这对我来说工作得更好一点,而且形式操作更少。

如果你使用的是Django version < 1.9(1.9添加了Field. conf)。禁用属性),你可以尝试向你的form __init__方法添加以下装饰器:

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):
        ...

主要思想是,如果字段是只读的,你不需要任何其他值,除了初始值。

注:不要忘记设置yuor_form_field.widget。attrs['readonly'] = True

在小部件上设置readonly只会使浏览器中的输入变为只读。添加一个返回实例的clean_sku。Sku确保字段值不会在表单级别上改变。

def clean_sku(self):
    if self.instance: 
        return self.instance.sku
    else: 
        return self.fields['sku']

这样您就可以使用model的(未修改的保存)并避免获得字段所需的错误。

基于yamiack的回答,我找到了一个更好的、非常简单的解决方案,它也可以处理ModelMultipleChoiceField字段。

从表单中删除字段。Cleaned_data禁止保存字段:

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()

用法:

class MyFormWithReadOnlyFields(ReadOnlyFieldsMixin, MyForm):
    readonly_fields = ('field1', 'field2', 'fieldx')