Django管理界面 - 让所有字段只读

50
我想让所有字段都只读,而不需要明确列出它们。
类似于:
class CustomAdmin(admin.ModelAdmin):
    def get_readonly_fields(self, request, obj=None):
        if request.user.is_superuser:
            return self.readonly_fields

        return self.fields

问题在于此时未设置CustomAdmin.fields

有什么想法吗?


请查看此链接 https://dev59.com/x2855IYBdhLWcg3wbzxl - Prateek
那里的任何答案都没有回答我的问题 - “…而不需要明确列出它们”。 - yprez
1
希望这个能够帮到你:https://dev59.com/pWsz5IYBdhLWcg3wZ29q - Prateek
@Prateek 谢谢你提醒我更新那个答案;-) - Danny W. Adair
@DannyW.Adair:不用谢,因为我很快就会使用更新后的答案。但还是感谢你发布了有用的帖子。 - Prateek
11个回答

73

从Django 2.1开始,您可以通过从ModelAdminhas_change_permission方法返回False来防止编辑,同时允许查看,示例如下:

class CustomAdmin(admin.ModelAdmin):
    def has_change_permission(self, request, obj=None):
        return False

在 Django 2.1 之前,这种方式不能起作用,因为它也会拒绝任何仅尝试查看的用户的权限。

Translated:

在 Django 2.1 之前,这个方法不会生效,因为它还会拒绝任何只想查看的用户的权限。


1
这是一个非常漂亮和不错的解决方案。为什么没有更多的赞? - Erik Kalkoken
@ErikKalkoken:这只适用于 Django 版本 2.1 及以上版本(https://docs.djangoproject.com/en/2.1/releases/2.1/#model-view-permission),其中新增了[视图权限](https://docs.djangoproject.com/en/2.1/ref/contrib/admin/#django.contrib.admin.ModelAdmin.has_view_permission)。在此之前(即 Django 2.0 及以下版本),此答案建议的解决方案将导致管理站点拒绝所有试图查看相关模型的用户的权限。同意对于最新的 Django,这是最简洁的解决方案。 - Jonathan
2
很棒的答案!对于某些应用程序,您可能还想添加has_delete_permissionhas_add_permission方法。 - andrewdotn
请注意,inline函数可以添加。 - krafter

44

小心,self.model._meta.fields并不一定是CustomAdmin所拥有的相同字段!

"管理员界面的所有字段"看起来更像这样:

from django.contrib import admin
from django.contrib.admin.utils import flatten_fieldsets

class CustomAdmin(admin.ModelAdmin):
    def get_readonly_fields(self, request, obj=None):
        if request.user.is_superuser:
            return self.readonly_fields

        if self.declared_fieldsets:
            return flatten_fieldsets(self.declared_fieldsets)
        else:
            return list(set(
                [field.name for field in self.opts.local_fields] +
                [field.name for field in self.opts.local_many_to_many]
            ))

1
没关系。如果你有一个不是内联的m2m,请小心。虽然它应该会自己表现出来 :-) - Danny W. Adair
谢谢。这是我找到的最合理的答案。 - Greg Wang
注意,这无法处理只读字段和自定义字段集的混合。 - Cerin
6
declared_fieldsets自Django 1.7起已被弃用,并将在Django 1.9中删除;用get_fieldsets(req,obj)替代它会导致递归限制。此外,admin.util已更名为admin.utils - Afriza N. Arief
2
这对我在Django 1.10上有效。` def get_readonly_fields(self, request, obj=None): if request.user.is_superuser: return self.readonly_fields return list(set( [field.name for field in self.opts.local_fields] + [field.name for field in self.opts.local_many_to_many] ))` - user2111922
显示剩余3条评论

27

好的,现在有这个:

class CustomAdmin(admin.ModelAdmin):
    def get_readonly_fields(self, request, obj=None):
        # ...

        return [f.name for f in self.model._meta.fields]

仍在寻找一种不那么丑陋的方式。


实际上,我在别处找到了这个。在给你的答案点赞并稍微调整一下以适应我在代码中使用和测试的版本后,我发布了自己的答案。 - yprez
if语句对你的问题没有任何帮助。 - Hedde van der Heide
1
是的。这样更加简洁。而且,在创建对象时,它不会出现这种情况:AttributeError: type object 'NoneType' has no attribute '_meta' - yprez
这不包括任何额外的字段(例如:使用自定义表单)。 - WhyNotHugo

12
您可以遍历模型元字段:
def get_readonly_fields(self, request, obj=None):
    if obj:
        self.readonly_fields = [field.name for field in obj.__class__._meta.fields]
    return self.readonly_fields

2
当用户尝试创建新对象时,您会在此处遇到异常:obj.class._meta.fields - Sergey Lyapustin
好的,我删除了我的回答,你确实是第一个 :) - Sergey Lyapustin
3
仅仅因为你先写出答案并不意味着你有权得到答案,而且仅仅因为别人采用了同样的方法并不意味着他们在抄袭你或应该遭受负面评价。 - Timmy O'Mahony
非常感谢。回答得很好!! - Sunny Chaudhari
这不包括任何额外的字段(例如:使用自定义表单)。 - WhyNotHugo

6

对于行内(选项卡或堆栈)

def get_readonly_fields(self, request, obj=None):
    fields = []
    for field in self.model._meta.get_all_field_names():
        if field != 'id':
            fields.append(field)
    return fields

def has_add_permission(self, request):
    return False

返回[self.model._meta.get_all_field_names()中的字段,如果字段不等于'id'] - Ohad the Lad
2
self.model._meta.get_all_field_names() 方法已在 Django 1.10+ 中被弃用:https://docs.djangoproject.com/en/2.1/ref/models/meta/#migrating-from-the-old-api - user535883

4

如果有人仍在寻找更好的方法,您可以像这样使用:

@admin.register(ClassName)
class ClassNameAdmin(admin.ModelAdmin):
    readonly_fields = [field.name for field in ClassName._meta.fields]

ClassName是你的模型类。


1
感谢您回来并发布这个! - blakev

4
这对我在使用Django 1.10时有效。
def get_readonly_fields(self, request, obj=None):
    if request.user.is_superuser:
        return self.readonly_fields

    return list(set(
        [field.name for field in self.opts.local_fields] +
        [field.name for field in self.opts.local_many_to_many]
    ))

0

我的需求类似。我只需要一个字段显示为只读。这个方法很有效:

class ChoiceInline(admin.TabularInline):
    model = Choice
    extra = 1
    fields = ['choice_text', 'votes']
    readonly_fields = ['votes']

class QuestionAdmin(admin.ModelAdmin):
    #fields = ['pub_date', 'question_text']
    fieldsets = [
        (None, {'fields': ['question_text']}),
        ('Date Information', {'fields': ['pub_date']}),
    ]
    search_fields = ['question_text']


    inlines = [ChoiceInline]

参考: C:\Python27\Lib\site-packages\django\contrib\admin\options.py


0

假设你已经定义了user_mode为:

Admin, Customers and Staff

如果您想剥夺员工(当然,也包括客户)删除客户、产品、订单等的权限...

您可以使用以下代码:

def get_readonly_fields(self, request: HttpRequest, obj=None):
    if request.user.user_mode != "Admin":
        return self.readonly_fields + ['user_mode']
    return super().get_readonly_fields(request, obj)

其中 user_mode 是一个模型字段,用于保存用户类型。

注意:我的代码使用的是“Pylance”(类似于 JS 中的 TypeScript)。


-1

问题是如何在不显式列出所有字段的情况下获取所有字段的只读属性。您的答案建议显式列出它们。 - weAreStarsDust

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接