@Lenciel

Correct Django Site Name During DB Migration

Don't touch me

Problem

就像截图上显示的那样,真正上线过的 Django 项目都会好像被施放过诅咒一般,让你在某一天看到那个诡异的example.com

它可能是在系统发出去的重置密码的邮件里面,可能是在 Sentry 显示的日志里面,也可能就在你用 site_name tag 渲染的模板里面。

这个诅咒来自于 Django 的sites framework的设计。简单来说,它提供了一个 Site 对象的manager,来方便你用一套代码给多个部署环境使用。换句话说,虽然settings.py文件里面也有一个SITE_NAME,但其实用Site.objects.get_current().name或者是模板里面的site_name取到的不是那个值,而是数据库django_site里面某个site_id对应的 Site 对象的name

而如果你syncdb之后没有手工修改过,Sitedomainname都被默认初始化为example.com,这就是问题所在了。

Solution

stackoverflow 上得票最高的答案这样把site_name放到responselocal()里面或者是直接做个context_processor是可以的。但这样的坏处是完全抛弃了 Django 自带的sites,需要在用的地方都专门的处理。

如果要继续使用自带的sites,就得自己写类似下面的 fixture:

[
  {
    "pk": 1,
    "model": "sites.site",
    "fields": {
      "name": "LeiFun Production",
      "domain": "leifun.net"
    }
  },
  {
    "pk": 2,
    "model": "sites.site",
    "fields": {
      "name": "LeiFun Stage",
      "domain": "stage.leifun.net"
    }
  },
  {
    "pk": 3,
    "model": "sites.site",
    "fields": {
      "name": "LeiFun Test",
      "domain": "test.leifun.net"
    }
  },

  {
    "pk": 4,
    "model": "sites.site",
    "fields": {
      "name": "LeiFun Local Dev",
      "domain": "yawp.dev:8000"
    }
  }
]

然后在部署的环境里面用django_admin.py或者manage.py运行loaddata。这样的坏处是fixture这东西本来主要是给本地测试生成 mock 数据的,所以syncdb命令其实不会发起 fixture 的导入,于是很多时候你部署了新版本之后,会忘记重新导入fixture(其实本来也不该导入 fixture),牛皮癣一样的example.com又回来了。

Solution 2

通过修改某个现成 app 的Migration类的forwards方法,强制它读取一次settings文件里面的配置项:

class Migration(DataMigration):

    def forwards(self, orm):
        Site = orm['sites.Site']
        site = Site.objects.get(id=settings.SITE_ID)
        site.domain = settings.DOMAIN_NAME
        site.name = settings.SITE_NAME
        site.save()

这样一来,就可以在syncdb的时候刷新django_site这张表的配置。

Solution Finally

在 Django 1.7 里面,这个倒霉的设计终于被改掉了

To enable the sites framework, follow these steps:

1. Add 'django.contrib.sites' to your INSTALLED_APPS setting.
2. Define a SITE_ID setting
3. Run migrate.

django.contrib.sites registers a post_migrate signal handler which creates a default site named example.com with the domain example.com. This site will also be created after Django creates the test database. To set the correct name and domain for your project, you can use a data migration.

不但如此,Django 1.7 还引入了django.contrib.sites.middleware.CurrentSiteMiddleware, 如果启用,就可以直接使用request.site而不需要在你的view里面自己去调用site = Site.objects.get_current()了。

欢迎留言