“站点”框架

Django提供了一个可选的“站点”框架。它是一个将对象和功能关联到特定网站的钩子,它是域名和Django支持的站点的“详细”名称的存放处。

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

站点框架主要基于此模型:

class models.Site

用于存储 domainname 网站的属性。

domain

与网站关联的完全限定域名。例如, www.example.com .

name

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

这个 SITE_ID 设置指定的数据库ID Site 与该特定设置文件关联的对象。如果省略设置,则 get_current_site() 函数将尝试通过比较 domain 主机名来自 request.get_host() 方法。

如何使用它取决于您自己,但是Django通过一些约定自动使用它。

示例用法

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

将内容与多个网站关联

这个 LJWorld.com 和Lawrence.com网站是由同一新闻机构运营的--堪萨斯州劳伦斯的《劳伦斯日报-世界报》。LJWorld.com专注于新闻,而Lawrence.com专注于当地娱乐。但有时编辑们想发表一篇关于 both 网站。

解决这个问题的幼稚方法是要求网站制作者发布同一篇文章两次:一次为ljworld.com,一次为lawrence.com。但是对于站点生产者来说,这是低效的,在数据库中存储同一故事的多个副本是多余的。

更好的解决方案可以消除内容重复:两个站点使用相同的文章数据库,并且一篇文章与一个或多个站点相关联。在Django模型术语中,它由 ManyToManyFieldArticle 型号:

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


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")
        # ...
    

将内容与单个网站关联

同样,您可以将模型与 Site 在多对一关系中建模,使用 ForeignKey .

例如,如果一篇文章只允许在一个站点上使用,那么您将使用如下模型:

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


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 如果不是,则执行实例。

如果您没有访问请求对象的权限,则可以使用 get_current() 方法 Site 模型经理。然后应确保设置文件包含 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都有电子邮件提醒功能,读者可以注册,在新闻发生时获得通知。这很简单:读者在网络表格上注册后,马上就会收到一封电子邮件,上面写着:“谢谢您的订阅。”

两次实现这个注册处理代码是低效和冗余的,因此站点在幕后使用相同的代码。但是对于每个站点,“感谢您注册”通知需要有所不同。通过使用 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 alerts”。在ljworld.com上,此电子邮件的主题为“感谢订阅ljworld.com alerts”。电子邮件的邮件正文也是如此。

请注意,更灵活(但更重)的方法是使用Django的模板系统。假设lawrence.com和ljworld.com有不同的模板目录 (DIRS ),您可以像这样转包到模板系统:

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


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

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

    # ...

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

利用 Site 尽可能多的对象,以消除不必要的复杂性和冗余。

获取完整URL的当前域

姜戈的 get_absolute_url() 约定适用于获取不带域名的对象的URL,但在某些情况下,您可能希望显示完整的URL-- https:// 以及域和所有东西--对于一个对象。为此,您可以使用Sites框架。举个例子:

>>> 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 寄存器A post_migrate 创建名为 example.com 随域 example.com . 此站点也将在Django创建测试数据库之后创建。要为项目设置正确的名称和域,可以使用 data migration .

为了在生产中为不同的站点提供服务,您将创建一个单独的设置文件 SITE_ID (可能从公共设置文件导入以避免复制共享设置),然后指定适当的 DJANGO_SETTINGS_MODULE 对于每个站点。

缓存当前 Site 对象

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

如果出于任何原因您想要强制执行数据库查询,可以告诉Django使用 Site.objects.clear_cache() ::

# 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

class managers.CurrentSiteManager

如果 Site 在应用程序中扮演关键角色,请考虑使用 CurrentSiteManager 在您的模型中。这是一个模型 manager 自动筛选查询以仅包含与当前 Site .

强制性的 SITE_ID

这个 CurrentSiteManager 只有在 SITE_ID 设置是在设置中定义的。

使用 CurrentSiteManager 通过将它显式添加到模型中。例如::

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


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()

