“网站”框架

Django带有一个可选的“网站”框架。 这是将对象和功能与特定网站相关联的钩子,它是Django站点的域名和“详细”名称的持有者。

如果您的单个Django安装支持多个站点,并且您需要以某种方式区分这些站点,请使用它。

网站框架主要基于一个简单的模型:

楷模。网站 T0> ¶ T1>

用于存储网站的domainname属性的模型。

域 T0> ¶ T1>

与网站相关的完全合格的域名。 例如,www.example.com

名称 T0> ¶ T1>

网站上一个人类可读的“详细”名称。

SITE_ID设置指定与特定设置文件关联的Site对象的数据库ID。 如果省略设置,get_current_site()函数将通过比较domainrequest.get_host()方法。

你如何使用这个取决于你,但是Django通过简单的约定自动使用它。

用法示例

你为什么要使用网站? 最好通过例子来解释。

将内容与多个网站相关联

The Django-powered sites LJWorld.com and Lawrence.com are operated by the same news organization – the Lawrence Journal-World newspaper in Lawrence, Kansas. LJWorld.com专注于新闻,而Lawrence.com专注于本地娱乐。 但是有时候,编辑们希望在这两个网站上发布一篇文章。

解决问题的天真的方式是要求网站制作者两次发布相同的故事:一次为LJWorld.com,一次为Lawrence.com。 但是这对于网站制作者来说效率不高,在数据库中存储同一个故事的多个副本是多余的。

更好的解决方案很简单:两个网站使用相同的文章数据库,并且文章与一个或多个网站相关联。 在Django模型术语中,它是由Article模型中的ManyToManyField表示的:

from django.db import models
from django.contrib.sites.models import Site

class Article(models.Model):
    headline = models.CharField(max_length=200)
    # ...
    sites = models.ManyToManyField(Site)

这很好地完成了几件事情:

  • 它允许网站制作者在单个界面(Django管理员)中编辑所有内容(在两个站点上)。

  • 这意味着相同的故事不必在数据库中发布两次;它在数据库中只有一个记录。

  • 它使站点开发人员可以为这两个站点使用相同的Django视图代码。 显示给定故事的视图代码只是检查以确保请求的故事是在当前网站上。 它看起来像这样:

    from django.contrib.sites.shortcuts import get_current_site
    
    def article_detail(request, article_id):
        try:
            a = Article.objects.get(id=article_id, sites__id=get_current_site(request).id)
        except Article.DoesNotExist:
            raise Http404("Article does not exist on this site")
        # ...
    

将内容与单个网站关联

同样,您可以使用ForeignKey以多对一的关系将模型关联到Site模型。

例如,如果一篇文章只允许在一个网站上,您可以使用像这样的模型:

from django.db import models
from django.contrib.sites.models import Site

class Article(models.Model):
    headline = models.CharField(max_length=200)
    # ...
    site = models.ForeignKey(Site, on_delete=models.CASCADE)

这与上一节中描述的相同。

从视图挂钩到当前网站

您可以在Django视图中使用网站框架,根据调用视图的网站来执行特定的操作。 例如:

from django.conf import settings

def my_view(request):
    if settings.SITE_ID == 3:
        # Do something.
        pass
    else:
        # Do something else.
        pass

当然,像这样硬编码站点ID是丑陋的。 这种硬编码对于你需要快速完成的修复是最好的。 完成同样事情的更简洁的方法是检查当前网站的域名:

from django.contrib.sites.shortcuts import get_current_site

def my_view(request):
    current_site = get_current_site(request)
    if current_site.domain == 'foo.com':
        # Do something
        pass
    else:
        # Do something else.
        pass

这也有检查站点框架是否安装的优点,如果不是,则返回一个RequestSite实例。

如果您无权访问请求对象,则可以使用Site模型管理器的get_current()方法。 您应该确保您的设置文件包含SITE_ID设置。 这个例子相当于前一个例子:

from django.contrib.sites.models import Site

def my_function_without_request():
    current_site = Site.objects.get_current()
    if current_site.domain == 'foo.com':
        # Do something
        pass
    else:
        # Do something else.
        pass

获取当前显示的域

LJWorld.com和Lawrence.com都有电子邮件提醒功能,让读者在新闻发生时注册以获得通知。 这是非常基本的:读者在Web表单上注册,并立即收到一封电子邮件,说:“感谢您的订阅。”

实施这个注册处理代码两次是没有效率和冗余的,所以这些站点在后台使用相同的代码。 但是,对于每个站点,“感谢您注册”通知都需要不同。 通过使用Site对象,我们可以抽象“谢谢”通知来使用当前网站的namedomain的值。

以下是表单处理视图的一个例子:

