Dans un formulaire Django, comment puis-je rendre un champ en lecture seule (ou désactivé) afin qu'il ne puisse pas être modifié?

431

Dans un formulaire Django, comment puis-je rendre un champ en lecture seule (ou désactivé)?

Lorsque le formulaire est utilisé pour créer une nouvelle entrée, tous les champs doivent être activés - mais lorsque l'enregistrement est en mode de mise à jour, certains champs doivent être en lecture seule.

Par exemple, lors de la création d'un nouveau Itemmodèle, tous les champs doivent être modifiables, mais lors de la mise à jour de l'enregistrement, existe-t-il un moyen de désactiver le skuchamp pour qu'il soit visible, mais ne puisse pas être modifié?

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

La classe peut-elle ItemFormêtre réutilisée? Quels changements seraient nécessaires dans la classe ItemFormou le Itemmodèle? Aurais-je besoin d'écrire une autre classe, " ItemUpdateForm", pour mettre à jour l'élément?

def update_item_view(request):
    if request.method == 'POST':
        form = ItemUpdateForm(request.POST)
        # Validate and save
    else:
        form = ItemUpdateForm()
X10
la source
Voir aussi SO question: Pourquoi les champs de formulaire en lecture seule dans Django sont-ils une mauvaise idée? @ stackoverflow.com/questions/2902024 , la réponse acceptée (par Daniel Naab) prend en charge les hacks POST malveillants.
X10

Réponses:

422

Comme indiqué dans cette réponse , Django 1.9 a ajouté l' attribut Field.disabled :

L'argument booléen désactivé, lorsqu'il est défini sur True, désactive un champ de formulaire à l'aide de l'attribut HTML désactivé afin qu'il ne soit pas modifiable par les utilisateurs. Même si un utilisateur altère la valeur du champ soumise au serveur, elle sera ignorée en faveur de la valeur des données initiales du formulaire.

Avec Django 1.8 et versions antérieures, pour désactiver l'entrée sur le widget et empêcher les hacks POST malveillants, vous devez nettoyer l'entrée en plus de définir l' readonlyattribut dans le champ du formulaire:

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

Ou, remplacez if instance and instance.pkpar une autre condition indiquant que vous modifiez. Vous pouvez également définir l'attribut disableddans le champ de saisie au lieu de readonly.

La clean_skufonction s'assurera que la readonlyvaleur ne sera pas remplacée par a POST.

Sinon, il n'y a pas de champ de formulaire Django intégré qui rendra une valeur tout en rejetant les données d'entrée liées. Si c'est ce que vous désirez, vous devez plutôt créer un ModelFormélément séparé qui exclut le ou les champs non modifiables, et les imprimer simplement dans votre modèle.

Daniel Naab
la source
3
Daniel, Merci d'avoir posté une réponse. Il n'est pas clair pour moi comment utiliser ce code? ce code ne fonctionnerait-il pas pour le même pour le nouveau mode de mise à jour? Pouvez-vous modifier votre réponse pour donner des exemples sur la façon de l'utiliser pour les nouveaux formulaires et les mises à jour? Merci.
X10
8
La clé de l'exemple de Daniel est de tester le champ .id. Les objets nouvellement créés auront id == Aucun. Soit dit en passant, l'un des plus anciens tickets Django ouverts concerne ce problème. Voir code.djangoproject.com/ticket/342 .
Peter Rowell
2
@moadeep ajoute une clean_descriptionméthode à la classe de formulaire.
Daniel Naab
3
sur linux (ubuntu 15) / chrome v45, change le pointeur en "main désactivée" en lecture seule mais le champ est alors cliquable. avec handicapé cela fonctionne comme prévu
simone cittadini
7
Cette réponse doit être mise à jour. Un nouvel argument de champ disabledest ajouté dans Django 1.9. Si Field.disabledest défini sur True, la valeur POST pour cela Fieldest ignorée. Donc, si vous utilisez 1.9, il n'est pas nécessaire de remplacer clean, il suffit de définir disabled = True. Vérifiez cette réponse.
narendra-choudhary
174

