Django get_model() 模型没有属性 'objects'

Django get_model() model has no attribute 'objects'

我正在写一个迁移来更新数据,我像文档描述的那样使用 get_model() 来获取模型 class,如下所示:

from django.db import migrations

def update_student(apps, schema_editor):
    # We can't import the model directly as it may be a newer
    # version than this migration expects. We use the historical version.
    CourseClass = apps.get_model('backend', 'CourseClass')
    from pprint import pprint
    pprint(vars(CourseClass))
    for course_class in CourseClass.objects.all():
        if course_class.course:
            if course_class.course.student:
                course_class.students.add(course_class.course.student)
    
    Course = apps.get_model('backend', 'Course')
    for course in Course.objects.all():
        if course.student:
            course.students.add(course.student)

class Migration(migrations.Migration):

    dependencies = [
        ('backend', '0199_auto_20211027_1026'),
    ]

    operations = [
        migrations.RunPython(update_student, migrations.RunPython.noop)
    ]

这是我的模型和一些自定义管理器:

class CourseClass(models.Model):
    class Meta:
        db_table = 'classes'
        verbose_name = _('Class')
        verbose_name_plural = _('Classes')
    
    student_manager = StudentManager()
    teacher_manager = TeacherManager()
    objects = models.Manager()
    # other fields

当 运行 我的迁移时,我收到以下错误:

  File "/Users/admin/Library/Caches/pypoetry/virtualenvs/base.django-AnqsdXoZ-py3.8/lib/python3.8/site-packages/django/db/migrations/operations/special.py", line 190, in database_forwards
    self.code(from_state.apps, schema_editor)
  File "/Users/admin/Documents/git/icts/vietphil.hoola/backend/migrations/0200_update_student.py", line 13, in update_student
    for course_class in CourseClass.objects.all():
AttributeError: type object 'CourseClass' has no attribute 'objects

使用 pprint return 打印 CourseClass 的属性如下:

mappingproxy({'DoesNotExist': <class '__fake__.CourseClass.DoesNotExist'>,
              'MultipleObjectsReturned': <class '__fake__.CourseClass.MultipleObjectsReturned'>,
              '__doc__': 'CourseClass(id, name, date, time, duration, course, '
                         'substitute_teacher, delay_from, status, over, '
                         'class_type, canceled_by, note, teacher, '
                         'datetimestart, real_duration, screen_share, '
                         'time_end, time_start, extra_course)',
              '__module__': '__fake__',
              '_meta': <Options for CourseClass>,
              'assignment_class': <django.db.models.fields.related_descriptors.ReverseOneToOneDescriptor object at 0x112f1ce50>,
              'assignmentfile_set': <django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor object at 0x112eced00>,
              'canceled_by': <django.db.models.fields.related_descriptors.ForwardManyToOneDescriptor object at 0x112f1c250>,
              'canceled_by_id': <django.db.models.fields.related_descriptors.ForeignKeyDeferredAttribute object at 0x112f1c1f0>,
              'class_type': <django.db.models.query_utils.DeferredAttribute object at 0x112f1c130>,
              'classes': <django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor object at 0x112fcd1f0>,
              'classproblem_set': <django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor object at 0x112f1cdf0>,
              'course': <django.db.models.fields.related_descriptors.ForwardManyToOneDescriptor object at 0x112f13ca0>,
              'course_id': <django.db.models.fields.related_descriptors.ForeignKeyDeferredAttribute object at 0x112f13c40>,
              'date': <django.db.models.query_utils.DeferredAttribute object at 0x112f13ac0>,
              'datetimestart': <django.db.models.query_utils.DeferredAttribute object at 0x112f1c4f0>,
              'delay_from': <django.db.models.query_utils.DeferredAttribute object at 0x112f13ee0>,
              'duration': <django.db.models.query_utils.DeferredAttribute object at 0x112f13b80>,
              'eps': <django.db.models.fields.related_descriptors.ManyToManyDescriptor object at 0x112f1ca30>,
              'esl_feedback': <django.db.models.fields.related_descriptors.ReverseOneToOneDescriptor object at 0x112f599a0>,
              'exam_class': <django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor object at 0x112fc6610>,
              'extra_course': <django.db.models.fields.related_descriptors.ForwardManyToOneDescriptor object at 0x112f1c8e0>,
              'extra_course_id': <django.db.models.fields.related_descriptors.ForeignKeyDeferredAttribute object at 0x112f1c880>,
              'feedback': <django.db.models.fields.related_descriptors.ReverseOneToOneDescriptor object at 0x112f4eaf0>,
              'get_class_type_display': functools.partialmethod(<function Model._get_FIELD_display at 0x1032f14c0>, , field=<django.db.models.fields.CharField: class_type>),
              'homework_set': <django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor object at 0x112f415b0>,
              'id': <django.db.models.query_utils.DeferredAttribute object at 0x112f13a00>,
              'ielts_feedback': <django.db.models.fields.related_descriptors.ReverseOneToOneDescriptor object at 0x112f5f790>,
              'lesson': <django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor object at 0x112fe7a00>,
              'name': <django.db.models.query_utils.DeferredAttribute object at 0x112f13a60>,
              'note': <django.db.models.query_utils.DeferredAttribute object at 0x112f1c340>,
              'over': <django.db.models.query_utils.DeferredAttribute object at 0x112f1c0d0>,
              'rate': <django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor object at 0x112f482b0>,
              'real_duration': <django.db.models.query_utils.DeferredAttribute object at 0x112f1c550>,
              'screen_share': <django.db.models.query_utils.DeferredAttribute object at 0x112f1c5b0>,
              'status': <django.db.models.fields.related_descriptors.ForwardManyToOneDescriptor object at 0x112f13fa0>,
              'status_id': <django.db.models.fields.related_descriptors.ForeignKeyDeferredAttribute object at 0x112f13f40>,
              'student_manager': <django.db.models.manager.ManagerDescriptor object at 0x112f1c9d0>,
              'students': <django.db.models.fields.related_descriptors.ManyToManyDescriptor object at 0x112f1c670>,
              'substitute_teacher': <django.db.models.fields.related_descriptors.ForwardManyToOneDescriptor object at 0x112f13df0>,
              'substitute_teacher_id': <django.db.models.fields.related_descriptors.ForeignKeyDeferredAttribute object at 0x112f13d90>,
              'teacher': <django.db.models.fields.related_descriptors.ForwardManyToOneDescriptor object at 0x112f1c400>,
              'teacher_id': <django.db.models.fields.related_descriptors.ForeignKeyDeferredAttribute object at 0x112f1c3a0>,
              'time': <django.db.models.query_utils.DeferredAttribute object at 0x112f13b20>,
              'time_end': <django.db.models.query_utils.DeferredAttribute object at 0x112f1c610>,
              'time_start': <django.db.models.query_utils.DeferredAttribute object at 0x112f1c7f0>,
              'video': <django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor object at 0x112f5ff40>})

