简体   繁体   English

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

[英]How do I add a link from the Django admin page of one object to the admin page of a related object?

To deal with the lack of nested inlines in django-admin, I've put special cases into two of the templates to create links between the admin change pages and inline admins of two models. 为了解决django-admin中缺少嵌套内联的问题 ,我将特殊情况放入两个模板中,以便在管理员更改页面和两个模型的内联管理员之间创建链接。

My question is: how do I create a link from the admin change page or inline admin of one model to the admin change page or inline admin of a related model cleanly, without nasty hacks in the template? 我的问题是:如何从管理员更改页面或一个模型的内联管理员创建一个链接到管理员更改页面或相关模型的内联管理员干净利落,模板中没有令人讨厌的黑客?

I would like a general solution that I can apply to the admin change page or inline admin of any model. 我想要一个通用的解决方案,我可以应用于任何模型的管理员更改页面或内联管理员。


I have one model, post (not its real name) that is both an inline on the blog admin page, and also has its own admin page. 我有一个模型, post (不是它的真实姓名),它既是blog管理页面的内联,也有自己的管理页面。 The reason it can't just be inline is that it has models with foreign keys to it that only make sense when edited with it, and it only makes sense when edited with blog . 它不能仅仅是内联的原因是它具有带有外键的模型,只有在使用它编辑时才有意义,并且只有在使用blog编辑时才有意义。

For the post admin page, I changed part of "fieldset.html" from: 对于post admin页面,我更改了“fieldset.html”的一部分:

{% if field.is_readonly %}
    <p>{{ field.contents }}</p>
{% else %}
    {{ field.field }}
{% endif %}

to

{% 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 %}

to create a link to the blog admin page, where blog_link is a method on the model: 创建指向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))

I couldn't find the id of the blog instance anywhere outside field.field.form.instance . 我无法在field.field.form.instance之外的任何地方找到blog实例的id

On the blog admin page, where post is inline, I modified part of "stacked.html" from: post为内联的blog管理页面上,我修改了“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>

to

<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>

to create a link to the post admin page since here I was able to find the id stored in the foreign key field. 创建一个到post管理页面的链接,因为在这里我能够找到存储在外键字段中的id


I'm sure there is a better, more general way to do add links to admin forms without repeating myself; 我确信有更好,更通用的方法来添加管理表单的链接而不重复自己; what is it? 它是什么?

New in Django 1.8 : show_change_link for inline admin . Django 1.8中的新功能: show_change_link用于内联管理员

Set show_change_link to True (False by default) in your inline model, so that inline objects have a link to their change form (where they can have their own inlines). 在内联模型中将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)

Use readonly_fields : 使用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

This is my current solution, based on what was suggested by Pannu (in his edit) and Mikhail. 这是我目前的解决方案,基于Pannu(在他的编辑中)和Mikhail的建议。

I have a couple of top-level admin change view I need to link to a top-level admin change view of a related object, and a couple of inline admin change views I need to link to the top-level admin change view of the same object. 我有一些顶级管理员更改视图,我需要链接到相关对象的顶级管理员更改视图,以及一些内联管理员更改视图,我需要链接到顶级管理员更改视图同一个对象。 Because of that, I want to factor out the link method rather than repeating variations of it for every admin change view. 因此,我想分析链接方法,而不是为每个管理员更改视图重复它的变体。

I use a class decorator to create the link callable, and add it to readonly_fields . 我使用类装饰器来创建可调用的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

You can also pass a custom callable if you need to get your link text in some way than just calling unicode on the object you're linking to. 如果您需要以某种方式获取链接文本而不是仅仅在您要链接的对象上调用unicode ,则还可以传递自定义可调用对象。

I use it like this: 我这样使用它:

# 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

Of course none of this would be necessary if I could nest the admin change views for SubPost and Definition inside the inline admin of Post on the Blog admin change page without patching Django. 当然,如果我可以在Blog管理员更改页面上的Post内联管理员中嵌入SubPostDefinition的管理员更改视图而不修补Django,那么这一切都不是必需的。

