Как в форме 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? Какие изменения потребуются в классе модели ItemForm или Item? Нужно ли мне писать другой класс "ItemUpdateForm" для обновления элемента?

def update_item_view(request):
    if request.method == 'POST':
        form = ItemUpdateForm(request.POST)
        # Validate and save
    else:
        form = ItemUpdateForm()

person X10    schedule 27.11.2008    source источник
comment
См. Также вопрос SO: Почему поля формы только для чтения в Django - плохая идея? @ stackoverflow.com/questions/2902024, Принятый ответ (от Дэниела Нааба) защищает от вредоносных взломов POST.   -  person X10    schedule 13.08.2011
comment
forms.fields.Field.disabled   -  person djvg    schedule 06.10.2020


Ответы (26)


Как указано в этот ответ, 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, который исключает неотредактируемые поля, и просто распечатать их внутри вашего шаблона.

person Daniel Naab    schedule 28.11.2008
comment
Даниэль, Спасибо, что разместили ответ. Мне непонятно, как пользоваться этим кодом? не будет ли этот код работать так же для нового режима обновления? Можете ли вы отредактировать свой ответ, чтобы привести примеры того, как его использовать для новых и обновленных форм? Спасибо. - person X10; 29.11.2008
comment
Ключ к примеру Даниэля - это тестирование поля .id. Вновь созданные объекты будут иметь id == None. Кстати, об этой проблеме посвящен один из старейших открытых тикетов Django. См. code.djangoproject.com/ticket/342. - person Peter Rowell; 29.11.2008
comment
а как насчет полей foreignkey. Это не для foreignkey - person ha22109; 26.05.2009
comment
Изменение instance.id на instance.pk означает, что он должен работать, если вы установили нестандартный первичный ключ. У меня есть код, который использует атрибут name, поэтому id не существует. - person Matt S.; 28.02.2011
comment
@Daniel, как применить clean_sku к нескольким полям (например, sku и description) - person moadeep; 17.04.2015
comment
@moadeep добавляет метод clean_description в класс формы. - person Daniel Naab; 17.04.2015
comment
в linux (ubuntu 15) / chrome v45 только чтение изменяет указатель на отключенную руку, но в этом случае поле становится интерактивным. с отключенным он работает как положено - person simone cittadini; 08.10.2015
comment
Этот ответ необходимо обновить. В Django 1.9 добавлен новый аргумент поля disabled. Если Field.disabled установлен в True, то значение POST для этого Field игнорируется. Поэтому, если вы используете 1.9, нет необходимости переопределять clean, просто установите disabled = True. Проверьте этот ответ. - person narendra-choudhary; 05.06.2016
comment
@DanielNaab: Что делать, если количество полей, которые нужно установить только для чтения, очень велико? вы рекомендуете переопределить так много clean_<field> функций? Каков наилучший способ в таком случае? - person Rahul Verma; 04.06.2018
comment
@batMan В настоящее время используйте в поле атрибут disabled, а не этот метод: docs.djangoproject.com/en/1.9/ref/forms/fields/#disabled - person Daniel Naab; 05.06.2018
comment
Я нашел, что code.djangoproject.com/ticket/17031 является кратким примером. - person Anthony Petrillo; 28.03.2021

Django 1.9 добавил атрибут Field.disabled: https://docs.djangoproject.com/en/stable/ref/forms/fields/#disabled

Логический аргумент disabled, если ему задано значение True, отключает поле формы с использованием атрибута disabled HTML, чтобы пользователи не могли его редактировать. Даже если пользователь изменяет значение поля, отправленное на сервер, оно будет проигнорировано в пользу значения из исходных данных формы.

