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

当前回答

从禁用字段mixin开始:

class ModelAllDisabledFormMixin(forms.ModelForm):
    def __init__(self, *args, **kwargs):
    '''
    This mixin to ModelForm disables all fields. Useful to have detail view based on model
    '''
    super().__init__(*args, **kwargs)
    form_fields = self.fields
    for key in form_fields.keys():
        form_fields[key].disabled = True

然后:

class MyModelAllDisabledForm(ModelAllDisabledFormMixin, forms.ModelForm):
    class Meta:
        model = MyModel
        fields = '__all__'

准备视图:

class MyModelDetailView(LoginRequiredMixin, UpdateView):
    model = MyModel
    template_name = 'my_model_detail.html'
    form_class = MyModelAllDisabledForm

把它放在my_model_detail.html模板中:

  <div class="form">
     <form method="POST" enctype="multipart/form-data">
         {% csrf_token %}
         {{ form | crispy }}
     </form>
  </div>

您将获得与更新视图中相同的表单,但禁用了所有字段。

其他回答

沃克的回答对我帮助很大!

我用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字段是读写的,但在更改时它变成只读的。

正如在这个回答中指出的,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来排除不可编辑的字段,并在模板中打印它们。

另外两种(类似的)方法和一个通用的例子:

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)第二种方法-在clean方法中将字段重置为初始值:

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)

对于django 1.9+ 您可以使用Fields disabled参数使字段禁用。 例:在下面forms.py文件的代码片段中,我已经禁用了employee_code字段

class EmployeeForm(forms.ModelForm):
    employee_code = forms.CharField(disabled=True)
    class Meta:
        model = Employee
        fields = ('employee_code', 'designation', 'salary')

参考 https://docs.djangoproject.com/en/dev/ref/forms/fields/#disabled

我刚刚为一个只读字段创建了一个最简单的小部件——我真的不明白为什么表单还没有这个:

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,这样你就可以在上面添加类了。