Trong một hình thức Django, làm cách nào để tạo một trường chỉ đọc (hoặc bị vô hiệu hóa) để nó không thể được chỉnh sửa?


430

Trong một hình thức Django, làm cách nào để tạo một trường chỉ đọc (hoặc bị vô hiệu hóa)?

Khi biểu mẫu đang được sử dụng để tạo một mục mới, tất cả các trường sẽ được bật - nhưng khi bản ghi ở chế độ cập nhật, một số trường cần phải ở chế độ chỉ đọc.

Ví dụ: khi tạo một Itemmô hình mới , tất cả các trường phải có thể chỉnh sửa được, nhưng trong khi cập nhật bản ghi, có cách nào để vô hiệu hóa skutrường để nó hiển thị, nhưng không thể chỉnh sửa?

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

Lớp học ItemFormcó thể được tái sử dụng? Những thay đổi nào sẽ được yêu cầu trong lớp ItemFormhoặc Itemmô hình? Tôi có cần viết một lớp khác không, " ItemUpdateForm", để cập nhật mục này?

def update_item_view(request):
    if request.method == 'POST':
        form = ItemUpdateForm(request.POST)
        # Validate and save
    else:
        form = ItemUpdateForm()

Xem thêm câu hỏi SO: Tại sao các trường mẫu chỉ đọc trong Django là một ý tưởng tồi? @ stackoverflow.com/questions/2902024 , Câu trả lời được chấp nhận (của Daniel Naab) sẽ xử lý các bản hack POST độc hại.
X10

Câu trả lời:


420

Như đã chỉ ra trong câu trả lời này , Django 1.9 đã thêm thuộc tính Field.disables :

Đối số boolean bị vô hiệu hóa, khi được đặt thành True, sẽ vô hiệu hóa trường biểu mẫu bằng cách sử dụng thuộc tính HTML bị vô hiệu hóa để người dùng không thể chỉnh sửa nó. Ngay cả khi người dùng chỉnh sửa giá trị của trường được gửi đến máy chủ, nó sẽ bị bỏ qua có lợi cho giá trị từ dữ liệu ban đầu của biểu mẫu.

Với Django 1.8 trở về trước, để vô hiệu hóa mục nhập trên tiện ích và ngăn chặn các bản hack POST độc hại, bạn phải xóa đầu vào bên cạnh việc đặt readonlythuộc tính trên trường biểu mẫu:

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

Hoặc, thay thế if instance and instance.pkbằng một điều kiện khác cho biết bạn đang chỉnh sửa. Bạn cũng có thể đặt thuộc tính disabledtrên trường đầu vào, thay vì readonly.

Các clean_skuchức năng sẽ đảm bảo rằng các readonlygiá trị sẽ không được ghi đè bởi một POST.

Mặt khác, không có trường biểu mẫu Django tích hợp sẽ hiển thị giá trị trong khi từ chối dữ liệu đầu vào bị ràng buộc. Nếu đây là những gì bạn mong muốn, thay vào đó, bạn nên tạo một phần riêng ModelFormloại trừ trường không thể chỉnh sửa và chỉ in chúng trong mẫu của bạn.


3
Daniel, Cảm ơn bạn đã gửi câu trả lời. Tôi không rõ làm thế nào để sử dụng mã này? Mã này có hoạt động tương tự cho chế độ cập nhật mới không? Bạn có thể chỉnh sửa câu trả lời của mình để đưa ra ví dụ về cách sử dụng nó cho các biểu mẫu mới và cập nhật không? Cảm ơn.
X10

8
Chìa khóa cho ví dụ của Daniel là kiểm tra trường .id. Các đối tượng mới được tạo sẽ có id == Không có. Nhân tiện, một trong những vé Django mở lâu nhất là về vấn đề này. Xem mã.djangoproject.com/ticket/342 .
Peter Rowell

2
@moadeep thêm một clean_descriptionphương thức vào lớp biểu mẫu.
Daniel Naab

