【问题标题】:one-to-many inline select with django admin使用 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

我创建了一个管理员,效果很好。它甚至可以在我编辑学生时自动设置班级。但是,当我去创建/编辑一个类时,我得到的只是名称的输入框。

有没有办法添加一个框/字段,可以从班级管理页面将学生添加为班级成员?我可以内联表格,但那是创建新学生。我已经创建了所有学生,并且正在寻找一种将多个现有学生添加到不同班级的快速方法。

【问题讨论】:

  • 没办法。经过非常连续的谷歌搜索后我没有找到解决方案......

标签: python django django-models django-admin


【解决方案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)
    

    【讨论】:

      猜你喜欢
      • 2010-12-20
      • 2011-12-31
      • 2013-03-17
      • 2020-04-16
      • 2016-02-08
      • 2012-06-09
      • 1970-01-01
      • 2018-11-13
      • 2013-11-17
      相关资源
      最近更新 更多