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

当前回答

今天,我在一个类似的用例中遇到了完全相同的问题。然而,我必须处理基于类的视图。基于类的视图允许继承属性和方法,从而更容易以简洁的方式重用代码。

我将通过讨论为用户创建个人资料页面所需的代码来回答您的问题。在这个页面上,他们可以更新他们的个人信息。但是,我想在不允许用户更改信息的情况下显示电子邮件字段。

是的,我本可以省略电子邮件字段,但我的强迫症不允许这样做。

在下面的例子中,我使用了一个form类和disabled = True方法的组合。这段代码在Django==2.2.7上测试。


# form class in forms.py

# Alter import User if you have created your own User class with Django default as abstract class.
from .models import User 
# from django.contrib.auth.models import User

# Same goes for these forms.
from django.contrib.auth.forms import UserCreationForm, UserChangeForm


class ProfileChangeForm(UserChangeForm):

    class Meta(UserCreationForm)
        model = User
        fields = ['first_name', 'last_name', 'email',]

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['email'].disabled = True

可以看到,已经指定了所需的用户字段。这些字段必须显示在配置文件页面上。如果需要添加其他字段,则必须在User类中指定它们,并将属性名称添加到此表单的Meta类的字段列表中。

获得所需的元数据后,调用__init__方法初始化表单。然而,在这个方法中,email字段参数“disabled”被设置为True。通过这样做,前端字段的行为被改变,导致一个只读字段,即使改变HTML代码也不能编辑。参考Field.disabled

为了完成,在下面的例子中可以看到使用表单所需的基于类的视图。


# view class in views.py

from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import TemplateView, UpdateView
from django.utils.translation import gettext_lazy as _


class ProfileView(LoginRequiredMixin, TemplateView):
    template_name = 'app_name/profile.html'
    model = User


   def get_context_data(self, **kwargs):
      context = super().get_context_data(**kwargs)
      context.update({'user': self.request.user, })
      return context


class UserUpdateView(LoginRequiredMixin, SuccesMessageMixin, UpdateView):
    template_name = 'app_name/update_profile.html'
    model = User
    form_class = ProfileChangeForm
    success_message = _("Successfully updated your personal information")


    def get_success_url(self):
        # Please note, one has to specify a get_absolute_url() in the User class
        # In my case I return:  reverse("app_name:profile")
        return self.request.user.get_absolute_url()


    def get_object(self, **kwargs):
        return self.request.user


    def form_valid(self, form):
        messages.add_message(self.request, messages.INFO, _("Successfully updated your profile"))
        return super().form_valid(form)


ProfileView类只显示带有用户信息的HTML页面。此外,它还拥有一个按钮,如果按下该按钮,就会转到由UserUpdateView配置的HTML页面,即“app_name/update_profile.html”。可以看到,UserUpdateView拥有两个额外的属性,即'form_class'和'success_message'。

视图知道页面上的每个字段都必须用来自User模型的数据填充。然而,通过引入'form_class'属性,视图不会得到User字段的默认布局。相反,它被重定向为通过表单类检索字段。这在灵活性方面有巨大的优势。

通过使用表单类,可以为不同的用户显示具有不同限制的不同字段。如果在模型内部设置限制,每个用户都会得到相同的待遇。

模板本身不是那么壮观,但可以在下面看到。


# HTML template in 'templates/app_name/update_profile.html' 

{% extends "base.html" %}
{% load static %}
{% load crispy_form_tags %}


{% block content %}


<h1>
    Update your personal information
<h1/>
<div>
    <form class="form-horizontal" method="post" action="{% url 'app_name:update' %}">
        {% csrf_token %} 
        {{ form|crispy }}
        <div class="btn-group">
            <button type="submit" class="btn btn-primary">
                Update
            </button>
        </div>
</div>


{% endblock %}

可以看到,表单标记包含一个操作标记,该操作标记包含视图URL路由。 在按下Update按钮后,UserUpdateView被激活,并验证是否满足所有条件。如果是,将触发form_valid方法并添加一条成功消息。在成功更新数据之后,用户将返回到get_success_url方法中指定的URL。

下面是允许视图的URL路由的代码。

# URL routing for views in urls.py

from django.urls import path
from . import views

app_name = 'app_name'

urlpatterns = [
    path('profile/', view=views.ProfileView.as_view(), name='profile'),
    path('update/', view=views.UserUpdateView.as_view(), name='update'),
    ]

你知道了。一个使用form的基于类的视图的完整实现,因此可以将电子邮件字段更改为只读和禁用。

我为这个极其详细的例子道歉。可能有更有效的方法来设计基于类的视图,但这应该是可行的。当然,有些话我可能说错了。我也还在学习。如果任何人有任何意见或改进,让我知道!

其他回答

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

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

我也遇到过类似的问题。 看起来我可以通过在ModelAdmin类中定义get_readonly_fields方法来解决这个问题。

就像这样:

# In the admin.py file

class ItemAdmin(admin.ModelAdmin):

    def get_readonly_display(self, request, obj=None):
        if obj:
            return ['sku']
        else:
            return []

好的事情是,当你添加一个新的项目时,obj将是None,或者当你改变一个现有的项目时,它将是正在编辑的对象。

Get_readonly_display记录在这里。

如果你正在使用Django admin,这里有一个最简单的解决方案。

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',)

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

对于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