ChatGPT解决这个技术问题 Extra ChatGPT

在 Django 表单中,如何将字段设置为只读(或禁用)以使其无法编辑?

在 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()
另请参阅 SO 问题:为什么 Django 中的只读表单字段是个坏主意? @ stackoverflow.com/questions/2902024 ,接受的答案(丹尼尔·纳布)负责恶意 POST 黑客攻击。

S
Saransh Singh

正如 this answer 中所指出的,Django 1.9 添加了 Field.disabled 属性:

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 and instance.pk 替换为另一个表明您正在编辑的条件。您还可以在输入字段上设置属性 disabled,而不是 readonly

clean_sku 函数将确保 readonly 值不会被 POST 覆盖。

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


丹尼尔,感谢您发布答案。我不清楚如何使用此代码?此代码对于新模式和更新模式是否同样适用?您能否编辑您的答案以举例说明如何将其用于新表单和更新表单?谢谢。
Daniel 示例的关键是测试 .id 字段。新创建的对象将具有 id==None。顺便说一句,最古老的开放 Django 票之一就是关于这个问题的。请参阅code.djangoproject.com/ticket/342
@moadeep 向表单类添加一个 clean_description 方法。
在 linux (ubuntu 15 ) / chrome v45 上,只读将指针更改为“残疾手”,但该字段随后是可点击的。禁用它按预期工作
这个答案需要更新。在 Django 1.9 中添加了一个新的字段参数 disabled。如果 Field.disabled 设置为 True,则忽略该 Field 的 POST 值。因此,如果您使用的是 1.9,则无需覆盖 clean,只需设置 disabled = True。检查 this 答案。
S
Saransh Singh

Django 1.9 添加了 Field.disabled 属性:https://docs.djangoproject.com/en/stable/ref/forms/fields/#disabled

disabled 布尔参数,当设置为 True 时,使用 disabled HTML 属性禁用表单字段,以便用户无法编辑它。即使用户篡改了提交给服务器的字段值,它也会被忽略,取而代之的是表单初始数据中的值。


1.8 LTS 什么都没有?
知道我们如何在 UpdateView 上使用它吗?当它从模型生成字段时...
正确答案。我的解决方案类 MyChangeForm(forms.ModelForm): def __init__(self, *args, **kwargs): super(MyChangeForm, self).__init__(*args, **kwargs) self.fields['my_field'].disabled =真的
这是一个有问题的答案 - 设置 disabled=True 将导致模型向用户吐出验证错误。
如果你能包括一个例子,那就太棒了
L
LostMyGlasses

在小部件上设置 readonly 只会使浏览器中的输入变为只读。添加返回 instance.skuclean_sku 可确保字段值不会在表单级别更改。

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

这样您就可以使用模型(未修改的保存)并避免出现所需字段错误。


+1 这是避免更复杂的 save() 覆盖的好方法。但是,您希望在返回之前进行实例检查(在无换行注释模式下):“if self.instance: return self.instance.sku; else: return self.fields['sku']”
对于最后一行,return self.cleaned_data['sku'] 会一样好还是更好? docs 似乎建议使用 cleaned_data:“此方法的返回值替换 cleaned_data 中的现有值,因此它必须是 cleaned_data 中的字段值(即使此方法没有更改它)或新的清洁值。”
C
Community

awalker's answer对我帮助很大!

我已使用 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 []

它工作正常。现在,如果您添加一个项目,url 字段是可读写的,但在更改时它变为只读。


如何做到这一点,而不能在现场写下来?
第一个代码片段完全禁止在 url 字段上写入,第二个代码片段仅在现有 Item 实例上禁止在 url 字段上写入。您可以更改条件以获得不同的行为,但如果我正确理解问题,则不能同时使用两者。
我尝试了 readonly_fields,但它不起作用,因为我还必须有 fields。我所做的是显示变量中的值,现在它们只是只读的。
D
Daniel Holmes

要使这项工作适用于 ForeignKey 字段,需要进行一些更改。首先,SELECT HTML 标签没有 readonly 属性。我们需要改用 disabled="disabled"。但是,浏览器不会为该字段发送回任何表单数据。所以我们需要将该字段设置为不需要,以便该字段正确验证。然后我们需要将值重置回原来的值,这样它就不会被设置为空白。

因此,对于外键,您需要执行以下操作:

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 方法以将字段的值设置为实例中的原始值。


我尝试将其用作 TabularInline 中的表单,但失败了,因为 attrswidget 实例之间共享,并且除第一行之外的所有实例(包括新添加的行)都呈现为只读。
一个很棒的(更新)解决方案!不幸的是,当所有“禁用”值都被清空时,当出现表单错误时,这个和其他问题都会出现问题。
P
Peter Mortensen

