에 Django 형태로,어떻게 만들 필드 readonly(또는 비활성화)도록 편집할 수 없습니다?

StackOverflow https://stackoverflow.com/questions/324477

  •  11-07-2019
  •  | 
  •  

문제

에 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 재사용 될?어떤 변화가 필요한 것에 ItemFormItem 모델 class?내가 쓸 필요가 다른 클래스를,"ItemUpdateForm"에 대한 업데이트 항목?

def update_item_view(request):
    if request.method == 'POST':
        form = ItemUpdateForm(request.POST)
        # Validate and save
    else:
        form = ItemUpdateForm()
도움이 되었습니까?

해결책

지적한대로 이 답변, django 1.9가 추가되었습니다 field.disabled 기인하다:

장애인 부울 인수는 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.

그만큼 clean_sku 기능은 readonly 값은 a POST.

그렇지 않으면, 바운드 입력 데이터를 거부하면서 값을 렌더링하는 내장 Django Form 필드가 없습니다. 이것이 당신이 원하는 것이라면, 당신은 대신 별도를 만들어야합니다. ModelForm 이는 부탁 할 수없는 필드를 제외하고 템플릿 내부에 인쇄합니다.

다른 팁

Django 1.9는 필드를 추가했습니다. https://docs.djangoproject.com/en/stable/ref/forms/fields/#disabled

장애인 부울 인수는 true로 설정 될 때 비활성화 된 HTML 속성을 사용하여 양식 필드를 비활성화하여 사용자가 편집 할 수 없도록합니다. 사용자가 필드의 값을 서버에 제출 한 탬퍼에해도 양식의 초기 데이터에서 값에 유리하게 무시됩니다.

위젯에서만 읽기 만하면 브라우저에서만 입력됩니다. 인스턴스를 반환하는 clean_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 필드는 읽기 쓰기이지만 변경시 읽기 전용이됩니다.

이 작업을 위해 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 메소드 필드의 값을 원래 인스턴스에 있던 것으로 설정하는 방법.

Django 1.2+의 경우 : SO와 같은 필드를 무시할 수 있습니다.

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

나는 당신이 비 첫 편집에서 필드를 비활성화하고 보안 할 read_only 반복 필드를 추가 할 수 있도록 상속받을 수있는 Mixin 클래스를 만들었습니다.

(Daniel과 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')

방금 Readonly 필드를 위해 가장 간단한 위젯을 만들었습니다. 왜 형태가 이미 이것을 가지고 있지 않은지 알지 못합니다.

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

매우 간단합니다 - 그리고 저를 출력하게 만듭니다. 읽기 전용 값이 많은 양식 세트에 편리합니다. 물론 - 당신은 또한 조금 더 영리하고 attrs와 함께 div를 줄 수 있으므로 수업을 추가 할 수 있습니다.

나는 비슷한 문제를 겪었다. ModelAdmin 클래스에서 "get_readonly_fields"메소드를 정의하여 해결할 수있는 것 같습니다.

이 같은:

# 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

간단한 옵션 중 하나는 단지 입력하는 것입니다 form.instance.fieldName 대신 템플릿에서 form.fieldName.

유용한 추가 험프리의 게시물, 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)

아직 댓글을 달 수 없으므로 (무 후크의 해결책), 나는 별도의 답변으로 응답 할 것이다. 이것은 나에게 효과가있는 완전한 코드 예입니다.

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.모든 것이 작업에 처음 요청을 합니다.그러나,경우 formset 도 새로운 항목을 추가하려면과 유효성을 검사하지 못했,뭔가 잘못된 것으로'기존'형태를 선택한 옵션이었는 기본값으로 재설정"---------".

어쨌든,나는 방법을 알아낼 수 없을 수정합니다.그래서 그 대신,(고 내 생각에 이것은 실제로 깨끗 양식에서),나는 분야 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()

한 후 템플릿에서,당신이 할 필요가 일부 수동 반복의 formset.

그래서,이 경우 다음과 같은 것이 가능합니다 템플릿에서:

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

이 근무 조금 더 나은 나를 위해 그리고 더 적은 양식을 조작입니다.

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

하나의 일반화 된 예제와 함께 두 가지 (유사한) 접근 방식 :

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)

여러 개의 읽기 전용 필드가 필요한 경우 아래에 주어진 메소드를 사용할 수 있습니다.

방법 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',)

관리자 버전의 경우, 여러분이 둘 이상의 필드를 가지고 있다면 더 컴팩트 한 방법이라고 생각합니다.

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

다음은 다음을 기반으로 약간 더 관련된 버전입니다. Christophe31의 대답. "Readonly"속성에 의존하지 않습니다. 이로 인해 일부 상자가 여전히 변하기 쉬우 며 DataPickers가 여전히 팝업되고 사라지는 것과 같은 문제가 발생합니다.

대신, 그것은 형태의 위젯을 준비된 위젯에 랩핑하여 형태가 여전히 유효성을 유지합니다. 원래 위젯의 내용이 내부에 표시됩니다 <span class="hidden"></span> 태그. 위젯에 a 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)

잘 작동합니다!

Django의 경우 1.9+
필드 비활성화 인수를 사용하여 필드 비활성화를 수행 할 수 있습니다. eg forms.py 파일에서 다음 코드 스 니펫에서 다음과 같은 코드 필드를 비활성화했습니다.

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 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.

추신 : 설정하는 것을 잊지 마십시오 yuor_form_field.widget.attrs['readonly'] = True

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 위젯 및 스크럽 포스트 데이터는 훌륭한 솔루션입니다.

이 문제를 다음과 같이 해결했습니다.

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

그게 다야.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top