You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django如何关联查询两个模型并保留与父模型的关联?

问题需求

我有一个父模型Profile,以及另外两个模型Courses和Course_student,模型定义如下:

class Profile(models.Model):
    ...
    first_name = models.CharField(verbose_name=_('First Name'), max_length=255, null=True, blank=True)
    middle_name = models.CharField(verbose_name=_('Middle Name'), max_length=255, null=True, blank=True)
    last_name = models.CharField(verbose_name=_('Last Name'), max_length=255, null=True, blank=True)
    ...


class Courses(models.Model):
    YESNO = (
        ('Yes', 'Yes'),
        ('No', 'No'),
    )

    profile                     = models.ManyToManyField('Profile', related_name='course_profile', verbose_name=_('Profile'))
    course_taken                = models.CharField(verbose_name=_('Course'), max_length=255)
    name_of_school              = models.CharField(verbose_name=_('Name of School'), max_length=255)
    school_location             = models.CharField(verbose_name=_('School Location'), max_length=255)
    period_of_attendance_from   = models.DateField(verbose_name=_('Period of Attendance (From)'), max_length=255)
    period_of_attendance_to     = models.DateField(verbose_name=_('Period of Attendance (To)'), max_length=255)
    nr_students                 = models.IntegerField(verbose_name=_('Total Number of Students'), null=True, blank=True)
    created_on                  = models.DateTimeField(auto_now_add=True)
    modified_on                 = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name = _('Course')
        verbose_name_plural = _('Course')

    def __str__(self):
        return '%s' % (self.course_taken)


class Course_student(models.Model):
    YESNO = (
        ('Yes', 'Yes'),
        ('No', 'No'),
    )

    course = models.ForeignKey('Courses', related_name='course_student_details', on_delete=models.DO_NOTHING, verbose_name=_('Course'), blank=True, null=True)
    profile = models.ForeignKey('Profile', related_name='course_student_profile', on_delete=models.DO_NOTHING, verbose_name=_('Profile'), blank=True, null=True)
    standing = models.IntegerField(verbose_name=_('Standing'), null=True, blank=True)
    grade = models.CharField(verbose_name=_('Grade'), max_length=255, null=True, blank=True)
    completed = models.CharField(verbose_name=_('Completed'), choices=YESNO, max_length=255)

    class Meta:
        verbose_name = _('Student Details')
        verbose_name_plural = _('Student Details')

    def __str__(self):
        return '%s' % (self.course)

admin.py代码如下:

class CourseUploaderInline(admin.StackedInline):
    model = CourseUploader
    extra = 1

class CourseStudentInline(admin.StackedInline):
    model = Course_student
    extra = 1

@admin.register(Courses)
class CoursesAdmin(admin.ModelAdmin):
     inlines = [Course_studentInline, CorseUploaderInline,]

模型关联逻辑:

  • Course_student通过ForeignKey关联Courses和Profile,用来存储特定学生在特定课程中的成绩、完成状态等详情;
  • Courses通过ManyToMany关联Profile,用来标记学生修读的课程;
  • Profile存储学生基础信息。

我想实现的是:通过Profile的ID过滤,合并查询Courses和Course_student的数据,当前在view.py中尝试的代码如下:

def profile(request, pk):
    profile = Profile.objects.filter(profile=pk)
    course = Course.objects.filter(profile=pk) # 我试过在这里用select_related()

注:我是Django和Python新手,欢迎大家给出解决方案和优化建议:)


解决方案与优化建议

1. 修正基础查询错误

你的view代码存在两个明显问题:

  • Profile.objects.filter(profile=pk) 字段名错误,应该用pk而非profile,如果确定ID存在可以用get(pk=pk),不确定则用filter(pk=pk);
  • Course.objects.filter(profile=pk) 多对多关系过滤需用profile__pk=pk,或者利用反向关联查询。

2. 合并查询Courses和Course_student数据

方式一:从Profile实例出发关联查询

先获取指定Profile,再通过反向关联拿到所有课程,同时预加载对应的学生详情,避免多次数据库查询:

def profile(request, pk):
    # 获取指定学生的Profile实例
    profile = Profile.objects.get(pk=pk)
    # 预加载该学生修读的所有课程,以及每个课程对应的个人详情
    courses = profile.course_profile.prefetch_related('course_student_details')
    
    # 遍历课程和个人详情示例
    for course in courses:
        # 取出当前学生在该课程的详情(一个学生对应一门课程只有一条记录)
        student_detail = course.course_student_details.filter(profile=profile).first()
        # 可以直接访问课程和详情的字段
        print(course.course_taken, student_detail.grade, student_detail.completed)
    
    # 传递数据到模板
    return render(request, 'profile_detail.html', {'profile': profile, 'courses': courses})

方式二:直接从Course_student出发关联查询

如果只需要学生的课程详情数据,直接查询Course_student并关联Courses和Profile更高效:

def profile(request, pk):
    # 关联查询学生的所有课程详情,同时加载对应的课程和个人信息
    student_course_details = Course_student.objects.filter(profile__pk=pk).select_related('course', 'profile')
    
    # 遍历示例
    for detail in student_course_details:
        print(detail.profile.first_name, detail.course.course_taken, detail.grade)
    
    return render(request, 'profile_detail.html', {'details': student_course_details})

3. 模型结构优化建议

当前模型存在冗余:Courses与Profile的多对多关系,已经被Course_student表覆盖(Course_student本身就是关联课程和学生的中间表,还额外存储了成绩等信息)。建议去掉Courses中的profile多对多字段,避免数据不一致问题。

修改后的Courses模型:

class Courses(models.Model):
    YESNO = (
        ('Yes', 'Yes'),
        ('No', 'No'),
    )

    # 移除原有的多对多字段
    course_taken                = models.CharField(verbose_name=_('Course'), max_length=255)
    name_of_school              = models.CharField(verbose_name=_('Name of School'), max_length=255)
    school_location             = models.CharField(verbose_name=_('School Location'), max_length=255)
    period_of_attendance_from   = models.DateField(verbose_name=_('Period of Attendance (From)'))
    # 注意:DateField不需要max_length参数,原写法有误
    period_of_attendance_to     = models.DateField(verbose_name=_('Period of Attendance (To)'))
    nr_students                 = models.IntegerField(verbose_name=_('Total Number of Students'), null=True, blank=True)
    created_on                  = models.DateTimeField(auto_now_add=True)
    modified_on                 = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name = _('Course')
        verbose_name_plural = _('Courses')  # 复数改为Courses更符合规范

    def __str__(self):
        return self.course_taken

优化后,查询学生课程信息仅需通过Course_student关联即可,逻辑更清晰,无重复数据。

4. Admin代码修正

你的CoursesAdmin存在拼写错误:CorseUploaderInline应为CourseUploaderInline,修正后:

class CourseUploaderInline(admin.StackedInline):
    model = CourseUploader
    extra = 1

class CourseStudentInline(admin.StackedInline):
    model = Course_student
    extra = 1

@admin.register(Courses)
class CoursesAdmin(admin.ModelAdmin):
     inlines = [CourseStudentInline, CourseUploaderInline,]

内容的提问来源于stack exchange,提问作者Markmeplease

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 10:10:31