Django多对多添加功能不一致,可在控制台上运行

时间:2018-12-11 01:44:15

标签: django python-3.x

我正在创建课程计划工具。每个课程涵盖多个教学大纲点,并具有相关的资源。保存课程时,我要确保将课程的课程提要点也保存到资源中。

我尝试同时使用signals和重写save方法来使这项工作有效,但始终得到奇怪的结果。

models.py

class Lesson(models.Model):
    lessonslot = models.ForeignKey(TimetabledLesson, on_delete=models.CASCADE)
    classgroup = models.ForeignKey(ClassGroup, null=True, blank=False, on_delete=models.SET_NULL)
    status = models.CharField(max_length=20, null=True, blank=True)
    syllabus_points_covered = models.ManyToManyField(SyllabusPoint, blank=True)
    lesson_title = models.CharField(max_length=200, null=True, blank=True)
    description = models.TextField(null=True, blank=True)
    requirements = models.TextField(null=True, blank=True)
    sequence = models.IntegerField(null=False, blank=True)
    date = models.DateField(null=True, blank=True)
    syllabus = models.ForeignKey(Syllabus, blank=True, null=True, on_delete=models.SET_NULL)

    class Meta:
        unique_together = ( ("lessonslot", "date"),
                           ("classgroup", "sequence"))

class LessonResources(models.Model):
    lesson = models.ForeignKey(Lesson, blank=True, null=True, on_delete=models.SET_NULL)
    resource_type = models.CharField(max_length=100, choices=RESOURCE_TYPES, null=False, blank=False)
    resource_name = models.CharField(max_length=100, null=True, blank=False)
    link = models.URLField(blank=True, null=True)
    students_can_view_before = models.BooleanField()
    students_can_view_after = models.BooleanField()
    available_to_all_classgroups = models.BooleanField()
    syllabus_points = models.ManyToManyField(SyllabusPoint, blank=True)

    def set_syllabus_points(self):
        if self.lesson:
            points = self.lesson.syllabus_points_covered.all().order_by('pk')
            for point in points:
                self.syllabus_points.add(point)
            print ('In set_syllabus_points')
            print(self.syllabus_points.all())
        return self

signals.py

from timetable.models import LessonResources, Lesson
from django.db.models.signals import post_save, m2m_changed
from django.dispatch import receiver


@receiver(m2m_changed, sender=Lesson.syllabus_points_covered.through)
def post_update_lesson_syllabus_pts(sender, instance, **kwargs):
    """ After adding a syllabus point to a lesson, update its resources"""
    resources = instance.resources()
    for resource in resources:
        resource.set_syllabus_points()


@receiver(post_save, sender=LessonResources)
def update_resources(sender, instance, **kwargs):
""" After adding a resource, check its syllabus points match its lesson """
    print('Before set_syllabus_points')
    print(instance.pk)
    print(instance.syllabus_points.all())
    instance.set_syllabus_points()
    print('After set_syllabus_points')
    print(instance.syllabus_points.all())

控制台输出

Before set_syllabus_points
105
<QuerySet []>
After set_syllabus_points
<QuerySet [<SyllabusPoint: Motion and Measurement 1.1.1 Use and describe the use of rules and measuring cylinders to find a length or a volume>, <SyllabusPoint: Motion and Measurement 1.1.3 Obtain an average value for a small distance and for a short interval of time by measuring multiples (including the period of a pendulum)>, <SyllabusPoint: Motion and Measurement 1.1.3 Understand that a micrometer screw gauge is used to measure very small distances>, <SyllabusPoint: Motion and Measurement 1.2.1 Define speed>, <SyllabusPoint: Motion and Measurement 1.2.2 Calculate Average speed from total distance / total time>, <SyllabusPoint: Forces 1.3.1 Explain that In the absence of an unbalanced force, an object will either remain at rest or travel with a constant speed in a straight line. Unbalanced forces change motion.>]>

第一个(post_update_lesson_syllabus_pts)工作正常,但是在创建资源后将点添加到资源中是不可行的。

