I have a standard many-to-one relationship set up. There are a bunch of fields, but for our purposes here, the relevant model is:
class Class(models.Model):
You could also run the student names trough a second model so that they are a ForeignKey
. For example after the original code post add:
class AssignedStudents(models.Model):
assigned_to = models.ForeignKey(Class, on_delete=models.CASCADE)
student = models.ForeignKey(Student, on_delete=models.CASCADE)
Then add the inline to the admin like Luke Sneeringer said. You end up with a drop down list that you can select the student names from. Although, there is still the option to create new students.
Here is "custom form" solution as Luke Sneeringer suggested. Anyway, I'm suprised by absence of out-of-the-box Django solution to this (rather natural and probably common) problem. Am I missing something?
from django import forms
from django.db import models
from django.contrib import admin
class Foo(models.Model):
pass
class Bar(models.Model):
foo = models.ForeignKey(Foo)
class FooForm(forms.ModelForm):
class Meta:
model = Foo
bars = forms.ModelMultipleChoiceField(queryset=Bar.objects.all())
def __init__(self, *args, **kwargs):
super(FooForm, self).__init__(*args, **kwargs)
if self.instance:
self.fields['bars'].initial = self.instance.bar_set.all()
def save(self, *args, **kwargs):
# FIXME: 'commit' argument is not handled
# TODO: Wrap reassignments into transaction
# NOTE: Previously assigned Foos are silently reset
instance = super(FooForm, self).save(commit=False)
self.fields['bars'].initial.update(foo=None)
self.cleaned_data['bars'].update(foo=instance)
return instance
class FooAdmin(admin.ModelAdmin):
form = FooForm
If the intention is to have students exist independently from a class, and then be able to add or remove them from a class, then this sets up a different relationship:
In reality this is describing a Many-To-Many relationship. Simply exchanging
class Student(models.Model):
class = models.ForeignKey(Class) ...
for
class Student(models.Model):
class = models.ManyToManyField(Class)...
will give the desired effect in Django admin immediately
Django Many-to-many
Probably, this will help:
I used the described approach, but changed methods save
and save_m2m
in the following way:
from django import forms
from django.db import models
from django.contrib import admin
class Foo(models.Model):
pass
class Bar(models.Model):
foo = models.ForeignKey(Foo)
class FooForm(forms.ModelForm):
class Meta:
model = Foo
bars = forms.ModelMultipleChoiceField(queryset=Bar.objects.all())
def __init__(self, *args, **kwargs):
super(FooForm, self).__init__(*args, **kwargs)
if self.instance:
self.fields['bars'].initial = self.instance.bar_set.all()
def save_m2m(self):
pass
def save(self, *args, **kwargs):
self.fields['bars'].initial.update(foo=None)
foo_instance = Foo()
foo_instance.pk = self.instance.pk
# Copy all other fields.
# ... #
foo_instance.save()
self.cleaned_data['bars'].update(foo=instance)
return instance
class FooAdmin(admin.ModelAdmin):
form = FooForm
There is! You want InlineModelAdmin
(see InlineModelAdmin documentation here)
Sample code in brief:
class StudentAdminInline(admin.TabularInline):
model = Student
class ClassAdmin(admin.ModelAdmin):
inlines = (StudentAdminInline, )
admin.site.register(Class, ClassAdmin)