为了处理django-admin中的lack of nested inlines,我将特殊情况放入两个模板中,以便在管理员更改页面和两个模型的内联管理员之间创建链接。
我的问题是:如何创建一个链接,从一个模型的管理员更改页面或内联管理员干净地管理更改页面或相关模型的内联管理员,而模板中没有讨厌的黑客?
我想要一个通用的解决方案,我可以应用于任何模型的管理员更改页面或内联管理员。
我有一个模型,post
(不是它的真实姓名),它既是blog
管理页面的内联,也有自己的管理页面。它不能仅仅是内联的原因是它具有带有外键的模型,只有在使用它编辑时才有意义,并且只有在使用blog
进行编辑时才有意义。
对于post
管理页面,我更改了“fieldset.html”的一部分:
{% if field.is_readonly %}
<p>{{ field.contents }}</p>
{% else %}
{{ field.field }}
{% endif %}
到
{% if field.is_readonly %}
<p>{{ field.contents }}</p>
{% else %}
{% ifequal field.field.name "blog" %}
<p>{{ field.field.form.instance.blog_link|safe }}</p>
{% else %}
{{ field.field }}
{% endifequal %}
{% endif %}
创建指向blog
管理页面的链接,其中blog_link
是模型上的方法:
def blog_link(self):
return '<a href="%s">%s</a>' % (reverse("admin:myblog_blog_change",
args=(self.blog.id,)), escape(self.blog))
我在id
之外的任何地方找不到blog
个实例的field.field.form.instance
。
在blog
管理页面上,post
是内联的,我修改了“stacked.html”的一部分:
<h3><b>{{ inline_admin_formset.opts.verbose_name|title }}:</b>
<span class="inline_label">{% if inline_admin_form.original %}
{{ inline_admin_form.original }}
{% else %}#{{ forloop.counter }}{% endif %}</span>
到
<h3><b>{{ inline_admin_formset.opts.verbose_name|title }}:</b>
<span class="inline_label">{% if inline_admin_form.original %}
{% ifequal inline_admin_formset.opts.verbose_name "post" %}
<a href="/admin/myblog/post/{{ inline_admin_form.pk_field.field.value }}/">
{{ inline_admin_form.original }}</a>
{% else %}{{ inline_admin_form.original }}{% endifequal %}
{% else %}#{{ forloop.counter }}{% endif %}</span>
创建指向post
管理页面的链接,因为我在这里找到了存储在外键字段中的id
。
我确信有一种更好,更通用的方式来添加管理表单的链接而不重复自己;它是什么?
答案 0 :(得分:14)
class MyInline(admin.TabularInline):
model = MyModel
readonly_fields = ['link']
def link(self, obj):
url = reverse(...)
return mark_safe("<a href='%s'>edit</a>" % url)
# the following is necessary if 'link' method is also used in list_display
link.allow_tags = True
答案 1 :(得分:14)
Django 1.8中的新内容:show_change_link for inline admin。
在内联模型中将 show_change_link 设置为 True (默认为False),以便内联对象具有指向其更改表单的链接(他们可以在其中拥有自己的内联)。
from django.contrib import admin
class PostInline(admin.StackedInline):
model = Post
show_change_link = True
...
class BlogAdmin(admin.ModelAdmin):
inlines = [PostInline]
...
class ImageInline(admin.StackedInline):
# Assume Image model has foreign key to Post
model = Image
show_change_link = True
...
class PostAdmin(admin.ModelAdmin):
inlines = [ImageInline]
...
admin.site.register(Blog, BlogAdmin)
admin.site.register(Post, PostAdmin)
答案 2 :(得分:10)
这是我目前的解决方案,基于Pannu(在他的编辑中)和米哈伊尔的建议。
我有一些顶级管理员更改视图,我需要链接到相关对象的顶级管理员更改视图,以及一些内联管理员更改视图,我需要链接到顶级管理员更改查看同一个对象。因此,我想分析链接方法,而不是为每个管理员更改视图重复它的变体。
我使用类装饰器来创建link
可调用对象,并将其添加到readonly_fields
。
def add_link_field(target_model = None, field = '', link_text = unicode):
def add_link(cls):
reverse_name = target_model or cls.model.__name__.lower()
def link(self, instance):
app_name = instance._meta.app_label
reverse_path = "admin:%s_%s_change" % (app_name, reverse_name)
link_obj = getattr(instance, field, None) or instance
url = reverse(reverse_path, args = (link_obj.id,))
return mark_safe("<a href='%s'>%s</a>" % (url, link_text(link_obj)))
link.allow_tags = True
link.short_description = reverse_name + ' link'
cls.link = link
cls.readonly_fields = list(getattr(cls, 'readonly_fields', [])) + ['link']
return cls
return add_link
如果您需要以某种方式获取链接文本而不是仅在您要链接的对象上调用unicode
,则还可以传递自定义可调用对象。
我这样用:
# the first 'blog' is the name of the model who's change page you want to link to
# the second is the name of the field on the model you're linking from
# so here, Post.blog is a foreign key to a Blog object.
@add_link_field('blog', 'blog')
class PostAdmin(admin.ModelAdmin):
inlines = [SubPostInline, DefinitionInline]
fieldsets = ((None, {'fields': (('link', 'enabled'),)}),)
list_display = ('__unicode__', 'enabled', 'link')
# can call without arguments when you want to link to the model change page
# for the model of an inline model admin.
@add_link_field()
class PostInline(admin.StackedInline):
model = Post
fieldsets = ((None, {'fields': (('link', 'enabled'),)}),)
extra = 0
当然,如果我可以在SubPost
管理员更改的Definition
的内联管理员中嵌套Post
和Blog
的管理员更改视图,那么这一切都不是必需的页面没有修补Django。
答案 3 :(得分:10)
我认为agf的解决方案非常棒 - 给他带来很多荣誉。但我需要更多功能:
解决方案:
def add_link_field(target_model = None, field = '', app='', field_name='link',
link_text=unicode):
def add_link(cls):
reverse_name = target_model or cls.model.__name__.lower()
def link(self, instance):
app_name = app or instance._meta.app_label
reverse_path = "admin:%s_%s_change" % (app_name, reverse_name)
link_obj = getattr(instance, field, None) or instance
url = reverse(reverse_path, args = (link_obj.id,))
return mark_safe("<a href='%s'>%s</a>" % (url, link_text(link_obj)))
link.allow_tags = True
link.short_description = reverse_name + ' link'
setattr(cls, field_name, link)
cls.readonly_fields = list(getattr(cls, 'readonly_fields', [])) + \
[field_name]
return cls
return add_link
用法:
# 'apple' is name of model to link to
# 'fruit_food' is field name in `instance`, so instance.fruit_food = Apple()
# 'link2' will be name of this field
@add_link_field('apple','fruit_food',field_name='link2')
# 'cheese' is name of model to link to
# 'milk_food' is field name in `instance`, so instance.milk_food = Cheese()
# 'milk' is the name of the app where Cheese lives
@add_link_field('cheese','milk_food', 'milk')
class FoodAdmin(admin.ModelAdmin):
list_display = ("id", "...", 'link', 'link2')
我很抱歉这个例子太不合逻辑了,但我不想使用我的数据。
答案 4 :(得分:3)
我同意很难进行模板编辑,因此,我创建了一个自定义小部件,以便在管理员更改视图页面上显示anchor
(可以在表单和内联表单中使用)。
因此,我使用了锚点小部件,以及表单覆盖以获取页面上的链接。
<强> forms.py:强>
class AnchorWidget(forms.Widget):
def _format_value(self,value):
if self.is_localized:
return formats.localize_input(value)
return value
def render(self, name, value, attrs=None):
if not value:
value = u''
text = unicode("")
if self.attrs.has_key('text'):
text = self.attrs.pop('text')
final_attrs = self.build_attrs(attrs,name=name)
return mark_safe(u"<a %s>%s</a>" %(flatatt(final_attrs),unicode(text)))
class PostAdminForm(forms.ModelForm):
.......
def __init__(self,*args,**kwargs):
super(PostAdminForm, self).__init__(*args, **kwargs)
instance = kwargs.get('instance',None)
if instance.blog:
href = reverse("admin:appname_Blog_change",args=(instance.blog))
self.fields["link"] = forms.CharField(label="View Blog",required=False,widget=AnchorWidget(attrs={'text':'go to blog','href':href}))
class BlogAdminForm(forms.ModelForm):
.......
link = forms..CharField(label="View Post",required=False,widget=AnchorWidget(attrs={'text':'go to post'}))
def __init__(self,*args,**kwargs):
super(BlogAdminForm, self).__init__(*args, **kwargs)
instance = kwargs.get('instance',None)
href = ""
if instance:
posts = Post.objects.filter(blog=instance.pk)
for idx,post in enumerate(posts):
href = reverse("admin:appname_Post_change",args=(post["id"]))
self.fields["link_%s" % idx] = forms..CharField(label=Post["name"],required=False,widget=AnchorWidget(attrs={'text':post["desc"],'href':href}))
现在在ModelAdmin
覆盖form
属性,您应该得到所需的结果。我假设你在这些表之间有OneToOne
的关系,如果你有一对多,那么BlogAdmin
方将无效。
<强>更新强>
我已经进行了一些更改以动态添加链接,这也解决了OneToMany
到Blog
Post
问题,希望这可以解决问题。 :)
在Pastebin之后:
在PostAdmin
我注意到blog_link
,这意味着您尝试在blog
上显示列出所有帖子的changelist_view
链接。如果我是正确的,那么你应该添加一个方法来显示页面上的链接。
class PostAdmin(admin.ModelAdmin):
model = Post
inlines = [SubPostInline, DefinitionInline]
list_display = ('__unicode__', 'enabled', 'blog_on_site')
def blog_on_site(self, obj):
href = reverse("admin:appname_Blog_change",args=(obj.blog))
return mark_safe(u"<a href='%s'>%s</a>" %(href,obj.desc))
blog_on_site.allow_tags = True
blog_on_site.short_description = 'Blog'
就post
BlogAdmin
上显示changelist_view
链接而言,您可以执行与上述相同的操作。我之前的解决方案会在change_view
页面向您显示较低级别的链接,您可以在其中编辑每个实例。
如果您希望BlogAdmin
页面显示指向post
页面中change_view
的链接,那么您必须通过覆盖fieldsets
动态地将每个链接包含在get_form
中class BlogAdmin
的{{1}}方法并动态添加链接,get_form
设置self.fieldsets
,但首先不要使用元组来代替fieldsets
使用列表。
答案 5 :(得分:2)
基于agfs和SummerBreeze的建议,我已经改进了装饰器以更好地处理unicode并且能够链接到backwards-foreignkey字段(ManyRelatedManager有一个结果)。此外,您现在可以添加short_description作为列表标题:
from django.core.urlresolvers import reverse
from django.core.exceptions import MultipleObjectsReturned
from django.utils.safestring import mark_safe
def add_link_field(target_model=None, field='', app='', field_name='link',
link_text=unicode, short_description=None):
"""
decorator that automatically links to a model instance in the admin;
inspired by http://stackoverflow.com/questions/9919780/how-do-i-add-a-link-from-the-django-admin-page-of-one-object-
to-the-admin-page-o
:param target_model: modelname.lower or model
:param field: fieldname
:param app: appname
:param field_name: resulting field name
:param link_text: callback to link text function
:param short_description: list header
:return:
"""
def add_link(cls):
reverse_name = target_model or cls.model.__name__.lower()
def link(self, instance):
app_name = app or instance._meta.app_label
reverse_path = "admin:%s_%s_change" % (app_name, reverse_name)
link_obj = getattr(instance, field, None) or instance
# manyrelatedmanager with one result?
if link_obj.__class__.__name__ == "RelatedManager":
try:
link_obj = link_obj.get()
except MultipleObjectsReturned:
return u"multiple, can't link"
except link_obj.model.DoesNotExist:
return u""
url = reverse(reverse_path, args = (link_obj.id,))
return mark_safe(u"<a href='%s'>%s</a>" % (url, link_text(link_obj)))
link.allow_tags = True
link.short_description = short_description or (reverse_name + ' link')
setattr(cls, field_name, link)
cls.readonly_fields = list(getattr(cls, 'readonly_fields', [])) + \
[field_name]
return cls
return add_link
编辑:由于链接消失而更新。
答案 6 :(得分:0)
查看管理类的来源是有启发性的:它表明上下文中有一个对象可用于名为“original”的管理视图。
这是类似的情况,我需要在变更列表视图中添加一些信息:Adding data to admin templates(在我的博客上)。