Get_readonly_fields в классе TabularInline в Django?

Я пытаюсь использовать 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, новые формы объектов не отображаются должным образом. Цель состоит в том, чтобы сделать определенные поля только для чтения, в то же время позволяя вводить данные в новые объекты. Любые идеи для обходного пути или другой стратегии?

Ответ 1

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

Ответ 2

Это по-прежнему нелегко выполнить из-за того, что 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)

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

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

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

Ответ 3

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

admin.py:

...

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

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

Ответ 4

Как добавили другие, это ошибка дизайна в django, как показано в этот билет Django (спасибо Дэнни 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')

Ответ 5

Вы на правильном пути. Обновите 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