我想使用Django管理员界面来生成一个只读视图,其中包含一个“编辑”按钮,该按钮可以使您切换到同一对象的通常更改视图。
我知道如何使用readonly属性来生成只读视图,但我不知道如何生成两个视图,一个是只读的,另一个允许更改。
我希望尽可能地重用管理员界面,而不是从头编写视图。
请注意,这个问题与权限无关:所有用户都将有权更改对象。只是我希望他们在不打算进行更改时不要使用change_view,以减少意外更改或同时更改的风险。
我想使用Django管理员界面来生成一个只读视图,其中包含一个“编辑”按钮,该按钮可以使您切换到同一对象的通常更改视图。
我知道如何使用readonly属性来生成只读视图,但我不知道如何生成两个视图,一个是只读的,另一个允许更改。
我希望尽可能地重用管理员界面,而不是从头编写视图。
请注意,这个问题与权限无关:所有用户都将有权更改对象。只是我希望他们在不打算进行更改时不要使用change_view,以减少意外更改或同时更改的风险。
这里有一个答案,只需要几行代码和一些模板更改,就能够完全满足我的要求:
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',
)
我建议重新考虑使用自定义视图。借助通用的DetailView
,您只需要编写两行代码。模板也不需要太多工作。您只需扩展标准的change_form.html模板,覆盖field_sets
块。
我知道如何使用只读属性生成只读视图,但我不知道如何生成两个视图,一个是只读的,另一个允许更改。
实际上,您可以使用代理模型在管理中注册一个模型两次[1]。(代理模型的权限存在一些不一致,但在您的情况下可能不是问题。)
似乎也可以注册多个管理站点[2]。
我希望尽可能地重用管理界面,而不是从头开始编写视图。
接口重用本身与视图无关,主要与模板和样式相关。但是,视图应该提供必要的模板上下文以实现接口重用,正如您所指出的那样。
如果您决定为一个ModelAdmin
使用多个视图,则可以查看django-reversion
项目如何实现其管理集成:reversion/admin.py。
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)
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" %}
来保持管理员的外观和感觉。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)
您需要更改Django管理界面使用的模板,使其只读,并在原始模板中添加一个按钮,链接到另一个URL。
注意:
我强烈反对这种方法,它肯定不能防止同时更改。应该通过锁定来解决这个问题。
此外,我建议使用Django-reversion来保留对象历史记录并消除“意外更改”的风险。
ModelAdmin
更加合适。这更直接,并允许轻松实现进一步的自定义,这在这种情况下是常见的(例如在任一视图中显示字段或内联的子集)。 - Anton Strogonoff