如何在Django中添加外键到现有类中

46qrfjad  于 2023-06-07  发布在  Go
关注(0)|答案(3)|浏览(558)

我有两个简单的乡村和城镇模型:

from django.db import models

class Country(models.Model):
    name = models.CharField(max_length=50)
    
class Town(models.Model):
    belongs_to = models.ForeignKey(country, on_delete=models.CASCADE)
    name = models.CharField(max_length=50)

现在我想玩一玩...我想加上大陆。我想从一开始正确的方法就是这样做:

from django.db import models

class Continent(models.Model):
    name = models.CharField(max_length=50)

class Country(models.Model):
    belongs_to = models.ForeignKey(continent, on_delete=models.CASCADE)
    name = models.CharField(max_length=50)
    
class Town(models.Model):
    belongs_to = models.ForeignKey(country, on_delete=models.CASCADE)
    name = models.CharField(max_length=50)

如果我这样做并保存文件,将www.example.com中的continents添加admin.py到admin.site.register(continent),从makemigrations开始...我得到了这个:
您正试图添加一个不可为空的字段“属于”没有默认值的国家;我们不能这样做(数据库需要一些东西来填充现有的行)。请选择修复:
1.)现在提供一个一次性的默认值(将在此列为空值的所有现有行上设置)2.)退出,让我在www.example.com中添加一个默认值models.py
我被困在这里,因为我不知道从这里做什么?

83qze16e

83qze16e1#

我认为你得到的信息是相当不言自明的。
您正在将belongs_to字段添加到country模型中(类的名称应该使用第一个大写字母,就像Country一样),但它被声明为不可空。也就是说,它必须有一个声明的值。
现在,因为您已经填充了country模型的一些行(对象),Django会询问您应该向这些现有行添加belongs_to的值。
有两种解决方案:
1.将一个整数(它将成为continent模型的id)添加到所有这些变量中,并在以后更改它,或者
1.更改为belongs_to = models.ForeignKey(continent, on_delete=models.CASCADE, null=True, blank=True),以便在migrate之后,此字段可以为空,因此现有行将具有该字段为null
希望你能理解。

b09cbbtk

b09cbbtk2#

我经常为此而挣扎。多亏了@nik_m的回答,我才能想出(经过一些测试和失败之后)从一个已经有对象的模型中向一个没有对象的模型添加一个新的ForeignKey的最简单的方法。
1.使字段可为空(如nik_m在上面答案的第2步中所解释的)。
1.创建外部模型的(默认)对象。它将自动具有id=1(如果还不存在对象)。
1.通过将null=True, blank=True替换为default=1来更改外键字段,以将创建的新对象与所有现有行相关联。
1.最后,您可以(最终)删除default=1,以强制每个新对象在创建时声明外键。

6l7fqoea

6l7fqoea3#

如果您希望新的ForeignKey字段从一开始就不可为空,并且能够顺利运行所有迁移,而不会出现错误,并且不需要在中间暂停(例如创建外键记录),可以在迁移文件中添加新记录(用于外键):
1.不要在模型的字段上设置null=True

class Country(models.Model):
    ...
    belongs_to = models.ForeignKey(continent, on_delete=models.CASCADE)
    ...

1.生成移植文件。

python3 manage.py makemigrations

选择 “立即提供一次性默认值”。将引用的外键ID的默认值设置为任意值,例如1。该值无关紧要,因为我们稍后将更改它。
此时,如果Country表中存在现有记录,则运行迁移将失败,因为将Country的所有记录中的Country.belongs_to的值设置为不存在的Continent外键记录(使用您设置为默认值的任何外键ID)将失败。

  • 修改生成的迁移文件。

在定义了新Continent模型的创建的迁移操作之后,在数据库中创建一条新的Continent记录。然后,将新的外键belongs_to字段添加到Country模型中,并将其值设置为新创建的记录的ID,作为外键的默认值。

  • 因此,如果运行迁移,将创建新的模型Continent表,然后将创建新的Continent记录,然后将新字段continent_id外键添加到模型Country表,其中每个记录的值将设置为所创建的Continent记录的ID。
...

class _InitialContinentID:
    # Static attribute. Accessible both from class and class instances. All instances will point to
    # the same variable in memory, thus it will have a common value across all instances.
    value = None

    def __call__(self):
        return self.value

def set_initial_continent_id(apps, schema_editor):
    # To run this migration successfully, we need a valid value for the ForeignKey 'belongs_to' of
    # the model 'Country' referencing a 'Continent' record. Thus, create a dummy 'Continent' record
    # that will be mapped to all existing countries.
    Continent = apps.get_model('appname', 'Continent')
    db_alias = schema_editor.connection.alias
    queryset = Continent.objects.using(db_alias)
    if queryset.exists():
        # This is not needed anymore if the model is newly created and guaranteed to be empty.
        _InitialContinentID.value = queryset.first().id
    else:
        # Create a new 'Continent' record. Later, just manually correct all 'Country' records
        # that relates to this dummy continent.
        _InitialContinentID.value = queryset.create(name="_OVERWRITE_THIS").id

class Migration(migrations.Migration):
    dependencies = [
        ...
        # If the 'Continent' model is created in another migration file, then that migration file
        # must be a dependency here as we need to create a 'Continent' record.
        ...
    ]

    operations = [
        # If the 'Continent' model is to be created in this migration file, then create it here
        # e.g. migrations.CreateModel(name='Continent', ...)

        # Create a dummy 'Continent' record
        migrations.RunPython(set_initial_continent_id),

        # Add thew new foreign key field to 'Country' and set it to the created dummy 'Continent'
        # record.
        migrations.AddField(
            model_name='country',
            name='belongs_to',
            # The easiest option is to just set this to 'default=1' since '1' is usually the ID of
            # the 1st created record in a table. If we want to make it more dynamic, we must set
            # 'default' to a callable object, here being an instance of the class
            # '_InitialContinentID' which implements '__call__' thus making it a callable object.
            # You can't just directly set this to the (immutable) fixed (integer) value e.g.
            # 'default=_InitialContinentID().value' or 'default=_InitialContinentID.value'
            # because the original value will persist (in this example is 'None') and be stored
            # directly to the default value. If you want any updates to the original value be
            # visible, then set it to a callable object that would be called when executed and
            # retrieve the most updated value.
            field=models.ForeignKey(default=_InitialContinentID(), to='appname.continent', ...),
            ...
    ]

...
  • 迁移。从现在开始,您可以始终安全地运行此迁移(即使在新环境中),而无需人工干预手动创建外键记录。
python3 manage.py migrate

正如其他人所提到的,另一种选择是使用null=True将模型字段声明为可空,迁移(将字段添加到数据库),然后设置字段值(数据库中的每条记录),然后删除null=True以使其不可空。

相关问题