Подтвердить что ты не робот

Как добавить ссылку с страницы администрирования Django одного объекта на страницу администратора связанного объекта?

Чтобы справиться с отсутствием вложенных встроенных строк в django-admin, я поместил специальные случаи в два из шаблонов для создания связей между страницами изменения admin и встроенных админов двух моделей.

Мой вопрос: как я могу создать ссылку со страницы изменения администрирования или встроенного администратора одной модели на страницу изменения администратора или встроенный администратор связанной модели без каких-либо неприятных хаков в шаблоне?

Я хотел бы получить общее решение, которое я могу применить к странице изменения администратора или встроенному администратору любой модели.


У меня есть одна модель, post (не ее настоящее имя), которая является встроенной на странице администрирования blog, а также имеет собственную страницу администратора. Причина, по которой он не может быть просто встроен, заключается в том, что он имеет модели с внешними ключами, которые имеют смысл только при редактировании с ним, и это имеет смысл только при редактировании с помощью blog.

Для страницы post admin, я изменил часть "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 admin, где 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 admin, где 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 admin, так как здесь я смог найти id, хранящийся в поле внешнего ключа.


Я уверен, что есть лучший, более общий способ добавления ссылок на формы администратора, не повторяя себя; что это?

4b9b3361

Ответ 1

Новое в Django 1.8: show_change_link для встроенного администратора.

Установите show_change_link на True (по умолчанию ложно) в вашей встроенной модели, чтобы встроенные объекты имели ссылку на их форму изменения (где они могут иметь свои собственные строки).

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

Используйте 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

Ответ 3

Это мое текущее решение, основанное на том, что было предложено Панну (в его редактировании) и Михаилом.

У меня есть пара изменений верхнего уровня администратора, которые мне нужно связать с представлением об изменении администратора верхнего уровня связанного объекта, а также несколько встроенных представлений об изменении администрирования, которые мне нужно связать с администратором верхнего уровня вид того же объекта. Из-за этого я хочу отмерить метод ссылок, а не повторять его варианты для каждого вида просмотра admin.

Я использую декоратор класса для создания вызываемого 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 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 admin без исправления Django.

Ответ 4

Я думаю, что решение 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')

Мне жаль, что пример настолько нелогичен, но я не хотел использовать свои данные.

Ответ 5

Я согласен с тем, что его трудно сделать редактирование шаблона, поэтому я создаю собственный виджет, чтобы показать anchor на странице просмотра изменений admin (может использоваться как для обеих форм, так и для встроенных форм).

Итак, я использовал виджет привязки вместе с формой, переопределяющей, чтобы получить ссылку на странице.

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 и динамически добавляя ссылку, в get_form установите self.fieldsets, но сначала не используйте кортежи для fieldsets вместо этого используйте список.

Ответ 6

Основываясь на предложениях agfs и SummerBreeze, я улучшил декоратор, чтобы лучше обрабатывать юникод и иметь возможность ссылаться на поля назад-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

Изменить: обновлено из-за исчезновения ссылки.

Ответ 7

Просматривая источник админ-классов, он просвещает: он показывает, что есть объект в контексте, доступный для представления администратора, называемого "оригиналом".

Вот аналогичная ситуация, когда мне нужна информация, добавленная в список изменений: Добавление данных в шаблоны администратора (в моем блоге).