在 Django 迁移创建期间忽略 CharField "choices" 中的更改

Ignore changes in CharField "choices" during Django migration creation

我正在构建一个开源可重复使用的应用程序,它需要该应用程序的用户能够在 settings 中的某些 CharField 字段上设置自己的 choices

是否 possible/reasonable 让 Django 忽略对 choices 字段选项的更改并且从不(应该添加该功能)实现数据库级选择选择?

这不是实际代码,而是

这将在 models.py

class Owner(models.Model):
    city = models.CharField(
        verbose_name=u'City',
        max_length=255,
        blank=True,
        choices=settings.CITIES,
        db_index=True)

这将在 settings.py

CITIES = (
    ('chicago', 'Chicago, IL'),
    ('milwaukee', 'Milwaukee, WI')
)

这将导致此迁移

class Migration(migrations.Migration):
    operations = [
        migrations.CreateModel(
            name='owner',
            fields=[
                ('city', models.CharField(blank=True, max_length=3, db_index=True, choices=[(b'chicago', b'Chicago, IL'), (b'milwaukee', b'Milwaukee, WI')])),
    ]

现在,假设最终用户想要更改他们的应用程序以在他们的 settings.py

CITIES = (
    ('los_angeles', 'Los Angeles, CA'),
    ('san_fransisco', 'San Fransisco, CA')
)

这将导致使用 python manage.py makemigrations 创建另一个迁移,如下所示:

class Migration(migrations.Migration):

    dependencies = [
        ('appname', '0001_initial'),
    ]

    operations = [
        migrations.AlterField(
            model_name='user',
            name='city',
            field=models.CharField(blank=True, max_length=255, verbose_name='City', db_index=True, choices=[(b'los_angeles', b'Los Angeles, CA'), (b'san_fransisco', b'San Fransisco, CA')]),
        ),
    ]

即使该应用的其他用户可能拥有完全不同的支持城市列表。

稍后当我发布具有 0002 迁移编号的开源应用程序的新版本时,这可能会导致冲突,并且如果有数据库级别的选择强制执行,这可能会造成严重破坏。

是否可以让 Django 在迁移创建期间忽略对 choices 字段的更改?扩展 CharField 似乎是合理的。

或者我是否必须使用永不更改的 ForeignKey 重构它并将 select_related() 添加到管理器?

供参考,here's the Django makemigrations inspector code

我不会让我在 database/ORM 明智地验证所有事情变得那么困难,但是以 controller/the 形式进行验证。

以下是我将基于您的示例代码执行的操作:

models.py:

class Owner(models.Model):
    city = models.CharField(
        verbose_name=u'City', max_length=255, blank=True, db_index=True
    )

(这里没有选择!)

views.py:

class CitySelectionView(FormView):
    template_name = "city_selection.html"
    form_class = forms.CitySelectionForm

     def form_valid(self, form):
         obj = models.Owner(city=form.cleaned_data['city']
         obj.save()
         return redirect('index')

forms.py:

class CitySelectionForm(forms.Form):
    city = forms.MultipleChoiceField(choices=settings.CITIES)

如果城市现在是

CITIES = (('dusseldorf', 'Düsseldorf, Germany'), ('berlin', 'Berlin, Germany'))

表格将显示杜塞尔多夫和柏林,如果是

CITIES = (('london', 'London, UK'), ('paris', 'Paris, France'))

表格将显示伦敦和巴黎。

事实证明这是出于某种原因未启用(来自 this answer

This is by design. There are several reasons, not least of which for me that datamigrations at points in history need to have a full accurate representation of the models, including all their options not just those which affect the database.

但是,您可以返回初始迁移并执行

class Migration(migrations.Migration):
    operations = [
        migrations.CreateModel(
            name='owner',
            fields=[
                ('city', models.CharField(choices=settings.CITIES, blank=True, max_length=3, db_index=True)),
    ]

不是很漂亮,理论上它可能会反过来咬你一口,所以在管理器的 get_queryset() 中使用 ForeignKeyselect_related('cities') 进行重构可能是 safest/least-hacky 解决此问题的方法,但这绝不会导致新的迁移发生 settings 更改导致 choices 更改。