Jak w formularzu Django mogę zrobić pole tylko do odczytu (lub wyłączyć), aby nie można było go edytować?

430

W jaki sposób w formularzu Django mogę ustawić pole tylko do odczytu (lub wyłączone)?

Gdy formularz jest używany do utworzenia nowego wpisu, wszystkie pola powinny być włączone - ale gdy rekord jest w trybie aktualizacji, niektóre pola muszą być tylko do odczytu.

Na przykład podczas tworzenia nowego Itemmodelu wszystkie pola muszą być edytowalne, ale czy podczas aktualizacji rekordu istnieje sposób na wyłączenie skupola, aby było widoczne, ale nie można go edytować?

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

Czy klasa może ItemFormbyć ponownie wykorzystana? Jakie zmiany byłyby wymagane w klasie ItemFormlub Itemmodelu? Czy muszę napisać inną klasę „ ItemUpdateForm”, aby zaktualizować element?

def update_item_view(request):
    if request.method == 'POST':
        form = ItemUpdateForm(request.POST)
        # Validate and save
    else:
        form = ItemUpdateForm()
X10
źródło
Zobacz także pytanie SO: Dlaczego pola formularza tylko do odczytu w Django są złym pomysłem? @ stackoverflow.com/questions/2902024 , Zaakceptowana odpowiedź (autor: Daniel Naab) zajmuje się złośliwymi włamaniami POST.
X10

Odpowiedzi:

421

Jak wskazano w tej odpowiedzi , Django 1.9 dodał atrybut Field.disabled :

Argument disabled boolean, gdy jest ustawiony na True, wyłącza pole formularza za pomocą atrybutu disabled HTML, aby nie można go było edytować przez użytkowników. Nawet jeśli użytkownik manipuluje wartością pola przesłaną do serwera, zostanie on zignorowany na korzyść wartości z danych początkowych formularza.

W wersji Django 1.8 i wcześniejszych, aby wyłączyć wpis w widżecie i zapobiec złośliwym włamaniom POST, oprócz ustawienia readonlyatrybutu w polu formularza należy wyczyścić dane wejściowe :

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']

Lub zastąp if instance and instance.pkinnym warunkiem wskazującym, że edytujesz. Możesz także ustawić atrybut disabledw polu wejściowym zamiast readonly.

clean_skuFunkcja zapewnia, że readonlywartość ta nie będzie przesłonięta przez POST.

W przeciwnym razie nie ma wbudowanego pola formularza Django, które wyświetla wartość podczas odrzucania powiązanych danych wejściowych. Jeśli tego właśnie chcesz, powinieneś zamiast tego utworzyć osobne pole, ModelFormktóre wyklucza nieedytowalne pola, i po prostu wydrukować je w szablonie.

Daniel Naab
źródło
3
Daniel, dziękuję za opublikowanie odpowiedzi. Nie jest dla mnie jasne, jak korzystać z tego kodu? czy ten kod nie działałby tak samo dla nowego trybu aktualizacji? Czy możesz edytować swoją odpowiedź, aby podać przykłady wykorzystania jej w nowych i aktualizowanych formularzach? Dzięki.
X10,
8
Kluczem do przykładu Daniela jest testowanie pola .id. Nowo utworzone obiekty będą miały identyfikator == Brak. Nawiasem mówiąc, jeden z najstarszych otwartych biletów Django dotyczy tego problemu. Zobacz code.djangoproject.com/ticket/342 .
Peter Rowell
2
@moadeep dodaj clean_descriptionmetodę do klasy formularza.
Daniel Naab
3
w systemie Linux (ubuntu 15) / chrome v45, readonly zmienia wskaźnik na „niepełnosprawną rękę”, ale pole można wtedy kliknąć. z wyłączonym działa zgodnie z oczekiwaniami
simone cittadini
7
Ta odpowiedź musi zostać zaktualizowana. W disabledDjango 1.9 dodano nowy argument pola . Jeśli Field.disabledjest ustawiony na True, wówczas wartość POST Fieldjest ignorowana. Więc jeśli używasz 1.9, nie musisz zmieniać clean, po prostu ustaw disabled = True. Sprawdź odpowiedź.
narendra-choudhary
174

Django 1.9 dodał atrybut Field.disabled: https://docs.djangoproject.com/en/stable/ref/forms/fields/#disabled

Argument disabled boolean, gdy jest ustawiony na True, wyłącza pole formularza za pomocą atrybutu disabled HTML, aby nie można go było edytować przez użytkowników. Nawet jeśli użytkownik manipuluje wartością pola przesłaną do serwera, zostanie on zignorowany na korzyść wartości z danych początkowych formularza.

