使用 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 对象时。它给了我错误:未保存的模型实例 不能在 ORM 查询中使用,有什么想法吗?【参考方案3】:

您还可以通过第二个模型运行学生姓名,使其成为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】:

这可能会有所帮助: 我使用了所描述的方法,但通过以下方式更改了方法 savesave_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 年,这个问题没有直接的解决方案。

我所做的是使用ManyToManysymmetrical=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 进行一对多内联选择的主要内容,如果未能解决你的问题,请参考以下文章

Django Admin -> 更改字段顺序,包括内联字段

如何在 django admin 中自定义多对多内联模型

在 Django admin 中添加内联多对多对象

用于递归多对多的 Django Admin 内联

格式化 django admin 中呈现的内联多对多相关模型

Django Admin,我的表的所有内容都没有显示和内联问题