I think that agf's solution is pretty awesome -- lots of kudos to him. 我认为agf的解决方案非常棒 - 给他带来很多赞誉。 But I needed a couple more features: 但我需要更多功能:

  • to be able to have multiple links for one admin 能够为一个管理员提供多个链接
  • to be able to link to model in different app 能够链接到不同的应用程序中的模型

Solution: 解:

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

Usage: 用法:

# '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')

I am sorry that the example is so illogical, but I didn't want to use my data. 我很抱歉这个例子太不合逻辑了,但我不想使用我的数据。

I agree that its hard to do template editing so, I create a custom widget to show an anchor on the admin change view page(can be used on both forms and inline forms). 我同意很难进行模板编辑,因此,我创建了一个自定义小部件,以在管理员更改视图页面上显示anchor (可以在表单和内联表单中使用)。

So, I used the anchor widget, along with form overriding to get the link on the page. 所以,我使用了锚点小部件,以及表单覆盖来获取页面上的链接。

forms.py: 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}))

now in your ModelAdmin override the form attribute and you should get the desired result. 现在在您的ModelAdmin覆盖form属性,您应该得到所需的结果。 I assumed you have a OneToOne relationship between these tables, If you have one to many then the BlogAdmin side will not work. 我假设你在这些表之间有一个OneToOne关系,如果你有一对多,那么BlogAdmin方面将无效。

update: I've made some changes to dynamically add links and that also solves the OneToMany issue with the Blog to Post hope this solves the issue. 更新:我已经做了一些动态添加链接的更改,这也解决了Blog to PostOneToMany问题,希望这可以解决问题。 :) :)

After Pastebin: In Your PostAdmin I noticed blog_link , that means your trying to show the blog link on changelist_view which lists all the posts. PostAdmin 之后:在你的PostAdmin我发现了blog_link ,这意味着你试图在changelist_view上显示列出所有帖子的blog链接。 If I'm correct then you should add a method to show the link on the page. 如果我是正确的,那么你应该添加一个方法来显示页面上的链接。

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'

As far as the showing post links on BlogAdmin changelist_view you can do the same as above. 至于BlogAdmin changelist_view上的显示post链接,您可以像上面一样做。 My earlier solution will show you the link one level lower at the change_view page where you can edit each instance. 我之前的解决方案将在change_view页面向您显示一个级别较低的链接,您可以在其中编辑每个实例。

If you want the BlogAdmin page to show the links to the post in the change_view page then you will have to include each in the fieldsets dynamically by overriding the get_form method for class BlogAdmin and adding the link's dynamically, in get_form set the self.fieldsets , but first don't use tuples to for fieldsets instead use a list. 如果你想BlogAdmin页面显示链接到postchange_view页面,那么你将必须包括每个在fieldsets动态地通过重写get_form的方法class BlogAdmin并添加链接的动态,在get_form设置self.fieldsets ,但首先不要使用元组来代替fieldsets而是使用列表。

Based on agfs and SummerBreeze's suggestions, I've improved the decorator to handle unicode better and to be able to link to backwards-foreignkey fields (ManyRelatedManager with one result). 基于agfs和SummerBreeze的建议,我已经改进了装饰器以更好地处理unicode并且能够链接到backwards-foreignkey字段(ManyRelatedManager有一个结果)。 Also you can now add a short_description as a list header: 此外,您现在可以添加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

Edit: updated due to link being gone. 编辑:由于链接消失而更新。

Looking through the source of the admin classes is enlightening: it shows that there is an object in context available to an admin view called "original". 查看管理类的来源是有启发性的:它表明在上下文中有一个对象可用于名为“original”的管理视图。

Here is a similar situation, where I needed some info added to a change list view: Adding data to admin templates (on my blog). 这是类似的情况,我需要将一些信息添加到更改列表视图中: 将数据添加到管理模板 (在我的博客上)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM