J'ai écrit une classe générique pour gérer la vue ReadOnly en fonction des autorisations de l'utilisateur, y compris en ligne;)
Dans models.py:
class User(AbstractUser):
    ...
    def is_readonly(self):
        if self.is_superuser:
            return False
        
        adminGroup = Group.objects.filter(name="admins")
        if adminGroup in self.groups.all():
            return False
        return True
Dans admin.py:
class ReadOnlyAdmin(admin.ModelAdmin):
    def __init__(self, *args, **kwargs):
        
        self._init_readonly_fields = self.readonly_fields
        
        for inline in self.inlines:
            inline._init_readonly_fields = inline.readonly_fields
        super().__init__(*args,**kwargs)
    
    def change_view( self, request, object_id, form_url='', extra_context=None ):
        context = extra_context or {}
        
        if request.user.is_readonly():
            
            self.readonly_fields = [ field.name for field in self.model._meta.get_fields() if not field.auto_created ]
            
            for inline in self.inlines:
                inline.readonly_fields = [field.name for field in inline.model._meta.get_fields() if not field.auto_created]
            
            self.save_on_top = False
            context['show_save'] = False
            context['show_save_and_continue'] = False
        else:
            
            self.readonly_fields = self._init_readonly_fields
            
            for inline in self.inlines:
                inline.readonly_fields = self._init_readonly_fields
        return super().change_view(
                    request, object_id, form_url, context )
    def save_model(self, request, obj, form, change):
        
        
        if request.user.is_readonly():
            
            return False
        
        return super().save_model( request, obj, form, change )
Ensuite, nous pouvons simplement hériter normalement de nos classes dans admin.py:
class ContactAdmin(ReadOnlyAdmin):
    list_display = ("name","email","whatever")
    readonly_fields = ("updated","created")
    inlines = ( PhoneInline, ... )