Django管理嵌套内联

68

我需要一个嵌套的 Django admin inline,可以将日期字段内联到另一个内联中,如下所示。

我有以下模型:

class Person(models.Model):
     name = models.CharField(max_length=200)
     id_no = models.IntegerField()

class Certificate(models.Model):
     cerfificate_no = models.CharField(max_length=200)
     certificate_date = models.DateField(max_length=100)
     person = models.ForeignKey(Person)
     training = models.CharField(max_length=200)

class Training_Date(models.Model):
      date = models.DateField()
      certificate = models.ForeignKey(Certificate)

还有,下面的管理员:

class CertificateInline(admin.StackedInline):
    model = Certificate

class PersonAdmin(admin.ModelAdmin):
     inlines = [CertificateInline,]
admin.site.register(Person,PersonAdmin)

但是,我需要将Training_Date模型作为内联模型包含在证书管理的内联部分中。

有任何想法吗?

8个回答

48

最近在https://code.djangoproject.com/ticket/9025中有一些进展,但我不会抱太大的希望。

解决这个问题的一种常见方法是通过为同一个模型同时添加ModelAdmin和内联(Inline)来链接第一级和第二级(或第二级和第三级):

Certificate添加一个ModelAdmin,并将TrainingDate设置为内联。 为CertificateInline设置show_change_link=True,以便您可以单击内联以进入其ModelAdmin更改表单。

admin.py:

# Certificate change form has training dates as inline

class TrainingDateInline(admin.StackedInline):
    model = TrainingDate

class CertificateAdmin(admin.ModelAdmin):
    inlines = [TrainingDateInline,]
admin.site.register(Certificate ,CertificateAdmin)

# Person has Certificates inline but rather
# than nesting inlines (not possible), shows a link to
# its own ModelAdmin's change form, for accessing TrainingDates:

class CertificateLinkInline(admin.TabularInline):
    model = Certificate
    # Whichever fields you want: (I usually use only a couple
    # needed to identify the entry)
    fields = ('cerfificate_no', 'certificate_date')
    # Django 1.8 introduced this, no need to make your own link
    show_change_link = True

class PersonAdmin(admin.ModelAdmin):
    inlines = [CertificateLinkInline,]
admin.site.register(Person, PersonAdmin)

12
这是一个好的解决方案。我想指出你可以将 changeform_link 放在 CertificateLinkInline 中,这可能是更好的位置,因为它是Django管理特定的。请注意,这样做时,应使用 instance.id 而不是 self.id 来访问模型的实例。请参见 https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.readonly_fields - gitaarik
如果changeform_link方法产生异常,那么Django将吞噬它并继续执行,留下该字段为空。我从未能找到Django放置回溯的位置,或者它是否对其进行任何处理。我建议使用try/except将其包装起来,以确保异常被记录在某个地方。为了简化,创建一个装饰器来处理所有这些可能会很有用。 - Kevin S.
2
看起来在Django 2+中,你只需要在你的admin.TabularInline类中添加“show_change_link = True”,并且只要你也有一个常规的admin.ModelAdmin,它也将在内联中显示一个“更改”链接。 - MrColes
@MrColes @chaulap show_change_link是由Django 1.8于2015年4月1日发布时引入的。虽然比我的答案晚了一年,但是是目前的一行代码等价形式。 :) - Danny W. Adair
1
@DannyW.Adair 很好的解决方案 +1!如果您用show_change_link = True更新答案,对于访问者和新手来说将非常有帮助,即使已经在评论中注意到了。当然,您不必这样做,但这会非常友好。 - Ramon K.
显示剩余3条评论

32

更普适的解决方案

from django.utils.safestring import mark_safe
from django.urls import reverse

class EditLinkToInlineObject(object):
    def edit_link(self, instance):
        url = reverse('admin:%s_%s_change' % (
            instance._meta.app_label,  instance._meta.model_name),  args=[instance.pk] )
        if instance.pk:
            return mark_safe(u'<a href="{u}">edit</a>'.format(u=url))
        else:
            return ''

class MyModelInline(EditLinkToInlineObject, admin.TabularInline):
    model = MyModel
    readonly_fields = ('edit_link', )