在调试器中,我可以看到在创建或修改新资源后正在调用resource.set_syllabus_points()。我还可以看到self.syllabus_points.add(point)传递了一个有效的教学大纲点point。但是,当我随后检查资源时(例如,在完成所有内容之后的print(resources.syllabus_points.all())时,我得到一个空的查询集!我正在管理界面中创建资源(使用附在课程中的内联表单集),而没有选择教学大纲点。

函数运行时,set_syllabus_points中的print语句输出正确的查询集-为什么它们不进入数据库?

在此先感谢您提供的所有帮助,我敢肯定我错过了一些愚蠢的事情。

更新1

好的,所以我认为我已经将其范围缩小到了管理界面中正在发生的事情。

我添加了上面显示的一些print语句以查看发生了什么。

这是运行交互式shell的输出:

>>>> resource = LessonResources.objects.get(pk=115)
>>>> resource.syllabus_points.all()
<QuerySet []>
>>>> resource.set_syllabus_points()
In set_syllabus_points
<QuerySet [<SyllabusPoint: Motion and Measurement 1.1.1 Use and describe the use of rules and measuring cylinders to find a length or a volume>, <SyllabusPoint: Motion and Measurement 1.1.3 Obtain an average value for a small distance and for a short interval of time by measuring multiples (including the period of a pendulum)>, <SyllabusPoint: Motion and Measurement 1.1.3 Understand that a micrometer screw gauge is used to measure very small distances>, <SyllabusPoint: Motion and Measurement 1.2.1 Define speed>, <SyllabusPoint: Motion and Measurement 1.2.2 Calculate Average speed from total distance / total time>, <SyllabusPoint: Forces 1.3.1 Explain that In the absence of an unbalanced force, an object will either remain at rest or travel with a constant speed in a straight line. Unbalanced forces change motion.>]>
>>>> resource.syllabus_points.all()
<QuerySet [<SyllabusPoint: Motion and Measurement 1.1.1 Use and describe the use of rules and measuring cylinders to find a length or a volume>, <SyllabusPoint: Motion and Measurement 1.1.3 Obtain an average value for a small distance and for a short interval of time by measuring multiples (including the period of a pendulum)>, <SyllabusPoint: Motion and Measurement 1.1.3 Understand that a micrometer screw gauge is used to measure very small distances>, <SyllabusPoint: Motion and Measurement 1.2.1 Define speed>, <SyllabusPoint: Motion and Measurement 1.2.2 Calculate Average speed from total distance / total time>, <SyllabusPoint: Forces 1.3.1 Explain that In the absence of an unbalanced force, an object will either remain at rest or travel with a constant speed in a straight line. Unbalanced forces change motion.>]>

由于创建或更新资源,set_syllabus_points()调用signals时,我们似乎也看到了正确的输出。

但是,即使我们的控制台日志显示它已添加了这些要点,但在加载管理界面时,它们似乎在某些时候又重新设置为空的查询集。可能是由于管理视图/表单中的某些内容缓存多对多关系并重新设置然后返回其初始值引起的吗?

更新2

怀疑,这绝对是modelAdmin的事情。 This StackOverflow postthis blog post解释了正在发生的事情-现在,我只需要解决如何覆盖tabularInLine表单的默认行为...

3 个答案:

答案 0 :(得分:0)

一旦您致电post_update_lesson_syllabus_pts,请在您的resource.save()中添加语句set_syllabus_points()。发布您应该能够看到您的资源的课程提要点。

答案 1 :(得分:0)

如果您所说的“我什么也没回来”的意思是您有一个看起来像这样的对象:

<django.db.models.fields.related_descriptors.create_forward_many_to_many_manager.<locals>.ManyRelatedManager object at 0x1111eb518>

那不是您想的那样。执行resource.syllabus_points时,您正在做的是通过ManyToMany管理器访问与LessonResources实例的相关 SyllabusPoints对象。如果您调用SyllabusPoints.objects,则会返回一个约束较少的类似对象。就像您使用任何其他模型管理器一样,您需要在该管理器上调用allgetfilter之类的东西。在此示例中,我手头仅有一个Blog应用程序,其中有一个blog.Post模型和一个blog.Tag的ManyToManyField。都是一样的

>>> from naguine.apps import get_model
>>> Post = get_model('blog.Post')
>>> Tag = get_model('blog.Tag')
>>> p = Post.objects.create()
>>> p.tags.add(Tag.objects.create(name='testtag'))
>>> p.tags
<django.db.models.fields.related_descriptors.create_forward_many_to_many_manager.<locals>.ManyRelatedManager object at 0x1111eb518>
>>> p.tags.all()
<QuerySet [<Tag: testtag>]>

答案 2 :(得分:0)

更新2 中所述,此问题是由Django管理界面处理多对多关系的方式引起的。

django管理界面正在使用TabularInline管理表单,因此我可以一次编辑课程及其相关资源;

admin.py

from django.contrib import admin
from timetable.models import *
from timetable.forms import LessonForm
# Register your models here.


class ResourcesInLine(admin.TabularInline):
    model = LessonResources

class LessonAdmin(admin.ModelAdmin):
    form = LessonForm
    inlines = [
        ResourcesInLine,
    ]


admin.site.register(LessonSlot)
admin.site.register(TimetabledLesson)
admin.site.register(Lesson, LessonAdmin)
admin.site.register(LessonResources)
admin.site.register(LessonSuspension)

当我保存管理表单时,发生的基本顺序是:

  1. 课程详细信息已保存。
  2. 已保存资源,而忽略了m2m关系。
  3. 保存资源触发了post-save的{​​{1}}信号
  4. 课程大纲点已添加到资源中。
  5. 如Django文档中所述,管理模块清除了所有m2m关系。
  6. 管理模块添加了每个大纲点,如管理界面所示。

问题是步骤5。当我保存set_syllabus_points()表单时,课程资源的lesson都是空白。该信号按预期方式工作并添加了它们,但是admin模块随后再次清除了它们,因为它的预期行为是使syllabus_points实例适合初始形式上的内容-没什么!

我对此的长期解决方案是在管理界面外构建自定义表单以自己完成此操作。由于这不会在多对多关系上调用LessonResoure方法,因此此问题将停止。

同时,我已经从管理界面中删除了clean,以创建新课程。这意味着不会调用syllabus_points方法,并解决了该问题。

修复 编辑管理界面以删除多对多关系,例如

clear

非常感谢@Cole和@Rajesh为我指出正确的方向!