Wie mache ich in einem Django-Formular ein Feld schreibgeschützt (oder deaktiviert), damit es nicht bearbeitet werden kann?

430

Wie mache ich in einem Django-Formular ein Feld schreibgeschützt (oder deaktiviert)?

Wenn das Formular zum Erstellen eines neuen Eintrags verwendet wird, sollten alle Felder aktiviert sein. Wenn sich der Datensatz jedoch im Aktualisierungsmodus befindet, müssen einige Felder schreibgeschützt sein.

Wenn Sie beispielsweise ein neues ItemModell erstellen , müssen alle Felder bearbeitet werden können. Gibt es jedoch beim Aktualisieren des Datensatzes eine Möglichkeit, das skuFeld so zu deaktivieren , 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

Kann der Unterricht ItemFormwiederverwendet werden? Welche Änderungen wären in der ItemFormoder ItemModellklasse erforderlich ? Muss ich eine weitere Klasse " ItemUpdateForm" schreiben , um das Element zu aktualisieren?

def update_item_view(request):
    if request.method == 'POST':
        form = ItemUpdateForm(request.POST)
        # Validate and save
    else:
        form = ItemUpdateForm()
X10
quelle
Siehe auch SO-Frage: Warum sind schreibgeschützte Formularfelder in Django eine schlechte Idee? @ stackoverflow.com/questions/2902024 , Akzeptierte Antwort (von Daniel Naab) kümmert sich um böswillige POST-Hacks.
X10

Antworten:

420

Wie in dieser Antwort ausgeführt , hat Django 1.9 das Attribut Field.disabled hinzugefügt :

Wenn das boolesche Argument deaktiviert auf True gesetzt ist, wird ein Formularfeld mithilfe des deaktivierten HTML-Attributs deaktiviert, sodass es von Benutzern nicht bearbeitet werden kann. Selbst wenn ein Benutzer den an den Server gesendeten Feldwert manipuliert, wird dieser zugunsten des Werts aus den Anfangsdaten des Formulars ignoriert.

Mit Django 1.8 und früheren Versionen müssen Sie zusätzlich zum Festlegen des readonlyAttributs im Formularfeld die Eingabe bereinigen, um den Eintrag im Widget zu deaktivieren und böswillige POST-Hacks zu verhindern :

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 Sie sie if instance and instance.pkdurch eine andere Bedingung, die angibt, dass Sie bearbeiten. Sie können das Attribut auch disabledim Eingabefeld anstelle von festlegen readonly.

Die clean_skuFunktion stellt sicher, dass der readonlyWert nicht von a überschrieben wird POST.

Andernfalls gibt es kein integriertes Django-Formularfeld, das einen Wert rendert, während gebundene Eingabedaten zurückgewiesen werden. Wenn Sie dies wünschen, sollten Sie stattdessen ein separates ModelFormFeld erstellen , das die nicht bearbeitbaren Felder ausschließt, und sie einfach in Ihre Vorlage drucken.

Daniel Naab
quelle
3
Daniel, Danke, dass du eine Antwort gepostet hast. Es ist mir nicht klar, wie ich diesen Code verwenden soll? Würde dieser Code nicht auch für den neuen und den neuen Aktualisierungsmodus funktionieren? Können Sie Ihre Antwort bearbeiten, um Beispiele für die Verwendung für neue und aktualisierte Formulare zu geben? Vielen Dank.
X10
8
Der Schlüssel zu Daniels Beispiel ist das Testen des .id-Feldes. Neu erstellte Objekte haben die ID == Keine. Übrigens, eines der ältesten offenen Django-Tickets befasst sich mit diesem Thema. Siehe code.djangoproject.com/ticket/342 .
Peter Rowell
2
@moadeep fügt clean_descriptionder Formularklasse eine Methode hinzu.
Daniel Naab
3
Unter Linux (Ubuntu 15) / Chrome V45 wird der Zeiger schreibgeschützt in eine "deaktivierte Hand" geändert, aber das Feld kann dann angeklickt werden. mit deaktiviert funktioniert es wie erwartet
simone cittadini
7
Diese Antwort muss aktualisiert werden. In disabledDjango 1.9 wird ein neues Feldargument hinzugefügt. Wenn auf gesetzt Field.disabledist True, wird der POST-Wert dafür Fieldignoriert. Wenn Sie also 1.9 verwenden, müssen Sie nicht überschreiben clean, sondern nur festlegen disabled = True. Überprüfen Sie diese Antwort.
Narendra-Choudhary
174

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