3
trên linux (ubfox 15) / chrome v45, chỉ đọc thay đổi con trỏ thành "bàn tay bị vô hiệu hóa" nhưng sau đó trường có thể nhấp được. với chức năng bị vô hiệu hóa, nó hoạt động như mong đợi
simone cittadini

7
Câu trả lời này cần được cập nhật. Một đối số trường mới disabledđược thêm vào trong Django 1.9. Nếu Field.disabledđược đặt thành True, thì giá trị POST cho đó Fieldsẽ bị bỏ qua. Vì vậy, nếu bạn đang sử dụng 1.9, không cần ghi đè clean, chỉ cần đặt disabled = True. Kiểm tra câu trả lời này .
narendra-choudhary

174

Django 1.9 đã thêm thuộc tính Field.disables: https://docs.djangoproject.com/en/urdy/ref/forms/fields/#disables

Đối số boolean bị vô hiệu hóa, khi được đặt thành True, sẽ vô hiệu hóa trường biểu mẫu bằng cách sử dụng thuộc tính HTML bị vô hiệu hóa để người dùng không thể chỉnh sửa nó. Ngay cả khi người dùng chỉnh sửa giá trị của trường được gửi đến máy chủ, nó sẽ bị bỏ qua có lợi cho giá trị từ dữ liệu ban đầu của biểu mẫu.


Không có gì cho 1,8 LTS?
dnit13

9
Bất cứ ý tưởng nào làm thế nào chúng ta có thể sử dụng điều này trên UpdateView? Khi nó tạo các trường từ mô hình ...
bcsanches

6
Câu trả lời chính xác. Lớp giải pháp của tôi MyChangeForm (Forms.ModelForm): def __init __ (self, * args, ** kwargs): super (MyChangeForm, self) .__ init __ (* args, ** kwargs) self.fields ['my_field']. Đúng
Vijay Katam

8
Đây là một câu trả lời có vấn đề - cài đặt disabled=Truesẽ khiến mô hình bị trả lại cho người dùng có lỗi xác thực.
Ben

1
Sẽ thật tuyệt vời nếu bạn có thể bao gồm một ví dụ
địa lý

95

Cài đặt readonlytrên một widget chỉ làm cho đầu vào trong trình duyệt chỉ đọc. Thêm một clean_skutrả về instance.skuđảm bảo giá trị trường sẽ không thay đổi ở cấp biểu mẫu.

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

Bằng cách này, bạn có thể sử dụng mô hình (lưu chưa sửa đổi) và tránh gặp phải trường lỗi yêu cầu.


15
+1 Đây là một cách tuyệt vời để tránh ghi đè () phức tạp hơn. Tuy nhiên, bạn muốn thực hiện kiểm tra phiên bản trước khi trả về (trong chế độ nhận xét không có dòng mới): "if self.instance: return self.instance.sku; other: return self.fields ['sku']"
Daniel Naab

2
Đối với dòng cuối cùng, sẽ return self.cleaned_data['sku']là tốt hay tốt hơn? Các tài liệu dường như đề xuất sử dụng cleaned_data: "Giá trị trả về của phương thức này thay thế giá trị hiện có cleaned_data, vì vậy nó phải là giá trị của trường từ cleaned_data(ngay cả khi phương thức này không thay đổi) hoặc giá trị được làm sạch mới."
pianoJames

67

câu trả lời của awalker đã giúp tôi rất nhiều!

Tôi đã thay đổi ví dụ của anh ấy để làm việc với Django 1.3, sử dụng get_readonly_fields .

Thông thường bạn nên khai báo một cái gì đó như thế này trong app/admin.py:

class ItemAdmin(admin.ModelAdmin):
    ...
    readonly_fields = ('url',)

Tôi đã thích nghi theo cách này:

# In the admin.py file
class ItemAdmin(admin.ModelAdmin):
    ...
    def get_readonly_fields(self, request, obj=None):
        if obj:
            return ['url']
        else:
            return []

Và nó hoạt động tốt. Bây giờ nếu bạn thêm một Mục, urltrường sẽ đọc-ghi, nhưng khi thay đổi, nó sẽ trở thành chỉ đọc.


