ในรูปแบบ Django ฉันจะสร้างฟิลด์แบบอ่านอย่างเดียว (หรือปิดใช้งาน) เพื่อที่จะไม่สามารถแก้ไขได้อย่างไร


430

ในรูปแบบ 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()

ดูเพิ่มเติมที่คำถาม SO: เหตุใดเขตข้อมูลฟอร์มอ่านอย่างเดียวใน Django จึงเป็นแนวคิดที่ไม่ดี @ stackoverflow.com/questions/2902024คำตอบที่ยอมรับ (โดย Daniel Naab) ดูแลแฮ็ก POST ที่เป็นอันตราย
X10

คำตอบ:


421

ดังที่ได้อธิบายไว้ในคำตอบนี้ 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ด้วยเงื่อนไขอื่นที่ระบุว่าคุณกำลังแก้ไข นอกจากนี้คุณยังสามารถตั้งค่าแอตทริบิวต์ในช่องใส่แทนdisabledreadonly

clean_skuฟังก์ชั่นจะให้แน่ใจว่าค่าจะไม่ถูกแทนที่ด้วยreadonlyPOST

มิฉะนั้นจะไม่มีฟิลด์ฟอร์ม Django ในตัวซึ่งจะแสดงค่าในขณะที่ปฏิเสธข้อมูลอินพุตที่ถูกผูกไว้ หากนี่คือสิ่งที่คุณต้องการคุณควรสร้างแยกModelFormที่ไม่รวมฟิลด์ที่ไม่สามารถแก้ไขได้และพิมพ์ลงในแม่แบบของคุณ


3
ขอบคุณสำหรับการโพสต์คำตอบ ฉันไม่ทราบวิธีใช้รหัสนี้อย่างชัดเจนหรือไม่ รหัสนี้จะไม่ทำงานเหมือนกันสำหรับโหมดใหม่และอัพเดตหรือไม่ คุณสามารถแก้ไขคำตอบของคุณเพื่อให้ตัวอย่างเกี่ยวกับวิธีใช้สำหรับแบบฟอร์มใหม่และแบบอัพเดตได้หรือไม่ ขอบคุณ
X10

8
กุญแจสำคัญในตัวอย่างของ Daniel คือการทดสอบฟิลด์. id วัตถุที่สร้างขึ้นใหม่จะมี id == ไม่มี อย่างไรก็ตามหนึ่งในตั๋ว Django แบบเปิดที่เก่าแก่ที่สุดที่เกี่ยวกับปัญหานี้ ดูcode.djangoproject.com/ticket/342
Peter Rowell

2
@meepeep เพิ่มclean_descriptionวิธีการในชั้นเรียนแบบฟอร์ม
Daniel Naab

3
บน linux (ubuntu 15) / chrome v45 แบบอ่านอย่างเดียวจะเปลี่ยนตัวชี้เป็น "มือพิการ" แต่ฟิลด์นั้นสามารถคลิกได้ กับคนพิการมันทำงานได้ตามที่คาดไว้
simone cittadini

7
คำตอบนี้จะต้องมีการปรับปรุง อาร์กิวเมนต์ฟิลด์ใหม่disabledถูกเพิ่มใน Django 1.9 หากField.disabledถูกตั้งค่าเป็นค่าTruePOST สำหรับสิ่งนั้นFieldจะถูกละเว้น ดังนั้นหากคุณใช้ 1.9 คุณไม่จำเป็นต้องยกเลิกการcleanตั้งค่าdisabled = Trueใด ๆ ตรวจสอบนี้คำตอบ
narendra-choudhary

174

Django 1.9 เพิ่มแอตทริบิวต์ Field.disabled: https://docs.djangoproject.com/en/stable/ref/forms/fields/#disabled

อาร์กิวเมนต์บูลีนที่ปิดใช้งานเมื่อตั้งค่าเป็น True จะปิดใช้งานเขตข้อมูลฟอร์มโดยใช้แอตทริบิวต์ HTML ที่ถูกปิดใช้งานเพื่อที่ผู้ใช้จะไม่สามารถแก้ไขได้ แม้ว่าผู้ใช้จะปรับเปลี่ยนค่าของเขตข้อมูลที่ส่งไปยังเซิร์ฟเวอร์ผู้ใช้จะไม่สนใจค่าจากข้อมูลเริ่มต้นของฟอร์ม


ไม่มีอะไรสำหรับ 1.8 LTS
dnit13

9
มีความคิดว่าเราจะใช้สิ่งนี้กับ UpdateView ได้อย่างไร? ในขณะที่มันสร้างเขตข้อมูลจากรูปแบบ ...
bcsanches

6
คำตอบที่ถูกต้อง โซลูชันระดับ MyChangeForm ของฉัน (forms.ModelForm): def __init __ (self, * args, ** kwargs): super (MyChangeForm, self) .__ init __ (* args, ** kwargs) self.fields ['my_field'] True
Vijay Katam

