How to concatenate strings in django templates? - django

I want to concatenate a string in a Django template tag, like:
{% extend shop/shop_name/base.html %}
Here shop_name is my variable and I want to concatenate this with rest of path.
Suppose I have shop_name=example.com and I want result to extend shop/example.com/base.html.

Use with:
{% with "shop/"|add:shop_name|add:"/base.html" as template %}
{% include template %}
{% endwith %}

Don't use add for strings, you should define a custom tag like this :
Create a file : <appname>\templatetags\<appname>_extras.py
from django import template
register = template.Library()
#register.filter
def addstr(arg1, arg2):
"""concatenate arg1 & arg2"""
return str(arg1) + str(arg2)
and then use it as #Steven says
{% load <appname>_extras %}
{% with "shop/"|addstr:shop_name|addstr:"/base.html" as template %}
{% include template %}
{% endwith %}
Reason for avoiding add:
According to the docs
This filter will first try to coerce both values to integers...
Strings that can be coerced to integers will be summed, not concatenated...
If both variables happen to be integers, the result would be unexpected.

I have changed the folder hierarchy
/shop/shop_name/base.html To /shop_name/shop/base.html
and then below would work.
{% extends shop_name|add:"/shop/base.html"%}
Now its able to extend the base.html page.

You do not need to write a custom tag. Just evaluate the vars next to each other.
"{{ shop name }}{{ other_path_var}}"

Refer to Concatenating Strings in Django Templates:
For earlier versions of Django:
{{ "Mary had a little"|stringformat:"s lamb." }}
"Mary had a little lamb."
Else:
{{ "Mary had a little"|add:" lamb." }}
"Mary had a little lamb."

Have a look at the add filter.
Edit: You can chain filters, so you could do "shop/"|add:shop_name|add:"/base.html". But that won't work because it is up to the template tag to evaluate filters in arguments, and extends doesn't.
I guess you can't do this within templates.

From the docs:
This tag can be used in two ways:
{% extends "base.html" %} (with quotes) uses the literal value "base.html" as the name of the parent template to extend.
{% extends variable %} uses the value of variable. If the variable evaluates to a string, Django will use that string as the name of the parent template. If the variable evaluates to a Template object, Django will use that object as the parent template.
So seems like you can't use a filter to manipulate the argument. In the calling view you have to either instantiate the ancestor template or create an string variable with the correct path and pass it with the context.

I found working with the {% with %} tag to be quite a hassle. Instead I created the following template tag, which should work on strings and integers.
from django import template
register = template.Library()
#register.filter
def concat_string(value_1, value_2):
return str(value_1) + str(value_2)
Then load the template tag in your template at the top using the following:
{% load concat_string %}
You can then use it the following way:
123
I personally found this to be a lot cleaner to work with.

How about this! We have first_name and last_name, which we want to display space " " separated.
{% with first_name|add:' '|add:last_name as name %}
<h1>{{ name }}</h1>
{% endwith %}
What we're actually doing is: first_name + ' ' + last_name

#error's answer is fundamentally right, you should be using a template tag for this. However, I prefer a slightly more generic template tag that I can use to perform any kind of operations similar to this:
from django import template
register = template.Library()
#register.tag(name='captureas')
def do_captureas(parser, token):
"""
Capture content for re-use throughout a template.
particularly handy for use within social meta fields
that are virtually identical.
"""
try:
tag_name, args = token.contents.split(None, 1)
except ValueError:
raise template.TemplateSyntaxError("'captureas' node requires a variable name.")
nodelist = parser.parse(('endcaptureas',))
parser.delete_first_token()
return CaptureasNode(nodelist, args)
class CaptureasNode(template.Node):
def __init__(self, nodelist, varname):
self.nodelist = nodelist
self.varname = varname
def render(self, context):
output = self.nodelist.render(context)
context[self.varname] = output
return ''
and then you can use it like this in your template:
{% captureas template %}shop/{{ shop_name }}/base.html{% endcaptureas %}
{% include template %}
As the comment mentions, this template tag is particularly useful for information that is repeatable throughout a template but requires logic and other things that will bung up your templates, or in instances where you want to re-use data passed between templates through blocks:
{% captureas meta_title %}{% spaceless %}{% block meta_title %}
{% if self.title %}{{ self.title }}{% endif %}
{% endblock %}{% endspaceless %} - DEFAULT WEBSITE NAME
{% endcaptureas %}
and then:
<title>{{ meta_title }}</title>
<meta property="og:title" content="{{ meta_title }}" />
<meta itemprop="name" content="{{ meta_title }}">
<meta name="twitter:title" content="{{ meta_title }}">
Credit for the captureas tag is due here: https://www.djangosnippets.org/snippets/545/

