495

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

28 に答える 28

473

この回答で指摘されているように、Django 1.9 はField.disabled属性を追加しました。

disabled ブール値引数を True に設定すると、無効化された HTML 属性を使用してフォーム フィールドが無効になり、ユーザーが編集できなくなります。ユーザーがサーバーに送信されたフィールドの値を改ざんした場合でも、フォームの初期データの値が優先されるため無視されます。

readonlyDjango 1.8 以前では、ウィジェットのエントリを無効にして悪意のある POST ハッキングを防ぐために、フォーム フィールドに属性を設定するだけでなく、入力をスクラブする必要があります。

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、編集できないフィールドを除外する別のフィールドを作成し、テンプレート内にそれらを印刷する必要があります。

于 2008-11-28T04:09:23.527 に答える
191

Django 1.9 は Field.disabled 属性を追加しました: https://docs.djangoproject.com/en/stable/ref/forms/fields/#disabled

disabled ブール値引数を True に設定すると、無効化された HTML 属性を使用してフォーム フィールドが無効になり、ユーザーが編集できなくなります。ユーザーがサーバーに送信されたフィールドの値を改ざんした場合でも、フォームの初期データの値が優先されるため無視されます。

于 2015-12-30T22:22:00.283 に答える
99

ウィジェットでの設定readonlyは、ブラウザーでの入力を読み取り専用にするだけです。clean_skuwhich を追加するとinstance.sku、フォーム レベルでフィールド値が変更されなくなります。

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

このようにして、モデルの (変更されていない保存) を使用して、フィールドに必要なエラーが発生するのを回避できます。

于 2008-12-01T17:25:04.500 に答える
63

フィールドでこれを機能させる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、フィールドの値をインスタンス内の元の値に設定します。

于 2009-09-14T23:47:47.857 に答える
35

Django 1.2+ の場合、次のようにフィールドをオーバーライドできます。

sku = forms.CharField(widget = forms.TextInput(attrs={'readonly':'readonly'}))
于 2011-01-10T14:03:02.673 に答える
18

最初の編集以外でフィールドを無効にして保護するread_only反復可能フィールドを追加できるように継承できるMixInクラスを作成しました。

(ダニエルとムフクの回答に基づく)

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')
于 2011-07-22T11:54:23.557 に答える
12

読み取り専用フィールド用に可能な限り単純なウィジェットを作成しました - フォームにこれがまだない理由がよくわかりません:

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 を指定して、クラスを追加することもできます。

于 2013-02-28T11:40:12.783 に答える
11

同様の問題に遭遇しました。クラスにget_readonly_fieldsメソッドを定義することで解決できたようです。ModelAdmin

このようなもの:

# 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ここに文書化されています。

于 2011-03-08T20:51:34.080 に答える
6

1 つの簡単なオプションはform.instance.fieldName、代わりにテンプレートを入力することですform.fieldName

于 2011-03-16T08:09:03.427 に答える
6

繰り返しますが、もう 1 つの解決策を提供します :) 私はHumphrey のコードを使用していたので、これはそれに基づいています。

ただし、フィールドがModelChoiceField. 最初のリクエストですべてが機能します。ただし、フォームセットが新しいアイテムを追加しようとして検証に失敗した場合、SELECTEDオプションがデフォルトにリセットされている「既存の」フォームで問題が発生していました---------

とにかく、私はそれを修正する方法を理解できませんでした。その代わりに (そして、これは実際にはフォームがきれいだと思います)、 fields を作成しました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()

次に、テンプレートで、 formset の手動ループを実行する必要があります。

したがって、この場合、テンプレートで次のようにします。

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

これは私にとっては少しうまく機能し、フォーム操作が少なくなりました.

于 2012-03-30T13:36:21.720 に答える
5

Humphrey の投稿への便利な追加として、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)
于 2011-03-10T03:28:10.370 に答える
5

まだコメントできないので(ムフクの解決策)、別の回答として回答します。これは私のために働いた完全なコード例です:

def clean_sku(self):
  if self.instance and self.instance.pk:
    return self.instance.sku
  else:
    return self.cleaned_data['sku']
于 2011-05-13T16:06:00.807 に答える
4

複数の読み取り専用フィールドが必要な場合は、以下のいずれかの方法を使用できます

方法 1

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

方法 2

継承方法

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',)
于 2016-08-19T10:13:11.297 に答える
3

管理者バージョンの場合、複数のフィールドがある場合、これはよりコンパクトな方法だと思います:

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
于 2012-06-15T21:44:12.160 に答える
2

これは、 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)
于 2012-09-04T10:50:36.010 に答える
0

あなたの最善の選択肢は、読み取り専用の場合はフォームに含めるのではなく、<span>レンダリングされたテンプレートに読み取り専用属性を含めることだと思います。<p>

フォームはデータを収集するためのものであり、表示するためのものではありません。readonlyそうは言っても、ウィジェットに表示して POST データをスクラブするオプションは優れたソリューションです。

于 2015-04-30T18:15:20.690 に答える
0

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',)
于 2015-03-24T06:37:40.760 に答える