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()
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
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.
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.
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.
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.
pour Django 1.2+, vous pouvez outrepasser le champ comme suit:
sku = forms.CharField(widget = forms.TextInput(attrs={'readonly':'readonly'}))
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')
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.
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
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)
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']
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')
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.
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)
une option simple est de taper form.instance.fieldName
dans le modèle au lieu de form.fieldName
.
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',)
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
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
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')
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)
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!
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.
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
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',)
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.
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.
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