And multiple concatenation:
from django import template
register = template.Library()
#register.simple_tag
def concat_all(*args):
"""concatenate all args"""
return ''.join(map(str, args))
And in Template:
{% concat_all 'x' 'y' another_var as string_result %}
concatenated string: {{ string_result }}

You can't do variable manipulation in django templates.
You have two options, either write your own template tag or do this in view,

extends has no facility for this. Either put the entire template path in a context variable and use that, or copy the exist template tag and modify it appropriately.

In my project I did it like this:
#register.simple_tag()
def format_string(string: str, *args: str) -> str:
"""
Adds [args] values to [string]
String format [string]: "Drew %s dad's %s dead."
Function call in template: {% format_string string "Dodd's" "dog's" %}
Result: "Drew Dodd's dad's dog's dead."
"""
return string % args
Here, the string you want concatenate and the args can come from the view, for example.
In template and using your case:
{% format_string 'shop/%s/base.html' shop_name as template %}
{% include template %}
The nice part is that format_string can be reused for any type of string formatting in templates

In my case I needed to concatenate to send a string concatenated by parameter to a simple_tag and I didn't need the with, which saves 2 lines:
{% method firstParam "stringSecondParam="|add:valueSecondParam thirdParam as result %}
In this case the solution to the problem would be: "string="|add:object

Related

Access a variable from template tag that takes arguments