Django 1.9 a ajouté l'attribut Field.disabled: https://docs.djangoproject.com/en/stable/ref/forms/fields/#disabled

L'argument booléen désactivé, lorsqu'il est défini sur True, désactive un champ de formulaire à l'aide de l'attribut HTML désactivé afin qu'il ne soit pas modifiable par les utilisateurs. Même si un utilisateur altère la valeur du champ soumise au serveur, elle sera ignorée en faveur de la valeur des données initiales du formulaire.

Mike Mahmud
la source
Rien pour 1,8 LTS?
dnit13
9
une idée de comment nous pouvons l'utiliser sur une UpdateView? Comme il génère les champs du modèle ...
bcsanches
6
Bonne réponse. Ma classe de solution MyChangeForm (forms.ModelForm): def __init __ (self, * args, ** kwargs): super (MyChangeForm, self) .__ init __ (* args, ** kwargs) self.fields ['my_field']. Disabled = Vrai
Vijay Katam
8
Il s'agit d'une réponse problématique - le réglage disabled=Trueentraînera le crachat du modèle à l'utilisateur avec des erreurs de validation.
Ben
1
Ce serait génial si vous pouviez inclure un exemple
geoidesic
95

La définition readonlyd'un widget ne fait que l'entrée dans le navigateur en lecture seule. L'ajout d'un clean_skuqui renvoie instance.skugarantit que la valeur du champ ne changera pas au niveau du formulaire.

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

De cette façon, vous pouvez utiliser le modèle (sauvegarde non modifiée) et éviter d'obtenir l'erreur de champ requise.

muhuk
la source
15
+1 C'est un excellent moyen d'éviter les remplacements de save () plus compliqués. Cependant, vous voudriez faire une vérification d'instance avant le retour (en mode de commentaire sans nouvelle ligne): "si self.instance: return self.instance.sku; else: return self.fields ['sku']"
Daniel Naab
2
Pour la dernière ligne, serait- return self.cleaned_data['sku']ce aussi bon ou meilleur? Les documents semblent suggérer d'utiliser cleaned_data: "La valeur de retour de cette méthode remplace la valeur existante dans cleaned_data, il doit donc s'agir de la valeur du champ cleaned_data(même si cette méthode ne l'a pas modifiée) ou d'une nouvelle valeur nettoyée."
pianoJames
67

réponse de awalker m'a beaucoup aidé!

J'ai changé son exemple pour travailler avec Django 1.3, en utilisant get_readonly_fields .

Habituellement, vous devez déclarer quelque chose comme ceci dans app/admin.py:

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

Je me suis adapté de cette façon:

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

Et ça marche bien. Maintenant, si vous ajoutez un élément, le urlchamp est en lecture-écriture, mais en cas de modification, il devient en lecture seule.

chirale
la source
56

Pour que cela fonctionne pour un ForeignKeychamp, quelques modifications doivent être apportées. Premièrement, la SELECT HTMLbalise n'a pas l'attribut readonly. Nous devons utiliserdisabled="disabled" place. Cependant, le navigateur n'envoie aucune donnée de formulaire pour ce champ. Nous devons donc définir ce champ pour qu'il ne soit pas requis afin que le champ soit validé correctement. Nous devons ensuite réinitialiser la valeur à ce qu'elle était pour qu'elle ne soit pas définie sur vide.

Donc, pour les clés étrangères, vous devrez faire quelque chose comme:

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)

De cette façon, le navigateur ne permettra pas à l'utilisateur de modifier le champ et restera toujours POSTtel qu'il a été laissé vide. Nous remplaçons ensuite la cleanméthode pour définir la valeur du champ comme ce qui était à l'origine dans l'instance.

Humphrey
la source
J'ai essayé de l'utiliser comme formulaire dans TabularInline, mais j'ai échoué car ils attrsétaient partagés entre les widgetinstances et toutes sauf la première ligne, y compris la nouvelle ligne ajoutée, restituée en lecture seule.
dhill
Une excellente (mise à jour) solution! Malheureusement, cela et les autres ont des problèmes lorsqu'il y a des erreurs de formulaire car toutes les valeurs "désactivées" sont vidées.
Michael Thompson
28

Pour Django 1.2+, vous pouvez remplacer le champ comme ceci:

sku = forms.CharField(widget = forms.TextInput(attrs={'readonly':'readonly'}))
StefanNch
la source
6
Cela ne permet pas non plus de modifier le champ au moment de l'ajout, ce qui est le sujet de la question d'origine.
Matt
C'est la réponse que je cherche. Field disabledne fait pas ce que je veux car il désactive le champ, mais supprime également l'étiquette / le rend invisible.
sivabudh
18

J'ai créé une classe MixIn dont vous pouvez hériter pour pouvoir ajouter un champ itérable en lecture seule qui désactivera et sécurisera les champs lors de la première modification:

(Basé sur les réponses de Daniel et 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')
christophe31
la source
11

Je viens de créer le widget le plus simple possible pour un champ en lecture seule - je ne vois pas vraiment pourquoi les formulaires ne l'ont pas déjà:

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

Sous la forme:

my_read_only = CharField(widget=ReadOnlyWidget())

Très simple - et me donne juste une sortie. Pratique dans un jeu de formulaires avec un tas de valeurs en lecture seule. Bien sûr, vous pourriez également être un peu plus intelligent et lui donner une div avec les attrs afin que vous puissiez y ajouter des classes.

Danny Staple
la source
2
Semble sexy, mais comment gérer la clé étrangère?
andilabs du
Faites cela unicode(value)dans le retour à la place peut-être. En supposant que le butin unicode est raisonnable, vous obtiendriez alors cela.
Danny Staple du
Pour les clés étrangères, vous devrez ajouter un attribut "modèle" et utiliser "get (valeur)". Check my gist
shadi
10

J'ai rencontré un problème similaire. Il semble que j'ai pu le résoudre en définissant une méthode "get_readonly_fields" dans ma classe ModelAdmin.

Quelque chose comme ça:

# In the admin.py file

class ItemAdmin(admin.ModelAdmin):

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

La bonne chose est que obj sera Aucun lorsque vous ajoutez un nouvel élément, ou ce sera l'objet en cours de modification lorsque vous modifiez un élément existant.

get_readonly_display est documenté ici: http://docs.djangoproject.com/en/1.2/ref/contrib/admin/#modeladmin-methods

traqueur
la source
6

Une option simple consiste à taper simplement form.instance.fieldNamele modèle au lieu de form.fieldName.

alzclarke
la source
Et qu'en est-il du verbos_nameou labeldu champ? Comment afficher `label dans le modèle django? @alzclarke
Whale 52Hz
6

Comment je le fais avec 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
Lucas B
la source
cela bloquera seulement de front. tout le monde peut contourner. cela posera un problème de sécurité si vous le faites sur des données sensibles
Sarath Ak
C'est sur; il bloque également en backend depuis Django> = 1.10 docs.djangoproject.com/en/1.10/ref/forms/fields/…
timdiels
5

Comme ajout utile au message de Humphrey , j'ai eu quelques problèmes avec django-reversion, car il enregistrait toujours les champs désactivés comme «modifiés». Le code suivant résout le problème.

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
la source
5

Comme je ne peux pas encore commenter ( la solution de muhuk ), je vais répondre comme une réponse distincte. Ceci est un exemple de code complet, qui a fonctionné pour moi:

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

Encore une fois, je vais offrir une solution de plus :) J'utilisais le code de Humphrey , c'est donc basé sur cela.

Cependant, j'ai rencontré des problèmes avec le domaine étant un ModelChoiceField. Tout fonctionnerait à la première demande. Cependant, si le jeu de formulaires tentait d'ajouter un nouvel élément et échouait à la validation, quelque chose n'allait pas avec les formulaires "existants" où l' SELECTEDoption était réinitialisée par défaut ---------.

