Приложения 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.
Несколько советов, которые не относятся к вашему вопросу:
- Вместо того, чтобы устанавливать значение по умолчанию 0 для
ForeignKey
, было бы гораздо лучше просто разрешить этому полю быть нулевым:
blog = models.ForeignKey('api.Blog', on_delete=models.CASCADE, null=True, related_name="authors")
Либо удалите null
и default
так, чтобы при попытке добавить автора/пост/тег без блога вы получали ошибку (если каждый автор/пост/тег должен всегда иметь связанный с ним блог).
- Ваше поле
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
Для имен свойств я бы выбрал либо полностью змеиный, либо полностью верблюжий регистр, а не смесь обоих (как у вас сейчас). Я предпочитаю использовать змеиный регистр, так как большинство свойств Django/Python имеют именно такой вид.
Для вашего
table_of_contents
JSON свойства, вы должны использоватьJSONField
(подробнее здесь) вместоTextField