Mike Mahmud
źródło
Nic za 1.8 LTS?
dnit13
9
jakiś pomysł, jak możemy to wykorzystać w UpdateView? Gdy generuje pola z modelu ...
bcsanches
6
Poprawna odpowiedź. Moja klasa rozwiązania MyChangeForm (forms.ModelForm): def __init __ (self, * args, ** kwargs): super (MyChangeForm, self) .__ init __ (* args, ** kwargs) self.fields ['my_field']. Disabled = Prawda
Vijay Katam
8
Jest to problematyczna odpowiedź - ustawienie disabled=Truespowoduje, że model zostanie odesłany do użytkownika z błędami sprawdzania poprawności.
Ben
1
Byłoby wspaniale, gdybyś mógł podać przykład
geoidesic
95

Ustawienie readonlywidżetu powoduje, że dane wejściowe w przeglądarce są tylko do odczytu. Dodanie clean_skuzwracanej wartości instance.skugwarantuje, że wartość pola nie zmieni się na poziomie formularza.

def clean_sku(self):
    if self.instance: 
        return self.instance.sku
    else: 
        return self.fields['sku']

W ten sposób możesz użyć modelu (niezmodyfikowanego zapisu) i uniknąć błędu wymaganego w polu.

muhuk
źródło
15
+1 Jest to świetny sposób na uniknięcie bardziej skomplikowanych przesłonięć funkcji save (). Jednak chciałbyś wykonać sprawdzenie instancji przed zwróceniem (w trybie komentarzy bez wiersza): „if self.instance: return self.instance.sku; else: return self.fields ['sku']”
Daniel Naab
2
Czy dla ostatniej linii byłby return self.cleaned_data['sku']tak dobry, czy lepszy? W docs wydają się sugerować przy użyciu cleaned_data: „Wartość zwracana tej metody zastępuje istniejącą wartość cleaned_data, więc musi to być wartość pola z cleaned_data(nawet jeśli ta metoda nie go zmienić) lub nowy oczyszczony wartość.”
pianoJames
67

odpowiedź awalkera bardzo mi pomogła!

Zmieniłem jego przykład do pracy z Django 1.3, używając get_readonly_fields .

Zwykle powinieneś zadeklarować coś takiego w app/admin.py:

class ItemAdmin(admin.ModelAdmin):
    ...
    readonly_fields = ('url',)

Dostosowałem w ten sposób:

# In the admin.py file
class ItemAdmin(admin.ModelAdmin):
    ...
    def get_readonly_fields(self, request, obj=None):
        if obj:
            return ['url']
        else:
            return []

I działa dobrze. Teraz, jeśli dodasz element, urlpole jest do odczytu-zapisu, ale po zmianie staje się tylko do odczytu.

chirale
źródło
55

Aby ta funkcja działała w ForeignKeypolu, należy wprowadzić kilka zmian. Po pierwsze, SELECT HTMLznacznik nie ma atrybutu „tylko do odczytu”. disabled="disabled"Zamiast tego musimy użyć . Jednak przeglądarka nie wysyła z powrotem żadnych danych formularza dla tego pola. Musimy więc ustawić to pole tak, aby nie było wymagane, aby pole było poprawnie sprawdzane. Następnie musimy zresetować wartość z powrotem do poprzedniej, aby nie była pusta.

Tak więc dla kluczy obcych będziesz musiał zrobić coś takiego:

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)

W ten sposób przeglądarka nie pozwoli użytkownikowi zmienić pola i zawsze POSTpozostanie pusta. Następnie zastępujemy cleanmetodę, aby ustawić wartość pola na pierwotną w instancji.

Humphrey
źródło
Próbowałem użyć go jako formularza w TabularInline, ale nie powiodło się, ponieważ attrszostały udostępnione między widgetinstancjami i wszystkimi oprócz pierwszego wiersza, w tym nowo dodanego, renderowanego tylko do odczytu.
zjazd
Świetne (aktualizacja) rozwiązanie! Niestety, to i reszta mają problemy z błędami formularza, ponieważ wszystkie wartości „wyłączone” są opróżniane.
Michael Thompson
28

W przypadku Django 1.2+ możesz zastąpić to pole w następujący sposób:

sku = forms.CharField(widget = forms.TextInput(attrs={'readonly':'readonly'}))
StefanNch
źródło
6
Nie pozwala to również na edycję pola podczas dodawania, o to właśnie chodzi w pierwotnym pytaniu.
Matt S.
Oto odpowiedź, której szukam. Field disablednie robi tego, co chcę, ponieważ wyłącza pole, ale także usuwa etykietę / czyni ją niewidoczną.
sivabudh
18