如您所见,没有 objects 属性,我尝试使用其他带有 get_model() 的模型,它具有 objects 属性。为什么 CourseClass 是唯一缺少 objects 属性的课程?

我在自定义迁移过程中遇到过类似的问题。我认为原因之一是 get_model 返回的 class 有时不是完整的模型 class 您可以通过正确导入它获得。但是,get_model 调用对于确保在迁移时正确加载模型是必要的。这是因为在迁移时,模型应该代表上次迁移后的模型状态。如果管理器是稍后定义的,它还不会存在一个对我们有用的解决方法:

def update_student(apps, schema_editor):
    _ = apps.get_model('backend', 'CourseClass')  
    # makes sure model is loaded
    from path.to.backend.models import CourseClass
    # gives you the proper class with all utils
    
    # do your thang

TLDR:如果您想在迁移文件中进行查询,请在您的自定义管理器中添加 use_in_migrations = True

经过几个小时的搜索,我在关于模型管理器和迁移的文档中找到了这一部分

https://docs.djangoproject.com/en/3.2/topics/migrations/#model-managers

为了在迁移中使用自定义管理器,use_in_migrations = True 需要在管理器中设置,所以我将我的管理器更改为以下内容:

class StudentManager(models.Manager):
    use_in_migrations = True
    def student_query(self, student):
        return super(StudentManager, self).get_queryset().filter(
            Q(students=student) | Q(course__students=student
        )).distinct('id')


class TeacherManager(models.Manager):
    use_in_migrations = True
    def teacher_query(self, teacher):
        return super(TeacherManager, self).get_queryset().filter(
            Q(teacher=teacher) | Q(course__teacher=teacher)|
            Q(substitute_teacher=teacher)).distinct('id')

class MainCourseClassManager(models.Manager):
    use_in_migrations = True
    
class CourseClass(models.Model):
    class Meta:
        db_table = 'classes'
        verbose_name = _('Class')
        verbose_name_plural = _('Classes')
    
    objects = MainCourseClassManager()
    student_manager = StudentManager()
    teacher_manager = TeacherManager()

现在我的迁移可以使用 get_model()

也从文档中注意到

因为无法序列化任意 Python 代码,所以这些历史模型不会有您定义的任何自定义方法。但是,它们将具有相同的字段、关系、managers (limited to those with use_in_migrations = True) 和元选项(也是版本化的,因此它们可能与您当前的不同)。

解决方案应该更多 migrations.AlterModelManagers。

你必须在你的迁移中有这个:

migrations.AlterModelManagers(
    name='entity',
    managers=[
        'objects', django.db.models.manager.Manager()),
    ],
),

这里解释一下:https://github.com/django-mptt/django-mptt/issues/489#issuecomment-243128053