class MySecondModelAdmin(admin.ModelAdmin):
    inlines = (MyModelInline, )

admin.site.register(MyModel)
admin.site.register(MySecondModel, MySecondModelAdmin)

简单又灵活。+1 - yekta
1
一个非常类似的解决方案在这篇文章中进行了更详细的描述:https://djangotricks.blogspot.com/2016/12/django-administration-inlines-for-inlines.html - natka_m

19
pip install django-nested-inline

这个软件包应该可以满足您的需求。


2
django-nested-inline在最新的Django版本中尚未得到支持(可能?)。但是您可以考虑使用https://github.com/theatlantic/django-nested-admin,它几乎相同。 - vmonteco
@vmonteco,你能给我一个链接到文档的地址吗?其中描述了如何在不使用软件包的情况下使用它? - Oleksandr Dashkov
1
@OleksandrDashkov http://django-nested-admin.readthedocs.io/en/latest/,在github页面的顶部。但是可能仍然需要安装该软件包。 - vmonteco
警告,我开始使用django-nested-inline并遇到了多个耗时的错误。在决定使用哪个模块之前,您可能需要评估其他选项。 - pspahn
1
@pspahn,你有找到任何合理的替代方案吗? - Aleem

19

据我所知,在默认的Django管理界面中,您无法拥有第二级内联。

Django管理界面只是一个普通的Django应用程序,因此没有任何东西阻止您实现第二级嵌套表单,但在我看来,这将是一种复杂的设计。也许这就是为什么没有为此提供任何规定的原因。


1
我面临着类似的情况。我想我会覆盖内联模板并在第二级中添加一些链接。 - Damon

6
一个更加现代的解决方案(截至2021年2月)是使用show_change_link配置变量:https://docs.djangoproject.com/en/3.1/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.show_change_link 这与上面提出的EditLinkToInlineObject完全相同,但代码更少,并且可能经过Django开发人员的充分测试。
您只需要在每个内联中定义show_change_link=True即可
更新(2022年1月25日): 这是文档中的更新链接(Django 4.0):https://docs.djangoproject.com/en/4.0/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.show_change_link

5

我尝试过了。它不允许我添加或删除一些内联元素,并强制我创建3个新的子对象。在开发的这个阶段,它是无法使用的。 - max

5

使用 django-nested-admin,这是最好的处理嵌套内联的包。

首先,安装"django-nested-admin"

pip install django-nested-admin

接下来,在 "settings.py" 中的 "INSTALLED_APPS" 中添加 "nested_admin"

# "settings.py"

INSTALLED_APPS = (
    # ...
    "nested_admin", # Here
)

然后,在 "urls.py" 中的 "urlpatterns" 中添加 "path('_nested_ad..."

# "urls.py"

from django.urls import include, path

urlpatterns = [
    # ...
    path('_nested_admin/', include('nested_admin.urls')), # Here
]

最后,按照下面的示例,在 "admin.py" 中扩展"NestedModelAdmin" 类,添加"PersonAdmin()" 类,并使用 "Training_DateInline()" 和 "CertificateInline()" 类扩展 "NestedTabularInline"

# "admin.py"

from .models import Training_Date, Certificate, Person
from nested_admin import NestedTabularInline, NestedModelAdmin

class Training_DateInline(NestedTabularInline):
    model = Training_Date

class CertificateInline(NestedTabularInline):
    model = Certificate
    inlines = [Training_DateInline]

@admin.register(Person)
class PersonAdmin(NestedModelAdmin):
    inlines = [CertificateInline]

2
我使用了@bigzbig提供的解决方案(非常感谢)。
我还想在更改保存后返回到第一个列表页面,所以添加了以下内容:
class MyModelInline(EditLinkToInlineObject, admin.TabularInline):
    model = MyModel
    readonly_fields = ('edit_link', )

    def response_post_save_change(self, request, obj):
        my_second_model_id = MyModel.objects.get(pk=obj.pk).my_second_model_id
        return redirect("/admin/mysite/mysecondmodel/%s/change/" % (my_second_model_id))

谢谢你提供的代码片段!我想补充一下,只有在将post_save_change函数包含在MyModelAdmin中后,这个代码才对我起作用。 - Hadi Kocabas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接