Wenn das boolesche Argument deaktiviert auf True gesetzt ist, wird ein Formularfeld mithilfe des deaktivierten HTML-Attributs deaktiviert, sodass es von Benutzern nicht bearbeitet werden kann. Selbst wenn ein Benutzer den an den Server gesendeten Feldwert manipuliert, wird dieser zugunsten des Werts aus den Anfangsdaten des Formulars ignoriert.

Mike Mahmud
quelle
Nichts für 1,8 LTS?
dnit13
9
Haben Sie eine Idee, wie wir dies in einem UpdateView verwenden können? Da es die Felder aus dem Modell generiert ...
bcsanches
6
Richtige Antwort. Meine Lösungsklasse MyChangeForm (forms.ModelForm): def __init __ (self, * args, ** kwargs): super (MyChangeForm, self) .__ init __ (* args, ** kwargs) self.fields ['my_field']. Disabled = Richtig
Vijay Katam
8
Dies ist eine problematische Antwort. Durch die Einstellung disabled=Truewird das Modell mit Validierungsfehlern an den Benutzer zurückgespuckt.
Ben
1
Wäre fantastisch, wenn Sie ein Beispiel
hinzufügen
95

Durch die Einstellung readonlyin einem Widget wird die Eingabe im Browser nur schreibgeschützt. Durch Hinzufügen einer clean_skuRückgabe wird instance.skusichergestellt, dass sich der Feldwert auf Formularebene nicht ändert.

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

Auf diese Weise können Sie Modelle (unverändertes Speichern) verwenden und vermeiden, dass der Feldfehler angezeigt wird.

Muhuk
quelle
15
+1 Dies ist eine großartige Möglichkeit, um kompliziertere save () - Überschreibungen zu vermeiden. Sie möchten jedoch vor der Rückgabe eine Instanzprüfung durchführen (im Kommentarmodus ohne Zeilenumbruch): "if self.instance: return self.instance.sku; else: return self.fields ['sku']"
Daniel Naab
2
Wäre für die letzte Zeile return self.cleaned_data['sku']so gut oder besser? In den Dokumenten wird anscheinend Folgendes vorgeschlagen cleaned_data: "Der Rückgabewert dieser Methode ersetzt den vorhandenen Wert in cleaned_data. Es muss also der Wert des Felds von cleaned_data(auch wenn diese Methode ihn nicht geändert hat) oder ein neuer bereinigter Wert sein."
PianoJames
67

Antwort des Walkers hat mir sehr geholfen!

Ich habe sein Beispiel geändert, um mit Django 1.3 unter Verwendung von get_readonly_fields zu arbeiten .

Normalerweise sollten Sie so etwas deklarieren in app/admin.py:

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

Ich habe mich folgendermaßen 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. Wenn Sie nun ein Element hinzufügen, ist das urlFeld schreibgeschützt, bei Änderung jedoch schreibgeschützt.

chirale
quelle
55

Damit dies für ein ForeignKeyFeld funktioniert , müssen einige Änderungen vorgenommen werden. Erstens hat das SELECT HTMLTag nicht das Attribut readonly. Wir müssen verwendendisabled="disabled" stattdessen verwenden. Dann sendet der Browser jedoch keine Formulardaten für dieses Feld zurück. Daher müssen wir festlegen, dass dieses Feld nicht erforderlich ist, damit das Feld korrekt validiert wird. Wir müssen dann den Wert auf den ursprünglichen Wert zurücksetzen, damit er nicht leer bleibt.

Für Fremdschlüssel müssen Sie also Folgendes 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 lässt der Browser den Benutzer das Feld nicht ändern und lässt es immer so, POSTwie es leer gelassen wurde. Anschließend überschreiben wir die cleanMethode, um den Wert des Felds auf den ursprünglichen Wert in der Instanz festzulegen.

Humphrey
quelle
Ich habe versucht, es als Formular in zu verwenden TabularInline, bin jedoch fehlgeschlagen, da attrses zwischen widgetInstanzen und allen außer der ersten Zeile, einschließlich der neu hinzugefügten, schreibgeschützt gerendert wurde.
Dhill
Eine großartige (Update-) Lösung! Leider haben dies und der Rest Probleme, wenn Formularfehler auftreten, da alle "deaktivierten" Werte geleert werden.
Michael Thompson
28

Für Django 1.2+ können Sie das Feld folgendermaßen überschreiben:

sku = forms.CharField(widget = forms.TextInput(attrs={'readonly':'readonly'}))
StefanNch
quelle
6
Dadurch kann das Feld auch nicht zum Zeitpunkt des Hinzufügens bearbeitet werden, worum es in der ursprünglichen Frage geht.
Matt S.
Dies ist die Antwort, die ich suche. Field disabledmacht nicht was ich will, weil es das Feld deaktiviert, sondern auch Beschriftung entfernt / unsichtbar macht.
Sivabudh
18

Ich habe eine MixIn-Klasse erstellt, die Sie möglicherweise erben, um ein iterierbares Feld read_only hinzufügen zu können, das Felder bei der nicht ersten Bearbeitung deaktiviert und sichert:

(Basierend auf Daniels und Muhuks 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')
christophe31
quelle
11

Ich habe gerade das einfachste Widget für ein schreibgeschütztes Feld erstellt. Ich verstehe nicht wirklich, warum Formulare dies 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 bringt mich nur zur Ausgabe. Praktisch in einem Formset mit einer Reihe von schreibgeschützten Werten. Natürlich - Sie könnten auch ein bisschen schlauer sein und es mit den attrs teilen, damit Sie Klassen daran anhängen können.

Danny Staple
quelle
2
Sieht sexy aus, aber wie geht man mit Fremdschlüsseln um?
Andilabs
Machen Sie unicode(value)das stattdessen vielleicht in der Rückgabe. Angenommen, der Unicode-Dunder ist sinnvoll, dann würden Sie das bekommen.
Danny Staple
Für Fremdschlüssel müssen Sie ein "Modell" -Attribut hinzufügen und "get (Wert)" verwenden. Überprüfen Sie meinen Kern
Shadi
10

Ich bin auf ein ähnliches Problem gestoßen. Es sieht so aus, als ob ich es lösen konnte, indem ich eine "get_readonly_fields" -Methode in meiner ModelAdmin-Klasse definierte.

Etwas wie das:

# 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 ist das obj beim Hinzufügen eines neuen Elements "Keine" oder beim Ändern eines vorhandenen Elements das Objekt bearbeitet wird.

get_readonly_display ist hier dokumentiert: http://docs.djangoproject.com/de/1.2/ref/contrib/admin/#modeladmin-methods

awalker
quelle
6

Eine einfache Möglichkeit besteht darin, statt nur form.instance.fieldNamedie Vorlage einzugeben form.fieldName.

alzclarke
quelle
Und wie wäre es mit dem verbos_nameoder labelvom Feld? Wie kann ich `label in der Django-Vorlage anzeigen? @alzclarke
Wal 52Hz
6

Wie ich es mit Django 1.11 mache:

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
Lucas B.
quelle
Dies wird nur von vorne blockiert. Jeder kann umgehen. Dies führt zu einem Sicherheitsproblem, wenn Sie vertrauliche Daten verarbeiten
Sarath Ak
Es ist sicher; Es blockiert auch im Backend, da Django> = 1.10 docs.djangoproject.com/de/1.10/ref/forms/fields/…
timdiels
5

Als nützliche Ergänzung zu Humphreys Beitrag hatte ich einige Probleme mit der Django-Umkehrung, da deaktivierte Felder immer noch als "geändert" registriert wurden. 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)
Evan Brumley
quelle
5

Da ich noch keinen Kommentar abgeben kann ( Muhuks Lösung ), werde ich als separate Antwort antworten. Dies ist ein vollständiges Codebeispiel, das für mich funktioniert hat:

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

Ich werde noch einmal eine weitere Lösung anbieten :) Ich habe Humphreys Code verwendet , also basiert dies darauf.

Ich stieß jedoch auf Probleme mit dem Feld a ModelChoiceField. Alles würde bei der ersten Anfrage funktionieren. Wenn das Formularset jedoch versuchte, ein neues Element hinzuzufügen, und die Validierung fehlschlug, lief bei den "vorhandenen" Formularen, bei denen die SELECTEDOption auf die Standardeinstellung zurückgesetzt wurde, ein Fehler--------- .

Wie auch immer, ich konnte nicht herausfinden, wie ich das beheben konnte. Also habe ich stattdessen (und ich denke, das ist tatsächlich sauberer in der Form) die Felder gemacht HiddenInputField(). Dies bedeutet nur, dass Sie etwas mehr Arbeit in der Vorlage erledigen müssen.

Die Lösung für mich bestand also darin, das Formular 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 müssen Sie in der Vorlage eine manuelle Schleife des Formularsatzes durchführen .

In diesem Fall würden Sie also in der Vorlage Folgendes tun:

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

Dies funktionierte etwas besser für mich und mit weniger Formmanipulation.

JamesD
quelle
4

Ich hatte das gleiche Problem und habe ein Mixin erstellt, das für meine Anwendungsfälle zu funktionieren scheint.

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, definieren Sie einfach, welche schreibgeschützt sein müssen:

class MyFormWithReadOnlyFields(ReadOnlyFieldsMixin, MyForm):
    readonly_fields = ('field1', 'field2', 'fieldx')
Michael
quelle
Ich nehme an, es ist etwas lesbarer als mein eigenes Mixin, das ich hier vorgeschlagen habe. Wahrscheinlich sogar effizienter, da diese Bereinigungen keine Validierungsfehler verursachen…
christophe31
Ich erhalte eine Fehlermeldung:'collections.OrderedDict' object has no attribute 'iteritems'
Geoidesic
4

Wenn Sie mehrere schreibgeschützte Felder benötigen, können Sie eine der unten angegebenen Methoden verwenden

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',)
Sarath Ak
quelle
3

Zwei weitere (ähnliche) Ansätze mit einem verallgemeinerten Beispiel:

1) erster Ansatz - Entfernen des Feldes in der 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) zweiter Ansatz - Feld in der sauberen Methode auf den Anfangswert zurücksetzen:

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

Basierend auf dem zweiten Ansatz habe ich es so verallgemeinert:

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)
Robert Lujo
quelle
3

Für die Admin-Version ist dies meiner Meinung nach kompakter, 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
Hassek
quelle
3

Basierend auf der Antwort von Yamikep habe ich eine bessere und sehr einfache Lösung gefunden, die auch ModelMultipleChoiceFieldFelder behandelt.

Das Entfernen von form.cleaned_dataFeldern aus verhindert, dass Felder gespeichert werden:

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

Verwendungszweck:

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

Hier ist eine etwas kompliziertere Version, die auf der Antwort von christophe31 basiert . Es ist nicht auf das Attribut "readonly" angewiesen. Dies führt dazu, dass die Probleme, z. B. dass Auswahlfelder immer noch veränderbar sind und Daten-Picker immer noch auftauchen, verschwinden.

Stattdessen wird das Widget für Formularfelder in ein schreibgeschütztes Widget eingeschlossen, sodass das Formular weiterhin gültig ist. Der Inhalt des ursprünglichen Widgets wird in <span class="hidden"></span>Tags angezeigt . Wenn das Widget eine render_readonly()Methode hat, verwendet es diese als sichtbaren Text, andernfalls analysiert es den HTML-Code des ursprünglichen Widgets 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)
Rune Kaagaard
quelle
1

Ist das der einfachste Weg?

Genau in einem Ansichtscode wie folgt:

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 gut!

fly_frog
quelle
1

Für Django 1.9+
Sie können das Argument Felder deaktiviert verwenden, um das Feld zu deaktivieren. Beispiel: Im folgenden Codeausschnitt aus der Datei forms.py habe ich das Feld employee_code 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/de/2.0/ref/forms/fields/#disabled

Ajinkya Bhosale
quelle
1

Wenn Sie mit Django ver < 1.9(das Attribut 1.9hat hinzugefügt Field.disabled) arbeiten, können Sie versuchen, Ihrer Formularmethode den folgenden Dekorator hinzuzufügen __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):
        ...

Die Hauptidee ist, dass readonlySie , wenn Feld ist , keinen anderen Wert benötigen als initial.

PS: Vergiss nicht einzustellen yuor_form_field.widget.attrs['readonly'] = True

Jaroslaw Varkhol
quelle
0

Wenn Sie Django admin verwenden, ist hier 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',)
utapyngo
quelle
0

Ich denke, Ihre beste Option wäre nur, das schreibgeschützte Attribut in Ihre Vorlage aufzunehmen, die in einem <span>oder gerendert ist, <p>anstatt es in das Formular aufzunehmen, wenn es schreibgeschützt ist.

Formulare dienen zum Sammeln und nicht zum Anzeigen von Daten. Abgesehen davon sind die Optionen zum Anzeigen in einem readonlyWidget und zum Bereinigen von POST-Daten gute Lösungen.

Austinheiman
quelle