没有外键关系的管理员内联

Admin inline with no ForeignKey relation

是否可以在不存在外键关系的情况下手动指定一组相关对象以内联方式显示?

# Parent
class Diary(models.Model):
    day = models.DateField()
    activities = models.TextField()

# Child
class Sleep(models.Model):
    start_time = models.DateTimeField()
    end_time = models.DateTimeField()

class SleepInline(admin.TabularInline):
    model=Sleep
    def get_queryset(self, request):
        # Return all Sleep objects where start_time and end_time are within Diary.day
        return Sleep.objects.filter(XXX) 

class DiaryAdmin(admin.ModelAdmin):
    inlines = (SleepInline, )

我希望我的 Diary 模型管理员显示 Sleep 模型的内联,这些模型 start_timeDiary.day 相同。问题是 Sleep 模型没有 ForeignKeyDiary (相反,关系是通过使用日期隐含的)。

使用上面的方法,Django 立即抱怨

<class 'records.admin.SleepInline'>: (admin.E202) 'records.Sleep' has no ForeignKey to 'records.Diary'.

如何在 Diary 管理页面上将相关 Sleep 实例显示为内联?

首先让我向您展示您逻辑的缺点:

  • 添加外键时,有2个不常见的操作需要调整关系:创建一个新的睡眠对象和更新睡眠对象的时间。
  • 当不使用外键时,每次请求日记时,都需要查找相应的 Sleep 对象。我假设阅读日记比改变睡眠对象更常见,因为它会出现在大多数项目中。

如您所见,另一个缺点是您不能使用关系功能。 InlineAdmin 是一个关系特性,所以正如你所说的那样"making the admin work",它确实是你需要一把锤子来拧开螺栓。

但是...管理员使用了 ModelForm。因此,如果您使用 formset 构造表单(出于同样的原因,它不能是内联表单集)并自己处理保存该表单集,那应该是可能的。 InlineFormset 和 InlineAdmin 的全部要点是使从相关模型生成表单集更容易,为此它需要知道关系。

最后,您可以 add urls 并构建自定义页面,并且在扩展 admin/base.html 模板时,您可以访问布局和 javascript 组件。

无法回避 Django 管理内联是围绕 ForeignKey 字段(或 ManyToManyFieldOneToOneField)构建的事实。但是,如果我理解你的目标,那就是避免必须在你的 Diary.daySleep.start_time 字段之间管理 "date integrity",即当外键关系真正由以下定义时,外键关系中的冗余Diary.day == Sleep.start_time.date()

Django ForiegnKey 字段有一个 to_field property 允许 FK 索引除 id 之外的列。但是,由于您在 Sleep 中有一个 DateTimeField 并且在 Diary 中有一个 DateField,我们需要将 DateTimeField 分开。此外,ForeignKey 必须与关系“1”端的唯一内容相关。 Diary.day 需要设置 unique=True

在这种方法中,您的模型看起来像

from django.db import models

# Parent
class Diary(models.Model):
    day = models.DateField(unique=True)
    activities = models.TextField()

# Child
class Sleep(models.Model):
    diary = models.ForeignKey(Diary, to_field='day', on_delete=models.CASCADE)
    start_time = models.TimeField()
    end_time = models.DateTimeField()

然后你的 admin.py 就是

from django.contrib import admin
from .models import Sleep, Diary

class SleepInline(admin.TabularInline):
    model=Sleep

@admin.register(Diary)
class DiaryAdmin(admin.ModelAdmin):
    inlines = (SleepInline, )

即使 Sleep.start_time 不再有日期,Django Admin 也完全符合您的期望,并且避免了 "date redundancy":


提前考虑一个更真实(和有问题)的用例,假设每个用户每天可以有 1 篇日记:

class Diary(models.Model):
    user = models.ForeignKey(User)
    day = models.DateField()
    activities = models.TextField()

    class Meta:
        unique_together = ('user', 'day')

有人想写这样的东西

class Sleep(models.Model):
    diary = models.ForeignKey(Diary, to_fields=['user', 'day'], on_delete=models.CASCADE)

但是,Django 1.11 中没有这样的功能,我也找不到任何关于添加它的认真讨论。当然,在 Postgres 和其他 SQL DBMS 中允许使用复合外键。我从 Django 源代码中得到印象,他们保持他们的选择开放:https://github.com/django/django/blob/stable/1.11.x/django/db/models/fields/related.py#L621 暗示未来的实施。

最后,https://pypi.python.org/pypi/django-composite-foreignkey 一开始看起来很有趣,但不会创建 "real" 复合外键,也不能与 Django 的管理一起使用。

您可以使用 nested_admin library 来实现。

所以在 myapp/admin.py:

from nested_admin.nested import NestedStackedInline, NestedModelAdmin

class SleepInline(NestedStackedInline):

    model = Sleep

class DiaryAdmin(NestedModelAdmin):

   inlines = [SleepInline]