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>
<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
管理ページへのリンクを作成します。
自分を繰り返さずに管理フォームにリンクを追加するより良い、より一般的な方法があると確信しています。それは何ですか?
Django 1.8の新機能: インライン管理者用のshow_change_link です。
インラインモデルで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)
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
私は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')
例があまりに非論理的であるのは残念ですが、自分のデータを使用したくありませんでした。
これは、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
のインライン管理者内にPost
とBlog
の管理者変更ビューをネストできる場合、これは必要ありません。 Djangoにパッチを適用せずにページを変更します。
テンプレート編集が難しいので、管理変更ビューページに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
関係があると想定しました。1対多の場合、BlogAdmin
側は機能しません。
pdate:動的にリンクを追加するようにいくつかの変更を加えました。これにより、OneToMany
の問題がBlog
からPost
に解決され、これにより問題。 :)
ペーストビン後:PostAdmin
でblog_link
に気づきました。つまり、changelist_view
にblog
リンクを表示しようとすると、投稿。私が正しい場合は、ページにリンクを表示するメソッドを追加する必要があります。
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
changelist_view
に表示されているBlogAdmin
リンクに関しては、上記と同じことができます。以前のソリューションでは、各インスタンスを編集できるchange_view
ページの1レベル下のリンクが表示されます。
BlogAdmin
ページでpost
へのリンクをchange_view
ページに表示する場合は、fieldsets
を動的にオーバーライドして、 get_form
のclass BlogAdmin
メソッドを使用してリンクを動的に追加するには、get_form
でself.fieldsets
を設定しますが、最初にfieldsets
にタプルを使用せず、代わりにリスト。
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
編集:リンクがなくなったため更新されました。
管理クラスのソースを調べることは賢明です。「オリジナル」と呼ばれる管理ビューで使用可能なコンテキスト内のオブジェクトがあることを示しています。
これは同様の状況で、変更リストビューにいくつかの情報を追加する必要がありました: データを管理用テンプレートに追加する (私のブログ上)。