In einer Django Form, wie mache ich ein Feld nur lesbar (oder deaktiviert), so dass sie nicht bearbeitet werden können?

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

  •  11-07-2019
  •  | 
  •  

Frage

In einer Django Form, wie kann ich ein Feld ist schreibgeschützt machen (oder deaktiviert)?

Wenn das Formular verwendet wird, um einen neuen Eintrag zu erstellen, werden alle Felder aktiviert werden soll -. Aber wenn der Datensatz im Update-Modus ist, müssen einige Felder schreibgeschützt werden

Wenn zum Beispiel ein neues Item Modell bestimmt, wird alle Felder bearbeitet werden müssen, aber während der Aufzeichnung zu aktualisieren, gibt es eine Möglichkeit, das sku Feld zu deaktivieren, so dass es sichtbar ist, aber nicht bearbeitet werden kann?

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

Can Klasse ItemForm wiederverwendet werden? Welche Veränderungen würden in der ItemForm oder Item Modellklasse erforderlich? Würde ich brauche eine andere Klasse zu schreiben, „ItemUpdateForm“, für das Element zu aktualisieren?

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

Lösung

Wie bereits ausgeführt in diese Antwort , Django 1.9 hinzugefügt, um die Field.disabled Attribut:

  

Das behinderte boolean Argument, wenn auf True setzt, wird ein Formularfeld des deaktivierte HTML-Attribut, so dass es nicht von Benutzern bearbeitet werden. Auch wenn ein Benutzer mit denen an den Server gesendet Wert des Feldes Stampfer, wird es aus der Form der Ausgangsdaten für den Wert ignoriert werden.

Mit Django 1.8 und früheren Versionen deaktivieren Eintrag auf dem Widget und verhindert, dass bösartigen POST Hacks Sie die Eingabe zusätzlich zu Einstellen des readonly Attributs auf dem Formularfeld schrubben müssen:

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

Oder ersetzen if instance and instance.pk mit einem anderen Zustand angibt, die Sie bearbeiten. Sie können auch das Attribut disabled auf dem Eingabefeld gesetzt, statt readonly.

Die clean_sku Funktion wird sichergestellt, dass der readonly Wert nicht durch eine POST außer Kraft gesetzt werden.

Ansonsten wird es keine eingebaute Feld Django Form, die einen Wert übertragen werden, während gebundene Eingangsdaten ablehnt. Wenn das ist, was Sie wünschen, sollten Sie stattdessen einen separaten ModelForm erstellen, die nicht bearbeitbare Feld schließt (s), und sie nur in Ihrer Vorlage gedruckt werden.

Andere Tipps

Django 1.9 hinzugefügt, um das Field.disabled Attribut: https: / /docs.djangoproject.com/en/stable/ref/forms/fields/#disabled

  

Das behinderte boolean Argument, wenn auf True setzt, wird ein Formularfeld des deaktivierte HTML-Attribut, so dass es nicht von Benutzern bearbeitet werden. Auch wenn ein Benutzer mit denen an den Server gesendet Wert des Feldes Stampfer, wird es aus der Form der Ausgangsdaten für den Wert ignoriert werden.

Einstellung NURLESE- auf Widget macht nur die Eingabe in den Browser schreibgeschützt. Hinzufügen eines clean_sku die instance.sku kehrt sorgt der Feldwert wird nicht auf Formularebene ändern.

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

Auf diese Weise können Modell verwenden können (unmodifiziert speichern) und aviod das Feld erforderlich Fehler.

awalker Antwort mir sehr geholfen!

Ich habe seine Beispiel änderte sich mit Django 1.3 zu arbeiten, mit get_readonly_fields .

In der Regel sollten Sie so etwas wie dies in app/admin.py deklarieren:

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

Ich habe auf diese Weise angepasst:

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

Und es funktioniert gut. Nun, wenn Sie ein Element hinzu, das url Feld ist read-write, aber auf Änderung wird es schreibgeschützt ist.

Um diese Arbeit für ein ForeignKey Feld zu machen, müssen einige Änderungen vorgenommen werden. Zum einen haben die SELECT HTML Tag nicht die Nur-Lese-Attribut. Wir brauchen stattdessen disabled="disabled" zu verwenden. Doch dann wird der Browser keine Formulardaten zurück für das Feld schicken. Also müssen wir das Feld setzen nicht erforderlich, so dass das Feld korrekt validiert. Wir müssen dann den Wert zurückzusetzen, was es verwendet wird, so zu sein, es zu leer nicht gesetzt ist.

Also für Fremdschlüssel müssen Sie so etwas wie tun:

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)

Auf diese Weise wird der Browser der Benutzer das Feld nicht zulassen, ändern, und wird, wie es immer POST es leer gelassen wurde. Wir haben dann die clean Methode überschreiben, den Wert des Felds zu setzen, was in der Instanz ursprünglich sein sollte.

Für Django 1.2+, können Sie das Feld außer Kraft setzen wie folgt:

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

Ich habe eine MixIn Klasse, die Sie in der Lage sein erben kann ein read_only iterable Feld hinzuzufügen, die auf der nicht zuerst bearbeiten wird deaktivieren und sichere Felder:

(Basierend auf Daniels und Muhuk der Antworten)

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

Ich habe gerade eine möglichst einfache Widget für ein Nur-Lese-Feld erstellt - ich sehe wirklich nicht, warum Formen diesen noch nicht haben:

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

In der Form:

my_read_only = CharField(widget=ReadOnlyWidget())

Sehr einfach - und wird mir nur ausgegeben. Handlich in einem Formset mit einem Bündel von Nur-Lese-Werten. Natürlich -. Sie auch ein bisschen klüger sein könnte und es ein div mit den attrs geben, so dass Sie Klassen, um es anhängen

Ich lief über ein ähnliches Problem. Es sieht aus wie ich war in der Lage, es zu lösen, indem eine „get_readonly_fields“ Methode in meiner Klasse Modeladmin definieren.

So etwas wie folgt aus:

# In the admin.py file

class ItemAdmin(admin.ModelAdmin):

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

Das Schöne daran ist, dass obj Keiner sein wird, wenn Sie ein neues Element hinzufügen, oder es wird das Objekt, das bearbeitet werden, wenn Sie ein vorhandenes Element ändern.

get_readonly_display ist hier dokumentiert: http://docs.djangoproject.com/en/1.2/ ref / contrib / admin / # Modeladmin-Methoden

Eine einfache Möglichkeit ist, nur statt form.instance.fieldName form.fieldName in der Vorlage ein.

Als sinnvolle Ergänzung zu Humphreys Post , ich hatte einige Probleme mit django-Reversion, weil es immer noch deaktiviert Felder als ‚geändert‘ registriert. Der folgende Code behebt das Problem.

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)

Wie kann ich noch nicht kommentieren ( muhuk Lösung ), ich werde Antwort als eine separate Antwort. Dies ist ein komplettes Codebeispiel, die für mich gearbeitet:

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

Ich wollte in das gleiche Problem, damit ich eine Mixin geschaffen, die für meine Anwendungsfälle.

scheint zu funktionieren
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

Verwendung, nur definieren, welche davon nur gelesen werden müssen:

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

Noch einmal, ich werde noch eine Lösung anbieten zu können :) Ich wurde mit Humphreys Code , so wird diese Basis weg davon.

Aber ich lief in Probleme mit dem Feld um ein ModelChoiceField zu sein. Alles würde auf der ersten Anfrage arbeiten. wenn die formset jedoch versucht, ein neues Element hinzuzufügen und Fehler bei der Überprüfung, würde etwas falsch mit den „bestehenden“ Formen, bei denen die gewählten Option zurückgesetzt wird auf den Standard war „---------“.

