如何在 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 对“默认”域一无所知,只是因为没有这样的东西——你可以使用https
和http
,服务多个域(甚至可能使用子域)等等。我同意这有点令人困惑,但这是 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 数据,无需表单或模型