【问题标题】:Auto-creating related objects on model creation in Django在 Django 中创建模型时自动创建相关对象
【发布时间】:2013-09-22 08:12:50
【问题描述】:

我是 Django 新手,请原谅我的无知 :)

假设我有一个具有几个外键关系的模型,当我创建模型的实例时,我希望它也自动为外键对象生成新实例。在这种情况下,我将课程注册建模为一个组,并将特定组引用为模型上的外键。

class Course(models.Model):
    student_group = models.OneToOneField(Group, related_name="course_taken")
    teacher_group = models.OneToOneField(Group, related_name="course_taught")

    def clean(self):
        if self.id:
            try:
                self.student_group
            except Group.DoesNotExist:
                self.student_group, _ = Group.objects.get_or_create(name='_course_' + self.id + '_student')

            try:
                self.teacher_group
            except Group.DoesNotExist:
                self.teacher_group, _ = Group.objects.get_or_create(name='_course_' + self.id + '_teacher')

似乎我可以使用模型的 clean 方法来执行此操作,但我希望能够将整个事情包装在一个事务中,这样如果稍后它无法创建课程,它不会创建相关的 Group 对象。有没有办法实现这一目标?

另外,我在这里做错了吗? Django 是否提供了更好的方法来做到这一点?

【问题讨论】:

标签: python django django-models


【解决方案1】:

您可以使用models.signals.post_save 信号来处理这种情况:

from django.db import models

class Course(models.Model):
    student_group = models.OneToOneField(Group, related_name="course_taken")
    teacher_group = models.OneToOneField(Group, related_name="course_taught")


def create_course_groups(instance, created, raw, **kwargs):
    # Ignore fixtures and saves for existing courses.
    if not created or raw:
        return

    if not instance.student_group_id:
        group, _ = Group.objects.get_or_create(name='_course_' + self.id + '_student')
        instance.student_group = group

    if not instance.teacher_group_id:
        teacher_group, _ = Group.objects.get_or_create(name='_course_' + self.id + '_teacher')
        instance.teacher_group = teacher_group

    instance.save()

models.signals.post_save.connect(create_course_groups, sender=Course, dispatch_uid='create_course_groups')

【讨论】:

  • 调用 instance.save 是否会触发另一个 post_save 信号再次调用该函数?
  • 我认为这也要求字段可以为空。与关闭事务自动提交相结合的 pre_save 信号有什么我可以做的吗?
  • created 对于已创建的对象,关键字设置为 True,并且由于回调是在 db save 之后运行的,因此该对象已经创建。您可以将代码移动到pre_save,但您必须使用try: except: 子句来检查组并处理异常。由于您正在创建具有课程的 one2one 组,因此我认为在 post_save 中根本不需要检查。
  • 我最终在 pre_save 中进行了验证,并启用了 Transactions 中间件将每个请求包装在单个事务中,因为我不想使字段可以为空。但是您的回答在其他方面很有帮助。
  • 我认为当你想确保保存过程的原子性时,这种方法效果不佳。您可以将save 包装在atomic 装饰器中,但它有两个问题。首先,atomic 装饰器为何存在并不明显。其次,当您有另一个信号处理程序时,例如发送一封电子邮件,并且如果此处理程序引发异常,您的整个事务将被回滚,这可能不是您想要的。
【解决方案2】:

最终我选择了:

from django.db import models, transaction
class Course(models.Model):
    student_group = models.OneToOneField(Group, related_name="course_taken")

    @transaction.commit_on_success
    def save(self, *args, **kwargs):
        if not self.student_group_id:
            self.student_group, _ = Group.objects.get_or_create(name='_course_' + self.id + '_student')

        super(Course, self).save(*args, **kwargs)

编辑(2014 年 12 月 1 日):@Shasanoglu 是正确的,由于 id 尚不存在,上述代码实际上无法正常工作。您必须在调用 save 之后创建相关对象(因此您调用 super.save,创建相关对象,更新此对象并再次调用 super.save - 不理想。或者您省略id 来自组名称,这很好)。但最终,我将自动相关对象的创建完全移出模型。我在一个更简洁的自定义表单的 save 方法中完成了这一切,并放弃了在管理界面中使用这个模型(这就是为什么我坚持首先在模型方法中做所有这些)

【讨论】:

  • 我有类似的问题,我正在尝试用表单来解决它。您可以链接到您的表单答案吗?我知道这是 4 年前的事了,但也许你对 my question 有一些答案。
  • AttributeError: 'module' object has no attribute 'commit_on_success'
【解决方案3】:

我在 Django 1.7 中的一个类似问题中使用了 wjin 的解决方案。我只需要进行 2 处更改:

  1. 必须将commit_on_success 更改为atomic
  2. self.id 不起作用,因为代码在创建新对象时设置 id 之前运行。我必须使用其他名称作为组名。

这是我最终做的:

from django.db import models
from django.contrib.auth.models import Group

class Audit(models.Model):

    @transaction.atomic
    def save(self, *args, **kwargs):
        if not hasattr(self,"reAssessmentTeam"):
            self.reAssessmentTeam, _ = Group.objects.get_or_create(name='_audit_{}_{}'.format(self.project.id,self.name))

        super(Audit, self).save(*args, **kwargs)

    project = models.ForeignKey(Project, related_name = 'audits')
    name = models.CharField(max_length=100)
    reAssessmentTeam = models.OneToOneField(Group)

我知道如果名称太长或有人设法使用相同的名称,这可能会导致问题,但我会在以后处理这些问题。

【讨论】:

    【解决方案4】:

    https://chris-lamb.co.uk/projects/django-auto-one-to-one 上查看我的项目,它可以在创建父类时自动创建子模型实例。

    例如,给定以下模型定义:

    from django.db import models
    from django_auto_one_to_one import AutoOneToOneModel
    
    class Parent(models.Model):
        field_a = models.IntegerField(default=1)
    
    class Child(AutoOneToOneModel(Parent)):
        field_b = models.IntegerField(default=2)
    

    ... 创建一个Parent 实例会自动创建一个相关的 Child 实例:

    >>> p = Parent.objects.create()
    >>> p.child
    <Child: parent=assd>
    >>> p.child.field_b
    2
    

    在创建User 实例时,为创建实例的常见情况提供了一个PerUserData 帮助器。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-11
      • 2015-05-20
      • 2017-10-08
      • 2016-08-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多