【问题标题】:unique_together with a field from a foreign key in a through table for a ManyToMany relationunique_together 与多对多关系的直通表中的外键字段
【发布时间】:2018-06-15 07:41:56
【问题描述】:

我正在开发一个 Django 2.0 项目应用程序。它有一个(不工作的)models.py 文件,看起来像这样:

from django.db import models
from django.utils import timezone


class Computer(models.Model):
    name = models.CharField(max_length=25)

    def __str__(self):
        return "Computer {}".format(self.name)


class Software(models.Model):
    name = models.CharField(max_length=25)
    description = models.CharField(max_length=1024, blank=True)

    def __str__(self):
        return self.name


class SoftwareVersion(models.Model):
    software = models.ForeignKey(Software, on_delete=models.CASCADE, related_name="versions")
    version = models.CharField(max_length=100)
    released_at = models.DateTimeField(default=timezone.now)

    def __str__(self):
        return "{} {}".format(self.software, self.version)


class ComputerSoftwareBundle(models.Model):
    computer = models.ForeignKey(Computer, on_delete=models.CASCADE, related_name="bundles")
    installed_at = models.DateTimeField(default=timezone.now)
    versions = models.ManyToManyField(SoftwareVersion, through="BundleSoftwareVersion", related_name="bundles")


class BundleSoftwareVersion(models.Model):
    bundle = models.ForeignKey(ComputerSoftwareBundle, on_delete=models.CASCADE)
    version = models.ForeignKey(SoftwareVersion, on_delete=models.CASCADE)

    class Meta:
        unique_together = (("bundle", "version__software"),)

该应用会跟踪当前或以前安装在计算机上的软件包。这里的问题是,捆绑软件不应包含多个版本的同一软件。此外,SoftwareVersion 应该包含对 Software 的引用,因为相同的版本字符串对于不同的软件具有不同的含义。

代码不像this Stackoverflow answer 中描述的那样工作。我留下了 unique_together 行来说明我想要实现的目标。

我试图通过覆盖 BundleSoftwareVersion 中的 save 和 validate_unique 方法来解决 Django 的这个限制(不能使用通过 unique_together 中的外键引用的字段),但效果并不好。这是我尝试过的实现:

class BundleSoftwareVersion(models.Model):
    bundle = models.ForeignKey(ComputerSoftwareBundle, on_delete=models.CASCADE)
    version = models.ForeignKey(SoftwareVersion, on_delete=models.CASCADE)

    def save(self, *args, **kwargs):
        self.validate_unique()
        super().save(*args, **kwargs)

    def validate_unique(self, exclude=None):
        super().validate_unique(exclude)

        bundle_versions = BundleSoftwareVersion.objects.filter(bundle=self.bundle,
                    version__software=self.version.software)
        count = len(bundle_versions)
        if not self.pk:
            # if this instance is not stored in the database,
            # we need to increment the count to take this instance
            # into account
            count += 1
        if count > 1:
            raise ValidationError("There already is an instance of software '{}' in this bundle.".format(self.version.software))

到目前为止,我已经通过管理站点试用了这些模型。检查在更改现有 ComputerSoftwareBundle 时起作用(管理站点在违规条目旁边显示一条消息),但添加会导致未捕获的异常。

有没有更好的方法来实现这种唯一性?

【问题讨论】:

    标签: python django python-3.x many-to-many django-orm


    【解决方案1】:

    我想出了一个解决方法:

    class BundleSoftwareVersion(models.Model):
        bundle = models.ForeignKey(ComputerSoftwareBundle, on_delete=models.CASCADE)
        version = models.ForeignKey(SoftwareVersion, on_delete=models.CASCADE)
        _software = models.ForeignKey(Software, on_delete=models.CASCADE, null=True, editable=False)
    
        class Meta:
            unique_together = (("bundle", "_software"),)
    
        def save(self, *args, **kwargs):
            self._software = self.version.software
            super().save(*args, **kwargs)
    

    如您所见,我现在有一个辅助字段 _software,它在 unique_together 中使用,每次保存时都会将 self.version.software 存储到其中。

    到目前为止,我遇到了这种方法的一个缺点:尝试保存包含重复软件实例的 ComputerSoftwareBundle 会导致显示 IntegrityError 的错误页面,而不是表单中的错误消息。

    我将不胜感激有关如何解决此缺点的建议,甚至是完全不同的方法的建议。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-01
      相关资源
      最近更新 更多