使用 django admin 进行一对多内联选择
Posted
技术标签:
【中文标题】使用 django admin 进行一对多内联选择【英文标题】:one-to-many inline select with django admin 【发布时间】:2011-08-27 09:40:48 【问题描述】:我建立了标准的多对一关系。有一堆字段,但是为了我们这里的目的,相关的模型是:
class Class(models.Model):
name = models.CharField(max_length=128)
class Student(models.Model):
class = models.ForeignKey(Class)
name = models.CharField(max_length=128)
address = models.CharField(max_length=128)
# ...etc
我创建了一个管理员,效果很好。它甚至可以在我编辑学生时自动设置班级。但是,当我去创建/编辑一个类时,我得到的只是名称的输入框。
有没有办法添加一个框/字段,可以从班级管理页面将学生添加为班级成员?我可以内联表格,但那是创建新学生。我已经创建了所有学生,并且正在寻找一种将多个现有学生添加到不同班级的快速方法。
【问题讨论】:
没办法。经过非常连续的谷歌搜索后我没有找到解决方案...... 【参考方案1】:有!你想要InlineModelAdmin
(see InlineModelAdmin documentation here)
简单的示例代码:
class StudentAdminInline(admin.TabularInline):
model = Student
class ClassAdmin(admin.ModelAdmin):
inlines = (StudentAdminInline, )
admin.site.register(Class, ClassAdmin)
【讨论】:
顺便说一下,将模型类命名为“类”时要非常小心。它确实有效,但稍后会咬你(例如,在Student
的第一行)。而且,class = models.ForeignKey(Class)
不会工作,因为“class”是一个保留字。
尝试实施,但这并不能满足我的需要。正如我在问题中所说,我知道如何添加内联表单,但 TabularInline 旨在创建一个新对象。我只想让用户从我已经拥有的现有学生列表中进行选择。
我定义为相关的那些会显示在内联中。我想要一个内联来将现有学生设置为相关的。
啊,我现在明白这个问题了。您可以定义一个自定义 Form
类并将其分配给您的 ModelAdmin
类的 form
属性。
这显然没有回答问题!它正在寻找一种将多个现有学生添加到不同班级的快速方法......再次,现有而不是创建新学生......【参考方案2】:
这是 Luke Sneeringer 建议的“自定义表单”解决方案。无论如何,我很惊讶没有开箱即用的 Django 解决方案来解决这个(相当自然且可能很常见)问题。我错过了什么吗?
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
【讨论】:
老实说,我真的不知道我最终做了什么来解决这个问题。很确定它与此类似。是的,他们没有预制/更简单的东西真的很奇怪。 我喜欢这个解决方案,但想确定您所说的“FIXME:'commit' 参数是热处理的”是什么意思。这里有什么问题? 为了与 ModelForm API 保持一致, foo_form.save(commit=False) 应该返回一个尚未保存到数据库的对象(参见docs.djangoproject.com/en/dev/topics/forms/modelforms/…);但是提供的实现忽略了“提交”参数。您很可能不会使用 commit=False;如果是这样,就没有问题。 两年过去了,这仍然是我能找到的唯一解决方案——除非你知道任何新的发展?请问:self.fields['bars'].initial.update(foo=None) 这行的目的是什么? @zag 你好!这只是 2018 年,这是我发现的最相关且实际上只有部分功能的代码,这很可悲。但是,当我尝试保存 Foo 对象时。它给了我错误:未保存的模型实例您还可以通过第二个模型运行学生姓名,使其成为ForeignKey
。例如在原始代码发布后添加:
class AssignedStudents(models.Model):
assigned_to = models.ForeignKey(Class, on_delete=models.CASCADE)
student = models.ForeignKey(Student, on_delete=models.CASCADE)
然后像 Luke Sneeringer 所说的那样将内联添加到管理员中。您最终会得到一个下拉列表,您可以从中选择学生姓名。不过,仍然可以选择创建新学生。
【讨论】:
这有效地创建了一个多对多关系,AssignedStudents
是关联表。它可以在管理界面中达到预期的效果,但代价是增加了一个额外的表。我想在大多数情况下这是一种不可接受的方法。【参考方案4】:
这可能会有所帮助:
我使用了所描述的方法,但通过以下方式更改了方法 save
和 save_m2m
:
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
【讨论】:
在 Django 3 中,这是我找到的唯一解决方案,非常感谢。我们不得不重写save_m2m
并手动创建一个实例,这很烦人,但是,嘿。【参考方案5】:
如果目的是让学生独立于班级而存在,然后能够在班级中添加或删除他们,那么这就建立了不同的关系:
许多学生可以成为一个班级的一部分。 一个学生可以参加多个班级实际上,这是描述多对多关系。简单交换
class Student(models.Model):
class = models.ForeignKey(Class) ...
为
class Student(models.Model):
class = models.ManyToManyField(Class)...
会立即在 Django admin 中给出想要的效果
Django Many-to-many
【讨论】:
是的,你刚才描述的应该是多对多的关系。我的问题不是关于那个,而是一对多。【参考方案6】:2021 年,这个问题没有直接的解决方案。
我所做的是使用ManyToMany
和symmetrical=False
。阅读更多Django official doc about symmetrical。只有在这种情况下,您才能在django-admin
中选择多个实体
parent_questions = models.ManyToManyField('self', related_name='parent_questions',
blank=True, symmetrical=False)
【讨论】:
【参考方案7】:Django 3.0.9
中对我有用的东西
在 models.py
中from django import forms
from django.db import models
class Student(models.Model):
name = models.CharField(max_length=100)
course = models.ForeignKey(Course, on_delete=models.CASCADE, null=True, blank=True)
def __str__(self):
return self.name
class CourseForm(forms.ModelForm):
students = forms.ModelMultipleChoiceField(
queryset=Student.objects.all(), required=False
)
name = forms.CharField(max_length=100)
class Meta:
model = Student
fields = ["students", "name"]
def __init__(self, *args, **kwargs):
super(CourseForm, self).__init__(*args, **kwargs)
if self.instance:
self.fields["students"].initial = self.instance.student_set.all()
def save_m2m(self):
pass
def save(self, *args, **kwargs):
self.fields["students"].initial.update(course=None)
course_instance = Course()
course_instance.pk = self.instance.pk
course_instance.name = self.instance.name
course_instance.save()
self.cleaned_data["students"].update(course=course_instance)
return course_instance
在 admin.py
from django.contrib import admin
from .models import Student, Course
class StudentAdmin(admin.ModelAdmin):
pass
class CourseAdmin(admin.ModelAdmin):
form = CourseForm
admin.site.register(Student, StudentAdmin)
admin.site.register(Course, CourseAdmin)
【讨论】:
以上是关于使用 django admin 进行一对多内联选择的主要内容,如果未能解决你的问题,请参考以下文章