Zrobiłem klasę MixIn, którą możesz odziedziczyć, aby móc dodać iterowalne pole read_only, które wyłączy i zabezpieczy pola przy pierwszej edycji:

(Na podstawie odpowiedzi Daniela i Muhuka)

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')
christophe31
źródło
11

Właśnie utworzyłem możliwie najprostszy widget dla pola tylko do odczytu - tak naprawdę nie rozumiem, dlaczego formularze jeszcze tego nie mają:

class ReadOnlyWidget(widgets.Widget):
    """Some of these values are read only - just a bit of text..."""
    def render(self, _, value, attrs=None):
        return value

W formie:

my_read_only = CharField(widget=ReadOnlyWidget())

Bardzo proste - i daje mi tylko efekt. Przydatny w zestawie z wieloma wartościami tylko do odczytu. Oczywiście - możesz też być nieco bardziej sprytny i dać mu div z attrs, abyś mógł do niego dodawać klasy.

Danny Staple
źródło
2
Wygląda seksownie, ale jak radzić sobie z kluczem obcym?
andilabs
unicode(value)Być może zrób to w zamian. Zakładając, że unicode dunder jest rozsądne, wtedy byś to dostał.
Danny Staple
W przypadku kluczy obcych musisz dodać atrybut „model” i użyć „get (wartość)”. Sprawdź moją treść
shadi
10

Natknąłem się na podobny problem. Wygląda na to, że udało mi się go rozwiązać, definiując metodę „get_readonly_fields” w mojej klasie ModelAdmin.

Coś takiego:

# In the admin.py file

class ItemAdmin(admin.ModelAdmin):

    def get_readonly_display(self, request, obj=None):
        if obj:
            return ['sku']
        else:
            return []

Fajną rzeczą jest to, że objpodczas dodawania nowego elementu będzie to Brak lub będzie to obiekt edytowany podczas zmiany istniejącego elementu.

get_readonly_display jest udokumentowany tutaj: http://docs.djangoproject.com/en/1.2/ref/contrib/admin/#modeladmin-methods

awalker
źródło
6

Jedną z prostych opcji jest po prostu wpisanie form.instance.fieldNameszablonu zamiast form.fieldName.

