只读Django admin inline中的现有项目的Readonly

时间:2011-04-11 09:14:55

标签: django django-admin

我在Django管理员中有一个表格内联模型。我需要1个字段在创建后不可更改,但将其设置为readonly(通过readonly_fields),工作正常,但在单击“添加其他项目”而不是下拉列表时将字段变为标签。

有没有办法保持字段只读,但仍然允许使用正确的字段输入创建新项目?

谢谢!

托马斯

编辑:管理通过自定义小部件来计算出来

class ReadOnlySelectWidget(forms.Select):
    def render(self, name, value, attrs=None):
        if value:
            final_attrs = self.build_attrs(attrs, name=name)
            output = u'<input value="%s" type="hidden" %s />' % (value, flatatt(final_attrs))
            return mark_safe(output + str(self.choices.queryset.get(id=value)))
        else:
            return super(ReadOnlySelectWidget, self).render(name, value, attrs)

如果有值,它只会将其变为隐藏,不会在每种情况下都有效(仅适用于1个只读字段)。

7 个答案:

答案 0 :(得分:34)

遇到同样的问题,我遇到了这个问题:

创建两个内联对象,一个没有更改权限,另一个包含所有字段只读。包括在模型管理员中。

class SubscriptionInline(admin.TabularInline):
    model = Subscription
    extra = 0
    readonly_fields = ['subscription', 'usedPtsStr', 'isActive', 'activationDate', 'purchaseDate']

    def has_add_permission(self, request):
        return False

class AddSupscriptionInline(admin.TabularInline):
    model = Subscription
    extra = 0
    fields = ['subscription', 'usedPoints', 'isActive', 'activationDate', 'purchaseDate']

    def has_change_permission(self, request, obj=None):
        return False

    # For Django Version > 2.1 there is a "view permission" that needs to be disabled too (https://docs.djangoproject.com/en/2.2/releases/2.1/#what-s-new-in-django-2-1)
    def has_view_permission(self, request, obj=None):
        return False

将它们包含在同一型号的管理员中:

class UserAdmin(admin.ModelAdmin):
    inlines = [ AddSupscriptionInline, SubscriptionInline]

要添加新订阅,请使用管理员中的AddSubscriptionInline。保存后,新订阅会从内联中消失,但现在确实显示在SubscriptionInline中,只读。

对于SubscriptionInline,提及extra = 0非常重要,因此它不会显示垃圾只读订阅。 最好隐藏SubscriptionInline的添加选项,只允许通过AddSubscriptionInline添加,方法是将has_add_permission设置为始终返回False

根本不完美,但它对我来说是最好的选择,因为我必须提供在用户管理页面上添加订阅的功能,但是在添加一个订阅后,它应该只通过内部应用程序逻辑进行更改。

答案 1 :(得分:2)

您可以仅使用一个内联来实现此目的,

class MyInline(admin.TabularInline):
    fields = [...]
    extra = 0

    def has_change_permission(self, request, obj):
        return False

答案 2 :(得分:1)

根据this post,此问题已被报告为Ticket15602中的错误。

解决方法是覆盖forms.py中内联模型的clean方法,并在更改现有内联时引发错误:

class NoteForm(forms.ModelForm):
    def clean(self):
        if self.has_changed() and self.initial:
            raise ValidationError(
                'You cannot change this inline',
                code='Forbidden'
            )
        return super().clean()

    class Meta(object):
        model = Note
        fields='__all__'

以上给出了模型级别的解决方案。

要在更改特定字段时引发错误,clean_<field>方法可以提供帮助。例如,如果该字段是名为ForeignKey的{​​{1}}:

category

答案 3 :(得分:1)

我实际上遇到了另一个看起来效果很好的解决方案(对此我不敢恭维,但是link here)。

您可以在get_readonly_fields上定义TabularInline方法,并在有一个对象(编辑)与没有一个对象(创建)时适当地设置只读字段。

def get_readonly_fields(self, request, obj=None):
    if obj is not None:  # You may have to check some other attrs as well
        # Editing an object
        return ('field_name', )
    else:
        # Creating a new object
        return ()

这具有以下作用:在编辑现有实例时将目标字段设置为只读,而在创建新实例时允许其可编辑。


正如下面的注释中指出的那样,这并没有达到预期的效果,因为传递的obj实际上是父项……有一张旧的django票正在讨论这个here。 / p>

答案 4 :(得分:1)

此代码可以根据您的要求完美地工作。

实际上,我从我自己的问题中得到了这个答案,但具体针对我的问题,我删除了一些与我的问题有关的行。并归功于@YellowShark。 Check here my question

创建新的内联之后,您将无法编辑现有的内联。

class XYZ_Inline(admin.TabularInline):
    model = YourModel

class RequestAdmin(admin.ModelAdmin):
inlines = [XYZ_Inline, ]

# If you wanted to manipulate the inline forms, to make one of the fields read-only:
def get_inline_formsets(self, request, formsets, inline_instances, obj=None):
    inline_admin_formsets = []
    for inline, formset in zip(inline_instances, formsets):
        fieldsets = list(inline.get_fieldsets(request, obj))
        readonly = list(inline.get_readonly_fields(request, obj))
        prepopulated = dict(inline.get_prepopulated_fields(request, obj))
        inline_admin_formset = helpers.InlineAdminFormSet(
            inline, formset, fieldsets, prepopulated, readonly,
            model_admin=self,
        )

        if isinstance(inline, XYZ_Inline):
            for form in inline_admin_formset.forms:
            #Here we change the fields read only.
                    form.fields['some_fields'].widget.attrs['readonly'] = True

        inline_admin_formsets.append(inline_admin_formset)
    return inline_admin_formsets

您只能添加新的内联,并且只能读取所有现有的内联。

答案 5 :(得分:0)

这是我之前使用过的更好的只读小部件:
https://bitbucket.org/stephrdev/django-readonlywidget/

from django_readonlywidget.widgets import ReadOnlyWidget

class TestAdmin(admin.ModelAdmin):
    def formfield_for_dbfield(self, db_field, **kwargs):
        field = super(TestAdmin, self).formfield_for_dbfield(db_field, **kwargs)
        if field:
            field.widget = ReadOnlyWidget(db_field=db_field)
        return field

答案 6 :(得分:0)

这可以通过猴子补丁来实现。

以下示例将使“note”字段仅对现有AdminNote对象进行读取。与其他答案中建议的隐藏转换字段不同,这实际上会从提交/验证工作流程中删除字段(这更安全,并使用现有的字段渲染器)。

#
# app/models.py
#

class Order(models.Model):
    pass

class AdminNote(models.Model):
    order = models.ForeignKey(Order)
    time = models.DateTimeField(auto_now_add=True)
    note = models.TextField()


#
# app/admin.py
#

import monkey_patches.admin_fieldset

...

class AdminNoteForm(forms.ModelForm):
    class Meta:
        model = AdminNote

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field in self.get_readonly_fields():
            del self.fields[field]

    def get_readonly_fields(self):
        if self.instance.pk:
            return ['note']
        return []


class AdminNoteInline(admin.TabularInline):
    model = AdminNote
    form = AdminNoteForm
    extra = 1
    fields = 'note', 'time'
    readonly_fields = 'time',


@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
    inlines = AdminNoteInline,


#
# monkey_patches/admin_fieldset.py
#

import django.contrib.admin.helpers


class Fieldline(django.contrib.admin.helpers.Fieldline):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if hasattr(self.form, 'get_readonly_fields'):
            self.readonly_fields = list(self.readonly_fields) + list(self.form.get_readonly_fields())

django.contrib.admin.helpers.Fieldline = Fieldline