Django管理界面:分离只读视图和更改视图。

28

我想使用Django管理员界面来生成一个只读视图,其中包含一个“编辑”按钮,该按钮可以使您切换到同一对象的通常更改视图。

我知道如何使用readonly属性来生成只读视图,但我不知道如何生成两个视图,一个是只读的,另一个允许更改。

我希望尽可能地重用管理员界面,而不是从头编写视图。

请注意,这个问题与权限无关:所有用户都将有权更改对象。只是我希望他们在不打算进行更改时不要使用change_view,以减少意外更改或同时更改的风险。

5个回答

20

这里有一个答案,只需要几行代码和一些模板更改,就能够完全满足我的要求:

class MyModelAdmin(admin.ModelAdmin):
    fieldsets = [...]

    def get_readonly_fields(self, request, obj=None):
        if 'edit' not in request.GET:
            return <list all fields here>
        else:
            return self.readonly_fields

现在,更改表单的常规URL将生成只读的更改表单,但是如果在URL末尾添加“?edit=1”,则可以进行编辑。

根据URL中是否包含“?edit=1”,还可以自定义更改表单模板。为此,在settings.py中的TEMPLATE_CONTEXT_PROCESSORS中放置'django.core.context_processors.request',然后在模板中使用request.GET.edit

例如,要在非编辑模式下添加“编辑”按钮,请插入

  {% if not request.GET.edit %}                                                 
  <li><a href="?edit=1">Edit</a></li>                                           
  {% endif %} 
change_form.html中的 <ul class="object-tools"> 标签后面。
作为另一个例子,将 change_form.html 修改为包含以下内容
{% if save_on_top and request.GET.edit %}{% submit_row %}{% endif %}            

意思是在编辑模式下仅显示提交行。同样,也可以使用此方法隐藏内联中的删除按钮等。

供参考,以下是我在settings.py中设置的内容:

TEMPLATE_CONTEXT_PROCESSORS = (                                                 
    'django.contrib.auth.context_processors.auth',                              
    'django.core.context_processors.debug',                                     
    'django.core.context_processors.i18n',                                      
    'django.core.context_processors.media',                                     
    'django.contrib.messages.context_processors.messages',                      
    # Above here are the defaults.                                              
    'django.core.context_processors.request',                                   
)

1

我建议重新考虑使用自定义视图。借助通用的DetailView,您只需要编写两行代码。模板也不需要太多工作。您只需扩展标准的change_form.html模板,覆盖field_sets块。

我知道如何使用只读属性生成只读视图,但我不知道如何生成两个视图,一个是只读的,另一个允许更改。

实际上,您可以使用代理模型在管理中注册一个模型两次[1]。(代理模型的权限存在一些不一致,但在您的情况下可能不是问题。)

似乎也可以注册多个管理站点[2]。

我希望尽可能地重用管理界面,而不是从头开始编写视图。

接口重用本身与视图无关,主要与模板和样式相关。但是,视图应该提供必要的模板上下文以实现接口重用,正如您所指出的那样。

如果您决定为一个ModelAdmin使用多个视图,则可以查看django-reversion项目如何实现其管理集成:reversion/admin.py

参考资料


Django admin已经知道如何以相当好的方式呈现内容,包括标签、帮助文本、隐藏/显示等。如果我编写自己的模板,难道不需要复制所有这些功能吗?而且我不知道如何轻松地扩展change_form.html,因为它试图访问在通用视图中不存在的变量。 - Dan Christensen
你说得对,视图也需要提供所有必要的上下文,这可能需要一些工作。不管怎样,似乎有一个更好的方法;我已经更新了答案。 - Anton Strogonoff
谢谢,这看起来是实现我想要的好方法。我会在有足够的声望时点赞。我还发布了一个不同的简单方法,并将很快发布第三种方法。 - Dan Christensen
你的方法很有趣,我不知道只读字段可以根据请求进行覆盖。然而,我认为在这里使用两个ModelAdmin更加合适。这更直接,并允许轻松实现进一步的自定义,这在这种情况下是常见的(例如在任一视图中显示字段或内联的子集)。 - Anton Strogonoff

0
你可以创建一个自定义视图并在那里显示你的对象。
要在管理模块中创建自定义视图,请覆盖get_urls()方法:
class MyAdmin(admin.ModelAdmin):
    …
    def get_urls(self):
        urls = super(MyAdmin, self).get_urls()
        my_urls = patterns('',
            url(r'^custom_view/(?P<my_id>\d+)/$', self.admin_site.admin_view(self.custom_viem), name='custom_view')
        )
        return my_urls + urls

    def custom_view(self, request, my_id):
        """Define your view function as usual in views.py

        Link to this view using reverse('admin:custom_view')

        """
        from myapp import views
        return views.custom_view(request, my_id, self)

在 views.py 文件中:
def custom_view(request, object_id, model_admin):
    admin_site = model_admin.admin_site
    opts = model_admin.model._meta
    my_object = get_object_or_404(MyObject, pk=object_id)

    # do stuff

    context = {
        'admin_site': admin_site.name,
        'opts': opts,
        'title': _('My custom view'),
        'root_path': '%s' % admin_site.root_path,
        'app_label': opts.app_label,
        'my_object': my_object,
    }

    return render_to_response('my_template.html', context,
            context_instance=RequestContext(request))

在您的模板中,使用{% extends "admin/base_site.html" %}来保持管理员的外观和感觉。

这与仅使用通用视图有何不同,后者只需要几行代码(除了模板)? - Dan Christensen
如果您可以通过通用视图实现相同的结果,那么这应该是正确的方法。 - Thibault J

0
以下代码是使用代理模型实现只读管理员的实现。

Models.py

//真实模型

class CompetitionEntry(models.Model):
    pass

//代理模式

class ReviewEntry(CompetitionEntry):
    class Meta:
        proxy = True
    def save(self, *args, **kwargs):
        pass

admin.py

//可编辑的管理员

class CompetitionEntryAdmin(admin.ModelAdmin):
     pass
admin.site.register(CompetitionEntry, CompetitionEntryAdmin)

// 只读管理员(仅分配“更改”权限)

class ReviewEntryAdmin(admin.ModelAdmin):
    pass
admin.site.register(ReviewEntry, ReviewEntryAdmin)

看起来这将呈现一个看起来可编辑的视图,但当用户保存页面时会悄悄地丢弃任何更改。 - Dan Christensen
是的,它可以。但是你可以使用modeladmin.save_model()方法来抛出警告信息。 - Ryu_hayabusa

0

您需要更改Django管理界面使用的模板,使其只读,并在原始模板中添加一个按钮,链接到另一个URL。

注意:

我强烈反对这种方法,它肯定不能防止同时更改。应该通过锁定来解决这个问题。

此外,我建议使用Django-reversion来保留对象历史记录并消除“意外更改”的风险。


你能再详细解释一下如何实现这个吗?(关于你的其他评论:我认为无论其他设计如何,拥有一个只读视图都不会是一个坏主意。我的唯一目标是通过利用管理员界面已经能够很好地渲染事物的方式来获得该只读视图。) - Dan Christensen

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