alzclarke
źródło
A jak o verbos_namelub labelpola? Jak mogę wyświetlić etykietę `w szablonie django? @alzclarke
Whale 52Hz
6

Jak to zrobić z 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
Lucas B.
źródło
zablokuje to tylko front. każdy może ominąć. spowoduje to problem z bezpieczeństwem, jeśli robisz na poufnych danych
Sarath Ak
To jest bezpieczne; blokuje również backend od czasu Django> = 1.10 docs.djangoproject.com/en/1.10/ref/forms/fields/…
timdiels
5

Jako przydatny dodatek do postu Humphrey'a miałem pewne problemy z django-reversion, ponieważ nadal rejestrował wyłączone pola jako „zmienione”. Poniższy kod rozwiązuje problem.

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)
Evan Brumley
źródło
5

Ponieważ nie mogę jeszcze komentować ( rozwiązanie muhuka ), odpowiem jako osobna odpowiedź. Oto kompletny przykład kodu, który działał dla mnie:

def clean_sku(self):
  if self.instance and self.instance.pk:
    return self.instance.sku
  else:
    return self.cleaned_data['sku']
Madis
źródło
5

Jeszcze raz zaproponuję jeszcze jedno rozwiązanie :) Użyłem kodu Humphreya , więc to jest oparte na tym.

Jednak wystąpiły problemy z polem będącym ModelChoiceField. Wszystko działałoby na pierwsze żądanie. Jednak jeśli zestaw formularzy próbował dodać nowy element i nie udało się sprawdzić poprawności, coś poszło nie tak z „istniejącymi” formularzami, w których SELECTEDopcja była resetowana do wartości domyślnej ---------.

W każdym razie nie mogłem wymyślić, jak to naprawić. Zamiast tego (i myślę, że w rzeczywistości jest to czystsze w formularzu), stworzyłem pola HiddenInputField(). Oznacza to po prostu, że musisz wykonać trochę więcej pracy w szablonie.

Naprawiłem więc uproszczenie formularza:

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

Następnie w szablonie musisz wykonać ręczne zapętlenie zestawu formularzy .

W takim przypadku zrobiłbyś coś takiego w szablonie:

<div>
    {{ form.instance.sku }} <!-- This prints the value -->
    {{ form }} <!-- Prints form normally, and makes the hidden input -->
</div>

To działało trochę lepiej dla mnie i przy mniejszej manipulacji formą.

JamesD
źródło
4

Miałem ten sam problem, więc stworzyłem Mixin, który wydaje się działać w moich przypadkach użycia.

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

Sposób użycia, po prostu określ, które z nich mają być tylko do odczytu:

class MyFormWithReadOnlyFields(ReadOnlyFieldsMixin, MyForm):
    readonly_fields = ('field1', 'field2', 'fieldx')
Michał
źródło
Przypuszczam, że jest to trochę bardziej czytelne niż mój własny mixin, który zasugerowałem tutaj. Nawet prawdopodobnie bardziej wydajny, ponieważ te czyszczenia nie powodują błędów sprawdzania poprawności…
christophe31
'collections.OrderedDict' object has no attribute 'iteritems'
Pojawia
4

jeśli potrzebujesz wielu pól tylko do odczytu. możesz użyć dowolnej z poniższych metod

metoda 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

metoda 2

metoda dziedziczenia

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',)
Sarath Ak
źródło
3

Dwa kolejne (podobne) podejścia z jednym uogólnionym przykładem:

1) pierwsze podejście - usunięcie pola w metodzie save (), np. (Nie testowane;)):

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) drugie podejście - zresetuj pole do wartości początkowej w czystej metodzie:

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

W oparciu o drugie podejście uogólniłem to w następujący sposób:

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)
Robert Lujo
źródło
3

W przypadku wersji administracyjnej uważam, że jest to bardziej kompaktowy sposób, jeśli masz więcej niż jedno pole:

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
Hassek
źródło
3

Na podstawie odpowiedzi Yamikepa znalazłem lepsze i bardzo proste rozwiązanie, które obsługuje również ModelMultipleChoiceFieldpola.

Usunięcie pola form.cleaned_datazapobiega zapisywaniu pól:

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

Stosowanie:

class MyFormWithReadOnlyFields(ReadOnlyFieldsMixin, MyForm):
    readonly_fields = ('field1', 'field2', 'fieldx')
Darklow
źródło
2

Oto nieco bardziej zaangażowana wersja, oparta na odpowiedzi Christophe31 . Nie opiera się na atrybucie „tylko do odczytu”. To sprawia, że ​​jego problemy, takie jak zaznaczanie pól, które wciąż są zmienne, a okna danych wciąż się pojawiają, znikają.

Zamiast tego zawija widżet pól formularza w widżecie tylko do odczytu, dzięki czemu formularz nadal jest sprawdzany. Zawartość oryginalnego widżetu jest wyświetlana wewnątrz <span class="hidden"></span>tagów. Jeśli widget ma render_readonly()metodę, używa go jako widocznego tekstu, w przeciwnym razie analizuje HTML oryginalnego widgetu i próbuje odgadnąć najlepszą reprezentację.

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)
Rune Kaagaard
źródło
1

Czy to najprostszy sposób?

Bezpośrednio w kodzie widoku coś takiego:

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)

To działa dobrze!

fly_frog
źródło
1

W wersji django 1.9+
możesz użyć argumentu Fields disabled, aby wyłączyć pole. np. W poniższym fragmencie kodu z pliku forms.py, wyłączyłem pole kod_ pracownika

class EmployeeForm(forms.ModelForm):
    employee_code = forms.CharField(disabled=True)
    class Meta:
        model = Employee
        fields = ('employee_code', 'designation', 'salary')

Odwołanie https://docs.djangoproject.com/en/2.0/ref/forms/fields/#disabled

Ajinkya Bhosale
źródło
1

Jeśli pracujesz z Django ver < 1.9( 1.9dodano Field.disabledatrybut), możesz spróbować dodać następujący dekorator do __init__metody formularza :

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):
        ...

Główną ideą jest to, że jeśli pole jest readonly, nie potrzebujesz żadnej innej wartości oprócz initial.

PS: Nie zapomnij ustawić yuor_form_field.widget.attrs['readonly'] = True

Jarosław Varkhol
źródło
0

Jeśli używasz administratora Django, oto najprostsze rozwiązanie.

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',)
utapyngo
źródło
0

Myślę, że najlepszą opcją byłoby włączenie atrybutu tylko do odczytu w szablonie renderowanym w <span>lub<p> raczej niż włączenie go w formularzu, jeśli jest „tylko do odczytu”.

Formularze służą do gromadzenia danych, a nie ich wyświetlania. To powiedziawszy, opcje wyświetlania w readonlywidżecie i przeszukiwania danych POST są świetnymi rozwiązaniami.

austinheiman
źródło