8
นี่เป็นคำตอบที่เป็นปัญหา - การตั้งค่าdisabled=Trueจะทำให้รูปแบบดังกล่าวถูกส่งคืนให้ผู้ใช้พร้อมกับข้อผิดพลาดในการตรวจสอบความถูกต้อง
Ben

1
คงจะยอดเยี่ยมถ้าคุณมีตัวอย่าง
geoidesic

95

การตั้งค่าreadonlyบนวิดเจ็ตทำให้อินพุตในเบราว์เซอร์อ่านได้อย่างเดียวเท่านั้น การเพิ่มclean_skuผลตอบแทนที่instance.skuมั่นใจได้ว่าค่าของเขตข้อมูลจะไม่เปลี่ยนแปลงในระดับฟอร์ม

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

วิธีนี้คุณสามารถใช้โมเดล (บันทึกที่ไม่ได้แก้ไข) และหลีกเลี่ยงการได้รับข้อผิดพลาดที่จำเป็นในฟิลด์


15
+1 นี่เป็นวิธีที่ดีในการหลีกเลี่ยงการบันทึกทับ () ที่ซับซ้อนยิ่งขึ้น อย่างไรก็ตามคุณต้องการตรวจสอบอินสแตนซ์ก่อนที่จะส่งคืน (ในโหมดแสดงความคิดเห็นที่ไม่ขึ้นบรรทัดใหม่): "ถ้า self.instance: return self.instance.sku; else: return self.fields ['sku']"
Daniel Naab

2
สำหรับบรรทัดสุดท้ายจะreturn self.cleaned_data['sku']ดีหรือดีกว่า เอกสารดูเหมือนจะแนะนำให้ใช้cleaned_data"ค่าตอบแทนของวิธีนี้แทนที่ค่าที่มีอยู่ในcleaned_dataดังนั้นมันจะต้องเป็นค่าของฟิลด์จากcleaned_data(แม้ว่าวิธีนี้ไม่สามารถเปลี่ยนได้) หรือค่าทำความสะอาดใหม่."
pianoJames

67

คำตอบของผู้รักษาประตูช่วยฉันมาก!

ฉันได้เปลี่ยนตัวอย่างของเขาในการทำงานกับ 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เขตข้อมูลเป็นแบบอ่าน - เขียน แต่เมื่อเปลี่ยนจะกลายเป็นแบบอ่านอย่างเดียว


55

ในการทำให้ForeignKeyฟิลด์นี้ทำงานได้ต้องมีการเปลี่ยนแปลงเล็กน้อย ประการแรกSELECT HTMLแท็กไม่มีแอตทริบิวต์แบบอ่านอย่างเดียว เราจำเป็นต้องใช้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อินสแตนซ์และทั้งหมดยกเว้นแถวแรกรวมถึงการเพิ่มการแสดงผลใหม่ที่อ่านได้อย่างเดียวเท่านั้น
dhill

ทางออกที่ยอดเยี่ยม! น่าเสียดายที่สิ่งนี้และที่เหลือมีปัญหาเมื่อมีข้อผิดพลาดในรูปแบบเนื่องจากค่า "ปิดใช้งาน" ทั้งหมดถูกทำให้ว่างเปล่า
Michael Thompson

28

สำหรับ Django 1.2+ คุณสามารถแทนที่ฟิลด์ดังนี้:

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

6
สิ่งนี้ไม่อนุญาตให้แก้ไขฟิลด์ในเวลาเพิ่มซึ่งเป็นคำถามเดิมที่เกี่ยวกับ
Matt S.

นี่คือคำตอบที่ฉันกำลังมองหา Field disabledไม่ได้ทำสิ่งที่ฉันต้องการเพราะมันปิดการใช้งานสนาม แต่ยังลบฉลาก / ทำให้มันมองไม่เห็น
sivabudh

18

ฉันสร้างคลาส MixIn ซึ่งคุณสามารถสืบทอดเพื่อให้สามารถเพิ่มฟิลด์ iterable read_only ซึ่งจะปิดการใช้งานและความปลอดภัยของเขตข้อมูลในการแก้ไขที่ไม่ใช่ครั้งแรก:

