如何将一个对象的Django管理页面中的链接添加到相关对象的管理页面?

时间:2012-03-29 05:53:00

标签: python django django-models django-admin django-templates

为了处理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>&nbsp;
<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>&nbsp;
<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


我确信有一种更好,更通用的方式来添加管理表单的链接而不重复自己;它是什么?

7 个答案:

答案 0 :(得分:14)

使用readonly_fields

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的内联管理员中嵌套PostBlog的管理员更改视图,那么这一切都不是必需的页面没有修补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方将无效。

<强>更新     我已经进行了一些更改以动态添加链接,这也解决了OneToManyBlog 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_formclass 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(在我的博客上)。