【问题标题】:In a Django form, how do I make a field readonly (or disabled) so that it cannot be edited?在 Django 表单中,如何将字段设置为只读(或禁用)以使其无法编辑?
【发布时间】:2010-09-24 09:07:21
【问题描述】:

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

【问题讨论】:

标签: django forms field readonly


【解决方案1】:

正如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 来排除不可编辑的字段,然后将它们打印在您的模板中。

【讨论】:

  • 丹尼尔,感谢您发布答案。我不清楚如何使用此代码?此代码对于新模式和更新模式是否同样适用?您能否编辑您的答案以举例说明如何将其用于新表单和更新表单?谢谢。
  • 丹尼尔示例的关键是测试 .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答案。
【解决方案2】:

在小部件上设置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 的字段值(即使这个方法没有改变它)或新的清洁值。”
【解决方案3】:

要使 ForeignKey 字段能够使用此功能,需要进行一些更改。首先,SELECT HTML 标签没有只读属性。我们需要改用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 实例之间共享,并且除了第一行之外的所有实例(包括新添加的行)都呈现为只读。
  • 一个很棒的(更新)解决方案!不幸的是,当所有“禁用”值都被清空时,当出现表单错误时,这个和其他问题都会出现问题。
【解决方案4】:

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

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

【讨论】:

  • 这也不允许在添加时编辑该字段,这是原始问题的内容。
  • 这是我正在寻找的答案。 Field disabled 没有做我想做的事,因为它会禁用该字段,但也会删除标签/使其不可见。
【解决方案5】:

我遇到了类似的问题。 看起来我可以通过在我的 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

【讨论】:

【解决方案6】:

作为对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)

【讨论】:

    【解决方案7】:

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

    【讨论】:

    • 那么字段的verbos_namelabel 怎么样?如何在 django 模板中显示“标签”? @alzclarke
    【解决方案8】:

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

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

    【讨论】:

      【解决方案9】:

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

      (基于 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')
      

      【讨论】:

        【解决方案10】:

        awalker's answer帮了我很多忙!

        我已将他的示例更改为使用 Django 1.3,使用 get_readonly_fields

        通常你应该在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 字段是可读写的,但在更改时它变为只读。

        【讨论】:

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

        再一次,我将提供另一个解决方案 :) 我使用的是 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>
        

        这对我来说效果更好,并且表单操作更少。

        【讨论】:

          【解决方案12】:

          对于管理员版本,如果您有多个字段,我认为这是一种更紧凑的方式:

          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
          

          【讨论】:

            【解决方案13】:

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

            相反,它将表单字段小部件包装在只读小部件中,从而使表单仍然有效。原始小部件的内容显示在&lt;span class="hidden"&gt;&lt;/span&gt; 标签内。如果小部件有 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)
            

            【讨论】:

              【解决方案14】:

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

              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
              【解决方案15】:

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

              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)
              

              【讨论】:

                【解决方案16】:

                这是最简单的方法吗?

                在视图代码中是这样的:

                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)
                

                效果很好!

                【讨论】:

                  【解决方案17】:

                  我遇到了同样的问题,所以我创建了一个似乎适用于我的用例的 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'
                  【解决方案18】:

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

                  【讨论】:

                    【解决方案19】:

                    我认为你最好的选择就是在你的模板中包含 readonly 属性,以 &lt;span&gt;&lt;p&gt; 呈现,而不是在表单中包含它(如果它是只读的)。

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

                    【讨论】:

                      【解决方案20】:

                      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 将导致模型向用户吐出验证错误。
                      • 如果你能包括一个例子会很棒
                      【解决方案21】:

                      基于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')
                      

                      【讨论】:

                        【解决方案22】:

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

                        方法一

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

                        【讨论】:

                          【解决方案23】:

                          我如何使用 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/…
                          • 非常感谢,它节省了很多时间,并且在后端也有验证!
                          【解决方案24】:

                          对于 django 1.9+
                          您可以使用 Fields disabled 参数来禁用字段。 例如在 forms.py 文件中的以下代码 sn-p 中,我已禁用 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

                          【讨论】:

                            【解决方案25】:

                            如果您正在使用Django ver &lt; 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,您不需要任何其他值。

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

                            【讨论】:

                              【解决方案26】:

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

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

                              【讨论】:

                                【解决方案27】:

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

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

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

                                在下面的示例中,我将表单类与 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 %}
                                
                                

                                可以看出,form标签包含一个action标签,它保存着视图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'),
                                    ]
                                
                                

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

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

                                【讨论】:

                                  【解决方案28】:

                                  你可以这样做:

                                  1. 检查请求是更新还是保存新对象。
                                  2. 如果请求是更新,则禁用字段sku
                                  3. 如果请求是添加一个新对象,那么您必须在不禁用字段 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.
                                  
                                  

                                  【讨论】:

                                    猜你喜欢
                                    • 2019-09-08
                                    • 2013-06-16
                                    • 2016-07-15
                                    • 1970-01-01
                                    • 1970-01-01
                                    • 1970-01-01
                                    • 2011-02-14
                                    • 2013-09-20
                                    • 1970-01-01
                                    相关资源
                                    最近更新 更多