55

Để làm cho công việc này cho một ForeignKeylĩnh vực, một vài thay đổi cần phải được thực hiện. Thứ nhất, SELECT HTMLthẻ không có thuộc tính chỉ đọc. Chúng ta cần sử dụngdisabled="disabled" thay thế. Tuy nhiên, sau đó trình duyệt không gửi lại bất kỳ dữ liệu biểu mẫu nào cho trường đó. Vì vậy, chúng ta cần đặt trường đó thành không bắt buộc để trường xác nhận chính xác. Sau đó chúng ta cần đặt lại giá trị về giá trị cũ để không bị đặt thành trống.

Vì vậy, đối với khóa ngoại, bạn sẽ cần phải làm một cái gì đó như:

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)

Bằng cách này, trình duyệt sẽ không cho phép người dùng thay đổi trường và sẽ luôn luôn POSTnhư vậy. Sau đó, chúng tôi ghi đè cleanphương thức để đặt giá trị của trường thành giá trị ban đầu trong thể hiện.


Tôi đã cố gắng sử dụng nó dưới dạng trong TabularInline, nhưng không thành công vì attrsđược chia sẻ giữa các widgetphiên bản và tất cả trừ hàng đầu tiên, bao gồm cả mới được thêm vào, chỉ hiển thị đọc.
khó khăn

Một giải pháp (cập nhật) tuyệt vời! Thật không may, điều này và phần còn lại có vấn đề khi có lỗi hình thức vì tất cả các giá trị "bị vô hiệu hóa" bị xóa.
Michael Thompson

28

Đối với Django 1.2+, bạn có thể ghi đè trường như vậy:

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

6
Điều này cũng không cho phép trường được chỉnh sửa tại thời điểm thêm, đó là những gì câu hỏi ban đầu là về.
Matt S.

Đây là câu trả lời mà tôi đang tìm kiếm. Field disabledkhông làm những gì tôi muốn bởi vì nó vô hiệu hóa trường, nhưng cũng loại bỏ nhãn / làm cho nó vô hình.
sivabudh

18

Tôi đã tạo một lớp MixIn mà bạn có thể kế thừa để có thể thêm trường có thể đọc read_only sẽ vô hiệu hóa và bảo mật các trường trong lần chỉnh sửa đầu tiên:

