Django应用存在循环依赖,导致外键模型无法迁移

0 投票
1 回答
29 浏览
提问于 2025-04-14 15:56

我有两个Django应用,它们之间互相有外键关系。

一个是api应用,另一个叫blog

这是我blog应用的models.py文件:

class Post(models.Model):

    uuid = models.UUIDField(default=uuid.uuid4, primary_key=False, unique=True)
    title = models.CharField(max_length=600)
    authors = models.ManyToManyField('Author', related_name='authors')
    tags = models.ManyToManyField('Tag', related_name='tags')
    date_published = models.DateField(auto_now_add=True)

    # store table of contents as JSON
    table_of_contents = models.TextField(default=dict)

    # s3 link to thumbnail
    thumbnail = models.URLField(max_length=300)

    # each post belongs to a blog
    blog = models.ForeignKey('api.Blog', on_delete=models.CASCADE, default=0)

    slug = models.SlugField(max_length=100, default='')

    # store markdown as text
    content = models.TextField(default='')

    # link to post
    url = models.URLField(default='')


    # for json serialization
    def as_dict(self):
        return {
            "title": self.title,
            "slug": self.slug,
            "authors": [ author.as_dict() for author in self.authors.all() ],
            "tags": [ tag.as_dict() for tag in self.tags.all() ],
            "date_published": "01/01/2001",  
            "table_of_contents": self.table_of_contents,
            "thumbnail": self.thumbnail,
            "id": str(self.uuid),
        }
    
    def __str___(self):
        return self.title


class Author(models.Model):
    name = models.CharField(max_length=100)
    blog = models.ForeignKey('api.Blog', on_delete=models.CASCADE, default=0)


    def as_dict(self):
        return {
            "name": self.name,
            "profile": self.name
        }

    def __str__(self):
        return self.name  

class Tag(models.Model):
    name = models.CharField(max_length=50)
    blog = models.ForeignKey('api.Blog', on_delete=models.CASCADE, default=0)

    def as_dict(self):
        return {
            "name": self.name
        }

    def __str__(self):
        return self.name    

这是我"api"应用的models.py文件:

class Blog(models.Model):

    # Foreign key to the associated tenant in case we need to changve schema_name
    tenant = models.ForeignKey('Client', on_delete=models.CASCADE, related_name="tenant_blog", default=0)

    class Templates(models.TextChoices):
        SAAS = 'SAAS'
        CHANGELOG = 'CHANGELOG'
        HELP = 'HELP'
        DEFAULT = 'DEFAULT'
        PERSONAL = 'PERSONAL'

    class CTAS(models.TextChoices):
        DEFAULT = 'DEFAULT'
        NEWSLETTER = 'NEWSLETTER'
        
    uuid = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=False)

    schema_name = models.CharField(default='', max_length=20)
    useCustomDomain = models.BooleanField(default=False)
    customDomain = models.CharField(max_length=100, default="")

    landing_header = models.CharField(max_length=100, default="")
    landing_subheader = models.CharField(max_length=100, default="")

    # choose template from the list
    template = models.CharField(max_length=18, choices=Templates.choices, default=Templates.DEFAULT)

    url = models.URLField(max_length=300, default='')
    name = models.CharField(max_length=100, default='')
    owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, related_name='blogs')

    posts = models.ManyToManyField('blog.Post', related_name='posts')
    featuredPosts = models.ManyToManyField('blog.Post', related_name='featured')
    authors = models.ManyToManyField('blog.Author', related_name="blog_authors")
    tags = models.ManyToManyField("blog.Tag", related_name="blog_tags")

    # call to action (newsletter or sign up)
    ctaType = models.CharField(max_length=50, choices=CTAS.choices, default=CTAS.DEFAULT)

    # display options
    defaultModeLight = models.BooleanField(default=True)
    showThemeToggle = models.BooleanField(default=True)
    showSpotlight = models.BooleanField(default=True)
    showFooter = models.BooleanField(default=True)
    showPoweredByBadge = models.BooleanField(default=True)
    showCTA = models.BooleanField(default=True)

    page_views_used = models.IntegerField(default=0)


    # Is the blog up/accesible to the public?
    isActive = models.BooleanField(default=True)

你可以看到这两个应用都有外键,互相依赖,这就造成了循环依赖的问题。

当我运行makemigrationsmigrate时,出现了relation "blog_author" does not exist的错误。

我已经尝试过只删除外键字段,进行迁移,然后再加回来并重新迁移,但还是遇到同样的relation "blog_author" does not exist错误……

我该怎么做才能让Django顺利迁移,而不出现这个错误呢?

1 个回答

1

回答你的问题:

当你在一个对象上定义了一个 ForeignKey 关系时,Django 会自动创建一种方法,让你可以从其他模型访问这些关系。因为你在 PostAuthorTag 上定义了 ForeignKey,所以你不需要在 Blog 上再定义 ManyToMany 关系。

如果你有一个 Blog 对象,你可以通过 blog.[模型名称]_set 来访问与这个博客相关的帖子、作者和标签。例如,要访问与某个博客相关的所有帖子,你可以使用 blog.author_set.filter...

如果你想给这些属性指定名称,可以在 ForeignKey 字段中使用 related_name 字段。

class Author(models.Model):
    blog = models.ForeignKey('api.Blog', on_delete=models.CASCADE, default=0, related_name="authors")

这样的话,如果你有博客对象,就可以直接使用 blog.authors.filter...

一些与问题无关的小建议:

  1. 与其给 ForeignKey 设置默认值为 0,不如让这个字段可以为空:
blog = models.ForeignKey('api.Blog', on_delete=models.CASCADE, null=True, related_name="authors")

或者,你可以同时去掉 nulldefault,这样当你尝试添加没有博客的作者/帖子/标签时,就会出现错误(如果每个作者/帖子/标签都必须关联一个博客的话)。

  1. 你在帖子、标签和作者之间的 ManyToMany 关系中的 related_name 字段设置是错误的。related_name 是用来从 另一个 模型访问这个关系的。所以,不应该是:
class Post(models.Model):
   tags = models.ManyToManyField('Tag', related_name='tags')

而应该是:

class Post(models.Model):
   tags = models.ManyToManyField('Tag', related_name='posts')  # This means if you have a `Tag` object, you can do `tag.posts` to get all the associated `Post`s
  1. 关于你的属性名称,我建议要么全部使用蛇形命名(snake case),要么全部使用驼峰命名(camel case),而不是两者混合(你现在就是这样)。我更喜欢使用蛇形命名,因为大多数 Django/Python 的属性都是这样。

  2. 对于你的 table_of_contents JSON 属性,应该使用 JSONField(可以在 这里 阅读更多)而不是 TextField

撰写回答