(ขึ้นอยู่กับคำตอบของ 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')

11

ฉันเพิ่งสร้างวิดเจ็ตที่เป็นไปได้ที่ง่ายที่สุดสำหรับฟิลด์แบบอ่านอย่างเดียว - ฉันไม่เห็นเลยว่าทำไมแบบฟอร์มจึงไม่มีสิ่งนี้:

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 เพื่อให้คุณสามารถต่อท้ายคลาสได้


2
ดูเซ็กซี่ แต่จะจัดการกับกุญแจต่างประเทศได้อย่างไร?
andilabs

ทำให้มันunicode(value)กลับมาแทนบางที ถ้าสมมุติว่าเดอร์ยูนิโค้ดนั้นมีเหตุผลคุณก็จะเข้าใจได้
Danny Staple

สำหรับคีย์ต่างประเทศคุณจะต้องเพิ่มแอตทริบิวต์ "model" และใช้ "get (value)" ตรวจสอบส่วนสำคัญของฉัน
Shadi

10

ฉันพบปัญหาที่คล้ายกัน ดูเหมือนว่าฉันสามารถแก้ไขได้โดยกำหนดวิธี "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


6

ทางเลือกหนึ่งที่ง่ายคือการเพียงแค่พิมพ์ในแม่แบบแทนform.instance.fieldNameform.fieldName


และวิธีการเกี่ยวกับverbos_nameหรือlabelของสนาม? ฉันจะแสดงฉลาก `ในเทมเพลต django ได้อย่างไร? @alzclarke
Whale 52Hz

6

ฉันจะทำอย่างไรกับ 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

สิ่งนี้จะบล็อกจากด้านหน้า คนใดคนหนึ่งสามารถข้าม สิ่งนี้จะทำให้เกิดปัญหาด้านความปลอดภัยหากคุณใช้ข้อมูลที่ละเอียดอ่อน
Sarath Ak

มันปลอดภัย; มันบล็อกในแบ็กเอนด์ตั้งแต่ Django> = 1.10 docs.djangoproject.com/en/1.10/ref/forms/fields/…
timdiels

5

ในฐานะที่เป็นประโยชน์นอกเหนือจากการโพสต์ของ Humphreyฉันมีปัญหาบางอย่างกับ 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)

5

ในขณะที่ฉันยังไม่ได้แสดงความคิดเห็น (คำตอบของ muhuk ) ฉันจะตอบเป็นคำตอบแยกต่างหาก นี่คือตัวอย่างของรหัสที่สมบูรณ์ซึ่งใช้ได้กับฉัน

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

5

อีกครั้งฉันจะเสนอทางออกอีกหนึ่งข้อ :) ฉันใช้รหัสของฮัมฟรีย์ดังนั้นนี่จึงเป็นไปตามนั้น

ModelChoiceFieldแต่ผมวิ่งเข้าไปในปัญหาเกี่ยวกับข้อมูลความเป็นอยู่ ทุกอย่างจะทำงานตามคำขอแรก แต่ถ้า formset พยายามที่จะเพิ่มรายการใหม่และการตรวจสอบล้มเหลวบางสิ่งบางอย่างเกิดผิดกับ "มีอยู่" รูปแบบที่ตัวเลือกที่ถูกตั้งค่าเริ่มต้น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>

มันใช้งานได้ดีกว่าสำหรับฉันเล็กน้อย


4

ฉันกำลังประสบปัญหาเดียวกันดังนั้นฉันจึงสร้าง 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')

ฉันคิดว่ามันอ่านง่ายกว่า mixin ของฉันเองที่ฉันแนะนำที่นี่ แม้อาจมีประสิทธิภาพมากกว่านี้เนื่องจากการชำระล้างจะไม่ทำให้เกิดข้อผิดพลาดในการตรวจสอบ…
christophe31

ฉันได้รับข้อผิดพลาด:'collections.OrderedDict' object has no attribute 'iteritems'
geoidesic

4

หากคุณต้องการฟิลด์อ่านอย่างเดียวหลายแห่งคุณสามารถใช้วิธีการใด ๆ ที่ระบุด้านล่าง

วิธีที่ 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',)

3

อีกสองวิธี (คล้ายกัน) ที่มีตัวอย่างทั่วไปหนึ่งตัวอย่าง:

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)

3

สำหรับเวอร์ชันผู้ดูแลระบบฉันคิดว่านี่เป็นวิธีที่กระชับยิ่งขึ้นถ้าคุณมีมากกว่าหนึ่งช่อง:

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

จากคำตอบของ 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')

2

นี่เป็นรุ่นเล็กน้อยที่เกี่ยวข้องมากขึ้นขึ้นอยู่กับคำตอบของ 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)

1

นี่เป็นวิธีที่ง่ายที่สุดหรือไม่?

ในมุมมองโค้ดบางอย่างเช่นนี้:

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)

มันใช้งานได้ดี!


1

สำหรับ django 1.9+
คุณสามารถใช้อาร์กิวเมนต์ที่ปิดใช้งานฟิลด์เพื่อปิดใช้งานฟิลด์ เช่นในตัวอย่างโค้ดต่อไปนี้จากไฟล์ 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


1

หากคุณกำลังทำงานกับ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ยกเว้น

PS: อย่าลืมที่จะตั้ง yuor_form_field.widget.attrs['readonly'] = True


0

หากคุณใช้ผู้ดูแลระบบ 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',)

0

ฉันคิดว่าตัวเลือกที่ดีที่สุดของคุณคือการรวมแอตทริบิวต์แบบอ่านอย่างเดียวในเทมเพลตของคุณที่แสดงใน<span>หรือ<p>แทนที่จะรวมไว้ในแบบฟอร์มหากอ่านได้อย่างเดียว

แบบฟอร์มสำหรับการรวบรวมข้อมูลไม่แสดง ที่ถูกกล่าวว่าตัวเลือกที่จะแสดงในreadonlyเครื่องมือและขัดข้อมูล POST เป็นโซลูชั่นที่ดี

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.