Is it possible to manually specify the set of related object to show in an inline, where no foreign key relation exists?
# 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, )
I want my Diary
model admin to display an inline for Sleep
models that have start_time
equal to the same day as Diary.day
. The problem is that the Sleep
model does not have a ForeignKey
to Diary
(instead, the relation is implicit by the use of dates).
Using the above, Django immediately complains that
<class 'records.admin.SleepInline'>: (admin.E202) 'records.Sleep' has no ForeignKey to 'records.Diary'.
How can I show the relevant Sleep
instances as inlines on the Diary
admin page?
There is no getting around the fact that Django admin inlines are built around ForeignKey
fields (or ManyToManyField
, OneToOneField
). However, if I understand your goal, it's to avoid having to manage "date integrity" between your Diary.day
and Sleep.start_time
fields, i.e., the redundancy in a foreign key relation when that relation is really defined by Diary.day == Sleep.start_time.date()
A Django ForiegnKey
field has a to_field property that allows the FK to index a column besides id
. However, as you have a DateTimeField
in Sleep
and a DateField
in Diary
, we'll need to split that DateTimeField
up. Also, a ForeignKey
has to relate to something unique on the "1" side of the relation. Diary.day
needs to be set unique=True
.
In this approach, your models look like
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()
and then your admin.py
is just
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, )
Even though Sleep.start_time
no longer has a date, the Django Admin is quite what you'd expect, and avoids "date redundancy":
Thinking ahead to a more real (and problematic) use case, say every user can have 1 Diary per day:
class Diary(models.Model):
user = models.ForeignKey(User)
day = models.DateField()
activities = models.TextField()
class Meta:
unique_together = ('user', 'day')
One would like to write something like
class Sleep(models.Model):
diary = models.ForeignKey(Diary, to_fields=['user', 'day'], on_delete=models.CASCADE)
However, there's no such feature in Django 1.11, nor can I find any serious discussion of adding that. Certainly composite foreign keys are allowed in Postgres and other SQL DBMS's. I get the impression from the Django source they're keeping their options open: https://github.com/django/django/blob/stable/1.11.x/django/db/models/fields/related.py#L621 hints at a future implementation.
Finally, https://pypi.python.org/pypi/django-composite-foreignkey looks interesting at first, but doesn't create "real" composite foreign keys, nor does it work with Django's admin.