I have a template tags that takes ID on template and returns a list, I want to check the list if the variable is available and make checkbox input checked.
`_checkbox.html`
{% load get_previous_response %}
{% user_response question.id %} {# this returns a list of ids #}
So I want to do something like this
<input type="checkbox" {% if option.id in user_response %}checked{% endif %}>
problem is the above won't work, I tried django templatetag `with template context`
My main goal is I want to access the list returned by {% user_response question.id %}.
EDIT
My custom templatetag.
get_previous_response.py
from django import template
from survey.models import Question
register = template.Library()
#register.simple_tag(takes_context=True)
def user_response(context, question_id):
question = Question.objects.filter(id=question_id).first()
user = context['request'].user
response = question.get_simple_answer(user)
return response
I think there are a couple of ways to achieve this.
From the django docs on simple tags:
It’s possible to store the tag results in a template variable rather than directly outputting it. This is done by using the as argument followed by the variable name. Doing so enables you to output the content yourself where you see fit
In your case, that would be something like this:
{% user_response question.id as user_responses %}
Another approach would be to add the list in the request context, and access it from there. The downside is that this will be available in all your templates(which I guess is not what you want).
See this SO answer on how to do that.
Implement a Templatetag class and set the context in the render method. Here is the Django doc for how to do that: https://docs.djangoproject.com/en/3.2/howto/custom-template-tags/#setting-a-variable-in-the-context
You can try:
{% if option.id in user_response %}
<input type="checkbox" checked>
{% else %}
<input type="checkbox">
{% endif %}

How to replace placeholders in placeholders (variables) in Django?

In a Django template, I'd like to add text from a model field. This text field can be understood as text-template itself. It may look like:
Dear {{user}},
thank your for...
Regards,
{{sender}}
This text field is available as emailtemplate inside a normal Django template. The fields from above (user=Joe, sender=Alice) are available as well.
{% extends 'base.html' %}
{% block content %}
{{ emailtemplate }}
{% endblock %}
The output shall be as follows
Dear Joe,
thank your for...
Regards,
Alice
I have no clue how to do this with built-in methods. My only idea is to parse emailtemplate manually before I hand it over to the template engine, thus inside the view. But I'm sure, I'm not the first one with that problem.
After several reworks, I came up with the following solution. Double check who can modify/alter the template string since this could be a big security flaw, if alterable by the wrong people.
views.py:
class YourView(TemplateView):
template_name = 'page.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["emailtemplate"] = "Dear {{user}}, {{sender}}"
context["user"] = "Joe"
context["sender"] = "Alice"
return context
page.html:
{% extends 'base.html' %}
{% load core_tags %}
{% block content %}
{% foobar emailtemplate %}
{% endblock %}
your_app/templatetags/core_tags.py (don't forget the __init__.py file to ensure the directory is treated as a Python package your_app must also be in INSTALLED_APPS):
from django import template
from django.template import Template
register = template.Library()
#register.simple_tag(takes_context=True)
def foobar(context, ts):
t = Template(template_string=ts)
return t.render(context)
See also:
https://docs.djangoproject.com/en/2.0/howto/custom-template-tags/#code-layout
https://docs.djangoproject.com/en/2.0/howto/custom-template-tags/#simple-tags

How to access an attribute of an object using a variable in django template?

How can I access an attribute of an object using a variable? I have something like this:
{% for inscrito in inscritos %}
{% for field in list_fields_inscrito %}
{{inscrito.field}} //here is the problem
{% endfor %}
{% endfor %}
For example Inscrito have: inscrito.id, inscrito.Name and inscrito.Adress and I only want to print inscrito.id and inscrito.Name because id and Name are in the list_fields_inscrito.
Does anybody know how do this?
You can write a template filter for that:
myapp/templatetags/myapp_tags.py
from django import template
register = template.Library()
#register.filter
def get_obj_attr(obj, attr):
return getattr(obj, attr)
Then in template you can use it like this:
{% load myapp_tags %}
{% for inscrito in inscritos %}
{% for field in list_fields_inscrito %}
{{ inscrito|get_obj_attr:field }}
{% endfor %}
{% endfor %}
You can read more about writing custom template tags.
Fixed answer for non string attributes
The selected answer don't cover cases where you need to access non string attributes.
If you are trying to access an attribute that isn't a string, then you must use this code:
from django import template
register = template.Library()
#register.filter
def get_obj_attr(obj, attr):
return obj[attr]
For this, create a folder named templatetags on your app's folder, then create a python file with whatever name you want and paste the code above
inside.
Inside your template load your brand new filter using the {% load YOUR_FILE_NAME %}, be sure to change YOUR_FILE_NAME to your actual file name.
Now, on your template you can access the object attribute by using the code bellow:
{{ PUT_THE_NAME_OF_YOUR_OBJECT_HERE|get_obj_attr:PUT_THE_ATTRIBUTE_YOU_WANT_TO_ACCESS_HERE }}

Can you make a custom template tag that returns a queryset? If yes, how? - Django

Let's make this very easy for my fellow SOians(?).
This is how normally the custom template tags work -
Template ->
{% block content %}
blah blah blah
{% custom_tag_load %}
{% endblock %}
The custom_tag_load is called and it returns a string. What I want to return is a queryset which I could possibly use like this ->
{% block content %}
blah blah blah
{% for x in custom_tag_load %}
{{ x.datetime }}
{% endfor %}
{% endblock %}
Note -> What I'm basically trying to do is to avoid passing the queryset through the view, and I'm not sure if I should be comfortable storing querysets in my global context.
You can return anything you like from a tag, including a queryset. However, you can't use a tag inside the for tag - you can only use a variable there (or a variable passed through a filter). What you could do is get your tag to put the queryset into a variable in the context, and use that variable in the for loop. See the docs on how to set a variable from a tag - although note that the development version has an easier method for doing this.
However, you shouldn't be concerned about putting a queryset into a context processor, either. Don't forget that querysets are lazy, so no database hit will be made unless the queryset is evaluated or iterated in the template.
A template tag can do whatever you want. From your pseudo code, you could accomplish what you need with an inclusion tag:
#my_tags.py
from django import template
from my_app.models import MyModel
register = template.Library()
#register.inclusion_tag('my_template.html')
def my_custom_tag():
things = MyModel.objects.all()
return {'things' : things}
#my_template.html
{% if things %}
<ul>
{% for thing in things %}
<li>{{ thing }}</li>
{% empty %}
<li>Sorry, no things yet.</li>
{% endfor %}
</ul>
{% endif %}
#the_view.html
{% load my_tags %}
{% my_custom_tag %}
Alternatively, you could write a custom tag that adds a queryset to the context. Hope that helps you out.
I had these same problems recently and most of the answers here were kind of outdated, but a little digging through Django's documentation and I was able to sort it out.
Like most of the answers above, you can return basically anything using a template tag, but it all depends on how you register the template tags. So say you want to use a template tag to return a queryset to be available for any template you wish, you could register the template tag as a simple tag just like this
from django import template
from blog.models import Post
from django.template.defaulttags import register
register = template.Library()
#register.simple_tag
def get_posts():
return Post.objects.all()
Then to be able to access this in your template, you first need to load this file in your template like
{% load templatetagfile %}
And then to loop through, you need to first assign it to a variable before looping through
{% get_posts as posts %}
{% for post in posts %}
{{ post.whatever }}
{% endfor %}
The first line there makes the queryset from the get_posts function available as a variable named posts which you can then loop through.

How to implement breadcrumbs in a Django template?

Some solutions provided on doing a Google search for "Django breadcrumbs" include using templates and block.super, basically just extending the base blocks and adding the current page to it. http://www.martin-geber.com/thought/2007/10/25/breadcrumbs-django-templates/
http://www.djangosnippets.org/snippets/1289/ - provides a template tag but I'm not sure this would work if you don't have your urls.py properly declared.
I'm wondering what's the best way? And if you have implemented breadcrumbs before how did you do it?
--- Edit --
My question was meant to be: is there a general accepted method of doing breadcrumbs in Django, but from the answers I see there is not, and there are many different solutions, I'm not sure who to award the correct answer to, as I used a variation of using the block.super method, while all the below answers would work.
I guess then this is too much of a subjective question.
Note: I provide the full snippet below, since djangosnippets has been finicky lately.
Cool, someone actually found my snippet :-) The use of my template tag is rather simple.
To answer your question there is no "built-in" django mechanism for dealing with breadcrumbs, but it does provide us with the next best thing: custom template tags.
Imagine you want to have breadcrumbs like so:
Services -> Programming
Services -> Consulting
Then you will probably have a few named urls: "services", and "programming", "consulting":
(r'^services/$',
'core.views.services',
{},
'services'),
(r'^services/programming$',
'core.views.programming',
{},
'programming'),
(r'^services/consulting$',
'core.views.consulting',
{},
'consulting'),
Now inside your html template (lets just look at consulting page) all you have to put is:
//consulting.html
{% load breadcrumbs %}
{% block breadcrumbs %}
{% breadcrumb_url 'Services' services %}
{% breadcrumb_url 'Consulting' consulting %}
{% endblock %}
If you want to use some kind of custom text within the breadcrumb, and don't want to link it, you can use breadcrumb tag instead.
//consulting.html
{% load breadcrumbs %}
{% block breadcrumbs %}
{% breadcrumb_url 'Services' services %}
{% breadcrumb_url 'Consulting' consulting %}
{% breadcrumb 'We are great!' %}
{% endblock %}
There are more involved situations where you might want to include an id of a particular object, which is also easy to do. This is an example that is more realistic:
{% load breadcrumbs %}
{% block breadcrumbs %}
{% breadcrumb_url 'Employees' employee_list %}
{% if employee.id %}
{% breadcrumb_url employee.company.name company_detail employee.company.id %}
{% breadcrumb_url employee.full_name employee_detail employee.id %}
{% breadcrumb 'Edit Employee ' %}
{% else %}
{% breadcrumb 'New Employee' %}
{% endif %}
{% endblock %}
DaGood breadcrumbs snippet
Provides two template tags to use in your HTML templates: breadcrumb and breadcrumb_url. The first allows creating of simple url, with the text portion and url portion. Or only unlinked text (as the last item in breadcrumb trail for example). The second, can actually take the named url with arguments! Additionally it takes a title as the first argument.
This is a templatetag file that should go into your /templatetags directory.
Just change the path of the image in the method create_crumb and you are good to go!
Don't forget to {% load breadcrumbs %} at the top of your html template!
from django import template
from django.template import loader, Node, Variable
from django.utils.encoding import smart_str, smart_unicode
from django.template.defaulttags import url
from django.template import VariableDoesNotExist
register = template.Library()
#register.tag
def breadcrumb(parser, token):
"""
Renders the breadcrumb.
Examples:
{% breadcrumb "Title of breadcrumb" url_var %}
{% breadcrumb context_var url_var %}
{% breadcrumb "Just the title" %}
{% breadcrumb just_context_var %}
Parameters:
-First parameter is the title of the crumb,
-Second (optional) parameter is the url variable to link to, produced by url tag, i.e.:
{% url person_detail object.id as person_url %}
then:
{% breadcrumb person.name person_url %}
#author Andriy Drozdyuk
"""
return BreadcrumbNode(token.split_contents()[1:])
#register.tag
def breadcrumb_url(parser, token):
"""
Same as breadcrumb
but instead of url context variable takes in all the
arguments URL tag takes.
{% breadcrumb "Title of breadcrumb" person_detail person.id %}
{% breadcrumb person.name person_detail person.id %}
"""
bits = token.split_contents()
if len(bits)==2:
return breadcrumb(parser, token)
# Extract our extra title parameter
title = bits.pop(1)
token.contents = ' '.join(bits)
url_node = url(parser, token)
return UrlBreadcrumbNode(title, url_node)
class BreadcrumbNode(Node):
def __init__(self, vars):
"""
First var is title, second var is url context variable
"""
self.vars = map(Variable,vars)
def render(self, context):
title = self.vars[0].var
if title.find("'")==-1 and title.find('"')==-1:
try:
val = self.vars[0]
title = val.resolve(context)
except:
title = ''
else:
title=title.strip("'").strip('"')
title=smart_unicode(title)
url = None
if len(self.vars)>1:
val = self.vars[1]
try:
url = val.resolve(context)
except VariableDoesNotExist:
print 'URL does not exist', val
url = None
return create_crumb(title, url)
class UrlBreadcrumbNode(Node):
def __init__(self, title, url_node):
self.title = Variable(title)
self.url_node = url_node
def render(self, context):
title = self.title.var
if title.find("'")==-1 and title.find('"')==-1:
try:
val = self.title
title = val.resolve(context)
except:
title = ''
else:
title=title.strip("'").strip('"')
title=smart_unicode(title)
url = self.url_node.render(context)
return create_crumb(title, url)
def create_crumb(title, url=None):
"""
Helper function
"""
crumb = """<span class="breadcrumbs-arrow">""" \
"""<img src="/media/images/arrow.gif" alt="Arrow">""" \
"""</span>"""
if url:
crumb = "%s<a href='%s'>%s</a>" % (crumb, url, title)
else:
crumb = "%s %s" % (crumb, title)
return crumb
The Django admin view modules have automatic breadcumbs, which are implemented like this:
{% block breadcrumbs %}
<div class="breadcrumbs">
{% trans 'Home' %}
{% block crumbs %}
{% if title %} › {{ title }}{% endif %}
{% endblock %}
</div>
{% endblock %}
So there is some kind of built-in support for this..
My view functions emit the breadcrumbs as a simple list.
Some information is kept in the user's session. Indirectly, however, it comes from the URL's.
Breadcrumbs are not a simple linear list of where they've been -- that's what browser history is for. A simple list of where they've been doesn't make a good breadcrumb trail because it doesn't reflect any meaning.
For most of our view functions, the navigation is pretty fixed, and based on template/view/URL design. In our cases, there's a lot of drilling into details, and the breadcrumbs reflect that narrowing -- we have a "realm", a "list", a "parent" and a "child". They form a simple hierarchy from general to specific.
In most cases, a well-defined URL can be trivially broken into a nice trail of breadcrumbs. Indeed, that's one test for good URL design -- the URL can be interpreted as breadcrumbs and displayed meaningfully to the users.
For a few view functions, where we present information that's part of a "many-to-many" join, for example, there are two candidate parents. The URL may say one thing, but the session's context stack says another.
For that reason, our view functions have to leave context clues in the session so we can emit breadcrumbs.
Try django-breadcrumbs — a pluggable middleware that add a breadcrumbs callable/iterable in your request object.
It supports simple views, generic views and Django FlatPages app.
I had the same issue and finally I've made simple django tempalate tag for it: https://github.com/prymitive/bootstrap-breadcrumbs
http://www.djangosnippets.org/snippets/1289/ - provides a template tag but i'm not sure this would work if you don't have your urls.py properly declared.
Nothing will work if you don't have your urls.py properly declared. Having said that, it doesn't look as though it imports from urls.py. In fact, it looks like to properly use that tag, you still have to pass the template some variables. Okay, that's not quite true: indirectly through the default url tag, which the breadcrumb tag calls. But as far as I can figure, it doesn't even actually call that tag; all occurrences of url are locally created variables.
But I'm no expert at parsing template tag definitions. So say somewhere else in the code it magically replicates the functionality of the url tag. The usage seems to be that you pass in arguments to a reverse lookup. Again, no matter what your project is, you urls.py should be configured so that any view can be reached with a reverse lookup. This is especially true with breadcrumbs. Think about it:
home > accounts > my account
Should accounts, ever hold an arbitrary, hardcoded url? Could "my account" ever hold an arbitrary, hardcoded url? Some way, somehow you're going to write breadcrumbs in such a way that your urls.py gets reversed. That's really only going to happen in one of two places: in your view, with a call to reverse, or in the template, with a call to a template tag that mimics the functionality of reverse. There may be reasons to prefer the former over the latter (into which the linked snippet locks you), but avoiding a logical configuration of your urls.py file is not one of them.
Try django-mptt.
Utilities for implementing Modified Preorder Tree Traversal (MPTT) with your Django Model classes and working with trees of Model instances.
This answer is just the same as #Andriy Drozdyuk (link). I just want to edit something so it works in Django 3.2 (in my case) and good in bootstrap too.
for create_crumb function (Remove the ">" bug in the current code)
def create_crumb(title, url=None):
"""
Helper function
"""
if url:
crumb = '<li class="breadcrumb-item">{}</li>'.format(url, title)
else:
crumb = '<li class="breadcrumb-item active" aria-current="page">{}</li>'.format(title)
return crumb
And for __init__ in BreadcrumbNode, add list() to make it subscriptable. And change smart_unicode to smart_text in render method
from django.utils.encoding import smart_text
class BreadcrumbNode(Node):
def __init__(self, vars):
"""
First var is title, second var is url context variable
"""
self.vars = list(map(Variable, vars))
def render(self, context):
title = self.vars[0].var
if title.find("'")==-1 and title.find('"')==-1:
try:
val = self.vars[0]
title = val.resolve(context)
except:
title = ''
else:
title=title.strip("'").strip('"')
title=smart_text(title)
And add this in base.html for the view for Bootstrap. Check the docs
<nav style="--bs-breadcrumb-divider: '>';" aria-label="breadcrumb">
<ol class="breadcrumb">
{% block breadcrumbs %}
{% endblock breadcrumbs %}
</ol>
</nav>
Obviously, no one best answer, but for practical reason I find that it is worth considering the naïve way. Just overwrite and rewrite the whole breadcrumb... (at least until the official django.contrib.breadcrumb released )
Without being too fancy, it is better to keep things simple. It helps the newcomer to understand. It is extremely customizable (e.g. permission checking, breadcrumb icon, separator characters, active breadcrumb, etc...)
Base Template
<!-- File: base.html -->
<html>
<body>
{% block breadcrumb %}
<ul class="breadcrumb">
<li>Dashboard</li>
</ul>
{% endblock breadcrumb %}
{% block content %}{% endblock content %}
</body>
</html>
Implementation Template
Later on each pages we rewrite and overwrite the whole breadcrumb block.
<!-- File: page.html -->
{% extends 'base.html' %}
{% block breadcrumb %}
<ul class="breadcrumb">
<li>Dashboard</li>
<li>Level 1</li>
<li class="active">Level 2</li>
</ul>
{% endblock breadcrumb %}
Practicallity
Realworld use cases:
Django Oscar: base template, simple bread
Django Admin: base template, simple bread, permission check breadcrumb
You could also reduce the boiler plate required to manage breadcrumbs using django-view-breadcrumbs, by adding a crumbs property to the view.
urls.py
urlpatterns = [
...
path('posts/<slug:slug>', views.PostDetail.as_view(), name='post_detail'),
...
]
views.py
from django.views.generic import DetailView
from view_breadcrumbs import DetailBreadcrumbMixin
class PostDetail(DetailBreadcrumbMixin, DetailView):
model = Post
template_name = 'app/post/detail.html'
base.html
{% load django_bootstrap_breadcrumbs %}
{% block breadcrumbs %}
{% render_breadcrumbs %}
{% endblock %}
Something like this may work for your situation:
Capture the entire URL in your view and make links from it. This will require modifying your urls.py, each view that needs to have breadcrumbs, and your templates.
First you would capture the entire URL in your urls.py file
original urls.py
...
(r'^myapp/$', 'myView'),
(r'^myapp/(?P<pk>.+)/$', 'myOtherView'),
...
new urls.py
...
(r'^(?P<whole_url>myapp/)$', 'myView'),
(r'^(?P<whole_url>myapp/(?P<pk>.+)/)$', 'myOtherView'),
...
Then in your view something like:
views.py
...
def myView(request, whole_url):
# dissect the url
slugs = whole_url.split('/')
# for each 'directory' in the url create a piece of bread
breadcrumbs = []
url = '/'
for slug in slugs:
if slug != '':
url = '%s%s/' % (url, slug)
breadcrumb = { 'slug':slug, 'url':url }
breadcrumbs.append(breadcrumb)
objects = {
'breadcrumbs': breadcrumbs,
}
return render_to_response('myTemplate.html', objects)
...
Which should be pulled out into a function that gets imported into the views that need it
Then in your template print out the breadcrumbs
myTemplate.html
...
<div class="breadcrumb-nav">
<ul>
{% for breadcrumb in breadcrumbs %}
<li>{{ breadcrumb.slug }}</li>
{% endfor %}
</ul>
</div>
...
One shortcoming of doing it this way is that as it stands you can only show the 'directory' part of the url as the link text. One fix for this off the top of my head (probably not a good one) would be to keep a dictionary in the file that defines the breadcrumb function.
Anyways that's one way you could accomplish breadcrumbs, cheers :)
You might want to try django-headcrumbs (don’t worry, they are not going to eat your brains).
It’s very lightweight and absolutely straightforward to use, all you have to do is annotate your views (because defining crumbs structure in templates sounds crazy to me) with a decorator that explains how to get back from the given view.
Here is an example from the documentation:
from headcrumbs.decorators import crumb
from headcrumbs.util import name_from_pk
#crumb('Staff') # This is the root crumb -- it doesn’t have a parent
def index(request):
# In our example you’ll fetch the list of divisions (from a database)
# and output it.
#crumb(name_from_pk(Division), parent=index)
def division(request, slug):
# Here you find all employees from the given division
# and list them.
There are also some utility functions (e.g. name_from_pk you can see in the example) that automagically generate nice names for your crumbs without you having to wright lots of code.
I've created template filter for this.
Apply your custom filter (I've named it 'makebreadcrumbs') to the request.path like this:
{% with request.resolver_match.namespace as name_space %}
{{ request.path|makebreadcrumbs:name_space|safe }}
{% endwith %}
We need to pass url namespace as an arg to our filter.
Also use safe filter, because our filter will be returning string that needs to be resolved as html content.
Custom filter should look like this:
#register.filter
def makebreadcrumbs(value, arg):
my_crumbs = []
crumbs = value.split('/')[1:-1] # slice domain and last empty value
for index, c in enumerate(crumbs):
if c == arg and len(crumbs) != 1:
# check it is a index of the app. example: /users/user/change_password - /users/ is the index.
link = '{}'.format(reverse(c+':index'), c)
else:
if index == len(crumbs)-1:
link = '<span>{}</span>'.format(c)
# the current bread crumb should not be a link.
else:
link = '{}'.format(reverse(arg+':' + c), c)
my_crumbs.append(link)
return ' > '.join(my_crumbs)
# return whole list of crumbs joined by the right arrow special character.
Important:
splited parts of the 'value' in our filter should be equal to the namespace in the urls.py, so the reverse method can be called.
Hope it helped.
A generic way, to collect all callable paths of the current url could be resolved by the following code snippet:
from django.urls import resolve, Resolver404
path_items = request.path.split("/")
path_items.pop(0)
path_tmp = ""
breadcrumb_config = OrderedDict()
for path_item in path_items:
path_tmp += "/" + path_item
try:
resolve(path_tmp)
breadcrumb_config[path_item] = {'is_representative': True, 'current_path': path_tmp}
except Resolver404:
breadcrumb_config[path_item] = {'is_representative': False, 'current_path': path_tmp}
If the resolve function can't get a real path from any urlpattern, the Resolver404 exception will be thrown. For those items we set the is_representative flag to false. The OrderedDict breadcrumb_config holds after that the breadcrumb items with there configuration.
For bootstrap 4 breadcrumb for example, you can do something like the following in your template:
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
{% for crumb, values in BREADCRUMB_CONFIG.items %}
<li class="breadcrumb-item {% if forloop.last or not values.is_representative %}active{% endif %}" {% if forloop.last %}aria-current="page"{% endif %}>
{% if values.is_representative %}
<a href="{{values.current_path}}">
{{crumb}}
</a>
{% else %}
{{crumb}}
{% endif %}
</li>
{% endfor %}
</ol>
</nav>
Only the links which won't raises a 404 are clickable.
I believe there is nothing simpler than that (django 3.2):
def list(request):
return render(request, 'list.html', {
'crumbs' : [
("Today", "https://www.python.org/"),
("Is", "https://www.python.org/"),
("Sunday", "https://www.djangoproject.com/"),
]
})
Breadcrumbs.html
<div class="page-title-right">
<ol class="breadcrumb m-0">
{% if crumbs %}
{% for c in crumbs %}
<li class="breadcrumb-item {{c.2}}">{{c.0}}</li>
{% endfor %}
{% endif %}
</ol>
</div>
css:
.m-0 {
margin: 0!important;
}
.breadcrumb {
display: flex;
flex-wrap: wrap;
padding: 0 0;
margin-bottom: 1rem;
list-style: none;
border-radius: .25rem;
}
dl, ol, ul {
margin-top: 0;
margin-bottom: 1rem;
}
ol, ul {
padding-left: 2rem;
}