1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
|
# Copyright (C) 2010-2018 Linaro Limited
#
# Author: Zygmunt Krynicki <zygmunt.krynicki@linaro.org>
#
# SPDX-License-Identifier: GPL-2.0-or-later
"""
lava_server.bread_crumbs
========================
Bread crumb management for LAVA server.
This system allows one to construct static trees of views or even site maps,
where each view has at most one parent. In this model any view could be
followed back through the parent link to create a bread crumb trail of named
URLs.
It is important to emphasize that this system is STATIC, that is, it is not
based on browsing history. Regardless on how the user got to a particular view
the bread crumb system will report the same set of pages. The idea is not to
let users go back (that's the what the browser allows them to do) but to put
the current page into context of where it "belongs".
To use this system apply the @BreadCrumb(name, parent=parent_view,
needs=['required', 'keywords']) decorator to your view function. To render
breadcrumbs you can use the default template that is a part of
"layouts/content.html" template. Your context must include the
bread_crumb_trail variable. To construct it call
BreadCrumbTrail.leading_to(your_view_name, ...) passing any of the keyword
arguments specified in needs of your and any parent views (yes this is
annoying).
A mistake in pairing 'needs' to keywords passed to BreadCrumbTrail.leading_to()
will result in logged warnings (either a name of the URL being not
constructible). To fix that simply add the missing keyword argument and reload.
"""
import logging
from django.urls import reverse
class BreadCrumb:
"""
A crumb of bread left in the forest of pages to let you go back to (no, not
to where you came from) where the developer desired you to go.
"""
def __init__(self, name, parent=None, needs=None):
"""
Construct a bread crumb object.
The name is the essential property creating the actual text visible on
web pages. It may be a static string or a new-style python string
template. Parent allows one to construct a static bread crumb tree where
each crumb may have at most one parent. Needs, if specified, must be
an array of strings that denote identifiers required to resolve the URL
of this bread crumb. The identifiers are obtained from the call
BreadCrumbTrail.leading_to().
"""
self.name = name
self.view = None
self.parent = parent
self.needs = needs or []
def __str__(self):
return "<BreadCrumb name=%r view=%r parent=%r>" % (
self.name,
self.view,
self.parent,
)
def __call__(self, view):
"""
Call method, used when decorating function-based views
Id does not redefine the function (so is not a real decorator) but
instead stores the bread crumb object in the _bread_crumb attribute of
the function.
"""
self.view = view
view._bread_crumb = self
return view
def get_name(self, kwargs):
"""
Get the name of this crumb.
The name is formatted with the specified keyword arguments.
"""
try:
return self.name.format(**kwargs)
except Exception:
logging.exception(
"Unable to construct breadcrumb name for view %r", self.view
)
raise
def get_absolute_url(self, kwargs):
"""
Get the URL of this crumb.
The URL is constructed with a call to Dajngo's reverse() function. It
is supplemented with the same variables that were listed in needs array
in the bread crumb constructor. The arguments are passed in order, from
the kwargs dictionary.
"""
try:
return reverse(self.view, args=[kwargs[name] for name in self.needs])
except Exception:
logging.exception(
"Unable to construct breadcrumb URL for view %r", self.view
)
raise
class LiveBreadCrumb:
"""
Bread crumb instance as observed by a particular request.
It is a binding between the global view-specific bread crumb object and
dynamic request-specific keyword arguments.
For convenience it provides two bread crumb functions (get_name() and
get_absolute_url()) that automatically provide the correct keyword
arguments.
"""
def __init__(self, bread_crumb, kwargs):
self.bread_crumb = bread_crumb
self.kwargs = kwargs
def __str__(self):
return self.get_name()
def get_name(self):
return self.bread_crumb.get_name(self.kwargs)
def get_absolute_url(self):
return self.bread_crumb.get_absolute_url(self.kwargs)
class BreadCrumbTrail:
"""
A list of live bread crumbs that lead from a particular view, along the
parent chain, all the way to the root view (that is without any parent
view).
"""
def __init__(self, bread_crumb_list, kwargs):
self.bread_crumb_list = bread_crumb_list
self.kwargs = kwargs
def __iter__(self):
for bread_crumb in self.bread_crumb_list:
yield LiveBreadCrumb(bread_crumb, self.kwargs)
@classmethod
def leading_to(cls, view, **kwargs):
"""
Create an instance of BreadCrumbTrail that starts at the specified
view.
Additional keyword arguments, if provided, will be available to
get_name() and get_absolute_url() of each LiveBreadCrumb that makes up
this trail. In practice they should contain a set of arguments that are
needed by any parent bread crumb URL or name.
TODO: could we check this statically somehow?
"""
lst = []
while view is not None:
lst.append(view._bread_crumb)
view = view._bread_crumb.parent
lst.reverse()
return cls(lst, kwargs or {})
@classmethod
def show_help(cls, view, **kwargs):
"""
Create a context-sensitive help string from this crumb.
The URL is constructed with a call to Dajngo's reverse() function. It
is supplemented with the same variables that were listed in needs array
in the bread crumb constructor. The arguments are passed in order, from
the kwargs dictionary.
"""
lst = []
while view is not None:
lst.append(view._bread_crumb)
view = view._bread_crumb.parent
lst.reverse()
return cls(lst, kwargs or {})
|