In a Django form, how do I make a field readonly (or disabled) so that it cannot be edited?

后端 未结 26 994
-上瘾入骨i
-上瘾入骨i 2020-11-22 04:09

In a Django form, how do I make a field read-only (or disabled)?

When the form is being used to create a new entry, all fields should be enabled - but when the recor

相关标签:
26条回答
  • 2020-11-22 04:19

    Yet again, I am going to offer one more solution :) I was using Humphrey's code, so this is based off of that.

    However, I ran into issues with the field being a ModelChoiceField. Everything would work on the first request. However, if the formset tried to add a new item and failed validation, something was going wrong with the "existing" forms where the SELECTED option was being reset to the default ---------.

    Anyway, I couldn't figure out how to fix that. So instead, (and I think this is actually cleaner in the form), I made the fields HiddenInputField(). This just means you have to do a little more work in the template.

    So the fix for me was to simplify the Form:

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

    And then in the template, you'll need to do some manual looping of the formset.

    So, in this case you would do something like this in the template:

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

    This worked a little better for me and with less form manipulation.

    0 讨论(0)
  • 2020-11-22 04:20

    As pointed out in this answer, Django 1.9 added the Field.disabled attribute:

    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.

    With Django 1.8 and earlier, to disable entry on the widget and prevent malicious POST hacks you must scrub the input in addition to setting the readonly attribute on the form field:

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

    Or, replace if instance and instance.pk with another condition indicating you're editing. You could also set the attribute disabled on the input field, instead of readonly.

    The clean_sku function will ensure that the readonly value won't be overridden by a POST.

    Otherwise, there is no built-in Django form field which will render a value while rejecting bound input data. If this is what you desire, you should instead create a separate ModelForm that excludes the uneditable field(s), and just print them inside your template.

    0 讨论(0)
  • 2020-11-22 04:20

    I made a MixIn class which you may inherit to be able to add a read_only iterable field which will disable and secure fields on the non-first edit:

    (Based on Daniel's and Muhuk's answers)

    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')
    
    0 讨论(0)
  • 2020-11-22 04:22

    Based on Yamikep's answer, I found a better and very simple solution which also handles ModelMultipleChoiceField fields.

    Removing field from form.cleaned_data prevents fields from being saved:

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

    Usage:

    class MyFormWithReadOnlyFields(ReadOnlyFieldsMixin, MyForm):
        readonly_fields = ('field1', 'field2', 'fieldx')
    
    0 讨论(0)
  • 2020-11-22 04:24

    Django 1.9 added the Field.disabled attribute: 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.

    0 讨论(0)
  • 2020-11-22 04:26

    I ran across a similar problem. It looks like I was able to solve it by defining a "get_readonly_fields" method in my ModelAdmin class.

    Something like this:

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

    The nice thing is that obj will be None when you are adding a new Item, or it will be the object being edited when you are changing an existing Item.

    get_readonly_display is documented here: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#modeladmin-methods

    0 讨论(0)
提交回复
热议问题