有了这个模型, Photo.objects.all() 将全部归还 Photo 数据库中的对象,但是 Photo.on_site.all() 只返回 Photo 与当前网站关联的对象,根据 SITE_ID 设置。

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

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

如何 CurrentSiteManager 知道哪个领域 PhotoSite ?默认情况下, CurrentSiteManager 找一个或一个 ForeignKey 调用 site 或A ManyToManyField 调用 sites 过滤。如果使用的字段名不是 sitesites 确定 Site 对象,然后需要将自定义字段名作为参数显式传递给 CurrentSiteManager 根据你的模型。下面的模型具有一个名为 publish_on ,演示如下:

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


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 .

最后,请注意,您可能希望保持正常(非特定站点) Manager 在你的模型上,即使你使用 CurrentSiteManager . 如中所述 manager documentation ,如果手动定义管理器,则Django不会创建自动 objects = models.Manager() 你的经理。还要注意,django的某些部分——即django管理站点和通用视图——使用定义的管理器 第一 在模型中,如果您希望您的管理站点能够访问所有对象(而不仅仅是特定于站点的对象),请将 objects = models.Manager() 在您的模型中,在您定义 CurrentSiteManager .

站点中间件

如果您经常使用此模式:

from django.contrib.sites.models import Site


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

为了避免重复,添加 django.contrib.sites.middleware.CurrentSiteMiddlewareMIDDLEWARE . 中间件设置 site 每个请求对象的属性,以便您可以使用 request.site 获取当前网站。

Django如何使用网站框架

虽然不要求使用站点框架,但强烈建议使用,因为Django在一些地方利用了它。即使您的Django安装只为一个站点供电,您也应该花两秒钟的时间用您的 domainname ,并在您的 SITE_ID 设置。

Django如何使用网站框架:

  • redirects framework ,每个重定向对象都与特定站点关联。当Django搜索重定向时,它会考虑到当前站点。

  • flatpages framework ,每个平面图都与特定站点关联。创建平面图时,可以指定其 SiteFlatpageFallbackMiddleware 在检索要显示的平面图时检查当前网站。

  • syndication framework ,模板 titledescription 自动访问变量 {{{{ site }}}} ,这就是 Site 表示当前站点的对象。另外,用于提供项目URL的钩子将使用 domain 从现在开始 Site 如果未指定完全限定的域,则为。

  • authentication frameworkdjango.contrib.auth.views.LoginView 通过电流 Site 模板名称为 {{{{ site_name }}}} .

  • 快捷方式视图 (django.contrib.contenttypes.views.shortcut )使用当前域 Site 计算对象的URL时为。

  • 在管理框架中,“现场查看”链接使用当前 Site 为它将重定向到的站点计算域。

RequestSite 对象

一些 django.contrib 应用程序利用了站点框架,但其架构却没有 要求 要安装在数据库中的站点框架。(有些人不想,或者只是不想 able 为了安装站点框架所需的额外数据库表。)对于这些情况,框架提供了 django.contrib.sites.requests.RequestSite 类,当数据库支持的站点框架不可用时,可以将其用作回退。

class requests.RequestSite

共享的主接口的类 Site (也就是说,它有 domainname 属性),但从Django获取数据 HttpRequest 对象,而不是来自数据库。

__init__(request)

设置 namedomain 属性的值 get_host() .

A RequestSite 对象具有与普通对象相似的接口 Site 对象,但其 __init__() 方法采用 HttpRequest 对象。它可以推断 domainname 通过查看请求的域。它有 save()delete() 匹配的接口的方法 Site 但是方法却提高了 NotImplementedError .

get_current_site 捷径

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

shortcuts.get_current_site(request)

检查是否 django.contrib.sites 安装并返回当前 Site 对象或 RequestSite 基于请求的对象。它根据以下内容查找当前网站 request.get_host() 如果 SITE_ID 未定义设置。

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