I am trying to show my uploaded icon on navigation but i guess, i am going through a wrong way.
this is my model that extended
class IconExtension(PageExtension):
image = models.ImageField(upload_to='icons')
extension_pool.register(IconExtension)
and this is my cms_toolbars.py file
#toolbar_pool.register
class IconExtensionToolbar(ExtensionToolbar):
# defines the model for the current toolbar
model = IconExtension
def populate(self):
# setup the extension toolbar with permissions and sanity checks
current_page_menu = self._setup_extension_toolbar()
# if it's all ok
if current_page_menu:
# retrieves the instance of the current extension (if any) and the toolbar item URL
page_extension, url = self.get_page_extension_admin()
if url:
# adds a toolbar item in position 0 (at the top of the menu)
current_page_menu.add_modal_item(_('Page Icon'), url=url,
disabled=not self.toolbar.edit_mode_active, position=0)
and this is my menu.html file
{% load menu_tags %}
{% for child in children %}
<li class="child{% if child.selected %} selected{% endif %}{% if child.ancestor %} ancestor{% endif %}{% if child.sibling %} sibling{% endif %}{% if child.descendant %} descendant{% endif %}">
<a href="{{ child.attr.redirect_url|default:child.get_absolute_url }}">{{ child.get_menu_title }}
<img src="{{ child.image.url }}">
</a>
{% if child.children %}
<ul>
{% show_menu from_level to_level extra_inactive extra_active template "" "" child %}
</ul>
{% endif %}
</li>
{% endfor %}
I am not getting why it is not working, I don't see any error even I don't see any image url here, my everything is working fine, only going through problem showing menu icon, can anyone help me in this case?
It looks like you're just missing the reference to the object from the page, you're going straight to it's attribute.
I've got a very similar setup with images associated with pages;
class PageImage(PageExtension):
""" Add images to pages """
image = models.FileField(
verbose_name=_("Image"),
upload_to=upload_to('page_images')
)
Which in my templates becomes;
{% if request.current_page.pageimage.image %}
{{ request.current_page.pageimage.image.url }}
{% endif %}
So in your example, if you did this in a template you'd do;
{% if request.current_page.iconextension %}
<img src="{{ request.current_page.iconextension.image.url }}">
{% endif %}
Checking if the extension exists is important to avoid attribute errors etc.
In a menu, the child isn't a Page object though, it's a NavigationNode from the menu system. So it doesn't have your extension.
I think the proper solution to this is to setup a navigation Modifier. The docs for this are here; http://docs.django-cms.org/en/latest/how_to/menus.html#navigation-modifiers
Alternatively you could setup a template tag which you pass your child which could then use the reverse_id attribute to query the database for the Page that corresponds to, and return the iconextension of that page. I've used this method before.
Based on #markwalker_ answer. I did the job successfully in 3 Steps
Step 1 - Add an FontAwesomeField in a page Extension :
https://docs.django-cms.org/en/latest/how_to/extending_page_title.html
For the record here is my code :
In models.py
from django.db import models
# Create your models here.
from cms.extensions import PageExtension
from cms.extensions.extension_pool import extension_pool
class IconExtension(PageExtension):
image = models.ImageField(upload_to='icons', null=True, blank=True)
fontawesomeicon = models.CharField(max_length=100, null=True, blank=True)
extension_pool.register(IconExtension)
In admin.py
from django.contrib import admin
from cms.extensions import PageExtensionAdmin
from .models import IconExtension
class IconExtensionAdmin(PageExtensionAdmin):
pass
admin.site.register(IconExtension, IconExtensionAdmin)
In cms_toolbars.py
from cms.toolbar_pool import toolbar_pool
from cms.extensions.toolbar import ExtensionToolbar
from django.utils.translation import gettext_lazy as _
from .models import IconExtension
#toolbar_pool.register
class IconExtensionToolbar(ExtensionToolbar):
# defines the model for the current toolbar
model = IconExtension
def populate(self):
# setup the extension toolbar with permissions and sanity checks
current_page_menu = self._setup_extension_toolbar()
# if it's all ok
if current_page_menu:
# retrieves the instance of the current extension (if any) and the toolbar item URL
page_extension, url = self.get_page_extension_admin()
if url:
# adds a toolbar item in position 0 (at the top of the menu)
current_page_menu.add_modal_item(_('Page Icon'), url=url,
disabled=not self.toolbar.edit_mode_active, position=0)
Step 2 - Implementing Navigation Modifier
https://docs.django-cms.org/en/latest/how_to/menus.html#navigation-modifiers
In cms_menu.py
from menus.base import Modifier
from menus.menu_pool import menu_pool
from cms.models import Page
class AddIconModifier(Modifier):
"""
This modifier makes the changed_by attribute of a page
accessible for the menu system.
"""
def modify(self, request, nodes, namespace, root_id, post_cut, breadcrumb):
if breadcrumb:
return nodes
# Only on last iteration (Voir : https://docs.django-cms.org/en/latest/how_to/menus.html#navigation-modifiers)
if not post_cut:
return nodes
# only consider nodes that refer to cms pages
# and put them in a dict for efficient access
page_nodes = {n.id: n for n in nodes if n.attr["is_page"]}
# retrieve the relevent pages
pages = Page.objects.filter(id__in=page_nodes.keys())
# loop over all relevant pages
for page in pages:
# take the node referring to the page
node = page_nodes[page.id]
if hasattr(page, 'iconextension') and hasattr(page.iconextension ,'fontawesomeicon'):
node.attr["faicon"] = page.iconextension.fontawesomeicon
else:
node.attr["faicon"] = 'fa-arrow-circle-right'
return nodes
menu_pool.register_modifier(AddIconModifier)
Step 3 - Specific navigation template
In my main template :
{% show_menu 0 100 100 100 "partials/navigation.html" %}
In my navigation template (navigation.html)
{% load cms_tags menu_tags cache %}
{% for child in children %}
<li class="nav-item">
<a class="nav-link" href="{{ child.attr.redirect_url|default:child.get_absolute_url }}"><i class="fas fa-fw {{ child.attr.faicon }}"></i>{{ child.get_menu_title }}</a>
{% if child.children %}
<ul class="sub_menu">
{% show_menu from_level to_level extra_inactive extra_active template '' '' child %}
</ul>
{% endif %}
</li>
{% endfor %}
I know I can change the display title for a model in Django Admin using
class Meta:
verbose_name='Custom Model Name Here'
However, is there a way to display which app heading a model is displayed under?
For example, if I create a custom user model Users in a new app also called users then the default user model goes from Authentication and Authorization > Users to Users > Users.
I would like to retain it under the original heading Authentication and Authorization > Users.
I have read this answer which suggests changes the app verbose_name, however it only changes the verbose name of the app associated with the model. I want to show the model in a different group on the admin panel. You can see the issue that approach takes here:
You could set up a proxy model
In app_1.models.py
class MyModel(models.Model):
...
In app_2.models.py
from app_1.models import MyModel
class MyModelProxy(MyModel):
class Meta:
proxy = True
Then register the MyModelProxy in admin as normal
There is no simple way of doing what you're intending, check out the Django code/template that generates the view:
#never_cache
def index(self, request, extra_context=None):
"""
Display the main admin index page, which lists all of the installed
apps that have been registered in this site.
"""
app_list = self.get_app_list(request)
context = {
**self.each_context(request),
'title': self.index_title,
'app_list': app_list,
**(extra_context or {}),
}
request.current_app = self.name
return TemplateResponse(request, self.index_template or 'admin/index.html', context)
templates/admin/index.html:
{% for app in app_list %}
<div class="app-{{ app.app_label }} module">
<table>
<caption>
{{ app.name }}
</caption>
{% for model in app.models %}
<tr class="model-{{ model.object_name|lower }}">
{% if model.admin_url %}
<th scope="row">{{ model.name }}</th>
{% else %}
<th scope="row">{{ model.name }}</th>
{% endif %}
{% if model.add_url %}
<td>{% trans 'Add' %}</td>
{% else %}
<td> </td>
{% endif %}
{% if model.admin_url %}
{% if model.view_only %}
<td>{% trans 'View' %}</td>
{% else %}
<td>{% trans 'Change' %}</td>
{% endif %}
{% else %}
<td> </td>
{% endif %}
</tr>
{% endfor %}
</table>
</div>
{% endfor %}
As you can see, Django admin template is looping your app_list directy, so the only way of doing it would be to override the admin/index.html template to place your models in your desired order.
From Django docs:
For those templates that cannot be overridden in this way, you may still override them for your entire project. Just place the new version in your templates/admin directory. This is particularly useful to create custom 404 and 500 pages.
https://docs.djangoproject.com/en/2.2/ref/contrib/admin/#overriding-vs-replacing-an-admin-template
In firstapp.models.py
class ActualModel(models.Model):
class DisplayModel(ActualModel):
class Meta:
proxy = True
in secondapp.admin.py
from firstapp.models import DisplayModel
admin.site.register(DisplayModel)
Easier way to do that:
In model class Meta add
app_label = 'app2'
db_table = 'app1_modelname'
so you assigning model to another app with app_label and assigning db_table with old (current) table name to prevent making migrations and table renami
My django admin interface looks like this:
Now I would like to add a view which does not correspond to a model.
I could overwrite the template of above page and add a custom link. But I think this would look ugly.
Example for overwriting admin/index.html:
{% extends "admin/index.html" %}
{% block content %}
{{ block.super }}
<div class="app-sonstiges module">
....
</div>
{% endblock %}
But maybe there is an official way to do add a custom view to the admin interface?
In my case I want to provide a form which can execute tcptraceroute to a remote server. The admin of my app needs this.
I used the same html tags. Now the link "tcptraceroute" looks nice, but unfortunately the messages moved down:
Is there a way to get a custom part like "Sontiges ... tcptraceroute" like in the screenshot, without moving the latest actions down?
Here is how the html structure looks like. My <div class="app-sonstiges"> is below content-main:
You have 3 options here:
Using third-party packages which provide menu
This is pretty straight forward, there are some good packages out there which support menu for admin, and some way to add your item to the menu.
An example of a 3rd party package would be django-admin-tools. The drawback is that it is a bit hard to learn. Another option would be to use django-adminplus but at the time of writing this, it does not support Django 2.2.
Hacking with django admin templates
You can always extend admin templates and override the parts you want, as mentioned in #Sardorbek answer, you can copy some parts from admin template and change them the way you want.
Using custom admin site
If your views are supposed to only action on admin site, then you can extend adminsite (and maybe change the default), beside from adding links to template, you should use this method to define your admin-only views as it's easier to check for permissions this way.
Considering you already extended adminsite, now you can use the previous methods to add your link to template, or even extend get_app_list method, example:
class MyAdminSite(admin.AdminSite):
def get_app_list(self, request):
app_list = super().get_app_list(request)
app_list += [
{
"name": "My Custom App",
"app_label": "my_test_app",
# "app_url": "/admin/test_view",
"models": [
{
"name": "tcptraceroute",
"object_name": "tcptraceroute",
"admin_url": "/admin/test_view",
"view_only": True,
}
],
}
]
return app_list
You can also check if current staff user can access to module before you show them the links.
It will look like this at then end:
Problem here that your div is not inside main-content. I suggest extending admin/index.html
Put in app/templates/admin/index.html
{% extends "admin/index.html" %}
{% load i18n static %}
{% block content %}
<div id="content-main">
{% if app_list %}
{% for app in app_list %}
<div class="app-{{ app.app_label }} module">
<table>
<caption>
{{ app.name }}
</caption>
{% for model in app.models %}
<tr class="model-{{ model.object_name|lower }}">
{% if model.admin_url %}
<th scope="row">{{ model.name }}</th>
{% else %}
<th scope="row">{{ model.name }}</th>
{% endif %}
{% if model.add_url %}
<td>{% trans 'Add' %}</td>
{% else %}
<td> </td>
{% endif %}
{% if model.admin_url %}
{% if model.view_only %}
<td>{% trans 'View' %}</td>
{% else %}
<td>{% trans 'Change' %}</td>
{% endif %}
{% else %}
<td> </td>
{% endif %}
</tr>
{% endfor %}
</table>
</div>
{% endfor %}
<!-- here you could put your div -->
<div class="app-sonstiges module">
....
</div>
<!-- here you could put your div -->
{% else %}
<p>{% trans "You don't have permission to view or edit anything." %}</p>
{% endif %}
</div>
{% endblock %}
The another approach is to add custom app to app_list. But it is far more ugly. So I suggest overriding template.
I assume you are overriding get_urls in AdminSite.
If you need a custom page to handle user input (from form), you may want to give django-etc 1.3.0+ a try:
from etc.admin import CustomModelPage
class MyPage(CustomModelPage):
title = 'My custom page' # set page title
# Define some fields you want to proccess data from.
my_field = models.CharField('some title', max_length=10)
def save(self):
# Here implement data handling.
super().save()
# Register the page within Django admin.
MyPage.register()
I followed the get started doc and everyting work well! :)
But i would like to replace the form in the /search/search.html by a custom form without selectable model checkbox.
In the form i would like to add a button which on click, order search results by a criteria
My questions are:
Which files i need to create or modified to perfoms that and what are their roles?
My codes are:
search_indexes.py
from haystack import indexes
from models import ProduitCommerce
class ProduitIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
commerce = indexes.CharField(model_attr = 'nom_commerce')
nom = indexes.CharField(model_attr = 'nom_produit')
price = indexes.DecimalField(model_attr = 'prix') #Field to filter ON
def get_model(self):
return ProduitCommerce
search/search.html
{% extends 'base.html' %}
{% block content %}
<h2>Search</h2>
<form method="get" action=".">
<table>
{{ form.as_table }} <!------ FORM TO CHANGE BY A CUSTOM FORM BLOCK TO INCLUDE WITH DJANGO ------->
<tr>
<td> </td>
<td>
<input type="submit" value="Search">
</td>
</tr>
</table>
{% if query %}
<h3>Results</h3>
<!---------------------------- PLACE TO INCLUDE THE BUTTON TO FILTER ON result.object.prix------------------>
{% for result in page.object_list %}
<p>
{{ result.object.nom_produit }}{{result.object.prix}}
</p>
{% empty %}
<p>No results found.</p>
{% endfor %}
{% if page.has_previous or page.has_next %}
<div>
{% if page.has_previous %}{% endif %}« Previous{% if page.has_previous %}{% endif %}
|
{% if page.has_next %}{% endif %}Next »{% if page.has_next %}{% endif %}
</div>
{% endif %}
{% else %}
{# Show some example queries to run, maybe query syntax, something else? #}
{% endif %}
</form>
produitcommerce_text.txt
{{ object.nom_produit }}
{{ object.nom_commerce }}
{{ object.description }}
{{ object.prix }}
PS: I'm working on a django project with the 1.5 .1 version and whoosh like haystack backend.
Thanks for you help :)
You are getting the selectable model checkboxes because you're extending the ModelSearchForm:
#forms.py
from haystack.forms import ModelSearchForm
class ProduitForm(ModelSearchForm):
You can easily avoid this behaviour by extending the simple search form variant, provided by the haystack:
#forms.py
from haystack.forms import SearchForm
class ProduitForm(SearchForm):
Then you're going to add something like the following to you application's urls.py:
#urls.py
from haystack.query import SearchQuerySet
from haystack.views import SearchView, search_view_factory
from myapp.models import Produit
urlpatterns = patterns('',
url(r'^produit/search/$', search_view_factory(
view_class=SearchView,
template='search/search.html',
searchqueryset=SearchQuerySet().models(Resume),
form_class=ProduitForm
), name='produit_search'),
)
Please note that there is a bug with .models() filter when trying to use it with latest versions of Haystack and Whoosh. If you would experience any kind of problems with the strategy above, then you should make sure, that your Haystack and Whoosh are of versions 2.0.0 and 2.4.1 relatively - tested and works well.
Also, not related to your question, you might want to avoid using HAYSTACK_SEARCH_RESULTS_PER_PAGE in your settings.py. It also has a very ugly bug. Just sharing my experience. Note that this info is related to Whoosh, everything should work fine with any other backend.
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;
}