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
之后没有手工修改过,Site
的domain
和name
都被默认初始化为example.com
,这就是问题所在了。
Solution
stackoverflow 上得票最高的答案这样把site_name
放到response
的local()
里面或者是直接做个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()
了。