In a Django app, a lot of class-based views are called directly from the urls.py like so
url(r'^manage/thing/$',
ListView.as_view(model=Thing,
queryset=Thing.objects.filter(parent=None)),
name='mg_thing_list'),
and most CBVs are not subclassed, so views.py is almost empty. I want to highlight the active section in the navbar based on the URL. For example, pages at /manage/thing/... should have class="active" next to the "thing" item in the navbar HTML. What's the DRYest way of doing this?
I want to avoid subclassing the CBVs just to add the request to the template context (which the standard templatetag solution seems to require). Currently we do it in a dumb way: add a {% block %} for every navbar item tag and set the relevant block to class="active" in each and every template. Seems a waste.
First of all, it's not recommended to hold logic in urls.py, also there are 2 ways to go about your issue here
First a template context processor: (in myapp/context_processors.py)
def mytag(request):
context = {'class': 'active'}
return context
Then
<div class='{{ class }}'>
Finally add the template context processor to TEMPLATE_CONTEXT_PROCESSORS in settings.py or in Django 1.8
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': ['components/templates'],
'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',
'myapp.context_processors.mytag',
],
},
},
Also, just as a very common opinion on views, you should keep view logic
in views, that way if you needed to build some mixins you could. Keep urls small.
You can use this snippet to add active class to your html code. It does a reverse resolve based on the url name parameter.
In your_app/templatetags/base_utils.py
from django import template
from django.core import urlresolvers
register = template.Library()
#register.simple_tag(takes_context=True)
def current(context, url_name, return_value=' active', **kwargs):
matches = current_url_equals(context, url_name, **kwargs)
return return_value if matches else ''
def current_url_equals(context, url_name, **kwargs):
resolved = False
try:
resolved = urlresolvers.resolve(context.get('request').path)
except:
pass
matches = resolved and resolved.url_name == url_name
if matches and kwargs:
for key in kwargs:
kwarg = kwargs.get(key)
resolved_kwarg = resolved.kwargs.get(key)
if kwarg:
# for the comparison of same type url arg d+ w+
kwarg = unicode(kwarg)
if not resolved_kwarg or kwarg != resolved_kwarg:
return False
return matches
In your_template.html
{% load base_utils %}
<li class="{% current 'your_url_name' param1=param1 param_N=param_N %}"> This is NavBar Button </li>
I have had the question if I understand you correctly, and I solved it by using jQuery like this:
function mainNavListAddClass() {
var theA = $("a[href=" + "'" + getCurrentUrlPath() + "'" + "]");
if (theA.length) {
theA.first().addClass("active");
}
}
function getCurrentUrlPath() {
return location.pathname;
}
Related
This question already has an answer here:
Django - How to make a variable available to all templates?
(1 answer)
Closed 1 year ago.
I'm very new to Django and in my application I have a dynamic navbar on top. The context are passed in as context. Imagine it looks like this:
<ul>
{% for link in links %}
<li></li>
{% endif %}
</ul>
The views may look like this:
def nav_links():
# Display links to first 5 MyModel objects
objects = MyModel.objects.all()[:5]
return [o.get_absolute_url() for o in objects]
def index(request):
return render("request", "app/index.html", context={"links": nav_links()})
def page1(request)
some_data = ...
return render("request", "app/page1.html", context={"links": nav_links(), "data": some_data})
def page2(request)
return render("request", "app/page2.html", context={"links": nav_links()})
...
Currently, I need to pass in links as context in each of the views. This is obviously bad design.
Is there a way to pass in the same context for all views?
You can work with a context processor, this is a function that each time runs to populate the context. Such context processor looks like your nav_links, except that it should be a dictionary with the variable name(s) and corresponding value(s):
# app_name/context_processors.py
def links(request):
from app_name.models import MyModel
objects = MyModel.objects.all()[:5]
return {'links': [o.get_absolute_url() for o in objects]}
Then you can register the context processor in the settings.py with:
# settings.py
# …
TEMPLATES = [
{
# …,
'OPTIONS': {
'context_processors': [
# …,
'app_name.context_processors.links'
],
},
},
]
I'm building a dashboard that has a button on a sidebar that allows a user to update their data. Which currently looks like this:
<a class="nav-link" href="{% url 'form-update' pk=id %}"> Questionnaire </a>
I'm trying to connect that id field to my views.py:
def dashboard(request):
user = request.user
account = 0
id = Form.objects.filter(author=user,account=account).values('id').get('id')
context = {
'id':id
}
return render(request, 'dashboard/index.html',context)
url pattern:
path('update/<int:pk>', FormUpdateView.as_view(),name='form-update')
I'm not sure the best way to send this data to the href tag?
EDIT
It seems to be an issue because this dashboard html file is a base HTML file that I use with other templates. If I load the data in a child template than it loads fine, just not at the parent html file.
views.py:
def dashboard(request):
user = request.user
account = 0
id = Form.objects.get(author=user,account=account)
context = {
'id':id.id
}
return render(request, 'dashboard/index.html',context)
urls.py:
<a class="nav-link" href="{% url 'form-update' pk=id %}"> Questionnaire </a>
If anyone stumbles across this, the issue actually arose from passing a variable to a base HTML file. In this case, you have to create a file in your app (context_processors.py) then create a function in there:
def add_variable_to_context(request):
user = request.user
account = 0
pk = Form.objects.filter(author=user,account=account).values('id').last().get('id')
return {
'id': pk
}
Then register this py file in settings.py context_processors.
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',
'dashboard.context_processors.add_variable_to_context',
],
},
},
]
You can then call that variable in the normal fashion.
<a class="nav-link" href="{% url 'form-update' pk=id %}">Questionnaire</a>
I need to check if the url is exactly:
http://127.0.0.1:8000/shop/
and based on this render a header.
If it is, for example:
http://127.0.0.1:8000/shop/stickers/stickers-por-hoja/medida-y-cantidad
Header shouldn't render.
I've read you can do something like this:
{% if "/shop/$" in request.path %}
{% include 'header.html' %}
{% endif %}
But this doesn't work. On the other hand this works, but is not what I need:
{% if "/shop/" in request.path %}
{% include 'header.html' %}
{% endif %}
So, Can I use regex in the if condition?
Create a context processor has_shop_in_url.py having this code below.
import re
def has_shop(request):
shop_in_request = your_regex_validation
return {
'has_shop_in_url': any([shop_in_request]) }
Add that context processor
TEMPLATES = [
{
.....
'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',
'your_module.has_shop_in_url.has_shop' # YOUR PROCESSOR
],
},
},
]
Now it is available for your whole web application. In template just write {{has_shop_in_url}}. Your can see True/False.
You could move the regex check into the view and add a field to the context for the template like so:
class MyView(View):
def action(self, request, *args, **kwargs):
shop_in_request = re.findall(r"/shop/$", request.path)
context = {"include_header": any(shop_in_request)}
return render(template, context)
Then in your view you could use:
{% if include_header %}
{% include 'header.html' %}
{% endif %}
Create a template tag to do that.
The url whitelisted must be somewhere in the settings. For example you can add something like this
HEADER_WHITELIST_URLS = (
'regex1',
'regex2',
)
2) Your template tags will check the current URL and if one of the regex matches, you can render your header. In a hypothetical version2, you can also add the header to show for a specific regex.
Use this snippet as starting point for your template tag
import datetime
from django import template
from django.conf import settings
register = template.Library()
#register.simple_tag(takes_context=True)
def render_header(context, format_string):
regexs = settings.HEADER_WHITELIST_URLS
# maybe you can add some try/exception if the settings is not available.
for regex in regexs:
# test the regex with the url in the context.
# if matches return the code to render the header and break
return None
Check here for more details about the template tags: https://docs.djangoproject.com/en/2.1/howto/custom-template-tags/#writing-custom-template-tags
I'm using Django 2.0
I have a Model within my notes application
notes/models.py
class ColorLabels(models.Model):
title = models.CharField(max_lenght=50)
In the sidebar navigation which will be displayed on all pages, I want to show the list of color labels and it will show on all pages.
How can I show dynamic data in all views or say on all pages?
Use a custom template context processor.
This will add the context (data) you need into the templates rendered by all views.
First create the context processor (notes/context_processors.py):
from .models import ColorLabel
def color_labels(request):
return {'color_labels': ColorLabel.objects.all()}
Then add it to the context_processors option of your template renderer in settings.py:
TEMPLATES = [{
'BACKEND': '...',
'OPTIONS': {
'context_processors': [
'notes.context_processors.color_labels',
...
],
},
}]
Finally, you will be able to use it in your templates:
templates/base.html
<nav>
{% for color_label in color_labels %}
{{ color_label.title }}
{% endfor %}
</nav>
I am reading the definitive guide to django and am in Chapter 4 on template inheritance. It seems that I am not doing something as elegant as should be possible as I am having to duplicate some code for the context to appear when calling the child view. Here is the code in views.py:
def homepage(request):
current_date = datetime.datetime.now()
current_section = 'Temporary Home Page'
return render_to_response("base.html", locals())
def contact(request):
current_date = datetime.datetime.now()
current_section = 'Contact page'
return render_to_response("contact.html", locals())
It seems redundant to have to include the current_date line in each function.
Here is the base html file that homepage calls:
<html lang= "en">
<head>
<title>{% block title %}Home Page{% endblock %}</title>
</head>
<body>
<h1>The Site</h1>
{% block content %}
<p> The Current section is {{ current_section }}.</p>
{% endblock %}
{% block footer %}
<p>The current time is {{ current_date }}</p>
{% endblock %}
</body>
</html>
and a child template file:
{% extends "base.html" %}
{% block title %}Contact{% endblock %}
{% block content %}
<p>Contact information goes here...</p>
<p>You are in the section {{ current_section }}</p>
{% endblock %}
If I don't include the current_date line when calling the child file, where that variable should appear is blank.
You can pass a variable to every template by using a Context Processor:
1. Adding the context processor to your settings file
First, you will need to add your custom Context Processor to your settings.py:
# settings.py
TEMPLATE_CONTEXT_PROCESSORS = (
'myapp.context_processors.default', # add this line
'django.core.context_processors.auth',
)
From that you can derive that you will need to create a module called context_processors.py and place it inside your app's folder. You can further see that it will need to declare a function called default (as that's what we included in settings.py), but this is arbitrary. You can choose whichever function name you prefer.
2. Creating the Context Processor
# context_processors.py
from datetime import datetime
from django.conf import settings # this is a good example of extra
# context you might need across templates
def default(request):
# you can declare any variable that you would like and pass
# them as a dictionary to be added to each template's context:
return dict(
example = "This is an example string.",
current_date = datetime.now(),
MEDIA_URL = settings.MEDIA_URL, # just for the sake of example
)
3. Adding the extra context to your views
The final step is to process the additional context using RequestContext() and pass it to the template as a variable. Below is a very simplistic example of the kind of modification to the views.py file that would be required:
# old views.py
def homepage(request):
current_date = datetime.datetime.now()
current_section = 'Temporary Home Page'
return render_to_response("base.html", locals())
def contact(request):
current_date = datetime.datetime.now()
current_section = 'Contact page'
return render_to_response("contact.html", locals())
# new views.py
from django.template import RequestContext
def homepage(request):
current_section = 'Temporary Home Page'
return render_to_response("base.html", locals(),
context_instance=RequestContext(request))
def contact(request):
current_section = 'Contact page'
return render_to_response("contact.html", locals(),
context_instance=RequestContext(request))
So, you can use django.views,generic.simple.direct_to_template instead of render_to_response. It uses RequestContext internaly.
from django.views,generic.simple import direct_to_template
def homepage(request):
return direct_to_template(request,"base.html",{
'current_section':'Temporary Home Page'
})
def contact(request):
return direct_to_template(request,"contact.html",{
'current_section':'Contact Page'
})
Or you can even specify it directly at urls.py such as
urlpatterns = patterns('django.views.generic.simple',
(r'^/home/$','direct_to_template',{
'template':'base.html'
'extra_context':{'current_section':'Temporary Home Page'},
}),
(r'^/contact/$','direct_to_template',{
'template':'contact.html'
'extra_context':{'current_section':'Contact page'},
}),
For django v1.8+ variables returned inside context processor can be accessed.
1. Add the context processor to your TEMPLATES list inside settings.py
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',
'your_app.context_processor_file.func_name', # add this line
],
},
},
]
2. Create new file for context processor and define method for context
context_processor_file.py
def func_name(request):
test_var = "hi, this is a variable from context processor"
return {
"var_for_template" : test_var,
}
3. Now you can get the var_for_template in any templates
for example, add this line inside: base.html
<h1>{{ var_for_template }}</h1>
this will render:
<h1>hi, this is a variable from context processor</h1>
for updating templates to django 1.8+ follow this django doc