Как в форме 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()

См. Также вопрос SO: Почему поля формы только для чтения в Django - плохая идея? @ stackoverflow.com/questions/2902024, Принятый ответ (Даниэль Нааб) устраняет вредоносные взломы POST.

X10 13.08.2011 06:14
forms.fields.Field.disabled
djvg 06.10.2020 17:18
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
466
2
311 716
26
Перейти к ответу Данный вопрос помечен как решенный

Ответы 26

Ответ принят как подходящий

Как указано в этот ответ, Django 1.9 добавил атрибут Поле. Отключено:

The disabled boolean argument, when set to True, disables a form field using the disabled HTML attribute so that it won’t be editable by users. Even if a user tampers with the field’s value submitted to the server, it will be ignored in favor of the value from the form’s initial data.

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

Даниэль, Спасибо, что разместили ответ. Мне непонятно, как пользоваться этим кодом? не будет ли этот код работать так же для нового режима обновления? Можете ли вы отредактировать свой ответ, чтобы привести примеры того, как его использовать для новых и обновленных форм? Спасибо.

X10 29.11.2008 19:29

Ключ к примеру Даниэля - это тестирование поля .id. Вновь созданные объекты будут иметь id == None. Кстати, об этой проблеме посвящен один из старейших открытых тикетов Django. См. code.djangoproject.com/ticket/342.

Peter Rowell 29.11.2008 19:52

а как насчет полей foreignkey. Это не для foreignkey

ha22109 26.05.2009 14:06

Изменение instance.id на instance.pk означает, что он должен работать, если вы установили не используемый по умолчанию первичный ключ. У меня есть код, который использует атрибут name, поэтому id не существует.

Matt S. 28.02.2011 08:35

@Daniel, как применить clean_sku к нескольким полям (например, sku и description)

moadeep 17.04.2015 16:28

@moadeep добавляет метод clean_description в класс формы.

Daniel Naab 17.04.2015 19:41

в linux (ubuntu 15) / chrome v45 только чтение изменяет указатель на «отключенную руку», но в этом случае поле становится активным. с отключенным он работает как положено

simone cittadini 08.10.2015 13:19

Этот ответ необходимо обновить. В Django 1.9 добавлен новый аргумент поля disabled. Если для Field.disabled задано значение True, то значение POST для этого Field игнорируется. Так что, если вы используете 1.9, нет необходимости переопределять clean, просто установите disabled = True. Проверить ответ это.

narendra-choudhary 05.06.2016 08:09

@DanielNaab: Что делать, если количество полей, которые нужно установить только для чтения, очень велико? вы рекомендуете переопределить так много функций clean_<field>? Каков наилучший способ в таком случае?

Rahul Verma 04.06.2018 21:11

@batMan В настоящее время используйте в поле атрибут disabled, а не этот метод: docs.djangoproject.com/en/1.9/ref/forms/fields/#disabled

Daniel Naab 05.06.2018 02:40

Я нашел code.djangoproject.com/ticket/17031 как краткий пример.

Anthony Petrillo 28.03.2021 21:10

Установка readonly на виджет делает ввод в браузере доступным только для чтения. Добавление clean_sku, которое возвращает instance.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']»

Daniel Naab 25.01.2009 09:08

Что касается последней строки, будет ли return self.cleaned_data['sku'] так же хорош или лучше? документы, кажется, предлагает использовать cleaned_data: «Возвращаемое значение этого метода заменяет существующее значение в cleaned_data, поэтому оно должно быть значением поля из cleaned_data (даже если этот метод не изменил его) или новым очищенным значением».

pianoJames 24.07.2019 21:28

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

dhill 01.04.2016 12:58

Отличное (обновленное) решение! К сожалению, у этого и остальных есть проблемы с ошибками формы, так как все "отключенные" значения очищаются.

Michael Thompson 23.05.2016 09:48

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

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

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

Matt S. 28.02.2011 08:36

Это ответ, который я ищу. Fielddisabled не делает то, что я хочу, потому что он отключает поле, но также удаляет метку / делает его невидимым.

sivabudh 29.06.2016 16:33

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

get_readonly_display задокументирован здесь: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#modeladmin-methods

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

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

А как насчет verbos_name или label поля? Как я могу показать метку в шаблоне django? @alzclarke

Whale 52Hz 05.06.2019 19:12

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

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

Я создал класс 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')

ответ авокера мне очень помог!

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

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

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

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

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

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

Вот немного более сложная версия, основанная на ответ 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)

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

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

Выглядит сексуально, но как обращаться с внешним ключом?

andilabs 08.04.2015 17:00

Возможно, вместо этого сделайте этот unicode(value) взамен. Если предположить, что обман юникода разумен, вы бы это получили.

Danny Staple 08.04.2015 18:24

Для внешних ключей вам нужно добавить атрибут «модель» и использовать «get (значение)». Проверить моя суть

Shadi 04.07.2017 17:30

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

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)

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

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

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)

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

Я столкнулся с той же проблемой, поэтому я создал 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')

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

christophe31 21.03.2015 20:09

Я получаю сообщение об ошибке: 'collections.OrderedDict' object has no attribute 'iteritems'

geoidesic 23.08.2018 18:52

Если вы используете администратор 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',)

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

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

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

The disabled boolean argument, when set to True, disables a form field using the disabled HTML attribute so that it won’t be editable by users. Even if a user tampers with the field’s value submitted to the server, it will be ignored in favor of the value from the form’s initial data.

Ничего для 1.8 LTS?

dnit13 11.04.2016 18:02

есть идеи, как мы можем использовать это в UpdateView? Поскольку он генерирует поля из модели ...

bcsanches 01.07.2016 21:54

Правильный ответ. Мой класс решения MyChangeForm (forms.ModelForm): def __init __ (self, * args, ** kwargs): super (MyChangeForm, self) .__ init __ (* args, ** kwargs) self.fields ['my_field']. Disabled = Правда

Vijay Katam 18.01.2017 23:03

Это проблемный ответ - установка disabled=True приведет к тому, что модель будет возвращена пользователю с ошибками проверки.

Ben 12.01.2018 00:55

Было бы здорово, если бы вы могли включить пример

geoidesic 23.08.2018 18:00

@Ben: Не уверен, применимо ли это к вашему случаю, но поле disabled возвращается к своему значению initial при сохранении, как указано в документы, поэтому вам может потребоваться установить FormField.initial, чтобы предотвратить ошибки проверки.

djvg 02.07.2020 18:25

Это работает хорошо, но в случае связанных полей не скрывает ссылки «Изменить выбранное» и «Добавить другое».

djvg 02.07.2020 18:29

На основе Ямикеп ответ я нашел лучшее и очень простое решение, которое также обрабатывает поля 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')

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

способ 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',)

Как я это делаю с 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

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

Sarath Ak 12.02.2020 19:02

Это безопасно; он также блокируется в бэкэнде, так как Django> = 1.10 docs.djangoproject.com/en/1.10/ref/forms/fields/…

timdiels 26.03.2020 21:29

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

Если вы работаете с 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

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

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

Другие вопросы по теме