对于 Django 1.2+,您可以像这样覆盖该字段:

sku = forms.CharField(widget = forms.TextInput(attrs={'readonly':'readonly'}))

这也不允许在添加时编辑该字段,这就是原始问题的含义。
这是我正在寻找的答案。 Field disabled 没有做我想做的事,因为它禁用了该字段,但也删除了标签/使其不可见。
P
Peter Mortensen

我创建了一个 MixIn 类,您可以继承它以便能够添加一个 read_only 可迭代字段,该字段将在非首次编辑时禁用和保护字段:

(基于 Daniel 和 Muhuk 的回答)

from django import forms
from django.db.models.manager import Manager

# I used this instead of lambda expression after scope problems
def _get_cleaner(form, field):
    def clean_field():
         value = getattr(form.instance, field, None)
         if issubclass(type(value), Manager):
             value = value.all()
         return value
    return clean_field

class ROFormMixin(forms.BaseForm):
    def __init__(self, *args, **kwargs):
        super(ROFormMixin, self).__init__(*args, **kwargs)
        if hasattr(self, "read_only"):
            if self.instance and self.instance.pk:
                for field in self.read_only:
                    self.fields[field].widget.attrs['readonly'] = "readonly"
                    setattr(self, "clean_" + field, _get_cleaner(self, field))

# Basic usage
class TestForm(AModelForm, ROFormMixin):
    read_only = ('sku', 'an_other_field')

D
Danny Staple

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

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,这样你就可以向它附加类。


看起来很性感,但是外键怎么处理?
也许在返回中使用 unicode(value)。假设 unicode dunder 是明智的,那么您就会明白这一点。
对于外键,您需要添加“模型”属性并使用“get(value)”。检查my gist
d
djvg

我遇到了类似的问题。看起来我可以通过在我的 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 将为无,或者当您更改现有项目时,它将是正在编辑的对象。

get_readonly_display 已记录在案 here


我猜 get_readonly_display 现在应该是 get_readonly_fields...
p
pyjavo

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


L
Lucas B

我如何使用 Django 1.11 做到这一点:

class ItemForm(ModelForm):
    disabled_fields = ('added_by',)

    class Meta:
        model = Item
        fields = '__all__'

    def __init__(self, *args, **kwargs):
        super(ItemForm, self).__init__(*args, **kwargs)
        for field in self.disabled_fields:
            self.fields[field].disabled = True

这只会从正面阻止。任何人都可以绕过。如果您对敏感数据进行操作,这将造成安全问题
它是安全的;由于 Django >= 1.10 docs.djangoproject.com/en/1.10/ref/forms/fields/…,它还在后端阻塞
非常感谢,它节省了很多时间,并且在后端也有验证!
P
Peter Mortensen

一种简单的选择是在模板中键入 form.instance.fieldName 而不是 form.fieldName


那么字段的 verbos_namelabel 呢?如何在 django 模板中显示“标签”? @alzclarke
p
pyjavo

再一次,我将提供另一种解决方案 :) 我使用的是 Humphrey's code,所以这是基于此的。

但是,我遇到了字段是 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()

然后在模板中,您需要做一些 manual looping of the formset

因此,在这种情况下,您将在模板中执行以下操作:

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

这对我来说效果更好,而且形式操作更少。


C
Community

作为对 Humphrey's post 的有用补充,我在 django-reversion 方面遇到了一些问题,因为它仍然将禁用字段注册为“已更改”。下面的代码解决了这个问题。

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:
            try:
                self.changed_data.remove('sku')
            except ValueError, e:
                pass
            return instance.sku
        else:
            return self.cleaned_data.get('sku', None)

C
Community

由于我还不能发表评论 (muhuk's solution),我将作为单独的答案回复。这是一个完整的代码示例,对我有用:

def clean_sku(self):
  if self.instance and self.instance.pk:
    return self.instance.sku
  else:
    return self.cleaned_data['sku']

M
Michael

我遇到了同样的问题,所以我创建了一个似乎适用于我的用例的 Mixin。

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):
        cleaned_data = super(ReadOnlyFieldsMixin,self).clean()
        for field in self.readonly_fields:
           cleaned_data[field] = getattr(self.instance, field)

        return cleaned_data

用法,只需定义哪些必须是只读的:

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

我想它比我在这里建议的我自己的 mixin 更具可读性。甚至可能更有效,因为这些清理不会引发验证错误……
我收到一个错误:'collections.OrderedDict' object has no attribute 'iteritems'
C
Community

基于 Yamikep's answer,我找到了一个更好且非常简单的解决方案,它还可以处理 ModelMultipleChoiceField 字段。

form.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')

S
Sarath Ak

如果您需要多个只读字段。您可以使用下面给出的任何方法