Wie auch immer, ich konnte nicht herausfinden, wie das zu beheben. Also statt, (und ich denke, dies ist tatsächlich sauberer in Form ist), habe ich die Felder HiddenInputField (). Dies bedeutet nur, dass Sie ein wenig mehr Arbeit in der Vorlage zu tun.

So ist die Lösung für mich war die Form zu vereinfachen:

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

Und dann in der Vorlage, die Sie benötigen einige Handbuch Looping des formset .

Also, in diesem Fall würden Sie so etwas wie dies in der Vorlage tun:

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

Das funktionierte ein wenig besser für mich und mit weniger Form Manipulation.

Wie ich es mit 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

Zwei weitere (ähnlich) nähert sich mit einem verallgemeinerten Beispiel:

1) erste Ansatz - Entfernen Feld in save () Methode, z.B. (Nicht getestet;)):

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) zweite Ansatz - Reset-Feldwertes in sauberen Methode Erstausbildung:

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

Basierend auf den zweiten Ansatz, den ich verallgemeinert es wie folgt aus:

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)

, wenn Ihre Notwendigkeit, mehr schreibgeschützt kann fields.you alle Methoden unten angegebenen

Methode 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

Methode 2

Vererbungsmethode

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

Für die Admin-Version, ich denke, das ist ein kompakter Art und Weise, wenn Sie mehr als ein Feld haben:

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

Basierend auf Yamikep Antwort , finde ich eine bessere und sehr einfache Lösung, die auch ModelMultipleChoiceField Felder behandelt.

Feld von form.cleaned_data Entfernen verhindert Felder nicht gespeichert:

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

Verbrauch:

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

Hier ist eine etwas aufwendigere Version, basierend auf Antwort der christophe31. Es verlässt sich nicht auf die „Nur-Lesen“ -Attribut. Dies macht seine Probleme, wie Auswahlboxen noch veränderbar ist und datapickers noch auftauchen, gehen sie weg.

Stattdessen wickelt es die Formularfelder Widget in einem Nur-Lese-Widget, damit die Form noch validieren zu machen. Der Inhalt des ursprünglichen Widget innerhalb <span class="hidden"></span>-Tags angezeigt. Wenn das Widget eine render_readonly() Methode hat nutzt es, dass als sichtbaren Text, sonst ist es den HTML-Code des ursprünglichen Widget analysiert und versucht, die beste Darstellung zu erraten.

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)

Ist dies der einfachste Weg?

Rechts in einer Ansicht Code etwas wie folgt aus:

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)

Es funktioniert!

Für django 1.9+
Sie können Felder deaktiviert Argument verwenden, um Feld zu deaktivieren zu machen. z.B. Im folgenden Code-Schnipsel aus forms.py Datei, habe ich gemacht employee_code Feld deaktiviert

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

Referenz https://docs.djangoproject.com/en/2.0/ref / forms / Felder / # deaktiviert

Wenn Sie mit Django ver < 1.9 arbeiten (die 1.9 hinzugefügt Field.disabled Attribute) Sie könnten versuchen, folgenden Dekorateur zu Ihrem Formular __init__ Methode hinzufügen:

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

Die Grundidee ist, dass, wenn das Feld ist readonly Sie brauchen keinen anderen Wert außer initial.

P. S: Vergessen Sie nicht yuor_form_field.widget.attrs['readonly'] = True einstellen

Wenn Sie Django Admin verwenden, hier ist die einfachste Lösung.

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

Ich denke, die beste Option würde nur das Read-only-Attribut in der Vorlage in einem <span> oder <p> gemacht enthalten sein, anstatt sie in Form ist, wenn es nur lesbar.

Formulare sind zum Sammeln von Daten, nicht angezeigt werden. Davon abgesehen, in einem readonly Widget und Gestrüpp POST-Daten sind feine Lösungen die Optionen anzuzeigen.

Ich löste dieses Problem wie folgt:

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

in Aussicht:

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

Es ist alles.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top