Django 指定现有多个选项之一的最佳方法“;“通过表格”;关系?

Django 指定现有多个选项之一的最佳方法“;“通过表格”;关系?,django,django-models,Django,Django Models,我有以下图书馆数据库模型: class Author(models.Model): ... class Task(models.Model): ... # detailed explanation of how the author collaborated in the book class BookTask(models.Model): book = models.ForeignKey(Book) author = models.ForeignKey(Author)

我有以下图书馆数据库模型:

class Author(models.Model):
  ...

class Task(models.Model):
  ...  # detailed explanation of how the author collaborated in the book

class BookTask(models.Model):
  book = models.ForeignKey(Book)
  author = models.ForeignKey(Author)
  task = models.ForeignKey(Task)

class Book(models.Model):
  authors = models.ManyToManyField(Author, through='BookTask'...)
那里的一切都很好,但我想指定一个现有的BookTask关系作为主要关系。想想一本书,其中有三位作者在其中工作过。我想把这三个都分配给这本书,然后把其中的一个作为主要的一个

我试过这个:

class Book(models.Model):
  authors = models.ManyToManyField(Author, through='BookTask'...)
  author_main = models.ForeignKey(BookTask...)
但是生成的管理员网页不会显示
author\u main
字段的预期选择小部件。有什么想法吗


(注意:我当前的解决方案是在
BookTask
模型中添加一个布尔字段,以指定哪个字段是主字段,并通过表单验证来控制是否为一本书选择了其中一个字段,并且只选择了其中一个。这是可行的,但可能有一个更优雅的解决方案).

作者/书籍与任务之间的关系是1:1吗?如果是,那么从数据库管理的角度来看,添加布尔字段的解决方案是最佳的。任何其他外键(如author_main)都会破坏数据库模式

如果关系是一对多任务(一个作者可以在同一本书上有多个任务),我会这样做:

class AuthorBook(models.Model):
  book = models.ForeignKey(Book)
  author = models.ForeignKey(Author)
  primary_author = models.BooleanField()

class AuthorBookTask(models.Model):
  author_book = models.ForeignKey(AuthorBook)
  task = models.ForeignKey(Task)
这样,您可以首先指定作者身份,然后将任务映射到该作者身份

不管上述内容如何,您还需要一些规则来指定每本书只能有一个主要作者。以下SQL查询应返回小于或等于1的结果:

select sum(primary_author)
from AuthorBook
group by book
希望这有助于……

您的解决方案在大多数方面都足够“优雅”。可能让你质疑解决方案的部分是必须在管理员级别对关系数据进行验证,不是吗

那样的话,你是对的。有一种简单的方法可以将其转移到数据层,并在那里强制实现一致性,而不是在管理验证级别

使用您的示例,您可以强制执行数据约束,如下所示:

class BookTask(models.Model):    
    book = models.ForeignKey(Book)
    author = models.ForeignKey(Author)
    task = models.ForeignKey(Task)
    main = models.BooleanField(default=False)

    def save(self, *args, **kwargs):
        if self.main:
            self.__class__._default_manager.filter(book=self.book, main=True).update(main=False)
        super(BookTask, self).save(*args, **kwargs)

这将基本上“切换”关闭该
书籍
的所有其他
书籍任务的main。这将有效地为数据库中的每本
保留一个“main”
BookTask

我认为您的解决方案足够优雅:)这绝对是一种很好的数据库数据结构方法。也许在前端,您可以用不同的方式表示它,但我会坚持使用DB中的布尔字段。不过需要注意的是:始终检查后端的数据一致性!(根据您的描述,不清楚您在哪里检查,所以我只想指出:))使用序列如何?您可以使用订购
BookTask
,然后使用
book.set\u BookTask\u order
将主任务设置为第一个。要验证的任务被延迟到Django的排序引擎。缺点是每次设置订单时都需要指定booktask id列表。依我看,你可以试着只设置一个main的ID,把其余的设置成obmitting,看看Django是否按照你想要的方式对它们进行排序。