方法一

class ItemForm(ModelForm):
    readonly = ('sku',)

    def __init__(self, *arg, **kwrg):
        super(ItemForm, self).__init__(*arg, **kwrg)
        for x in self.readonly:
            self.fields[x].widget.attrs['disabled'] = 'disabled'

    def clean(self):
        data = super(ItemForm, self).clean()
        for x in self.readonly:
            data[x] = getattr(self.instance, x)
        return data

方法二

继承方法

class AdvancedModelForm(ModelForm):


    def __init__(self, *arg, **kwrg):
        super(AdvancedModelForm, self).__init__(*arg, **kwrg)
        if hasattr(self, 'readonly'):
            for x in self.readonly:
                self.fields[x].widget.attrs['disabled'] = 'disabled'

    def clean(self):
        data = super(AdvancedModelForm, self).clean()
        if hasattr(self, 'readonly'):
            for x in self.readonly:
                data[x] = getattr(self.instance, x)
        return data


class ItemForm(AdvancedModelForm):
    readonly = ('sku',)

S
Sourabh SInha

您可以在小部件中优雅地添加只读:

class SurveyModaForm(forms.ModelForm):
    class Meta:
        model  = Survey
        fields = ['question_no']
        widgets = {
        'question_no':forms.NumberInput(attrs={'class':'form-control','readonly':True}),
        }

R
Robert Lujo

带有一个通用示例的另外两种(相似)方法:

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)

P
Peter Mortensen

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

M
Michael Mandel

这是基于 christophe31's answer 的稍微复杂的版本。它不依赖于“只读”属性。这使得它的问题,比如选择框仍然可以改变和数据选择器仍然弹出,消失了。

相反,它将表单字段小部件包装在只读小部件中,从而使表单仍然有效。原始小部件的内容显示在 <span class="hidden"></span> 标记内。如果小部件具有 render_readonly() 方法,它会将其用作可见文本,否则它会解析原始小部件的 HTML 并尝试猜测最佳表示。

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)

R
Richard Scholtens

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

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

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

在下面的示例中,我将表单类与 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__ 方法来初始化表单。但是,在此方法中,电子邮件字段参数“已禁用”设置为 True。这样做会改变前端字段的行为,从而导致只读字段即使更改 HTML 代码也无法编辑。 Reference 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”。

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

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

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


# 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 路由的操作标签。按下更新按钮后,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'),
    ]

你有它。使用表单的基于类的视图的完整实现,因此可以将电子邮件字段更改为只读和禁用。

对于非常详细的示例,我深表歉意。可能有更有效的方法来设计基于类的视图,但这应该可行。当然,我可能对某些事情说错了。我也在学习。如果有人有任何意见或改进,请告诉我!


D
Dhiaa Al-Shalabi

你可以这样做:

检查请求是更新还是保存新对象。如果请求是更新,则禁用字段 sku。如果请求是添加一个新对象,那么您必须在不禁用字段 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):
    def disable_sku_field(self):
        elf.fields['sku'].widget.attrs['readonly'] = True

    class Meta:
        model = Item
        exclude = ('added_by')

def new_item_view(request):
    if request.method == 'POST':
        form = ItemForm(request.POST)
        # Just create an object or instance of the form.
        # Validate and save
    else:
            form = ItemForm()
    # Render the view

def update_item_view(request):
    if request.method == 'POST':
        form = ItemForm(request.POST)
        # Just create an object or instance of the form.
        # Validate and save
    else:
        form = ItemForm()
        form.disable_sku_field() # call the method that will disable field.

    # Render the view with the form that will have the `sku` field disabled on it.


J
Jonas Kölker

这是最简单的方法吗?

就在这样的视图代码中:

def resume_edit(request, r_id):
    .....    
    r = Resume.get.object(pk=r_id)
    resume = ResumeModelForm(instance=r)
    .....
    resume.fields['email'].widget.attrs['readonly'] = True 
    .....
    return render(request, 'resumes/resume.html', context)

它工作正常!


Y
Yaroslav Varkhol

如果您正在使用 Django ver < 1.91.9 已添加 Field.disabled 属性),您可以尝试将以下装饰器添加到您的表单 __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):
        ...

主要思想是,如果字段为 readonly,您不需要除 initial 之外的任何其他值。

PS:别忘了设置yuor_form_field.widget.attrs['readonly'] = True


u
utapyngo

如果您使用的是 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',)

P
Peter Mortensen

我认为您最好的选择是将只读属性包含在以 <span><p> 呈现的模板中,而不是将其包含在表单中(如果它是只读的)。

表格用于收集数据,而不是显示它。话虽如此,在 readonly 小部件中显示和清理 POST 数据的选项是很好的解决方案。