Related
I want to write custom template loader for my Django app which looks for a specific folder based on a key that is part of the request.
Let me get into more details to be clear. Assume that I will be getting a key on every request(which I populate using a middleware).
Example: request.key could be 'india' or 'usa' or 'uk'.
I want my template loader to look for the template "templates/<key>/<template.html>". So when I say {% include "home.html" %}, I want the template loader to load "templates/india/home.html" or "templates/usa/home.html" or "templates/uk/home.html" based on the request.
Is there a way to pass the request object to a custom template loader?
I've been searching for the same solution and, after a couple days of searching, decided to use threading.local(). Simply make the request object global for the duration of the HTTP request processing! Commence rotten tomato throwing from the gallery.
Let me explain:
As of Django 1.8 (according to the development version docs) the "dirs" argument for all template finding functions will be deprecated. (ref)
This means that there are no arguments passed into a custom template loader other than the template name being requested and the list of template directories. If you want to access paramters in the request URL (or even the session information) you'll have to "reach out" into some other storage mechanism.
import threading
_local = threading.local()
class CustomMiddleware:
def process_request(self, request):
_local.request = request
def load_template_source(template_name, template_dirs=None):
if _local.request:
# Get the request URL and work your magic here!
pass
In my case it wasn't the request object (directly) I was after but rather what site (I'm developing a SaaS solution) the template should be rendered for.
To find the template to render Django uses the get_template method which only gets the template_name and optional dirs argument. So you cannot really pass the request there.
However, if you customize your render_to_response function to pass along a dirs argument you should be able to do it.
For example (assuming you are using a RequestContext as most people would):
from django import shortcuts
from django.conf import settings
def render_to_response(template_name, dictionary=None, context_instance=None, content_type=None, dirs):
assert context_instance, 'This method requires a `RequestContext` instance to function'
if not dirs:
dirs = []
dirs.append(os.path.join(settings.BASE_TEMPLATE_DIR, context_instance['request'].key)
return shortcuts.render_to_response(template_name, dictionary, context_instance, content_type, dirs)
I'm utilizing django-registration with a set of premade templates I found on Github for doing a two-step (registration-activation) workflow using HMAC.
I want to pass global variables (defined in context-processors) like my website's name to the emails sent by django-registration. the activation email sent to a new registrant, for example, or the password change one.
The "problem" is I don't directly have access to those views. That's kinda the point of django-registration, you include its path in the urls.py file, and everything works:
urlpatterns = [
url(r'^', include('core.urls')),
url(r'^admin/', admin.site.urls),
url(r'^accounts/', include('registration.backends.hmac.urls')),
]
What's the minimum effort way of adding context to those views? I've already created and am successfully passing context to emails in my own views (using context processors):
def send_some_email_view(request):
msg_plain = render_to_string('email_change_email.txt', context, request=request)
msg_html = render_to_string('email_change_email.html', context, request=request)
But what about views I didn't create?
Edit: So I made some progress, finding django-registration's registration view, and this method inside of it:
def send_activation_email(self, user):
"""
Send the activation email. The activation key is simply the
username, signed using TimestampSigner.
"""
activation_key = self.get_activation_key(user)
context = self.get_email_context(activation_key)
context.update({
'user': user
})
subject = render_to_string(self.email_subject_template,
context)
# Force subject to a single line to avoid header-injection
# issues.
subject = ''.join(subject.splitlines())
message = render_to_string(self.email_body_template,
context)
user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
I don't want to override it inside the source code because that would prevent me from updating. So now the question becomes: Is my only way out writing a view that subclasses this view, and overriding the method? This means I'm writing separate views for every view provided by django-registartion that needs to send an email...
First create your own view based on the vendor view and overwrite your desired method:
from registration.backends.hmac.views import RegistrationView
class CustomRegistrationView(RegistrationView):
def get_email_context(self, user):
context = super().get_email_context(user)
return RequestContext(self.request, context)
Than have a look at registration.backends.hmac.urls.py (source). They just define a bunch of urls there.
You can easily overwrite just one of them by adding your own before you include the one from the app.
from yourapp import views
urlpatterns = [
# [...]
url(r'^accounts/register/$', views.CustomRegistrationView.as_view(), name='registration_register'),
url(r'^accounts/', include('registration.backends.hmac.urls')),
# [...]
]
While changing just the necessary things you also get some insight of whats going on in your 3rd party app, which is always an advantage. This applies to most 3rd party apps not just the one you are currently using.
Here's what I ended up doing, Thanks to the direction dahrens' answer sent me to:
# myapp/processors.py
def get_website_name(request):
website_name = 'ExcitingWebsiteThatsComingSoon'
return {'mysite_name': website_name}
# some views.py file
from myapp.processors import get_website_name
class RegistrationViewWithContext(RegistrationView):
def get_email_context(self, user):
context = super().get_email_context(user)
context['req'] = get_website_name(self.request)
return context
Basically, I'm simply using my custom processor to inject the website's name. It isn't as clean as I hoped it would be: While in my templates I can simply use {{ mysite_name}}, in the email template I have to use {{req.mysite_name}}. But this does have the DRY-ness I aimed for: all templates updating accordingly if the variable in the function changes.
I'll mark my answer as correct for now and will update accordingly if any new answers come in.
'context_processors': [
...
...
"publicfront.views.context_processors.add_event_url"
],
I added this context processor in settings.py and want to use only for a specific app. How could I achieve this?
The context processor is run for all requests.
If you need to mimic the functionality you speak about, then you could add some if/else conditions in the context processor function, which gets the request object as first argument, so you may determine which app is running and populate the returned dict accordingly
Most of the answers I've found so far are about adding a context_processors into the site's settings.py. But I want my application to be an independent development unit. On deployment, I don't want to perform on the site unnecessary adjustments that I could've included into the distribution. As it was already hinted above (by Daniel Roseman), we can use custom tags (doc):
# file `extras.py`
from django.template.defaultfilters import register
import platform
CURRENT_OS = platform.system()
#register.simple_tag
def current_os():
return CURRENT_OS
That can be used like this:
{% load extras %}
. . .
<div>{% current_os %}</div>
As the request object is accessible in a template it may be processed with a custom filter (doc) like this:
# file `extras.py`
#register.filter
def my_filter(req):
return req.GET.get('q')
Usage:
<div>You searched for '{{ request|my_filter }}'.</div>
How can I get the full/absolute URL (e.g. https://example.com/some/path) in Django without the Sites module? That's just silly... I shouldn't need to query my DB to snag the URL!
I want to use it with reverse().
Use handy request.build_absolute_uri() method on request, pass it the relative url and it'll give you full one.
By default, the absolute URL for request.get_full_path() is returned, but you can pass it a relative URL as the first argument to convert it to an absolute URL.
>>> request.build_absolute_uri()
'https://example.com/music/bands/the_beatles/?print=true'
>>> request.build_absolute_uri('/bands/?print=true')
'https://example.com/bands/?print=true'
If you want to use it with reverse() you can do this : request.build_absolute_uri(reverse('view_name', args=(obj.pk, )))
If you can't get access to request then you can't use get_current_site(request) as recommended in some solutions here. You can use a combination of the native Sites framework and get_absolute_url instead. Set up at least one Site in the admin, make sure your model has a get_absolute_url() method, then:
>>> from django.contrib.sites.models import Site
>>> domain = Site.objects.get_current().domain
>>> obj = MyModel.objects.get(id=3)
>>> path = obj.get_absolute_url()
>>> url = 'http://{domain}{path}'.format(domain=domain, path=path)
>>> print(url)
'http://example.com/mymodel/objects/3/'
https://docs.djangoproject.com/en/dev/ref/contrib/sites/#getting-the-current-domain-for-full-urls
You can also use get_current_site as part of the sites app (from django.contrib.sites.models import get_current_site). It takes a request object, and defaults to the site object you have configured with SITE_ID in settings.py if request is None. Read more in documentation for using the sites framework
e.g.
from django.contrib.sites.shortcuts import get_current_site
request = None
full_url = ''.join(['http://', get_current_site(request).domain, obj.get_absolute_url()])
It isn't as compact/neat as request.build_absolute_url(), but it is usable when request objects are unavailable, and you have a default site url.
If you don't want to hit the database, you could do it with a setting. Then, use a context processor to add it to every template:
# settings.py (Django < 1.9)
...
BASE_URL = 'http://example.com'
TEMPLATE_CONTEXT_PROCESSORS = (
...
'myapp.context_processors.extra_context',
)
# settings.py (Django >= 1.9)
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
# Additional
'myapp.context_processors.extra_context',
],
},
},
]
# myapp/context_processors.py
from django.conf import settings
def extra_context(request):
return {'base_url': settings.BASE_URL}
# my_template.html
<p>Base url is {{ base_url }}.</p>
In your view, just do this:
base_url = "{0}://{1}{2}".format(request.scheme, request.get_host(), request.path)
This worked for me in my template:
{{ request.scheme }}://{{ request.META.HTTP_HOST }}{% url 'equipos:marca_filter' %}
I needed the full url to pass it to a js fetch function.
I hope this help you.
django-fullurl
If you're trying to do this in a Django template, I've released a tiny PyPI package django-fullurl to let you replace url and static template tags with fullurl and fullstatic, like this:
{% load fullurl %}
Absolute URL is: {% fullurl "foo:bar" %}
Another absolute URL is: {% fullstatic "kitten.jpg" %}
These badges should hopefully stay up-to-date automatically:
In a view, you can of course use request.build_absolute_uri instead.
Yet another way. You could use build_absolute_uri() in your view.py and pass it to the template.
view.py
def index(request):
baseurl = request.build_absolute_uri()
return render_to_response('your-template.html', { 'baseurl': baseurl })
your-template.html
{{ baseurl }}
Examine Request.META dictionary that comes in. I think it has server name and server port.
To create a complete link to another page from a template, you can use this:
{{ request.META.HTTP_HOST }}{% url 'views.my_view' my_arg %}
request.META.HTTP_HOST gives the host name, and url gives the relative name. The template engine then concatenates them into a complete url.
Try the following code:
{{ request.scheme }}://{{ request.META.HTTP_HOST }}
If you're using django REST framework, you can use the reverse function from rest_framework.reverse. This has the same behavior as django.core.urlresolvers.reverse, except that it uses a request parameter to build a full URL.
from rest_framework.reverse import reverse
# returns the full url
url = reverse('view_name', args=(obj.pk,), request=request)
# returns only the relative url
url = reverse('view_name', args=(obj.pk,))
Edited to mention availability only in REST framework
I know this is an old question. But I think people still run into this a lot.
There are a couple of libraries out there that supplement the default Django functionality. I have tried a few. I like the following library when reverse referencing absolute urls:
https://github.com/fusionbox/django-absoluteuri
Another one I like because you can easily put together a domain, protocol and path is:
https://github.com/RRMoelker/django-full-url
This library allows you to simply write what you want in your template, e.g.:
{{url_parts.domain}}
If anyone is interested in fetching the absolute reverse url with parameters in a template , the cleanest way is to create your own absolute version of the {% url %} template tag by extending and using existing default code.
Here is my code:
from django import template
from django.template.defaulttags import URLNode, url
register = template.Library()
class AbsURLNode(URLNode):
def __init__(self, view_name, args, kwargs, asvar):
super().__init__(view_name, args, kwargs, asvar)
def render(self, context):
url = super().render(context)
request = context['request']
return request.build_absolute_uri(url)
#register.tag
def abs_url(parser, token):
urlNode = url(parser, token)
return AbsURLNode( urlNode.view_name, urlNode.args, urlNode.kwargs, urlNode.asvar )
Usage in templates:
{% load wherever_your_stored_this_tag_file %}
{% abs_url 'view_name' parameter %}
will render(example):
http://example.com/view_name/parameter/
instead of
/view_name/parameter/
I got it:
wsgiref.util.request_uri(request.META)
Get the full uri with schema, host, port path and query.
You can either pass request reverse('view-name', request=request) or enclose reverse() with build_absolute_uri request.build_absolute_uri(reverse('view-name'))
Not for absolute url but I was looking just to get host. If you want to get host in your view.py you can do
def my_view(request):
host = f"{ request.scheme }://{ request.META.get('HTTP_HOST') }"
As mentioned in other answers, request.build_absolute_uri() is perfect if you have access to request, and sites framework is great as long as different URLs point to different databases.
However, my use case was slightly different. My staging server and the production server access the same database, but get_current_site both returned the first site in the database. To resolve this, you have to use some kind of environment variable. You can either use 1) an environment variable (something like os.environ.get('SITE_URL', 'localhost:8000')) or 2) different SITE_IDs for different servers AND different settings.py.
Hopefully someone will find this useful!
While working on a project I came to know to get the full/absolute URL in Django.
If your URL looks like this in the address bar:
https://stackoverflow.com/questions/2345708
And if you want to show the above URL to your template.
{{ request.path }} #Without GET parameters.
{{ request.get_full_path }} #with GET parameters
For the above two codes, this will print in your template will be
questions/2345708
and another way to get a full URL is:
{{request.build_absolute_uri}}
this will print in your template will be:
https://stackoverflow.com/questions/2345708
There is also ABSOLUTE_URL_OVERRIDES available as a setting
https://docs.djangoproject.com/en/2.1/ref/settings/#absolute-url-overrides
But that overrides get_absolute_url(), which may not be desirable.
Instead of installing sites framework just for this or doing some of the other stuff mentioned here that relies on request object, I think the better solution is to place this in models.py
Define BASE_URL in settings.py, then import it into models.py and make an abstract class (or add it to one you're already using) that defines get_truly_absolute_url(). It could be as simple as:
def get_truly_absolute_url(self):
return BASE_URL + self.get_absolute_url()
Subclass it and now you can use it everywhere.
I came across this thread because I was looking to build an absolute URI for a success page. request.build_absolute_uri() gave me a URI for my current view but to get the URI for my success view I used the following....
request.build_absolute_uri(reverse('success_view_name'))
<div class='col-12 col-md-6'>
<p class='lead'>Login</p>
{% include 'accounts/snippets/form.html' with form=login_form next_url=request.build_absolute_uri %}
</div>
Here for example am saying load the form and tell the form that the next URL is the current URL which this code rendred from
I use this code :
request.build_absolute_uri('/')[:-1]
response :
https://yourdomain.com
request.get_host() will give you the domain.
request.get_host()
Use this for request object for APIView in django
class WalletViewSet(mixins.ListModelMixin, GenericViewSet):
serializer_class = WalletSerializers
pagination_class = CustomPaginationInvestment
def get_queryset(self):
######################################################
print(self.request.build_absolute_uri())
#####################################################
wallet, created = Wallet.objects.get_or_create(owner=self.request.user)
return Wallet.objects.filter(id=wallet.id)
You get output like this
http://localhost:8000/v1/wallet
HTTP GET /v1/wallet 200 [0.03, 127.0.0.1:41608]
You can also use:
import socket
socket.gethostname()
This is working fine for me,
I'm not entirely sure how it works. I believe this is a bit more low level and will return your server hostname, which might be different than the hostname used by your user to get to your page.
You can try "request.get_full_path()"
I want to have some constants in a Django Projects. For example, let's say a constant called MIN_TIME_TEST.
I would like to be able to access this constant in two places: from within my Python code, and from within any Templates.
What's the best way to go about doing this?
EDIT:
To clarify, I know about Template Context Processors and about just putting things in settings.py or some other file and just importing.
My question is, how do I combine the two approaches without violating the "Don't Repeat Yourself" rule? Based on the answers so far, here's my approach:
I'd like to create a file called global_constants.py, which will have a list of constants (things like MIN_TIME_TEST = 5). I can import this file into any module to get the constants.
But now, I want to create the context processor which returns all of these constants. How can I go about doing this automatically, without having to list them again in a dictionary, like in John Mee's answer?
Both Luper and Vladimir are correct imho but you'll need both in order to complete your requirements.
Although, the constants don't need to be in the settings.py, you could put them anywhere and import them from that place into your view/model/module code. I sometimes put them into the __init__.py if I don't care to have them to be considered globally relevant.
a context processor like this will ensure that selected variables are globally in the template scope
def settings(request):
"""
Put selected settings variables into the default template context
"""
from django.conf import settings
return {
'DOMAIN': settings.DOMAIN,
'GOOGLEMAPS_API_KEY': settings.GOOGLEMAPS_API_KEY,
}
But this might be overkill if you're new to django; perhaps you're just asking how to put variables into the template scope...?
from django.conf import settings
...
# do stuff with settings.MIN_TIME_TEST as you wish
render_to_response("the_template.html", {
"MIN_TIME_TEST": settings.MIN_TIME_TEST
}, context_instance=RequestContext(request)
To build on other people's answers, here's a simple way you'd implement this:
In your settings file:
GLOBAL_SETTINGS = {
'MIN_TIME_TEST': 'blah',
'RANDOM_GLOBAL_VAR': 'blah',
}
Then, building off of John Mee's context processor:
def settings(request):
"""
Put selected settings variables into the default template context
"""
from django.conf import settings
return settings.GLOBAL_SETTINGS
This will resolve the DRY issue.
Or, if you only plan to use the global settings occasionally and want to call them from within the view:
def view_func(request):
from django.conf import settings
# function code here
ctx = {} #context variables here
ctx.update(settings.GLOBAL_SETTINGS)
# whatever output you want here
Consider putting it into settings.py of your application. Of course, in order to use it in template you will need to make it available to template as any other usual variable.
Use context processors to have your constants available in all templates (settings.py is a nice place to define them as Vladimir said).
Context processors are better suited at handling more dynamic object data--they're defined as a mapping in the documentation and in many of the posts here they're being modified or passed around to views--it doesn't make sense that a template may lose access to global information because, for example, your forgot to use a specialized context processor in the view. The data is global by definition & that couples the view to the template.
A better way is to define a custom template tag. This way:
templates aren't relying on views to have global information passed into them
it's DRY-er: the app defining the global settings can be exported to many projects, eliminating common code across projects
templates decide whether they have access to the global information, not the view functions
In the example below I deal with your problem--loading in this MIN_TIME_TEST variable--and a problem I commonly face, loading in URLs that change when my environment changes.
I have 4 environments--2 dev and 2 production:
Dev: django-web server, url: localhost:8000
Dev: apache web server: url: sandbox.com -> resolves to 127.0.0.1
Prod sandbox server, url: sandbox.domain.com
Prod server: url: domain.com
I do this on all my projects & keep all the urls in a file, global_settings.py so it's accessible from code. I define a custom template tag {% site_url %} that can be (optionally) loaded into any template
I create an app called global_settings, and make sure it's included in my settings.INSTALLED_APPS tuple.
Django compiles templated text into nodes with a render() method that tells how the data should be displayed--I created an object that renders data by returnning values in my global_settings.py based on the name passed in.
It looks like this:
from django import template
import global_settings
class GlobalSettingNode(template.Node):
def __init__(self, settingname):
self.settingname = settingname;
def render(self, context):
if hasattr(global_settings, self.settingname):
return getattr(global_settings, self.settingname)
else:
raise template.TemplateSyntaxError('%s tag does not exist' % self.settingname)
Now, in global_settings.py I register a couple tags: site_url for my example and min_test_time for your example. This way, when {% min_time_test %} is invoked from a template, it'll call get_min_time_test which resolves to load in the value=5. In my example, {% site_url %} will do a name-based lookup so that I can keep all 4 URLs defined at once and choose which environment I'm using. This is more flexible for me than just using Django's built in settings.Debug=True/False flag.
from django import template
from templatenodes import GlobalSettingNode
register = template.Library()
MIN_TIME_TEST = 5
DEV_DJANGO_SITE_URL = 'http://localhost:8000/'
DEV_APACHE_SITE_URL = 'http://sandbox.com/'
PROD_SANDBOX_URL = 'http://sandbox.domain.com/'
PROD_URL = 'http://domain.com/'
CURRENT_ENVIRONMENT = 'DEV_DJANGO_SITE_URL'
def get_site_url(parser, token):
return GlobalSettingNode(CURRENT_ENVIRONMENT)
def get_min_time_test(parser, token):
return GlobalSettingNode('MIN_TIME_TEST')
register.tag('site_url', get_site_url)
register.tag('min_time_test', get_min_time_test)
Note that for this to work, django is expecting global_settings.py to be located in a python packaged called templatetags under your Django app. My Django app here is called global_settings, so my directory structure looks like:
/project-name/global_settings/templatetags/global_settings.py
etc.
Finally the template chooses whether to load in global settings or not, which is beneficial for performance. Add this line to your template to expose all the tags registered in global_settings.py:
{% load global_settings %}
Now, other projects that need MIN_TIME_TEST or these environments exposed can simply install this app =)
In the context processor you can use something like:
import settings
context = {}
for item in dir(settings):
#use some way to exclude __doc__, __name__, etc..
if item[0:2] != '__':
context[item] = getattr(settings, item)
Variant on John Mee's last part, with a little elaboration on the same idea Jordan Reiter discusses.
Suppose you have something in your settings akin to what Jordan suggested -- in other words, something like:
GLOBAL_SETTINGS = {
'SOME_CONST': 'thingy',
'SOME_OTHER_CONST': 'other_thingy',
}
Suppose further you already have a dictionary with some of the variables you'd like to pass your template, perhaps passed as arguments to your view. Let's call it my_dict. Suppose you want the values in my_dict to override those in the settings.GLOBAL_SETTINGS dictionary.
You might do something in your view like:
def my_view(request, *args, **kwargs)
from django.conf import settings
my_dict = some_kind_of_arg_parsing(*args,**kwargs)
tmp = settings.GLOBAL_SETTINGS.copy()
tmp.update(my_dict)
my_dict = tmp
render_to_response('the_template.html', my_dict, context_instance=RequestContext(request))
This lets you have the settings determined globally, available to your templates, and doesn't require you to manually type out each of them.
If you don't have any additional variables to pass the template, nor any need to override, you can just do:
render_to_response('the_template.html', settings.GLOBAL_SETTINGS, context_instance=RequestContext(request))
The main difference between what I'm discussing here & what Jordan has, is that for his, settings.GLOBAL_SETTINGS overrides anything it may have in common w/ your context dictionary, and with mine, my context dictionary overrides settings.GLOBAL_SETTINGS. YMMV.