В форме Django как сделать поле доступным только для чтения (или отключенным), чтобы его нельзя было редактировать? - PullRequest
388 голосов
/ 27 ноября 2008

В форме 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()

Ответы [ 26 ]

386 голосов
/ 28 ноября 2008

Как указано в этом ответе , Django 1.9 добавил атрибут Field.disabled :

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

159 голосов
/ 31 декабря 2015

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

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

92 голосов
/ 01 декабря 2008

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

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

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

61 голосов
/ 13 августа 2011

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

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

52 голосов
/ 15 сентября 2009

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

24 голосов
/ 10 января 2011

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

sku = forms.CharField(widget = forms.TextInput(attrs={'readonly':'readonly'}))
16 голосов
/ 22 июля 2011

Я создал класс 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')
10 голосов
/ 28 февраля 2013

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

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

9 голосов
/ 08 марта 2011

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

6 голосов
/ 16 марта 2011

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

...