Приложения Django имеют круговую зависимость, из-за чего модели с внешними ключами не мигрируют

У меня есть два приложения django, которые имеют внешние ключи друг к другу

Приложение api и другое приложение 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    

И мой models.py для моего "api" приложения

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)

Как вы можете видеть, оба приложения имеют внешние ключи, которые зависят друг от друга, что приводит к круговой зависимости.

Когда я запускаю makemigrations и migrate, я получаю relation "blog_author" does not exist

Я уже пробовал удалять только поля внешнего ключа, делать миграции, добавлять их обратно и снова делать миграции. Но я получаю ту же самую ошибку relation "blog_author" does not exist...

Как заставить Django мигрировать, не сталкиваясь с этой ошибкой?

Ответ на ваш вопрос:

Когда вы определяете отношения ForeignKey на объекте, Django автоматически создаст способ доступа к этим отношениям из другой вашей модели. Поскольку вы определяете ForeignKey на Post, Author и Tag, вам не нужно определять отношения ManyToMany из Blog.

Если у вас есть объект Blog, вы можете получить доступ к записям, авторам и тегам, связанным с этим блогом, выполнив команду: blog.[modelname]_set. Например, чтобы получить доступ ко всем постам, связанным с записью в блоге, вы можете выполнить команду blog.author_set.filter....

Если вы хотите указать имена этих свойств, вы можете использовать поле related_name для поля ForeignKey.

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

Таким образом, теперь вы можете сделать blog.authors.filter..., если у вас есть объект blog.

Несколько советов, которые не относятся к вашему вопросу:

  1. Вместо того, чтобы устанавливать значение по умолчанию 0 для ForeignKey, было бы гораздо лучше просто разрешить этому полю быть нулевым:
blog = models.ForeignKey('api.Blog', on_delete=models.CASCADE, null=True, related_name="authors")

Либо удалите null и default так, чтобы при попытке добавить автора/пост/тег без блога вы получали ошибку (если каждый автор/пост/тег должен всегда иметь связанный с ним блог).

  1. Ваше поле related_name о ManyToMany отношениях между постами и тегами неверно. 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. Для имен свойств я бы выбрал либо полностью змеиный, либо полностью верблюжий регистр, а не смесь обоих (как у вас сейчас). Я предпочитаю использовать змеиный регистр, так как большинство свойств Django/Python имеют именно такой вид.

  2. Для вашего table_of_contents JSON свойства, вы должны использовать JSONField (подробнее здесь) вместо TextField

Вернуться на верх