(Dựa trên câu trả lời của Daniel và 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')

11

Tôi vừa tạo tiện ích đơn giản nhất có thể cho trường chỉ đọc - Tôi thực sự không hiểu tại sao các biểu mẫu chưa có điều này:

class ReadOnlyWidget(widgets.Widget):
    """Some of these values are read only - just a bit of text..."""
    def render(self, _, value, attrs=None):
        return value

Trong các hình thức:

my_read_only = CharField(widget=ReadOnlyWidget())

Rất đơn giản - và giúp tôi chỉ xuất ra. Tiện dụng trong một formset với một loạt các giá trị chỉ đọc. Tất nhiên - bạn cũng có thể thông minh hơn một chút và cung cấp cho nó một div với các nhân viên để bạn có thể nối các lớp với nó.


2
Trông thật quyến rũ, nhưng làm thế nào để xử lý chìa khóa nước ngoài?
andilabs

Hãy chắc rằng unicode(value)trong sự trở lại thay vì lẽ. Giả sử dunder unicode là hợp lý, sau đó bạn sẽ nhận được điều đó.
Daniel Staple

Đối với khóa ngoại, bạn sẽ cần thêm thuộc tính "mô hình" và sử dụng "get (value)". Kiểm tra ý chính của tôi
shadi

10

Tôi chạy qua một vấn đề tương tự. Có vẻ như tôi đã có thể giải quyết nó bằng cách định nghĩa phương thức "get_readonly_fields" trong lớp ModelAdmin của tôi.

Một cái gì đó như thế này:

# In the admin.py file

class ItemAdmin(admin.ModelAdmin):

    def get_readonly_display(self, request, obj=None):
        if obj:
            return ['sku']
        else:
            return []

Điều tốt đẹp là obj sẽ không có gì khi bạn thêm một Mục mới hoặc nó sẽ là đối tượng được chỉnh sửa khi bạn thay đổi một Mục hiện có.

get_readonly_display được ghi lại ở đây: http://docs.djangoproject.com/en/1.2/ref/contrib/admin/#modeladmin-methods


6

Một tùy chọn đơn giản là chỉ cần gõ form.instance.fieldNamevào mẫu thay vì form.fieldName.


Và làm thế nào về verbos_namehoặc labelcủa lĩnh vực? Làm thế nào tôi có thể hiển thị nhãn `trong mẫu django? @alzclarke
Cá voi 52Hz

6

Cách tôi làm với 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

Điều này sẽ chỉ chặn từ phía trước. bất kỳ ai có thể bỏ qua. điều này sẽ gây ra vấn đề bảo mật nếu bạn thực hiện trên dữ liệu nhạy cảm
Sarath Ak

Nó an toàn; nó cũng chặn trong phần phụ trợ kể từ Django> = 1.10 docs.djangoproject.com/en/1.10/ref/forms/fields/ Kẻ
timdiels

5

Là một bổ sung hữu ích cho bài đăng của Humphrey , tôi đã gặp một số vấn đề với sự đảo ngược django, bởi vì nó vẫn đăng ký các trường bị vô hiệu hóa khi 'thay đổi'. Các mã sau đây khắc phục vấn đề.

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)

5

Vì tôi chưa thể nhận xét ( giải pháp của muhuk ), tôi sẽ trả lời như một câu trả lời riêng. Đây là một ví dụ mã hoàn chỉnh, hoạt động với tôi:

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

5

Một lần nữa, tôi sẽ đưa ra một giải pháp nữa :) Tôi đã sử dụng mã của Humphrey , vì vậy điều này dựa trên cơ sở đó.

Tuy nhiên, tôi gặp vấn đề với lĩnh vực này là a ModelChoiceField. Tất cả mọi thứ sẽ làm việc theo yêu cầu đầu tiên. Tuy nhiên, nếu bộ định dạng đã cố gắng thêm một mục mới và xác thực không thành công, có điều gì đó không ổn với các biểu mẫu "hiện có" trong đó SELECTEDtùy chọn được đặt lại về mặc định ---------.

Dù sao, tôi không thể tìm ra cách khắc phục điều đó. Vì vậy, thay vào đó, (và tôi nghĩ rằng điều này thực sự sạch hơn trong biểu mẫu), tôi đã thực hiện các trường HiddenInputField(). Điều này chỉ có nghĩa là bạn phải làm thêm một chút công việc trong mẫu.

Vì vậy, cách khắc phục đối với tôi là đơn giản hóa Biểu mẫu:

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

Và sau đó trong mẫu, bạn sẽ cần thực hiện một số vòng lặp thủ công của bộ định dạng .

Vì vậy, trong trường hợp này, bạn sẽ làm một cái gì đó như thế này trong mẫu:

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

Điều này làm việc tốt hơn một chút đối với tôi và với ít thao tác hơn.


4

Tôi đã đi vào cùng một vấn đề vì vậy tôi đã tạo ra một Mixin dường như hoạt động cho các trường hợp sử dụng của tôi.

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

Cách sử dụng, chỉ cần xác định những cái nào phải được đọc:

class MyFormWithReadOnlyFields(ReadOnlyFieldsMixin, MyForm):
    readonly_fields = ('field1', 'field2', 'fieldx')

Tôi cho rằng nó dễ đọc hơn một chút so với mixin của riêng tôi mà tôi đã đề xuất ở đây. Thậm chí có thể hiệu quả hơn, vì những cách làm sạch đó không làm tăng các lỗi xác nhận lỗi
christophe31

Tôi gặp lỗi:'collections.OrderedDict' object has no attribute 'iteritems'
Geoidesic

4

nếu bạn cần nhiều trường chỉ đọc. bạn có thể sử dụng bất kỳ phương pháp nào được đưa ra dưới đây

phương pháp 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

phương pháp 2

phương pháp thừa kế

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

3

Hai cách tiếp cận (tương tự) khác với một ví dụ tổng quát:

1) cách tiếp cận đầu tiên - loại bỏ trường trong phương thức save (), ví dụ (không được kiểm tra;)):

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) cách tiếp cận thứ hai - đặt lại trường về giá trị ban đầu trong phương thức sạch:

def clean_<fieldname>(self):
    return self.initial[<fieldname>] # or getattr(self.instance, fieldname)

Dựa trên cách tiếp cận thứ hai, tôi đã khái quát nó như thế này:

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)

3

Đối với phiên bản Quản trị viên, tôi nghĩ đây là một cách nhỏ gọn hơn nếu bạn có nhiều hơn một lĩnh vực:

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

3

Dựa trên câu trả lời của Yamikep , tôi đã tìm thấy một giải pháp tốt hơn và rất đơn giản cũng xử lý ModelMultipleChoiceFieldcác trường.

Xóa trường khỏi form.cleaned_datangăn các trường được lưu:

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

Sử dụng:

class MyFormWithReadOnlyFields(ReadOnlyFieldsMixin, MyForm):
    readonly_fields = ('field1', 'field2', 'fieldx')

2

Đây là một phiên bản tham gia nhiều hơn một chút, dựa trên câu trả lời của christophe31 . Nó không dựa vào thuộc tính "chỉ đọc". Điều này làm cho các vấn đề của nó, như các hộp chọn vẫn có thể thay đổi và bộ dữ liệu vẫn bật lên, biến mất.

Thay vào đó, nó bao bọc tiện ích trường biểu mẫu trong một tiện ích chỉ đọc, do đó làm cho biểu mẫu vẫn còn hiệu lực. Nội dung của widget gốc được hiển thị bên trong <span class="hidden"></span>các thẻ. Nếu tiện ích có một render_readonly()phương thức, nó sử dụng văn bản đó làm văn bản hiển thị, nếu không, nó sẽ phân tích cú pháp HTML của tiện ích gốc và cố gắng đoán đại diện tốt nhất.

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)

1

Đây có phải là cách đơn giản nhất?

Ngay trong một mã xem như thế này:

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)

Nó hoạt động tốt!


1

Đối với django 1.9+
Bạn có thể sử dụng đối số trường bị vô hiệu hóa để vô hiệu hóa trường. ví dụ: Trong đoạn mã sau từ tệp Forms.txt, tôi đã làm cho trường worker_code bị vô hiệu hóa

class EmployeeForm(forms.ModelForm):
    employee_code = forms.CharField(disabled=True)
    class Meta:
        model = Employee
        fields = ('employee_code', 'designation', 'salary')

Tham khảo https://docs.djangoproject.com/en/2.0/ref/forms/fields/#disables


1

Nếu bạn đang làm việc với Django ver < 1.9( thuộc tính 1.9đã thêm Field.disabled), bạn có thể thử thêm trình trang trí sau vào __init__phương thức biểu mẫu của mình :

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

Ý tưởng chính là nếu trường là readonlybạn không cần bất kỳ giá trị nào khác ngoại trừ initial.

PS: Đừng quên thiết lập yuor_form_field.widget.attrs['readonly'] = True


0

Nếu bạn đang sử dụng quản trị viên Django, đây là giải pháp đơn giản nhất.

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

0

Tôi nghĩ rằng tùy chọn tốt nhất của bạn sẽ chỉ bao gồm thuộc tính chỉ đọc trong mẫu của bạn được hiển thị trong một <span>hoặc <p>thay vì đưa nó vào biểu mẫu nếu nó chỉ đọc.

Các hình thức là để thu thập dữ liệu, không hiển thị nó. Điều đó đang được nói, các tùy chọn để hiển thị trong một readonlywidget và xóa dữ liệu POST là giải pháp tốt.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.