from django.contrib.sites.shortcuts import get_current_site
from django.core.mail import send_mail

def register_for_newsletter(request):
    # Check form values, etc., and subscribe the user.
    # ...

    current_site = get_current_site(request)
    send_mail(
        'Thanks for subscribing to %s alerts' % current_site.name,
        'Thanks for your subscription. We appreciate it.\n\n-The %s team.' % (
            current_site.name,
        ),
        'editor@%s' % current_site.domain,
        [user.email],
    )

    # ...

在Lawrence.com上,这封电子邮件的主题是“感谢订阅lawrence.com提醒”。在LJWorld.com上,电子邮件的主题是“感谢订阅LJWorld.com提醒”。电子邮件的消息正文。

请注意,这样做更灵活(但更重要)的方式是使用Django的模板系统。 假设Lawrence.com和LJWorld.com有不同的模板目录(DIRS),那么您可以简单地按照以下方式将其转移到模板系统:

from django.core.mail import send_mail
from django.template import loader, Context

def register_for_newsletter(request):
    # Check form values, etc., and subscribe the user.
    # ...

    subject = loader.get_template('alerts/subject.txt').render(Context({}))
    message = loader.get_template('alerts/message.txt').render(Context({}))
    send_mail(subject, message, 'editor@ljworld.com', [user.email])

    # ...

在这种情况下,您必须为LJWorld.com和Lawrence.com模板目录创建subject.txtmessage.txt模板文件。 这给你更多的灵活性,但也更复杂。

尽可能多地利用Site对象是一个好主意,以消除不必要的复杂性和冗余。

获取完整网址的当前网域

Django的get_absolute_url()约定对于获取没有域名的对象URL是很好的,但在某些情况下,你可能想要显示完整的URL - http://和领域和一切 - 对于一个对象。 为此,您可以使用网站框架。 一个简单的例子:

>>> from django.contrib.sites.models import Site
>>> obj = MyModel.objects.get(id=3)
>>> obj.get_absolute_url()
'/mymodel/objects/3/'
>>> Site.objects.get_current().domain
'example.com'
>>> 'https://%s%s' % (Site.objects.get_current().domain, obj.get_absolute_url())
'https://example.com/mymodel/objects/3/'

启用网站框架

要启用网站框架,请按照下列步骤操作:

  1. 'django.contrib.sites'添加到INSTALLED_APPS设置中。

  2. 定义一个SITE_ID设置:

    SITE_ID = 1
    
  3. 运行migrate

django.contrib.sites registers a post_migrate signal handler which creates a default site named example.com with the domain example.com. 这个站点也将在Django创建测试数据库之后创建。 要为您的项目设置正确的名称和域名,您可以使用data migration

为了在生产中服务不同的站点,你需要创建一个独立的设置文件,每个设置文件都包含SITE_ID(可能从一个公共设置文件导入以避免重复共享设置),然后指定相应的/ t2> DJANGO_SETTINGS_MODULE

缓存当前Site对象

由于当前站点存储在数据库中,每次调用Site.objects.get_current()都可能导致数据库查询。 但是Django比这个更聪明一些:在第一个请求中,当前的站点被缓存,任何后续的调用都会返回缓存的数据而不是数据库。

如果出于某种原因想要强制执行数据库查询,可以使用Site.objects.clear_cache()来告诉Django清除缓存:

# First call; current site fetched from database.
current_site = Site.objects.get_current()
# ...

# Second call; current site fetched from cache.
current_site = Site.objects.get_current()
# ...

# Force a database query for the third call.
Site.objects.clear_cache()
current_site = Site.objects.get_current()

CurrentSiteManager

经理。 CurrentSiteManager T0> ¶ T1>

如果Site在您的应用程序中起着关键作用,请考虑在您的模型中使用有帮助的CurrentSiteManager 这是一个manager模型,它自动过滤查询,只包含与当前Site关联的对象。

必须填写SITE_ID

CurrentSiteManager仅在您的设置中定义SITE_ID设置时可用。

使用CurrentSiteManager将其明确添加到您的模型中。 例如:

from django.db import models
from django.contrib.sites.models import Site
from django.contrib.sites.managers import CurrentSiteManager

class Photo(models.Model):
    photo = models.FileField(upload_to='photos')
    photographer_name = models.CharField(max_length=100)
    pub_date = models.DateField()
    site = models.ForeignKey(Site, on_delete=models.CASCADE)
    objects = models.Manager()
    on_site = CurrentSiteManager()

With this model, Photo.objects.all() will return all Photo objects in the database, but Photo.on_site.all() will return only the Photo objects associated with the current site, according to the SITE_ID setting.

