如何在 Django 模板中实现面包屑?

Posted

技术标签:

【中文标题】如何在 Django 模板中实现面包屑?【英文标题】:How to implement breadcrumbs in a Django template? 【发布时间】:2010-10-24 00:52:22 【问题描述】:

在 Google 搜索“Django 面包屑”时提供的一些解决方案包括使用模板和 block.super,基本上只是扩展基本块并将当前页面添加到其中。 http://www.martin-geber.com/thought/2007/10/25/breadcrumbs-django-templates/

http://www.djangosnippets.org/snippets/1289/ - 提供了一个模板标签,但如果你没有正确声明你的 urls.py,我不确定这会起作用。

我想知道最好的方法是什么?如果你之前实现过面包屑导航,你是怎么做的?

--- 编辑 --

我的问题是:在 Django 中是否有一种普遍接受的做面包屑的方法,但从我看到的答案中没有,并且有很多不同的解决方案,我不确定谁来授予正确答案到,因为我使用了使用 block.super 方法的变体,而以下所有答案都可以。

我想这是一个过于主观的问题。

【问题讨论】:

按照@otto-kekäläinen 和@yeo 的建议,我会坚持在django 的管理中使用的实现。也许这类似于您的 block.super 方法,但我无法检查,因为该链接不再有效。是不是类似于this? 【参考方案1】:

我的视图函数将面包屑作为一个简单的列表发出。

一些信息保存在用户的会话中。但是,它间接地来自 URL。

面包屑不是它们所在位置的简单线性列表——这就是浏览器历史记录的用途。一个简单的他们去过的地方的列表并不能成为一个好的面包屑路径,因为它没有反映任何意义。

对于我们的大多数视图功能,导航是相当固定的,并且基于模板/视图/URL 设计。在我们的例子中,有很多深入细节,面包屑反映了这种缩小——我们有一个“领域”、一个“列表”、一个“父级”和一个“子级”。它们形成了一个从一般到特殊的简单层次结构。

在大多数情况下,一个定义明确的 URL 可以很容易地分解成一个很好的面包屑轨迹。事实上,这是对良好 URL 设计的一项测试—— URL 可以被解释为面包屑并有意义地显示给用户。

对于一些视图函数,我们提供的信息是“多对多”连接的一部分,例如,有两个候选父级。 URL 可能说一件事,但会话的上下文堆栈说另一件事。

因此,我们的视图函数必须在会话中留下上下文线索,以便我们发出面包屑。

【讨论】:

你的方法听起来像是我用来解决问题的第一种使用 block.super 方法的更复杂的变体【参考方案2】:

http://www.djangosnippets.org/snippets/1289/ - 提供了一个模板标签,但如果你没有正确声明你的 urls.py,我不确定这是否有效。

如果您没有正确声明您的urls.py,任何事情都不会奏效。话虽如此,它看起来不像是从urls.py 导入的。事实上,看起来要正确使用该标签,您仍然需要向模板传递一些变量。好吧,这并不完全正确:间接通过默认的url 标记,breadcrumb 标记调用该标记。但据我所知,它甚至没有真正调用那个标签。所有出现的url 都是本地创建的变量。

但我不是解析模板标签定义的专家。所以说在代码的其他地方它神奇地复制了 url 标签的功能。用法似乎是您将参数传递给反向查找。同样,无论您的项目是什么,都应该配置您的urls.py,以便可以通过反向查找访问任何视图。对于面包屑尤其如此。想一想:

home > accounts > my account

账户应该曾经拥有一个任意的硬编码网址吗? 可以“我的帐户”拥有任意硬编码的网址吗?以某种方式,您将以某种方式编写面包屑,以使您的urls.py 被反转。这实际上只会发生在两个地方之一:在您看来,调用reverse,或在模板中调用模仿reverse 功能的模板标签。可能有理由更喜欢前者而不是后者(链接的 sn-p 将您锁定在其中),但避免 urls.py 文件的逻辑配置不是其中之一。

【讨论】:

【参考方案3】:

这样的事情可能适合你的情况:

在您的视图中捕获整个 URL 并从中创建链接。这将需要修改您的 urls.py、需要有面包屑的每个视图以及您的模板。

首先,您将在 urls.py 文件中捕获整个 URL

原始网址.py
...
(r'^myapp/$', 'myView'),
(r'^myapp/(?P<pk>.+)/$', 'myOtherView'),
...
新的 urls.py
...
(r'^(?P<whole_url>myapp/)$', 'myView'),
(r'^(?P<whole_url>myapp/(?P<pk>.+)/)$', 'myOtherView'),
...

然后在您看来,类似:

视图.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)
...

应该将其拉出到导入到需要它的视图中的函数中

然后在你的模板中打印出面包屑