person MDB    schedule 30.12.2015
comment
Ничего для 1.8 LTS? - person dnit13; 11.04.2016
comment
есть идеи, как мы можем использовать это в UpdateView? Поскольку он генерирует поля из модели ... - person bcsanches; 01.07.2016
comment
Правильный ответ. Мой класс решения MyChangeForm (forms.ModelForm): def __init __ (self, * args, ** kwargs): super (MyChangeForm, self) .__ init __ (* args, ** kwargs) self.fields ['my_field']. Disabled = Правда - person Vijay Katam; 18.01.2017
comment
Это проблемный ответ - установка disabled=True приведет к тому, что модель будет возвращена пользователю с ошибками проверки. - person Ben; 12.01.2018
comment
Было бы здорово, если бы вы могли включить пример - person geoidesic; 23.08.2018
comment
@Ben: Не уверен, применимо ли это к вашему случаю, но поле disabled возвращается к своему значению initial при сохранении, как указано в docs, поэтому вам может потребоваться установить FormField.initial, чтобы предотвратить ошибки проверки. - person djvg; 02.07.2020
comment
Это работает хорошо, но в случае связанных полей не скрывает ссылки «Изменить выбранное» и «Добавить другое». - person djvg; 02.07.2020

Установка readonly в виджете делает ввод в браузере доступным только для чтения. Добавление clean_sku, которое возвращает instance.sku, гарантирует, что значение поля не изменится на уровне формы.

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

Таким образом, вы можете использовать модель (неизмененное сохранение) и избежать появления ошибки, связанной с обязательным полем.

person muhuk    schedule 01.12.2008
comment
+1 Это отличный способ избежать более сложных переопределений save (). Однако вы хотите выполнить проверку экземпляра перед возвратом (в режиме комментариев без новой строки): if self.instance: return self.instance.sku; else: вернуть self.fields ['sku'] - person Daniel Naab; 25.01.2009
comment
Что касается последней строки, будет ли return self.cleaned_data['sku'] так же хорошо или лучше? docs, похоже, предлагает использовать cleaned_data: возвращаемое значение этого заменяет существующее значение в cleaned_data, поэтому это должно быть значение поля из cleaned_data (даже если этот метод не менял его) или новое очищенное значение. - person pianoJames; 24.07.2019

ответ awalker мне очень помог!

Я изменил его пример для работы с 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 доступно для чтения и записи, но при изменении оно становится доступным только для чтения.

person chirale    schedule 13.08.2011

Чтобы это работало для поля 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, чтобы установить значение поля, которое было изначально в экземпляре.

person Humphrey    schedule 14.09.2009
comment
Я попытался использовать его как форму в TabularInline, но потерпел неудачу, потому что attrs использовались совместно widget экземплярами и всеми, кроме первой строки, включая недавно добавленную, отображаемую только для чтения. - person dhill; 01.04.2016
comment
Отличное (обновленное) решение! К сожалению, у этого и остальных есть проблемы с ошибками формы, так как все отключенные значения очищаются. - person Michael Thompson; 23.05.2016

Для Django 1.2+ вы можете переопределить это поле следующим образом:

sku = forms.CharField(widget = forms.TextInput(attrs={'readonly':'readonly'}))
person StefanNch    schedule 10.01.2011
comment
Это также не позволяет редактировать поле во время добавления, о чем и идет речь в исходном вопросе. - person Matt S.; 28.02.2011
comment
Это ответ, который я ищу. Field disabled не выполняет то, что я хочу, потому что отключает поле, но также удаляет метку / делает его невидимым. - person sivabudh; 29.06.2016

Я создал класс MixIn, который вы можете унаследовать, чтобы иметь возможность добавить итерируемое поле read_only, которое отключит и защитит поля при не первом редактировании:

(На основе ответов Даниэля и Мухука)

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')
person christophe31    schedule 22.07.2011

Я только что создал простейший виджет для поля только для чтения - я действительно не понимаю, почему в формах его еще нет:

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

Очень просто - и у меня получается просто вывод. Удобен в наборе форм с кучей значений, доступных только для чтения. Конечно, вы также можете быть немного умнее и дать ему div с attrs, чтобы вы могли добавлять к нему классы.

person Danny Staple    schedule 28.02.2013
comment
Выглядит сексуально, но как обращаться с внешним ключом? - person andilabs; 08.04.2015
comment
Вместо этого, возможно, сделайте это unicode(value) в возврате. Если предположить, что обман юникода разумен, вы бы это получили. - person Danny Staple; 08.04.2015
comment
Для внешних ключей вам нужно добавить атрибут модели и использовать get (value). Проверьте мою суть - person Shadi; 04.07.2017

