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

26 ответов:

как указано в ответ, в Django 1.9 добавил :

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

С Django 1.8 и более ранних, чтобы отключить запись в виджете и предотвратить вредоносные почтовые хаки, вы должны очистить вход в дополнение к установке 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.

The clean_sku функция гарантирует, что readonly значение не будет переопределено POST.

в противном случае нет встроенный Поле формы Django, которое будет отображать значение при отклонении связанных входных данных. Если это то, что вы хотите, вы должны создать отдельную ModelForm, что исключает нередактируемом поле(Ы), и просто распечатать их внутри вашего шаблона.

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

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

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

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

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

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

чтобы сделать эту работу для поля ForeignKey, необходимо внести несколько изменений. Во-первых, тег SELECT HTML не имеет атрибута readonly. Мы должны использовать вместо гостей с ограниченными="отключено". Однако, браузер не отправляет данные обратно на поле. Поэтому нам нужно установить, что это поле не требуется, чтобы поле правильно проверялось. Затем нам нужно сбросить значение обратно, чтобы оно не было пустым.

Так что для внешних ключей вам понадобится сделать что-то вроде:

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)

таким образом, браузер не позволит пользователю изменить поле, и всегда будет публиковать, как это было оставлено пустым. Затем мы переопределяем метод clean, чтобы установить значение поля таким, каким оно было изначально в экземпляре.

для Django 1.2+, вы можете переопределить поле вот так:

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

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

(основан на Дэниела и ответы по Muhuk)

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

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

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

я столкнулся с аналогичной проблемой. Похоже, я смог решить ее, определив метод "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 задокументирован здесь: http://docs.djangoproject.com/en/1.2/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)

Как я еще не могу прокомментировать (muhuk это), Я отвечу как отдельный ответ. Это полный пример кода, который работал для меня:

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

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

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

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

тем не менее, я столкнулся с проблемами с полем, являющимся ModelChoiceField. Все будет работать по первому требованию. Однако, если набор форм попытался добавить новый элемент и не прошел проверку, что-то пошло не так с "существующими" формами, где выбранный параметр сбрасывается по умолчанию "---------".

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

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

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

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)

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

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

Способ 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

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

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

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

вот немного более сложная версия, основанная на ответ Кристофа 31. Он не полагается на атрибут "только для чтения". Это делает его проблемы, такие как выбор коробки все еще изменчивы и datapickers все еще появляются, уходят.

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

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

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

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)

Он работает отлично!

Я решил эту проблему так:

    class UploadFileForm(forms.ModelForm):
     class Meta:
      model = FileStorage
      fields = '__all__'
      widgets = {'patient': forms.HiddenInput()}

В вид:

form = UploadFileForm(request.POST, request.FILES, instance=patient, initial={'patient': patient})

Это все.

для 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/2.0/ref/forms/fields/#disabled

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

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

формы предназначены для сбора данных, а не их отображения. Это, как говорится, параметры для отображения в readonly виджет и скраб пост данные являются прекрасными решениями.

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

Comments

    Ничего не найдено.