Quoi qu'il en soit, je n'ai pas réussi à résoudre ce problème. Donc à la place, (et je pense que c'est en fait plus propre dans la forme), j'ai fait les champs HiddenInputField(). Cela signifie simplement que vous devez faire un peu plus de travail dans le modèle.

La solution pour moi a donc été de simplifier le formulaire:

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

Et puis dans le modèle, vous devrez faire une boucle manuelle du formset .

Donc, dans ce cas, vous feriez quelque chose comme ça dans le modèle:

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

Cela a fonctionné un peu mieux pour moi et avec moins de manipulation de forme.

JamesD
la source
4

J'allais dans le même problème alors j'ai créé un Mixin qui semble fonctionner pour mes cas d'utilisation.

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

Utilisation, définissez simplement ceux qui doivent être en lecture seule:

class MyFormWithReadOnlyFields(ReadOnlyFieldsMixin, MyForm):
    readonly_fields = ('field1', 'field2', 'fieldx')
Michael
la source
Je suppose que c'est un peu plus lisible que mon propre mixin que j'ai suggéré ici. Même probablement plus efficace, car ces nettoyages ne soulèvent pas d'erreurs de validations…
christophe31
Je reçois une erreur:'collections.OrderedDict' object has no attribute 'iteritems'
geoidesic
4

Si vous avez besoin de plusieurs champs en lecture seule, vous pouvez utiliser l'une des méthodes indiquées ci-dessous

méthode 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

méthode 2

méthode d'héritage

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
la source
3

Deux autres approches (similaires) avec un exemple généralisé:

1) première approche - suppression du champ dans la méthode save (), par exemple (non testé;)):

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) deuxième approche - réinitialiser le champ à sa valeur initiale dans la méthode propre:

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

Basé sur la deuxième approche, je l'ai généralisé comme ceci:

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
la source
3

Pour la version Admin, je pense que c'est un moyen plus compact si vous avez plusieurs champs:

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
la source
3

Sur la base de la réponse de Yamikep , j'ai trouvé une solution meilleure et très simple qui gère également les ModelMultipleChoiceFieldchamps.

La suppression d'un champ form.cleaned_dataempêche l'enregistrement des champs:

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')
darklow
la source
2

Voici une version un peu plus impliquée, basée sur la réponse de christophe31 . Il ne repose pas sur l'attribut "en lecture seule". Cela fait disparaître ses problèmes, comme les zones de sélection toujours modifiables et les sélecteurs de données qui apparaissent encore.

Au lieu de cela, il enveloppe le widget de champs de formulaire dans un widget en lecture seule, ce qui rend le formulaire toujours valide. Le contenu du widget d'origine est affiché à l'intérieur des <span class="hidden"></span>balises. Si le widget a une render_readonly()méthode, il l'utilise comme texte visible, sinon il analyse le code HTML du widget d'origine et essaie de deviner la meilleure représentation.

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
la source
1

Est-ce le moyen le plus simple?

Dans un code de vue quelque chose comme ceci:

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)

Ça fonctionne bien!

fly_frog
la source
1

Pour django 1.9+
Vous pouvez utiliser l'argument Champs désactivés pour désactiver le champ. Par exemple, dans l'extrait de code suivant du fichier forms.py, j'ai désactivé le champ employee_code

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

Référence https://docs.djangoproject.com/en/2.0/ref/forms/fields/#disabled

Ajinkya Bhosale
la source
1

Si vous travaillez avec Django ver < 1.9(l' attribut 1.9a ajouté Field.disabled), vous pouvez essayer d'ajouter le décorateur suivant à votre __init__méthode de formulaire :

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

L'idée principale est que si le champ est, readonlyvous n'avez besoin d'aucune autre valeur sauf initial.

PS: N'oubliez pas de régler yuor_form_field.widget.attrs['readonly'] = True

Yaroslav Varkhol
la source
0

Si vous utilisez l'administrateur Django, voici la solution la plus simple.

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
la source
0

Je pense que votre meilleure option serait simplement d'inclure l'attribut readonly dans votre modèle rendu dans un <span>ou<p> plutôt que de l'inclure dans le formulaire s'il est en lecture seule.

Les formulaires servent à collecter des données, pas à les afficher. Cela étant dit, les options à afficher dans un readonlywidget et à nettoyer les données POST sont de bonnes solutions.

Austinheiman
la source