get_readonly_fields в классе TabularInline в Django? - PullRequest
13 голосов
/ 18 июля 2011

Я пытаюсь использовать get_readonly_fields в классе TabularInline в Django:

class ItemInline(admin.TabularInline):
    model = Item
    extra = 5

    def get_readonly_fields(self, request, obj=None):
        if obj:
            return ['name']

        return self.readonly_fields

Этот код был взят из другого вопроса StackOverflow: Сайт администратора Django: запретить редактирование полей?

Однако, когда он помещается в класс TabularInline, новые формы объектов не отображаются должным образом.Цель состоит в том, чтобы сделать определенные поля доступными только для чтения, в то же время позволяя вводить данные в новые объекты.Есть идеи для обхода или другой стратегии?

Ответы [ 5 ]

19 голосов
/ 13 октября 2012

Осторожно - "obj" - это не встроенный объект, это родитель. Это, возможно, ошибка - см. Например этот билет Django

4 голосов
/ 07 августа 2014

В качестве обходного пути к этой проблеме я связал форму и виджет с моим Inline:

admin.py:

...

class MasterCouponFileInline(admin.TabularInline):
    model = MasterCouponFile
    form = MasterCouponFileForm
    extra = 0

в Django 2.0:

forms.py

from django import forms
from . import models
from feedback.widgets import DisablePopulatedText


class FeedbackCommentForm(forms.ModelForm):
    class Meta:
        model = models.MasterCouponFile
        fields = ('Comment', ....)
        widgets = {
            'Comment':  DisablePopulatedText,
        }

в widgets.py

from django import forms

class DisablePopulatedText(forms.TextInput):
    def render(self, name, value, attrs=None, renderer=None):
        """Render the widget as an HTML string."""
        if value is not None:
            # Just return the value, as normal read_only fields do
            # Add Hidden Input otherwise the old fields are still required
            HiddenInput = forms.HiddenInput()
            return format_html("{}\n"+HiddenInput.render(name, value), self.format_value(value))
        else:
            return super().render(name, value, attrs, renderer)

старые версии Django:

forms.py

....

class MasterCouponFileForm(forms.ModelForm):
    class Meta:
        model = MasterCouponFile       

    def __init__(self, *args, **kwargs):
        super(MasterCouponFileForm, self).__init__(*args, **kwargs)
        self.fields['range'].widget = DisablePopulatedText(self.instance)
        self.fields['quantity'].widget = DisablePopulatedText(self.instance)

в widgets.py

...

from django import forms
from django.forms.util import flatatt
from django.utils.encoding import force_text

class DisablePopulatedText(forms.TextInput):
    def __init__(self, obj, attrs=None):
        self.object = obj
        super(DisablePopulatedText, self).__init__(attrs)
    def render(self, name, value, attrs=None):
        if value is None:
            value = ''
        final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
        if value != '':
            # Only add the 'value' attribute if a value is non-empty.
            final_attrs['value'] = force_text(self._format_value(value))
        if "__prefix__" not in name and not value:
            return format_html('<input{0} disabled />', flatatt(final_attrs))
        else:
            return format_html('<input{0} />', flatatt(final_attrs))
2 голосов
/ 23 июня 2014

В настоящее время это все еще нелегко сделать из-за того, что obj является экземпляром родительской модели, а не экземпляром, отображаемым встроенным.

Чтобы решить эту проблему, я сделал все поля в встроенной форме только для чтения и предоставил ссылку Add / Edit для ChangeForm для встроенной модели.

Как это

class ChangeFormLinkMixin(object):
    def change_form_link(self, instance):
        url = reverse('admin:%s_%s_change' % (instance._meta.app_label,
            instance._meta.module_name), args=(instance.id,))
        # Id == None implies and empty inline object
        url = url.replace('None', 'add')
        command = _('Add') if url.find('add') > -1 else _('Edit')
        return format_html(u'<a href="{}">%s</a>' % command, url)

И тогда в строке у меня будет что-то вроде этого

class ItemInline(ChangeFormLinkMixin, admin.StackedInline):
    model = Item
    extra = 5
    readonly_fields = ['field1',...,'fieldN','change_form_link']

Тогда в ChangeForm я смогу контролировать изменения так, как я хочу (у меня есть несколько состояний, каждое из которых имеет набор связанных редактируемых полей).

1 голос
/ 01 февраля 2018

Как уже добавили другие, это недостаток дизайна в django, как видно из этого билета Django (спасибо Danny W). get_readonly_fields возвращает родительский объект, который здесь не тот, что мы хотим.

Поскольку мы не можем сделать его доступным только для чтения, вот мое решение, чтобы проверить, что оно не может быть установлено формой, используя набор форм и чистый метод:

class ItemInline(admin.TabularInline):
    model = Item
    formset = ItemInlineFormset

class ItemInlineFormset(forms.models.BaseInlineFormSet):
    def clean(self):
        super(ItemInlineFormset, self).clean()
        for form in self.forms:
            if form.instance.some_condition:
                form.add_error('some_condition', 'Nope')
0 голосов
/ 12 октября 2012

Вы на правильном пути. Обновите self.readonly_fields с помощью набора полей, которые вы хотите установить только для чтения.

class ItemInline(admin.TabularInline):
    model = Item
    extra = 5

    def get_readonly_fields(self, request, obj=None):
        # add a tuple of readonly fields
        self.readonly_fields += ('field_a', 'field_b')
        return self.readonly_fields
...