我的模板.html
...
<div class="breadcrumb-nav">
    <ul>
    % for breadcrumb in breadcrumbs %
        <li><a href=" breadcrumb.url "> breadcrumb.slug </a></li>
    % endfor %
    </ul>
</div>
...

这样做的一个缺点是,您只能将 url 的“目录”部分显示为链接文本。解决这个问题的一个方法(可能不是一个好的)是在定义面包屑功能的文件中保留一个字典。

无论如何,这是你可以完成面包屑的一种方法,干杯:)

【讨论】:

我不太确定,还有其他人对此有意见吗? 是的,自从我开始使用 Django 以来,我还没有需要面包屑的项目,所以这是当场制作的,再次阅读它,你甚至不需要弄乱你的 urls.py文件,您可以将 url 从请求对象中提取出来(duh!),这样就少了一步,然后它甚至可以用作上下文处理器,然后任何需要它的模板都可以使用它。【参考方案4】:

注意:我在下面提供了完整的 sn-p,因为 djangosn-ps 最近很挑剔。

酷,居然有人找到mysn-p :-) 我的模板标签的使用比较简单。

要回答您的问题,没有用于处理面包屑的“内置”django 机制,但它确实为我们提供了下一个最好的东西:自定义模板标签。

想象一下你想要这样的面包屑:

Services -> Programming
Services -> Consulting

那么你可能会有几个命名的url:“services”、“programming”、“consulting”:

    (r'^services/$',
     'core.views.services',
     ,
     'services'),

    (r'^services/programming$',
     'core.views.programming',
     ,
     'programming'),

    (r'^services/consulting$',
     'core.views.consulting',
     ,
     'consulting'),

现在在您的 html 模板中(让我们看看咨询页面),您所要做的就是:

//consulting.html
% load breadcrumbs %

% block breadcrumbs %
% breadcrumb_url 'Services' services %
% breadcrumb_url 'Consulting' consulting %

% endblock %

如果您想在面包屑中使用某种自定义文本,并且不想链接它,您可以使用 breadcrumb 标签代替。

//consulting.html
% load breadcrumbs %

% block breadcrumbs %
  % breadcrumb_url 'Services' services %
  % breadcrumb_url 'Consulting' consulting %
  % breadcrumb 'We are great!' %  
% endblock %

在更多涉及的情况下,您可能希望包含特定对象的 id,这也很容易做到。这是一个比较现实的例子:

% 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 面包屑 sn-p

提供两个模板标签以在您的 HTML 模板中使用:breadcrumb 和 breadcrumb_url。第一个允许创建简单的 url,包括文本部分和 url 部分。或仅未链接的文本(例如,作为面包屑路径中的最后一项)。第二,实际上可以使用带参数的命名 url!此外,它将标题作为第一个参数。

这是一个模板标签文件,应该进入您的 /templatetags 目录。

只需在 create_crumb 方法中更改图像的路径即可!

别忘了在您的 html 模板顶部添加 % load breadcrumbs %!

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" >""" \
            """</span>"""
    if url:
        crumb = "%s<a href='%s'>%s</a>" % (crumb, url, title)
    else:
        crumb = "%s&nbsp;&nbsp;%s" % (crumb, title)

    return crumb

【讨论】:

我想是的,我确实访问了您的博客,但使用上述解释进行更新会有所帮助。我实现了一个版本,但不是使用命名链接,而是使用硬编码的 url!哎呀!我将尝试安装您的模板过滤器!谢谢 我的主要问题是它正在产生:第一个面包屑组件之前的箭头。因此,我得到的不是“主页 > 生物学”,而是“主页 > 生物学” 没错,我认为这是我故意的。因为“主页”通常是一个静态链接,可以在面包屑之前定义。我想可以创建一个 NoArrowUrlNode 类来调用新的 create_crumb_no_arrow 函数。 如果我想在面包屑标题中添加一个模板变量怎么办?我该怎么做% breadcrumb 'Order #'+order.pk %之类的事情?我知道那行不通,但我只是想给你举个例子。 您必须编写自己的模板标签,从参数中提取模板变量:docs.djangoproject.com/en/dev/howto/custom-template-tags/… 请注意,您会将 order.pk 作为单独的参数传递。【参考方案5】:

试试django-mptt。

使用 Django 模型类实现修改的预排序树遍历 (MPTT) 并使用模型实例树的实用程序。

【讨论】:

你知道这与 django-sitetrees 有什么不同吗【参考方案6】:

试试django-breadcrumbs — 一个可插入的中间件,在您的请求对象中添加可调用/可迭代的面包屑。

它支持简单视图、通用视图和 Django FlatPages 应用程序。

【讨论】:

Blog post on using django-breadcrumbs with CBV【参考方案7】:

Django 管理视图模块具有自动面包屑,其实现类似于this:

% block breadcrumbs %
    <div class="breadcrumbs">
        <a href="% url 'admin:index' %">% trans 'Home' %</a>
        % block crumbs %
            % if title % &rsaquo;  title % endif %
        % endblock %
    </div>
% endblock %

所以对此有某种内置支持..

【讨论】:

谢谢!!这种简单的方法应该是正确的答案【参考方案8】:

我遇到了同样的问题,最后我为它制作了简单的 django 模板标签:https://github.com/prymitive/bootstrap-breadcrumbs

【讨论】:

【参考方案9】:

你可能想试试django-headcrumbs(别担心,他们不会吃掉你的大脑)。

它非常轻量级并且使用起来绝对简单,您所要做的就是用一个解释如何从给定视图返回的装饰器来注释您的视图(因为在模板中定义 crumbs 结构对我来说听起来很疯狂)。

这是文档中的一个示例:

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.

还有一些实用函数(例如,您可以在示例中看到name_from_pk)可以自动为您的面包屑生成漂亮的名称,而无需您编写大量代码。

【讨论】:

【参考方案10】:

我为此创建了模板过滤器。

将您的自定义过滤器(我将其命名为“makebreadcrumbs”)应用到 request.path,如下所示:

% with request.resolver_match.namespace as name_space %
     request.path|makebreadcrumbs:name_space|safe 
% endwith %

我们需要将 url 命名空间作为参数传递给我们的过滤器。

也使用安全过滤器,因为我们的过滤器将返回需要解析为 html 内容的字符串。

自定义过滤器应如下所示:

@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 = '<a href=""></a>'.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 = '<a href=""></a>'.format(reverse(arg+':' + c), c)
        my_crumbs.append(link)
    return ' &gt; '.join(my_crumbs)  
    # return whole list of crumbs joined by the right arrow special character.

重要:

我们过滤器中'value'的分割部分应该等于urls.py中的命名空间,所以可以调用reverse方法。

希望对您有所帮助。

【讨论】:

【参考方案11】:

显然,没有一个最佳答案,但出于实际原因,我发现值得考虑幼稚的方式。 只需覆盖并重写整个面包屑......(至少在官方django.contrib.breadcrumb发布之前)

不要太花哨,最好保持简单。它有助于新人理解。它是高度可定制的(例如权限检查面包屑图标分隔符活动面包屑等... )

基础模板

<!-- File: base.html -->
<html>
<body>
  % block breadcrumb %
  <ul class="breadcrumb">
    <li><a href="% url 'dashboard:index' %">Dashboard</a></li>
  </ul>
  % endblock breadcrumb %
  % block content %% endblock content %
</body>
</html>

实施模板

稍后在每个页面上,我们重写覆盖整个面包屑块。

<!-- File: page.html -->
% extends 'base.html' %
% block breadcrumb %
<ul class="breadcrumb">
  <li><a href="% url 'dashboard:index' %">Dashboard</a></li>
  <li><a href="% url 'dashboard:level-1:index' %">Level 1</a></li>
  <li class="active">Level 2</li>
</ul>
% endblock breadcrumb %

实用性

现实世界的用例:

姜戈奥斯卡:base template, simple bread Django 管理员:base template、simple bread、permission check breadcrumb

【讨论】:

这种方法的权衡是父子页面模板和重复复制粘贴之间的一致性...... 谢谢,我同意,保持简单,尽量减少依赖。很高兴有 django 管理面包屑实现的链接。【参考方案12】:

您还可以通过向视图添加 crumbs 属性来减少使用 django-view-breadcrumbs 管理面包屑所需的样板。

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 %

【讨论】:

【参考方案13】:

一种通用的方式,收集当前url的所有可调用路径,可以通过以下代码sn-p解析:

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

如果resolve 函数无法从任何urlpattern 中获取真实路径,则会抛出Resolver404 异常。对于这些项目,我们将 is_representative 标志设置为 false。 OrderedDict breadcrumb_config 保持在那之后带有配置的面包屑项目。

例如,对于 bootstrap 4 面包屑,您可以在模板中执行以下操作:

<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>

只有不会引发404 的链接是可点击的。

【讨论】:

【参考方案14】:

我相信没有比这更简单的了(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/"),
            ]
        )

面包屑.html

<div class="page-title-right">
   <ol class="breadcrumb m-0">
      % if crumbs %
            % for c in crumbs %
                <li class="breadcrumb-item c.2"><a href="c.1">c.0</a></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;

【讨论】:

以上是关于如何在 Django 模板中实现面包屑?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Jinja 中实现类似 Django 的标签

您将如何在 asp.net mvc 中实现面包屑助手?

在模板(Django)中实现归档日历时出现 IF 运算符错误

如何在 django admin change_form 中实现 pygment 语法高亮?

django 在模板中实现过滤器

如何在 django 模板中设置自定义 forloop 起点