换句话说,这两个陈述是等价的:

Photo.objects.filter(site=settings.SITE_ID)
Photo.on_site.all()

CurrentSiteManager如何知道Photo的哪个字段是Site By default, CurrentSiteManager looks for a either a ForeignKey called site or a ManyToManyField called sites to filter on. 如果您使用名为sitesites之外的字段来标识与您的对象相关的Site对象,则需要明确地通过自定义字段名称作为您的模型上的CurrentSiteManager的参数。 以下模型具有名为publish_on的字段,它演示了这一点:

from django.db import models
from django.contrib.sites.models import Site
from django.contrib.sites.managers import CurrentSiteManager

class Photo(models.Model):
    photo = models.FileField(upload_to='photos')
    photographer_name = models.CharField(max_length=100)
    pub_date = models.DateField()
    publish_on = models.ForeignKey(Site, on_delete=models.CASCADE)
    objects = models.Manager()
    on_site = CurrentSiteManager('publish_on')

如果您尝试使用CurrentSiteManager并传递不存在的字段名称,则Django将引发ValueError

Finally, note that you’ll probably want to keep a normal (non-site-specific) Manager on your model, even if you use CurrentSiteManager. 正如manager documentation中所解释的那样,如果您手动定义了一个管理器,那么Django将不会创建自动的对象 = models.Manager() tt>经理为你。 另外请注意,Django的某些部分(即Django管理站点和通用视图)使用模型中定义的first中的任何一个管理器,因此如果您希望管理站点可以访问所有对象只是特定于站点的),在模型中放置对象 = models.Manager() CurrentSiteManager

网站中间件

如果你经常使用这种模式:

from django.contrib.sites.models import Site

def my_view(request):
    site = Site.objects.get_current()
    ...

有简单的方法来避免重复。 django.contrib.sites.middleware.CurrentSiteMiddleware添加到MIDDLEWARE 中间件为每个请求对象设置site属性,所以你可以使用request.site来获取当前的站点。

Django如何使用站点框架

虽然不需要使用网站框架,但强烈鼓励,因为Django在一些地方利用它。 Even if your Django installation is powering only a single site, you should take the two seconds to create the site object with your domain and name, and point to its ID in your SITE_ID setting.

以下是Django如何使用网站框架:

  • redirects framework中,每个重定向对象都与特定的站点相关联。 Django搜索重定向时,会考虑当前网站。
  • flatpages framework中,每个flatpage都与特定的网站相关联。 创建flatpage时,指定其SiteFlatpageFallbackMiddleware检查当前站点以检索要显示的flatpages。
  • syndication framework中,titledescription的模板自动有权访问一个变量,这是Site目前的网站。 此外,如果您未指定完全限定的域,则提供项目URL的挂钩将使用当前Site对象中的domain
  • In the authentication framework, django.contrib.auth.views.LoginView passes the current Site name to the template as {{ site_name }}.
  • 计算对象URL时,快捷视图(django.contrib.contenttypes.views.shortcut)使用当前Site对象的域。
  • 在管理框架中,“站点视图”链接使用当前的Site来计算将重定向到的站点的域。

RequestSite objects

Some django.contrib applications take advantage of the sites framework but are architected in a way that doesn’t require the sites framework to be installed in your database. (有些人不想,或者不是able来安装网站框架需要的额外数据库表。) 对于这些情况,框架提供了一个django.contrib.sites.requests.RequestSite类,当没有数据库支持的站点框架不可用时,该类可用作回退。

要求。 RequestSite T0> ¶ T1>

一个共享Site主要接口的类(即它具有domainname属性),但是从Django HttpRequest对象而不是来自数据库。

__初始化__ T0>(请求 T1>)¶ T2>

namedomain属性设置为get_host()的值。

A RequestSite object has a similar interface to a normal Site object, except its __init__() method takes an HttpRequest object. 它可以通过查看请求的域来推断domainname It has save() and delete() methods to match the interface of Site, but the methods raise NotImplementedError.

get_current_site快捷方式

最后,为了避免重复的回退代码,框架提供了一个django.contrib.sites.shortcuts.get_current_site()函数。

快捷键。 get_current_site T0>(请求 T1>)¶ T2>

一个函数,用于检查是否安装了django.contrib.sites,并根据请求返回当前的Site对象或RequestSite对象。 如果没有定义SITE_ID设置,它会根据request.get_host()查找当前站点。

当Host头部有明确指定的端口时,域和端口可以由request.get_host()返回。 example.com:80 在这种情况下,如果由于主机与数据库中的记录不匹配而导致查找失败,则会剥离该端口,并仅使用域部分重试查找。 这不适用于RequestSite,它始终使用未经修改的主机。