Hoe maak ik in een Django-formulier een veld alleen-lezen (of uitgeschakeld) zodat het niet kan worden bewerkt?

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 skuuit 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 ItemFormopnieuw worden gebruikt? Welke wijzigingen zijn vereist in de modelklasse ItemFormof 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 readonlyin 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.pkdoor een andere voorwaarde die aangeeft dat u aan het bewerken bent. U kunt ook het kenmerk disabledinstellen in het invoerveld, in plaats van readonly.

De functie clean_skuzorgt ervoor dat de waarde readonlyniet 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 ModelFormmaken 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 readonlyinstelt op een widget, wordt de invoer in de browser alleen-lezen. Het toevoegen van een clean_skudie instance.skuretourneert, 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 urllezen-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 HTMLtag 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 POSTzoals het leeg was gelaten. Vervolgens overschrijven we de methode cleanom 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 objGeen is wanneer je een nieuw item toevoegt, of dat het het object is dat wordt bewerkt wanneer je een bestaand item wijzigt.

get_readonly_displayis 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.fieldNamein 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 ModelChoiceFieldis. 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 SELECTEDwerd 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_datavoorkomt 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.9heeft het kenmerk Field.disabledtoegevoegd) 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 readonlyis, je geen andere waarde nodig hebt behalve initial.

P.S: vergeet niet yuor_form_field.widget.attrs['readonly'] = True

in te stellen

Other episodes