web-dev-qa-db-ja.com

Django 1つのオブジェクトの管理ページから関連オブジェクトの管理ページにリンクを追加するにはどうすればよいですか?

Django-adminで ネストされたインラインの欠如 に対処するために、2つのテンプレートに特別なケースを入れて、2つのモデルの管理者変更ページとインライン管理者間のリンクを作成しました。

私の質問は、テンプレートの厄介なハックなしに、管理変更ページまたはあるモデルのインライン管理から関連変更モデルの管理変更ページまたはインライン管理へのリンクをきれいに作成するにはどうすればよいですか? ?

任意のモデルの管理者変更ページまたはインライン管理者に適用できる一般的な解決策が欲しいです。


私はpost管理ページにインラインであるblog(実際の名前ではありません)というモデルが1つあり、独自の管理ページもあります。インラインにすることができないのは、外部キーを備えたモデルがあり、それを使用して編集した場合にのみ意味があり、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))

field.field.form.instanceの外側のidインスタンスのblogが見つかりませんでした。

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管理ページへのリンクを作成します。


自分を繰り返さずに管理フォームにリンクを追加するより良い、より一般的な方法があると確信しています。それは何ですか?

28
agf

Django 1.8の新機能: インライン管理者用のshow_change_link です。

インラインモデルでshow_change_linkTrue(デフォルトでは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)
14
bitnik

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
14
Mikhail Korobov

私はagfの解決策はかなり素晴らしいと思います-彼にはたくさんの称賛があります。しかし、さらにいくつかの機能が必要でした。

  • 1人の管理者に複数のリンクを設定できるようにする
  • 別のアプリのモデルにリンクできるようにするため

解決:

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

例があまりに非論理的であるのは残念ですが、自分のデータを使用したくありませんでした。

10
SummerBreeze

これは、Pannu(編集中)とMikhailによって提案された内容に基づく、私の現在の解決策です。

関連オブジェクトのトップレベルの管理者変更ビューにリンクする必要があるいくつかのトップレベルの管理者変更ビューと、同じオブジェクト。そのため、管理者の変更ビューごとにリンクのバリエーションを繰り返すのではなく、リンク方法を除外します。

クラスデコレータを使用して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を呼び出すだけではなく、何らかの方法でリンクテキストを取得する必要がある場合は、カスタムcallableを渡すこともできます。

私はそれを次のように使用します:

# 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にパッチを適用せずにページを変更します。

10
agf

テンプレート編集が難しいので、管理変更ビューページに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}))

ModelAdminform属性をオーバーライドすると、目的の結果が得られます。これらのテーブル間にOneToOne関係があると想定しました。1対多の場合、BlogAdmin側は機能しません。

pdate:動的にリンクを追加するようにいくつかの変更を加えました。これにより、OneToManyの問題がBlogからPostに解決され、これにより問題。 :)

ペーストビン後:PostAdminblog_linkに気づきました。つまり、changelist_viewblogリンクを表示しようとすると、投稿。私が正しい場合は、ページにリンクを表示するメソッドを追加する必要があります。

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'

postchangelist_viewに表示されているBlogAdminリンクに関しては、上記と同じことができます。以前のソリューションでは、各インスタンスを編集できるchange_viewページの1レベル下のリンクが表示されます。

BlogAdminページでpostへのリンクをchange_viewページに表示する場合は、fieldsetsを動的にオーバーライドして、 get_formclass BlogAdminメソッドを使用してリンクを動的に追加するには、get_formself.fieldsetsを設定しますが、最初にfieldsetsにタプルを使用せず、代わりにリスト。

3
Pannu

AgfsとSummerBreezeの提案に基づいて、ユニコードをより適切に処理し、後方外部キーフィールド(1つの結果を持つ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

編集:リンクがなくなったため更新されました。

2
panni

管理クラスのソースを調べることは賢明です。「オリジナル」と呼ばれる管理ビューで使用可能なコンテキスト内のオブジェクトがあることを示しています。

これは同様の状況で、変更リストビューにいくつかの情報を追加する必要がありました: データを管理用テンプレートに追加する (私のブログ上)。

0