Sous forme de Django, comment faire pour qu'un champ soit lisible (ou désactivé) pour qu'il ne puisse pas être édité?

sous la forme D'un Django, comment faire pour 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 Mise à jour, certains champs doivent être lus seulement.

par exemple, lors de la création d'un nouveau modèle Item , tous les champs doivent être modifiables, mais lors de la mise à jour de l'enregistrement, y a-t-il un moyen de désactiver le champ sku pour qu'il soit visible, mais ne puisse pas être édité?

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

peut-on réutiliser la classe ItemForm ? Quelles modifications faudrait-il apporter à la classe de modèle ItemForm ou Item ? 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()
352
demandé sur phoenix 2008-11-27 21:54:06

26 réponses

comme indiqué dans cette réponse , Django 1.9 a ajouté le champ .désactivé attribut:

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

avec Django 1.8 et avant, pour désactiver l'entrée sur le widget et prévenir les piratages post malveillants, vous devez effacer l'entrée en plus de définir l'attribut readonly sur le champ de 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, remplacer if instance and instance.pk par une autre condition indiquant que vous éditez. Vous pouvez également définir l'attribut disabled sur le champ d'entrée, au lieu de readonly .

la fonction clean_sku assure

360
répondu Daniel Naab 2017-05-23 12:26:42

Django 1.9 a ajouté le champ.attribut invalide: https://docs.djangoproject.com/en/1.9/ref/forms/fields/#disabled

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

147
répondu Mike Mahmud 2017-10-31 18:05:34

le réglage en lecture seule sur le widget rend l'entrée dans le navigateur en lecture seule. Ajouter un clean_sku qui renvoie l'instance.sku s'assure 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 (enregistrer non modifié) et aviod obtenir l'erreur de champ requise.

91
répondu muhuk 2014-04-09 17:58:10

la réponse d'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',)

j'ai 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 très bien. Maintenant, si vous ajoutez un élément, le champ url est read-write, mais sur le changement, il devient en lecture seule.

60
répondu chirale 2017-05-23 11:47:36

Pour faire ce travail pour un champ ForeignKey, quelques modifications doivent être apportées. Premièrement, la balise SELECT HTML n'a pas l'attribut readonly. Nous devons utiliser disabled="disabled" à la place. Cependant, le navigateur ne renvoie 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 valide correctement. Nous devons ensuite Réinitialiser la valeur à ce qu'elle était pour qu'elle ne soit pas définie à blanc.

donc pour les clés étrangères vous aurez besoin de 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 changer le champ, et affichera toujours comme il a été laissé en blanc. Nous outrepassons alors la méthode clean pour définir la valeur du champ à ce qui était à l'origine dans l'instance.

49
répondu Humphrey 2014-05-05 16:56:17

pour Django 1.2+, vous pouvez outrepasser le champ comme suit:

sku = forms.CharField(widget = forms.TextInput(attrs={'readonly':'readonly'}))
22
répondu StefanNch 2016-05-15 18:26:02

j'ai fait une classe MixIn dont vous pouvez hériter pour pouvoir ajouter un champ read_only iterable qui désactivera et sécurisera les champs sur la non-première édition:

(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')
16
répondu christophe31 2016-05-15 18:30:27

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 n'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 la sortie. Pratique dans un formset 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.

10
répondu Danny Staple 2013-02-28 11:46:27

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 ne sera Aucun lorsque vous ajoutez un nouvel élément, ou il sera l'objet étant édité lorsque vous changez un élément existant.

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

9
répondu awalker 2011-03-08 20:51:34

comme ajout utile à le post de Humphrey , j'ai eu quelques problèmes avec django-reversion, parce qu'il a encore enregistré des champs handicapés comme 'changé'. Le code suivant corrige 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)
5
répondu Evan Brumley 2017-05-23 11:55:19

comme je ne peux pas encore commenter ( solution de muhuk ), je vais répondre comme une réponse séparée. 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']
5
répondu Madis 2017-05-23 12:34:59

j'allais dans le même problème j'ai donc créé un Mixin qui semble fonctionner pour mon 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

Usage, Il suffit de définir ceux qui doivent être lus seulement:

class MyFormWithReadOnlyFields(ReadOnlyFieldsMixin, MyForm):
    readonly_fields = ('field1', 'field2', 'fieldx')
4
répondu Michael 2014-02-09 03:02:45

encore une fois, je vais offrir une solution de plus :) j'utilisais le code de Humphrey , donc ceci est basé sur cela.

cependant, j'ai rencontré des problèmes avec le terrain étant un champ ModelChoiceField. Tout fonctionnerait à la première demande. Cependant, si le formset a essayé d'ajouter un nouvel élément et a échoué la validation, quelque chose allait mal avec les formulaires "existants" où l'option sélectionnée était réinitialisée à la valeur par défaut "---------".

de toute façon, je n'ai pas trouvé comment réparer ça. Donc à la place, (et je pense que c'est en fait plus propre dans la forme), J'ai fait les champs HiddenInputField(). Cela signifie Juste que vous devez faire un peu plus de travail dans le modèle.

donc, la solution pour moi était de simplifier la forme:

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 aurez besoin de faire un peu de boucle manuelle du jeu de formulaires .

donc, dans ce cas, vous ferait 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.

4
répondu JamesD 2017-05-23 12:10:54

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

1) première approche - suppression du champ dans la méthode save (), p.ex. (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 jusqu'à la valeur initiale dans la méthode clean:

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

basé sur la seconde 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)
3
répondu Robert Lujo 2013-11-27 21:38:51

une option simple est de taper form.instance.fieldName dans le modèle au lieu de form.fieldName .

3
répondu alzclarke 2016-05-15 18:28:11

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',)
3
répondu Sarath Ak 2016-08-19 10:13:11

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
3
répondu Lucas B 2017-07-24 15:34:58

Pour l'Administrateur version, je pense que c'est une façon plus compacte si vous avez plus d'un domaine:

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
2
répondu Hassek 2016-05-15 18:36:25

basé sur la réponse de Yamikep , j'ai trouvé une solution meilleure et très simple qui traite également ModelMultipleChoiceField champs.

supprimer le champ de form.cleaned_data empêche les champs d'être sauvés:

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

Utilisation:

class MyFormWithReadOnlyFields(ReadOnlyFieldsMixin, MyForm):
    readonly_fields = ('field1', 'field2', 'fieldx')
2
répondu darklow 2017-05-23 12:18:33

Voici une version légèrement plus impliquée, basée sur la réponse de christophe31 . Il ne s'appuie pas sur l'attribut" readonly". Cela rend ses problèmes, comme les boîtes de sélection toujours être changeable et datapickers toujours surgir, aller loin.

à la place, il enroule le widget form fields dans un widget readonly, rendant ainsi le formulaire encore valide. Le contenu du widget original est affiché à l'intérieur des balises <span class="hidden"></span> . Si le widget a un render_readonly() méthode il utilise que comme le texte visible, sinon il parse le HTML du widget original et tente 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)
2
répondu Rune Kaagaard 2018-04-18 23:28:48

est-ce la façon la plus simple?

droit 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 marche très bien!

1
répondu fly_frog 2014-02-11 21:17:06

j'ai résolu ce problème comme ceci:

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

en vues:

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

c'est tout.

1
répondu Pavel 2018-02-08 10:52:57

pour django 1.9+

Vous pouvez utiliser L'argument Fields disabled pour désactiver le champ. par exemple, dans l'extrait de code suivant forms.py file, 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

1
répondu Ajinkya Bhosale 2018-03-26 08:15:13

si vous utilisez Django admin, 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',)
0
répondu utapyngo 2015-03-24 06:37:40

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 readonly.

Les formulaires

sont destinés à recueillir des données et non à les afficher. Cela étant dit, les options pour afficher dans un readonly widget et scrub POST data sont des solutions fines.

0
répondu austinheiman 2016-05-15 18:40:27

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

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 readonly vous n'avez pas besoin d'autre valeur que initial .

P. S: N'oubliez pas de mettre yuor_form_field.widget.attrs['readonly'] = True

0
répondu Yaroslav Varkhol 2018-09-04 05:45:50