如何在 Django(无需请求)中获取 URL(带有协议和域)?

Posted

技术标签:

【中文标题】如何在 Django(无需请求)中获取 URL(带有协议和域)?【英文标题】:How can I get the URL (with protocol and domain) in Django (without request)? 【发布时间】:2016-05-01 13:14:26 【问题描述】:

我想在 cron 作业中发送邮件。邮件应包含 指向我的应用程序的链接。

在 cron 作业中,我没有请求对象,无法使用 request.build_absolute_uri()。

AFAIK 网站框架可以在这里提供帮助。但是没有给我协议(http vs https)?

我的应用程序是可重用的,有时托管在 http 网站上,有时托管在 https 网站上。

更新

我搜索了一个常见的 django 方式。可以创建自定义设置,但首选 django 标准的解决方案。

【问题讨论】:

你在一个 Django 实例中服务了多少个不同的域? @AlexMorozov 在我当前的上下文中,一个单一的 Django 实例服务器只有一个域。但是该应用程序安装在许多不同的服务器上。有些通过 http 提供服务,有些通过 https 提供服务。 我在这种情况下使用设置来服务,尤其是当我没有请求对象时。 settings.HOST_ADDRESS + "0".format(resource_path) 之类的东西应该可以完成这项工作。通过将基本设置扩展到特定于站点使用的各种设置文件(例如:--settings=settings.site1),它应该可以通过最小配置更改重用 @rreddy settings.HOST_ADDRESS 是您创建的自定义设置吗?我想是的,因为我在这里找不到它docs.djangoproject.com/es/1.9/ref/settings。我问自己:我的用例没有通用的解决方案吗?我认为我的用例很常见。但也许我错了。 是的,@guettli,这是一个自定义设置。我认为没有其他方法可以在没有请求对象的情况下检索方案(http/https),除了采用不同的方法,比如声明这样的自定义设置。 【参考方案1】:

TL;DR没有任何“标准”“Django-ish”的方式来做到这一点,但框架提倡的 DRY 原则假设单个配置存储,因此自定义设置似乎是一个不错的方法。

默认情况下,Django 可以从单个实例为任意数量的域提供服务,HTTP 请求(更准确地说,它的HTTP_HOST 标头)是 Django 用来确定当前主机的唯一东西。 由于您的 cron 作业显然超出了 HTTP 周期,因此您应该将您的域存储在设置中的某个位置...

# settings.py
DEFAULT_DOMAIN = 'https://foobar.com'
# or, depending on your configuration:
DEFAULT_DOMAIN = 'https://'.format(ALLOWED_HOSTS[0])

...带有一个小型上下文处理器,可以更轻松地处理模板:

# yourapp/context_processors.py
from django.conf import settings

def default_domain(request):
    return 'default_domain': settings.DEFAULT_DOMAIN

...然后在您的电子邮件中使用它:

# yourapp/templates/email/body.html
<a href=" default_domain % url 'target' %">Click here</a>

您也可以使用sites 框架,但如果您服务于单个域,则基于设置的解决方案对我来说似乎更简单、更清晰。

【讨论】:

DEFAULT_DOMAIN 是自定义设置吗?我在这里找不到它:docs.djangoproject.com/es/1.9/topics/settings @guettli,对,这是我刚刚为说明解决方案而制作的自定义设置。将其命名为您喜欢的名称并将其设置为您正在使用的任何主机。默认情况下,Django 对“默认”域一无所知,只是因为没有这样的东西——你可以使用httpshttp,服务多个域(甚至可能使用子域)等等。我同意这有点令人困惑,但这是 Django 提供的开箱即用灵活性的代价。【参考方案2】:

这个任务有一个特殊的标准模块 - Sites Framework。 它添加了站点模型,描述了特定的站点。此模型具有用于项目域的字段 domain,以及一个 name - 站点的人类可读名称。

您将模型与站点相关联。像这样:

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)

当您需要为对象创建一个 url 时,您可以使用类似于以下代码的内容:

   >>> 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/'

这样您就可以在它们之间传播多个域和内容。 即使您只有一个域,我也建议您使用它来实现一种良好的标准、舒适的方式来保持域设置。

安装非常简单:

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

    定义 SITE_ID 设置:

     SITE_ID = 1
    

    运行迁移。

【讨论】:

看起来不错。缺少一部分:您使用“https”。配置 http-scheme 的常见位置在哪里,以便可重用应用无需猜测即可获取此值? 暂时没有这样的地方。我认为协议应该与站点一起存储。 django票务系统有一个问题和补丁-code.djangoproject.com/ticket/26079#no1我希望以后它会被采用并成为标准方式。现在我们可以继承 Site 模型并使用必填字段对其进行扩展。作为一种临时解决方法,可以将协议存储在 settings.py 中,因为它很少更改。 感谢您提供 django 票证的链接。很高兴看到这尚未解决的问题。 站点链接似乎已损坏;这是一个更新的:docs.djangoproject.com/en/3.2/ref/contrib/sites【参考方案3】:

对于我的特定场景,我需要向由 django 信号 触发的个人发送电子邮件(当用户提出问题并将其分配给另一个用户时 - 电子邮件是提出)

由于信号无法访问request,因此我需要在我的模板中访问 request.get_host

这个问题的答案并没有真正吸引我,因为我希望它能够通过更改主机(生产服务器域、测试服务器域、本地开发服务器)动态工作。为了从中提取主机,我也不想创建一个新字段并使表依赖于它。

我的解决方案

我的项目的主机名是托管服务器的虚拟机的名称。因此我可以区分它们并分配一个单独的HOST_ADDR

settings.py

import socket

try:
    HOSTNAME = socket.gethostname()
except:pass

if HOSTNAME == 'test':
    HOST_ADDR = 'http://test-<test-domain>'

elif HOSTNAME == 'production':
    HOST_ADDR = 'http://<production-domain>'
else:
    HOST_ADDR = 'http://localhost:8000'

然后我可以将它导入到信号中,并作为内容应用到我的render_to_string 函数中。

models.py

from django.conf import settings

def email_assignee(sender, **kwargs):
    instance = kwargs["instance"]
    if kwargs["created"]:
        email_context = render_to_string('<app_name>/email.html', context='issue':instance,'host':settings.HOST_ADDR)
        try:
            send_mail(
                'Subject','', None,
                [instance.assignee.email],
                fail_silently=False,
                html_message=email_context
            )
        except Exception as Error:
            print("Email could not be sent! For reason: ".format(Error))

post_save.connect(email_assignee, sender=Issue)

然后在我的模板中,我可以将其用作电子邮件链接,如下所示:

email.html

% load static %

<p>You have a new issue assigned to you from <b> issue.assigned_by </b></p>
<a href=" host % url 'emissions_dashboard:view_issue' issue.id %" target="_blank">View it here</a>

【讨论】:

如果您将请求存储在全局变量中,您可以在信号中访问请求(这发生在 http 请求期间)。只需搜索“django 全局请求中间件”即可。【参考方案4】:

如果我是你,我会使用 celery 之类的东西进行 cron 作业,并且现在将使用 django 提供的对象。

【讨论】:

以上是关于如何在 Django(无需请求)中获取 URL(带有协议和域)?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 django 的预检 CORS POST 请求中处理 CSRF?

从 Django REST Framework 中的 APIView 中获取完整的请求 URL

Django:从 HTML 中获取 LIST 数据,无需表单或模型

在 Django Url Dispatcher 中获取 http 请求数据?这可能吗?如果是这样,怎么做?

如何在:Django 中通过 URL 传递带空格的变量

Django中获取参数(路径,查询,请求头,请求体)