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





Как указано в этот ответ, 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, который исключает неотредактируемые поля, и просто распечатать их внутри вашего шаблона.
Даниэль, Спасибо, что разместили ответ. Мне непонятно, как пользоваться этим кодом? не будет ли этот код работать так же для нового режима обновления? Можете ли вы отредактировать свой ответ, чтобы привести примеры того, как его использовать для новых и обновленных форм? Спасибо.
Ключ к примеру Даниэля - это тестирование поля .id. Вновь созданные объекты будут иметь id == None. Кстати, об этой проблеме посвящен один из старейших открытых тикетов Django. См. code.djangoproject.com/ticket/342.
а как насчет полей foreignkey. Это не для foreignkey
Изменение instance.id на instance.pk означает, что он должен работать, если вы установили не используемый по умолчанию первичный ключ. У меня есть код, который использует атрибут name, поэтому id не существует.
@Daniel, как применить clean_sku к нескольким полям (например, sku и description)
@moadeep добавляет метод clean_description в класс формы.
в linux (ubuntu 15) / chrome v45 только чтение изменяет указатель на «отключенную руку», но в этом случае поле становится активным. с отключенным он работает как положено
Этот ответ необходимо обновить. В Django 1.9 добавлен новый аргумент поля disabled. Если для Field.disabled задано значение True, то значение POST для этого Field игнорируется. Так что, если вы используете 1.9, нет необходимости переопределять clean, просто установите disabled = True. Проверить ответ это.
@DanielNaab: Что делать, если количество полей, которые нужно установить только для чтения, очень велико? вы рекомендуете переопределить так много функций clean_<field>? Каков наилучший способ в таком случае?
@batMan В настоящее время используйте в поле атрибут disabled, а не этот метод: docs.djangoproject.com/en/1.9/ref/forms/fields/#disabled
Я нашел code.djangoproject.com/ticket/17031 как краткий пример.
Установка 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']»
Что касается последней строки, будет ли return self.cleaned_data['sku'] так же хорош или лучше? документы, кажется, предлагает использовать cleaned_data: «Возвращаемое значение этого метода заменяет существующее значение в cleaned_data, поэтому оно должно быть значением поля из cleaned_data (даже если этот метод не изменил его) или новым очищенным значением».
Чтобы это работало для поля 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 и всей строкой, кроме первой, включая недавно добавленную, отображаемую только для чтения.
Отличное (обновленное) решение! К сожалению, у этого и остальных есть проблемы с ошибками формы, так как все "отключенные" значения очищаются.
Для Django 1.2+ вы можете переопределить это поле следующим образом:
sku = forms.CharField(widget = forms.TextInput(attrs = {'readonly':'readonly'}))
Это также не позволяет редактировать поле во время добавления, о чем и идет речь в исходном вопросе.
Это ответ, который я ищу. Fielddisabled не делает то, что я хочу, потому что он отключает поле, но также удаляет метку / делает его невидимым.
Я столкнулся с похожей проблемой. Похоже, я смог решить эту проблему, определив метод 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
Поскольку я еще не могу комментировать (решение мухука), я отвечу отдельным ответом. Это полный пример кода, который у меня сработал:
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, чтобы вы могли добавлять к нему классы.
Выглядит сексуально, но как обращаться с внешним ключом?
Возможно, вместо этого сделайте этот unicode(value) взамен. Если предположить, что обман юникода разумен, вы бы это получили.
Для внешних ключей вам нужно добавить атрибут «модель» и использовать «get (значение)». Проверить моя суть
Еще два (похожих) подхода с одним обобщенным примером:
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')
Я полагаю, это немного более читабельно, чем мой собственный миксин, который я предложил здесь. Возможно, даже более эффективно, поскольку эти очистки не вызывают ошибок проверки ...
Я получаю сообщение об ошибке: 'collections.OrderedDict' object has no attribute 'iteritems'
Если вы используете администратор 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?
есть идеи, как мы можем использовать это в UpdateView? Поскольку он генерирует поля из модели ...
Правильный ответ. Мой класс решения MyChangeForm (forms.ModelForm): def __init __ (self, * args, ** kwargs): super (MyChangeForm, self) .__ init __ (* args, ** kwargs) self.fields ['my_field']. Disabled = Правда
Это проблемный ответ - установка disabled=True приведет к тому, что модель будет возвращена пользователю с ошибками проверки.
Было бы здорово, если бы вы могли включить пример
@Ben: Не уверен, применимо ли это к вашему случаю, но поле disabled возвращается к своему значению initial при сохранении, как указано в документы, поэтому вам может потребоваться установить FormField.initial, чтобы предотвратить ошибки проверки.
Это работает хорошо, но в случае связанных полей не скрывает ссылки «Изменить выбранное» и «Добавить другое».
На основе Ямикеп ответ я нашел лучшее и очень простое решение, которое также обрабатывает поля 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
это будет блокировать только вход на фронт. любой может обойти. это создаст проблему с безопасностью, если вы будете работать с конфиденциальными данными.
Это безопасно; он также блокируется в бэкэнде, так как Django> = 1.10 docs.djangoproject.com/en/1.10/ref/forms/fields/…
Для 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}),
}
См. Также вопрос SO: Почему поля формы только для чтения в Django - плохая идея? @ stackoverflow.com/questions/2902024, Принятый ответ (Даниэль Нааб) устраняет вредоносные взломы POST.