523 votes

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

Dans un formulaire Django, comment faire pour qu'un champ soit 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'une nouvelle Item tous les champs doivent être modifiables, mais lors de la mise à jour de l'enregistrement, existe-t-il un moyen de désactiver l'option sku 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 Can ItemForm être réutilisé ? Quels changements seraient nécessaires dans le ItemForm o Item classe modèle ? Devrais-je écrire une autre classe, " ItemUpdateForm ", pour la mise à jour de l'article ?

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

1 votes

Voir aussi la question sur le SO : 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.

0 votes

493voto

Daniel Naab Points 9857

Comme indiqué dans cette réponse Django 1.9 a ajouté la fonction Champ.désactivé attribut :

L'argument booléen disabled, lorsqu'il a la valeur True, désactive un champ de formulaire à l'aide de l'attribut HTML disabled afin qu'il ne soit pas modifiable par les utilisateurs. Même si un utilisateur modifie la valeur du champ soumise au serveur, celle-ci sera ignorée au profit de la valeur des données initiales du formulaire.

Avec Django 1.8 et les versions antérieures, pour désactiver l'entrée sur le widget et empêcher les piratages POST malveillants, vous devez scruter l'entrée en plus de définir la propriété readonly sur 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 bien, remplacez if instance and instance.pk avec une autre condition indiquant que vous êtes en train d'éditer. Vous pouvez également définir l'attribut disabled sur le champ de saisie, au lieu de readonly .

El clean_sku permettra de s'assurer que la readonly ne sera pas remplacée par une valeur de type 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 souhaitez, vous devriez plutôt créer un champ de formulaire séparé, le ModelForm qui exclut le(s) champ(s) non éditable(s), et les imprimer simplement dans votre modèle.

3 votes

Daniel, merci d'avoir posté une réponse. Je ne comprends pas très bien comment utiliser ce code. Ce code ne fonctionnerait-il pas de la même manière pour le mode nouveau et le mode 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 formulaires de mise à jour ? Merci.

9 votes

La clé de l'exemple de Daniel est de tester le champ .id. Les objets nouvellement créés auront id==None. D'ailleurs, l'un des plus anciens tickets Django ouverts concerne ce problème. Voir code.djangoproject.com/ticket/342 .

0 votes

Mais qu'en est-il des champs à clé étrangère ?

100voto

muhuk Points 6526

Réglage de readonly sur un widget ne fait que rendre l'entrée dans le navigateur en lecture seule. L'ajout d'un clean_sku qui renvoie instance.sku garantit que la valeur du champ ne sera pas modifiée 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 les modèles (sauvegarde non modifiée) et éviter d'obtenir l'erreur de champ obligatoire.

18 votes

+1 C'est un excellent moyen d'éviter les surcharges save() plus compliquées. Cependant, vous voudrez faire un contrôle d'instance avant le retour (en mode commentaire sans ligne nouvelle) : "if self.instance : return self.instance.sku ; else : return self.fields['sku']"

4 votes

Pour la dernière ligne, est-ce que return self.cleaned_data['sku'] être aussi bon ou meilleur ? Le site docs semblent suggérer d'utiliser cleaned_data : " La valeur de retour de cette méthode remplace la valeur existante dans la rubrique cleaned_data Il doit donc s'agir de la valeur du champ provenant de l'adresse suivante cleaned_data (même si cette méthode ne l'a pas modifié) ou une nouvelle valeur nettoyée".

85voto

chirale Points 651

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

J'ai modifié son exemple pour qu'il fonctionne avec Django 1.3, en utilisant les éléments suivants 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 cela fonctionne bien. Maintenant, si vous ajoutez un élément, le url est en lecture-écriture, mais lorsqu'il est modifié, il devient en lecture seule.

0 votes

Comment faire, sans pouvoir écrire sur le terrain ?

0 votes

Le premier extrait de code désactive entièrement l'écriture sur le champ url, le second extrait désactive l'écriture sur le champ url uniquement sur les instances d'éléments existants. Vous pouvez changer la condition pour obtenir un comportement différent mais vous ne pouvez pas utiliser les deux si je comprends bien la question.

0 votes

J'ai essayé readonly_fields mais ça n'a pas marché, parce que je devais avoir fields également. Ce que j'ai fait à la place, c'est d'afficher les valeurs dans des variables, maintenant elles ne sont qu'en lecture seule.

66voto

Humphrey Points 1057

Pour que cela fonctionne pour un ForeignKey champ, quelques changements doivent être effectués. Tout d'abord, le SELECT HTML ne possède pas l'attribut readonly. Nous devons utiliser disabled="disabled" à la place. Cependant, le navigateur ne renvoie pas de données de formulaire pour ce champ. Nous devons donc faire en sorte que ce champ ne soit pas obligatoire pour qu'il soit validé correctement. Nous devons ensuite réinitialiser la valeur du champ pour qu'elle ne soit pas vide.

Ainsi, 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 laissera pas l'utilisateur modifier le champ, et toujours POST car il a été laissé en blanc. Nous remplaçons ensuite le clean pour définir la valeur du champ comme étant celle qui se trouvait à l'origine dans l'instance.

0 votes

J'ai essayé de l'utiliser comme formulaire dans TabularInline mais a échoué parce que attrs ont été partagés entre widget et toutes les lignes sauf la première, y compris la nouvelle, sont en lecture seule.

0 votes

Une excellente solution (de mise à jour) ! Malheureusement, cette solution 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.

38voto

StefanNch Points 932

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

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

6 votes

Cela ne permet pas non plus de modifier le champ au moment de l'ajout, ce qui est l'objet de la question initiale.

0 votes

C'est la réponse que je cherche. Field disabled ne fait pas ce que je veux, car il désactive le champ, mais supprime également l'étiquette / le rend invisible.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X