Я столкнулся с похожей проблемой. Похоже, я смог решить эту проблему, определив метод 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 задокументирован здесь: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#modeladmin-methods

person awalker    schedule 08.03.2011

Как я это делаю с 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
person Lucas B    schedule 24.07.2017
comment
это будет блокировать только вход на фронт. любой может обойти. это создаст проблему с безопасностью, если вы будете работать с конфиденциальными данными. - person Sarath Ak; 12.02.2020
comment
Это безопасно; он также блокируется в бэкэнде, поскольку Django ›= 1.10 docs.djangoproject.com/en/1.10/ref/forms/fields/ - person timdiels; 26.03.2020

Один простой вариант - просто ввести в шаблоне form.instance.fieldName вместо form.fieldName.

person alzclarke    schedule 16.03.2011
comment
А как насчет verbos_name или label поля? Как я могу показать метку в шаблоне django? @alzclarke - person Whale 52Hz; 05.06.2019

И снова я собираюсь предложить еще одно решение :) Я использовал код Хамфри, так что это основано на этом.

Однако у меня возникли проблемы с полем 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()

А затем в шаблоне вам нужно сделать несколько ручное зацикливание набора форм.

Итак, в этом случае вы должны сделать что-то вроде этого в шаблоне:

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

Это сработало для меня немного лучше и с меньшим количеством манипуляций с формой.

person JamesD    schedule 30.03.2012

В качестве полезного дополнения к Сообщение Хамфри, у меня были некоторые проблемы с 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)
person Evan Brumley    schedule 10.03.2011

Поскольку я еще не могу комментировать (решение мухука), я отвечу отдельным ответом. Это полный пример кода, который у меня сработал:

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

Для 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

person Ajinkya Bhosale    schedule 26.03.2018

Я столкнулся с той же проблемой, поэтому я создал 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')
person Michael    schedule 09.02.2014
comment
Я полагаю, это немного более читабельно, чем мой собственный миксин, который я предложил здесь. Возможно, даже более эффективно, поскольку эти очистки не вызывают ошибок проверки ... - person christophe31; 21.03.2015
comment
Я получаю сообщение об ошибке: 'collections.OrderedDict' object has no attribute 'iteritems' - person geoidesic; 23.08.2018

На основе 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')
person darklow    schedule 20.04.2016

если вам нужно несколько полей только для чтения. вы можете использовать любой из методов, приведенных ниже

способ 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',)
person Sarath Ak    schedule 19.08.2016

Еще два (похожих) подхода с одним обобщенным примером:

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) второй подход - сбросить поле до начального значения в чистом методе:

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)
person Robert Lujo    schedule 27.11.2013

Для версии для администратора, я думаю, это более компактный способ, если у вас более одного поля:

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
person Hassek    schedule 15.06.2012

Вот немного более сложная версия, основанная на ответ christophe31. Он не полагается на атрибут «только для чтения». Это устраняет проблемы, такие как поля выбора, которые все еще можно изменить, а датапикеры все еще появляются.

Вместо этого он оборачивает виджет полей формы в виджет только для чтения, таким образом, форма все еще проверяется. Содержимое исходного виджета отображается внутри тегов <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)
person Rune Kaagaard    schedule 04.09.2012

Вы можете элегантно добавить в виджет только для чтения:

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

Это самый простой способ?

Прямо в коде просмотра что-то вроде этого:

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)

Работает нормально!

person fly_frog    schedule 02.02.2014

Если вы работаете с Django ver < 1.9 (1.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

person Yaroslav Varkhol    schedule 03.09.2018

Если вы используете администратор Django, вот простейшее решение.

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',)
person utapyngo    schedule 24.03.2015

Я думаю, что лучшим вариантом было бы просто включить атрибут readonly в ваш шаблон, отображаемый в <span> или <p>, а не включать его в форму, если он предназначен только для чтения.

Формы предназначены для сбора данных, а не для их отображения. При этом параметры для отображения в виджете readonly и очистки данных POST - прекрасное решение.

person austinheiman    schedule 30.04.2015