Hoe maak ik in een Django-formulier een veld alleen-lezen (of uitgeschakeld)?
Als het formulier wordt gebruikt om een nieuw item aan te maken, moeten alle velden zijn ingeschakeld, maar als het record in de updatemodus staat, moeten sommige velden alleen-lezen zijn.
Bij het maken van een nieuw Item
-model moeten bijvoorbeeld alle velden bewerkbaar zijn, maar is er tijdens het bijwerken van de record een manier om het veld sku
uit te schakelen zodat het is zichtbaar, maar kan niet worden bewerkt?
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
Kan klasse ItemForm
opnieuw worden gebruikt? Welke wijzigingen zijn vereist in de modelklasse ItemForm
of Item
? Moet ik een andere klasse schrijven, “ItemUpdateForm
“, om het item bij te werken?
def update_item_view(request):
if request.method == 'POST':
form = ItemUpdateForm(request.POST)
# Validate and save
else:
form = ItemUpdateForm()
Antwoord 1, autoriteit 100%
Zoals aangegeven in dit antwoord, Django 1.9 heeft de Field.disabledkenmerk:
Het uitgeschakelde booleaanse argument, indien ingesteld op True, schakelt een formulierveld uit dat het uitgeschakelde HTML-kenmerk gebruikt, zodat het niet door gebruikers kan worden bewerkt. Zelfs als een gebruiker knoeit met de waarde van het veld die naar de server is verzonden, wordt deze genegeerd ten gunste van de waarde uit de oorspronkelijke gegevens van het formulier.
Om bij Django 1.8 en eerder toegang tot de widget uit te schakelen en kwaadaardige POST-hacks te voorkomen, moet u de invoer scrubben en het kenmerk readonly
in het formulierveld instellen:
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']
Of vervang if instance and instance.pk
door een andere voorwaarde die aangeeft dat u aan het bewerken bent. U kunt ook het kenmerk disabled
instellen in het invoerveld, in plaats van readonly
.
De functie clean_sku
zorgt ervoor dat de waarde readonly
niet wordt overschreven door een POST
.
Anders is er geen ingebouwd Django-formulierveld dat een waarde weergeeft terwijl gebonden invoergegevens worden afgewezen. Als dit is wat u wenst, moet u in plaats daarvan een afzonderlijk ModelForm
maken dat de niet-bewerkbare velden uitsluit, en deze gewoon in uw sjabloon afdrukken.
Antwoord 2, autoriteit 40%
Django 1.9 heeft het kenmerk Field.disabled toegevoegd: https:// docs.djangoproject.com/en/stable/ref/forms/fields/#disabled
Het uitgeschakelde booleaanse argument, indien ingesteld op True, schakelt een formulierveld uit dat het uitgeschakelde HTML-kenmerk gebruikt, zodat het niet door gebruikers kan worden bewerkt. Zelfs als een gebruiker knoeit met de waarde van het veld die naar de server is verzonden, wordt deze genegeerd ten gunste van de waarde uit de oorspronkelijke gegevens van het formulier.
Antwoord 3, autoriteit 21%
Als u readonly
instelt op een widget, wordt de invoer in de browser alleen-lezen. Het toevoegen van een clean_sku
die instance.sku
retourneert, zorgt ervoor dat de veldwaarde niet verandert op formulierniveau.
def clean_sku(self):
if self.instance:
return self.instance.sku
else:
return self.fields['sku']
Op deze manier kunt u modellen gebruiken (onaangepast opslaan) en voorkomen dat u de veldvereiste fout krijgt.
Antwoord 4, autoriteit 17%
het antwoord van awalkerheeft me enorm geholpen!
Ik heb zijn voorbeeld gewijzigd om met Django 1.3 te werken, met behulp van get_readonly_fields.
Normaal gesproken zou je zoiets als dit moeten aangeven in app/admin.py
:
class ItemAdmin(admin.ModelAdmin):
...
readonly_fields = ('url',)
Ik heb me op deze manier aangepast:
# In the admin.py file
class ItemAdmin(admin.ModelAdmin):
...
def get_readonly_fields(self, request, obj=None):
if obj:
return ['url']
else:
return []
En het werkt prima. Als u nu een item toevoegt, is het veld url
lezen-schrijven, maar bij wijziging wordt het alleen-lezen.
Antwoord 5, autoriteit 13%
Om dit voor een ForeignKey
-veld te laten werken, moeten een paar wijzigingen worden aangebracht. Ten eerste heeft de SELECT HTML
tag niet het readonly attribuut. We moeten in plaats daarvan disabled="disabled"
gebruiken. De browser stuurt dan echter geen formuliergegevens terug voor dat veld. Dus we moeten dat veld zo instellen dat het niet verplicht is, zodat het veld correct valideert. We moeten dan de waarde terugzetten naar wat het was, zodat het niet leeg is.
Dus voor buitenlandse sleutels moet je iets doen als:
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)
Op deze manier laat de browser de gebruiker het veld niet wijzigen en zal het altijd POST
zoals het leeg was gelaten. Vervolgens overschrijven we de methode clean
om de waarde van het veld in te stellen op de waarde die oorspronkelijk in de instantie aanwezig was.
Antwoord 6, autoriteit 7%
Voor Django 1.2+ kunt u het veld als volgt overschrijven:
sku = forms.CharField(widget = forms.TextInput(attrs={'readonly':'readonly'}))
Antwoord 7, autoriteit 4%
Ik heb een MixIn-klasse gemaakt die je kunt erven om een read_only iterable veld toe te voegen dat velden bij de niet-eerste bewerking zal uitschakelen en beveiligen:
(Gebaseerd op de antwoorden van Daniel en 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')
Antwoord 8, autoriteit 3%
Ik heb zojuist de eenvoudigst mogelijke widget gemaakt voor een alleen-lezen veld – ik begrijp niet echt waarom formulieren dit niet al hebben:
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 de vorm:
my_read_only = CharField(widget=ReadOnlyWidget())
Heel eenvoudig – en ik krijg gewoon uitvoer. Handig in een formulierset met een heleboel alleen-lezen waarden.
Natuurlijk – je kunt ook wat slimmer zijn en het een div geven met de attrs, zodat je er klassen aan kunt toevoegen.
Antwoord 9, autoriteit 2%
Ik kwam een soortgelijk probleem tegen.
Het lijkt erop dat ik het heb kunnen oplossen door een get_readonly_fields
-methode te definiëren in mijn ModelAdmin
-klasse.
Zoiets:
# In the admin.py file
class ItemAdmin(admin.ModelAdmin):
def get_readonly_display(self, request, obj=None):
if obj:
return ['sku']
else:
return []
Het leuke is dat obj
Geen is wanneer je een nieuw item toevoegt, of dat het het object is dat wordt bewerkt wanneer je een bestaand item wijzigt.
get_readonly_display
is gedocumenteerd hier.
Antwoord 10, autoriteit 2%
Hoe ik het doe met 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
Antwoord 11, autoriteit 2%
Voor django 1.9+
U kunt het argument Velden uitgeschakeld gebruiken om veld uit te schakelen.
bijv. In het volgende codefragment uit het bestand Forms.py heb ik het veld employee_code uitgeschakeld
class EmployeeForm(forms.ModelForm):
employee_code = forms.CharField(disabled=True)
class Meta:
model = Employee
fields = ('employee_code', 'designation', 'salary')
Referentie
https://docs.djangoproject.com/en/dev/ref/ formulieren/velden/#disabled
Antwoord 12
Een eenvoudige optie is om gewoon form.instance.fieldName
in de sjabloon te typen in plaats van form.fieldName
.
Antwoord 13
Nogmaals, ik ga nog een oplossing aanbieden 🙂 Ik gebruikte Humphrey’s code, dus dit is daarop gebaseerd.
Ik kwam echter problemen tegen omdat het veld een ModelChoiceField
is. Alles zou werken op het eerste verzoek. Als de formulierset echter probeerde een nieuw item toe te voegen en de validatie mislukte, ging er iets mis met de “bestaande” formulieren waarbij de optie SELECTED
werd teruggezet naar de standaard ---------
.
Hoe dan ook, ik wist niet hoe ik dat moest oplossen. Dus in plaats daarvan (en ik denk dat dit in feite schoner is in de vorm), heb ik de velden HiddenInputField()
gemaakt. Dit betekent alleen dat u wat meer werk aan de sjabloon moet doen.
Dus de oplossing voor mij was om het formulier te vereenvoudigen:
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()
En dan moet je in de sjabloon wat handmatige looping van de formulierset.
Dus in dit geval zou je zoiets in de sjabloon doen:
<div>
{{ form.instance.sku }} <!-- This prints the value -->
{{ form }} <!-- Prints form normally, and makes the hidden input -->
</div>
Dit werkte iets beter voor mij en met minder vormmanipulatie.
Antwoord 14
Als een nuttige aanvulling op Humphrey’s post, ik had wat problemen met django-reversion, omdat uitgeschakelde velden nog steeds als ‘gewijzigd’ werden geregistreerd. De volgende code lost het probleem op.
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)
Antwoord 15
Omdat ik nog geen commentaar kan geven (muhuk’s oplossing), zal ik reageren als een afzonderlijk antwoord. Dit is een compleet codevoorbeeld dat voor mij werkte:
def clean_sku(self):
if self.instance and self.instance.pk:
return self.instance.sku
else:
return self.cleaned_data['sku']
Antwoord 16
Ik liep tegen hetzelfde probleem aan, dus ik heb een Mixin gemaakt die lijkt te werken voor mijn gebruiksscenario’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):
cleaned_data = super(ReadOnlyFieldsMixin,self).clean()
for field in self.readonly_fields:
cleaned_data[field] = getattr(self.instance, field)
return cleaned_data
Gebruik, bepaal gewoon welke alleen-lezen mogen zijn:
class MyFormWithReadOnlyFields(ReadOnlyFieldsMixin, MyForm):
readonly_fields = ('field1', 'field2', 'fieldx')
Antwoord 17
Gebaseerd op Yamikeps antwoord, ik heb een betere en zeer eenvoudige oplossing gevonden die ook ModelMultipleChoiceField
-velden verwerkt.
Het verwijderen van het veld uit form.cleaned_data
voorkomt dat velden worden opgeslagen:
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()
Gebruik:
class MyFormWithReadOnlyFields(ReadOnlyFieldsMixin, MyForm):
readonly_fields = ('field1', 'field2', 'fieldx')
Antwoord 18
als u meerdere alleen-lezen velden nodig heeft, kunt u een van de onderstaande methoden gebruiken
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
overervingsmethode
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',)
Antwoord 19
Nog twee (vergelijkbare) benaderingen met één algemeen voorbeeld:
1) eerste benadering – veld in save() methode verwijderen, b.v. (niet getest 😉 ):
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) tweede benadering – reset veld naar beginwaarde in schone methode:
def clean_<fieldname>(self):
return self.initial[<fieldname>] # or getattr(self.instance, fieldname)
Op basis van de tweede benadering heb ik het als volgt gegeneraliseerd:
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)
Antwoord 20
Voor de Admin-versie denk ik dat dit een compactere manier is als je meer dan één veld hebt:
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
Antwoord 21
Je kunt op elegante wijze alleen-lezen toevoegen in de widget:
class SurveyModaForm(forms.ModelForm):
class Meta:
model = Survey
fields = ['question_no']
widgets = {
'question_no':forms.NumberInput(attrs={'class':'form-control','readonly':True}),
}
Antwoord 22
Hier is een iets meer betrokken versie, gebaseerd op christophe31’s antwoord. Het is niet afhankelijk van het kenmerk “alleen lezen”. Dit zorgt ervoor dat de problemen, zoals selectievakken die nog steeds kunnen worden gewijzigd en datapickers die nog steeds verschijnen, verdwijnen.
In plaats daarvan verpakt het de widget voor formuliervelden in een alleen-lezen widget, waardoor het formulier nog steeds geldig is. De inhoud van de originele widget wordt weergegeven in <span class="hidden"></span>
-tags. Als de widget een render_readonly()
-methode heeft, gebruikt hij die als zichtbare tekst, anders ontleedt hij de HTML van de originele widget en probeert hij de beste weergave te raden.
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)
Antwoord 23
Is dit de eenvoudigste manier?
Rechts in een weergavecode zoiets als dit:
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)
Het werkt prima!
Antwoord 24
Als u werkt met Django ver < 1.9
(de 1.9
heeft het kenmerk Field.disabled
toegevoegd) je zou kunnen proberen om de volgende decorateur toe te voegen aan je formulier __init__
methode:
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):
...
Het belangrijkste idee is dat als het veld readonly
is, je geen andere waarde nodig hebt behalve initial
.
P.S: vergeet niet yuor_form_field.widget.attrs['readonly'] = True
in te stellen