In Wagtail, I have made a Block with an ImageChooserBlock in it like this:
class MyBlock(blocks.StructBlock):
background = ImageChooserBlock()
Now i want to add some extra fields to the ImageChooserBlock so I moved it to its own Block so now it looks like this:
class FancyImageChooserBlock(ImageChooserBlock):
extra = blocks.Charfield()
class MyBlock(blocks.StructBlock):
background = FancyImageChooserBlock()
My first issue is that the extra field doesn't get included. (Maybe because the block inherits from ImageChooserBlock?
My second and most important issue is that I want to able to have the extra field to be hidden in the form, but included in the template rendering. Does someone know how and if this is possible? I don't want to do any hacky stuff injecting js or css for this. There must be a way to do this using Blocks, Widgets and forms.HiddenInput or something like that.
I know i can do some calculations in the clean method of my FancyImageChooserBlock to manually set the value of extra. This is exactly what I want to do.
Any help is appreciated, I'm really stuck here.
ImageBlockChooser isn't like 'StructBlock' or 'ListBlock' or 'StreamBlock' which are all structural block types - that is which are designed to 'look for' any child fields you might define. Only the structural block types are prepared for that 'out of the box'. For a block to use fields it needs to be configured to use/generate a template with those fields.
Personally, I think there are better ways of achieving what you want than to subclass ImageChooser as it will generally be more robust to try and find ways of using the features that Wagtail provide, even if you have to be a bit creative with them.
However, in case you still want to know how it might be done (hacked) by subclassing ImageChooser:
SOLUTION 1 - subclassing ImageChooser (this wouldn't be my preferred option for this):
#the chooser block class:
class MyImageChooserBlock(ImageChooserBlock):
#cached_property
def widget(self):
from .mywidgetsfolder import MyAdminImageChooser
return MyAdminImageChooser
from wagtail.admin.widgets import AdminChooser
from wagtail.images import get_image_model
#the chooser admin class...
class MyAdminImageChooser(AdminChooser):
"""the only difference between this class and AdminImageChooser
is that this one provides a different template value to
render in the render_to_string method and the addition of certain
variables to the attrs dictionary. You could probably get away
with subclassing AdminImageChooser instead of AdminChooser and just
overriding the render_html method, but for some reason that seemed
to give me a duplicate 'choose image' button and it didn't seem
essential to fix it to demonstrate this principle"""
choose_one_text = _('Choose an image')
choose_another_text = _('Change image')
link_to_chosen_text = _('Edit this image')
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.image_model = get_image_model()
def render_html(self, name, value, attrs):
instance, value = self.get_instance_and_id(self.image_model,
value)
attrs['extra_hidden_fields'] = ('extra_1', 'extra_2')
original_field_html = super().render_html(name, value, attrs)
return render_to_string("my-widgets-folder/my_image_chooser.html", {
'widget': self,
'original_field_html': original_field_html,
'attrs': attrs,
'value': value,
'image': instance,
})
def render_js_init(self, id_, name, value):
return "createImageChooser({0});".format(json.dumps(id_))
#my-widgets-folder/my_image_chooser.html template:
{% extends "wagtailadmin/widgets/chooser.html" %}
{% load wagtailimages_tags %}
{% block chooser_class %}image-chooser{% endblock %}
{% block chosen_state_view %}
{% for a in attrs.extra_hidden_fields %}
<input type="hidden", name={{a}}, value="">
{% endfor %}
<div class="preview-image">
{% if image %}
{% image image max-300x300 class="show-transparency" %}
{% else %}
<img>
{% endif %}
</div>
{% endblock %}
{% block edit_chosen_item_url %}{% if image %}{% url 'wagtailimages:edit' image.id %}{% endif %}{% endblock %}
SOLUTION 2 - using a custom struct block and the group meta value:
This solution uses a custom struct block to hide the fields and labels. I'd recommend this approach for now as it uses customisation features provided by wagtail for your use. Although the documentation does not mention using the group meta in this way the group meta is documented and should be ok to rely on (and it could probably be quite easily replaced with another detail if necessary).
For anyone happening on this answer I'd suggest checking the wagtail docs before using this as it seems development on the project is fast and I wouldn't be at all surprised if they offered a 'built in' way of generating hidden fields soon.
In my own tests I didn't get the indent mentioned by the OP in his comment. As long as the struct is itself a top level element all the children were left aligned by default - so they were aligned with any fields outside of the struct block.
Of course you can create custom blocks of the basic types (say a custom CharBlock) and specify the widget kwarg as forms.HiddenInput but you still have the label to deal with - and passing a classname kwarg only applies it to the input not the label etc. Also using custom basic blocks means keeping them forever or providing deconstruct methods to avoid migration trouble. This circumvents all these sorts of issues.
Of course any of this could be achieved easily with some JS/css but this is offered assuming we want an html only solution.
class StructWithHiddenFields(StructBlock):
classMeta:
form_template = "blocks/admin/struct_with_hidden_fields.html"
"""Obviously you'd want to copy the template from the wagtail one
for StructBlocks (wagtailadmin/block_forms/struct.html) to ensure
similar behaviour and then add a bit of logic for the hiding.
This might look like this:"""
#blocks/admin/struct_with_hidden_fields.html template:
<div class="{{ classname }}">
{% if help_text %}
<div class="sequence-member__help help"><span class="icon-
help-inverse" aria-hidden="true"></span>{{ help_text }}
</div>
{% endif %}
<ul class="fields">
{% for child in children.values %}
{% if child.block.meta.group != "hidden-input" %}
<li{% if child.block.required %} class="required"{% endif %}>
{% if child.block.label %}
<label{% if child.id_for_label %} for="{{ child.id_for_label }}"{% endif %}>{{ child.block.label }}:</label>
{% endif %}
{{ child.render_form }}
</li>
{% endif %}
{% endfor %}
</ul>
{% for child in children.values %}
{% if child.block.meta.group == "hidden-input" %}
<input type="hidden" id="{{ prefix }}-{{child.block.label}}" name="{{ prefix }}-{{child.block.label}}" value="{{child.block.value}}">
{% endif %}
{% endfor %}
</div>
#Usage:
class MySpecificBlockWithHiddenFields(StructWithHiddenFields):
normal_field = CharBlock(required=False)
hidden_field = IntegerBlock(required=False, group="hidden-input")
Regarding your first question, why not just:
class MyBlock(blocks.StructBlock):
background = ImageChooserBlock()
extra = blocks.Charfield()
?
Sweet solution in the model
This answer isn't really the answer on the question but a better option to go with when trying to add some fields to the ImageChooser on the background. As learned from the Wagtail docs there is this thing called the Custom Image Model
So instead of trying to add the fields on the Block "layer", I added them on the Model. For me the code looks something like this:
class ImageModel(AbstractImage):
extra = models.CharField(max_length=255, blank=True, null=True)
admin_form_fields = Image.admin_form_fields # So that in the image edit page, the fields are shown
def save(self, **kwargs):
self.clean_extra()
return super(ImageModel, self).save(**kwargs)
def clean_extra(self):
if something():
extra = 'This gets added as an attribute of image'
# Needed for the rendition relation
class ImageRenditionModel(AbstractRendition):
image = models.ForeignKey(ImageModel, related_name='renditions')
class Meta:
unique_together = (
('image', 'filter_spec', 'focal_point_key'),
)
Also, the WAGTAILIMAGES_IMAGE_MODEL must point to your own model, more about this you can find in the docs.
A very hacky solution in the blocks
There is another way to do this, without using extra html or extra models, but it is very hacky and disapproved.
class FancyImageChooserBlock(ImageChooserBlock):
def clean_extra(self, value):
if something():
value['extra'] = 'This will get rendered in template'
return value
# for rendering in preview
def clean(self, value):
value = super(FancyImageChooserBlock, self).clean(value)
value = self.clean_extra(value)
return value
# for rendering the live view
def to_python(self, value):
value = super(FancyImageChooserBlock, self).to_python(value)
value = self.clean_extra(value)
return value
This way you don't need extra html, css or js when adding an extra value. The point why this is disencouraged is because it takes up a lot of UX performance and also overriding the to_python function and clean function to inject an extra variable is very, very hacky and dirty as can be. But it works, so if you don't mind design standards or performance, work alone and nobody else will ever, ever see your code, you could use this.
So don't..
Related
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;
}
Is it possible to sort a set of related items in a DJango template?
That is: this code (with HTML tags omitted for clarity):
{% for event in eventsCollection %}
{{ event.location }}
{% for attendee in event.attendee_set.all %}
{{ attendee.first_name }} {{ attendee.last_name }}
{% endfor %}
{% endfor %}
displays almost exactly want I want. The only thing I want to change is I the list of attendees to be sorted by last name. I've tried saying something like this:
{% for event in events %}
{{ event.location }}
{% for attendee in event.attendee_set.order_by__last_name %}
{{ attendee.first_name }} {{ attendee.last_name }}
{% endfor %}
{% endfor %}
Alas, the above syntax doesn't work (it produces an empty list) and neither does any other variation I have thought of (lot's of syntax errors reported, but no joy).
I could, of course, produce some kind of array of sorted attendee lists in my view, but that is an ugly and fragile (and did I mention ugly) solution.
Needless to say, but I'll say it anyway, I have perused the on-line docs and searched Stack Overflow and the archives of django-user without finding anything helpful (ah, if only a query set were a dictionary dictsort would do the job, but it's not and it doesn't)
==============================================
Edited to add additional thoughts
after accepting Tawmas's answer.
Tawmas addressed the issue exactly as I presented it -- although the solution was not what I expected. As a result I learned a useful technique that can be used in other situations as well.
Tom's answer proposed an approach I had already mentioned in my OP and tentatively rejected as being "ugly".
The "ugly" was a gut reaction, and I wanted to clarify what was wrong with it. In doing so I realized that the reason it was an ugly approach was because I was hung up on the idea of passing a query set to the template to be rendered. If I relax that requirement, there is an un-ugly approach that should work.
I haven't tried this yet, but suppose that rather than passing the queryset, the view code iterated through the query set producing a list of Events, then decorated each Event with a query set for the corresponding attendees which WAS sorted (or filtered, or whatever) in the desired way. Something like so:
eventCollection = []
events = Event.object.[filtered and sorted to taste]
for event in events:
event.attendee_list = event.attendee_set.[filtered and sorted to taste]
eventCollection.append(event)
Now the template becomes:
{% for event in events %}
{{ event.location }}
{% for attendee in event.attendee_list %}
{{ attendee.first_name }} {{ attendee.last_name }}
{% endfor %}
{% endfor %}
The downside is the view has to "actualize" all of the events at once which could be a problem if there were large numbers of events. Of course one could add pagination, but that complicates the view considerably.
The upside is the "prepare the data to be displayed" code is in the view where it belongs letting the template focus on formatting the data provided by the view for display. This is right and proper.
So my plan is to use Tawmas' technique for large tables and the above technique for small
tables, with the definition of large and small left to the reader (grin.)
You can use template filter dictsort https://docs.djangoproject.com/en/dev/ref/templates/builtins/#std:templatefilter-dictsort
This should work:
{% for event in eventsCollection %}
{{ event.location }}
{% for attendee in event.attendee_set.all|dictsort:"last_name" %}
{{ attendee.first_name }} {{ attendee.last_name }}
{% endfor %}
{% endfor %}
You need to specify the ordering in the attendee model, like this. For example (assuming your model class is named Attendee):
class Attendee(models.Model):
class Meta:
ordering = ['last_name']
See the manual for further reference.
EDIT. Another solution is to add a property to your Event model, that you can access from your template:
class Event(models.Model):
# ...
#property
def sorted_attendee_set(self):
return self.attendee_set.order_by('last_name')
You could define more of these as you need them...
One solution is to make a custom templatag:
#register.filter
def order_by(queryset, args):
args = [x.strip() for x in args.split(',')]
return queryset.order_by(*args)
use like this:
{% for image in instance.folder.files|order_by:"original_filename" %}
...
{% endfor %}
regroup should be able to do what you want, but is there a reason you can't order them the way you want back in the view?
I am looking for a way to have a Jinja macro that calls different implementations depending on the type of object that is being passed. Basically, standard Python method polymorphism. Right now, I'm using an ugly workaround similar to this:
{% macro menuitem(obj) %}
{% set type = obj.__class__.__name__ %}
{% if type == "ImageMenuItem" %}
{{ imagemenuitem(obj) }}
{% elif type == "FoobarMenuItem" %}
{{ foobarmenuitem(obj) }}
{% else %}
{{ textmenuitem(obj) }}
{% endif %}
{% endmacro %}
In pure Python, one can muck around with the module environment, e.g. globals()[x+'menuitem'], which isn't pretty but works very well. I've tried something similar using the Jinja context, but the latter doesn't seem to contain the macro definitions.
What better ways are there to achieve what I'm seeking?
The essence of OOP: polymorphism.
Create a presentation Layer for your objects:
class MenuPresentation:
def present(self):
raise NotImplementedException()
class ImageMenuPresentation(MenuPresentation):
def present(self):
return "magic url "
class TextMenuPresentation(MenuPresentation):
def present(self):
return "- text value here"
And then will be just a matter of:
{% macro menuitem(obj) %}
{{ obj.present() }}
{% endmacro %}
I have now solved my problem similarly to how fabrizioM suggested, with one notable difference: Since the menu item presentation can (and most of the time, does) contain HTML, I don't want mess around with HTML markup directly in the present methods. So I ended up implementing the menu definitions in Python, the presentation in Jinja, with mutual recursion bridging the gap.
Different types of menu items are represented by different subclasses:
class MenuItem(object):
def present(self, macromap):
return macromap[type(self).__name__](self, macromap)
class TextLink(MenuItem):
def __init__(self, url, text):
self.url, self.text = url, text
class Section(MenuItem):
def __init__(self, text, items):
self.text, self.items = text, items
class ImageLink(MenuItem):
...
The macromap referenced above is a dict mapping the type of menu item to the macro implementing its represenation. It's all defined in Jinja:
{% macro TextLink(l, macromap) %}
<a class="menuitem" href="{{l.url|escape}}">
{{ l.text|escape }}
</a>
{% endmacro %}
{% macro Section(s, macromap) %}
<div class="heading">{{s.text}}</div>
<ul class="items">
{% for item in s.items %}
<li>{{ item.present(macromap) }}</li>
{% endfor %}
</ul>
{% endmacro %}
{% set default_map = {'TextLink': TextLink, 'Section': Section, ...}
The actual menu definitions are cleanly expressed as trees of MenuItem subclasses:
main_menu = section("Main Menu", [
section("Product Line 1", [
TextLink("/products/...", "A product"),
...
]),
section(...),
])
To kick off the presentation, a template has to call the top level section's present method, passing a macro map to specify how to present the menu, e.g. main_menu.present(default_map). As can best be seen in the Section macro, menu items can then ask their children to present themselves, whose present method will call yet another Jinja macro, and so on, recursively.
Having to explicitly pass around the macro map is not very pretty, but it grants a valuable benefit: One can now easily render different representations of the menu data without touching the menu definitions at all. For example, macro maps may be defined to render the main website menu, or a variant for mobile devices (in case CSS doesn't suffice), or an XML sitemap, or even a plain text version. (We actually ended up using this system for the website menu and sitemap cases.)
How can you perform complex sorting on an object before passing it to the template? For example, here is my view:
#login_required
def overview(request):
physicians = PhysicianGroup.objects.get(pk=physician_group).physicians
for physician in physicians.all():
physician.service_patients.order_by('bed__room__unit', 'bed__room__order', 'bed__order')
return render_to_response('hospitalists/overview.html', RequestContext(request, {'physicians': physicians,}))
The physicians object is not ordered correctly in the template. Why not?
Additionally, how do you index into a list inside the template? For example, (this doesn't work):
{% for note_type in note_types %}
<div><h3>{{ note_type }}</h3>
{% for notes in note_sets.index(parent.forloop.counter0) %}
#only want to display the notes of this note_type!
{% for note in notes %}
<p>{{ note }}</p>
{% endfor %}
{% endfor %}
</div>
{% endfor %}
Thanks a bunch, Pete
As others have indicated, both of your problems are best solved outside the template -- either in the models, or in the view. One strategy would be to add helper methods to the relevant classes.
Getting a sorted list of a physician's patients:
class Physician(Model):
...
def sorted_patients(self):
return self.patients.order_by('bed__room__unit',
'bed__room__order',
'bed__order')
And in the template, use physician.sorted_patients rather than physician.patients.
For the "display the notes of this note_type", it sounds like you might want a notes method for the note_type class. From your description I'm not sure if this is a model class or not, but the principle is the same:
class NoteType:
...
def notes(self):
return <calculate note set>
And then the template:
{% for note_type in note_types %}
<div><h3>{{ note_type }}</h3></div>
{% for note in note_type.notes %}
<p>{{ note }}</p>
{% endfor %}
</div>
{% endfor %}
"I'd like to do this from within a template:"
Don't. Do it in the view function where it belongs.
Since the question is incomplete, it's impossible to guess at the data model and provide the exact solution.
results= physician.patients.order_by('bed__room__unit', 'bed__room__order', 'bed__order')
Should be sufficient. Provide results to the template for rendering. It's in the proper order.
If this isn't sorting properly (perhaps because of some model subtletly) then you always have this kind of alternative.
def by_unit_room_bed( patient ):
return patient.bed.room.unit, patient.bed.room.order, patient.bed.order
patient_list = list( physician.patients )
patient_list.sort( key=by_unit_room_bed )
Provide patient_list to the template for rendering. It's in the proper order.
"how do you index into a list inside the template"
I'm not sure what you're trying to do, but most of the time, the answer is "Don't". Do it in the view function.
The template just iterate through simple lists filling in simple HTML templates.
If it seems too complex for a template, it is. Keep the template simple -- it's only presentation. The processing goes in the view function
You should be able to construct the ordered query set in your view and pass it to your template:
def myview(request):
patients = Physician.patients.order_by('bed__room__unit',
'bed__room__order',
'bed__order')
return render_to_response('some_template.html',
dict(patients=patients),
mimetype='text/html')
Your template can then loop over patients which will contain the ordered results. Does this not work for you?
EDIT: For indexing, just use the dot syntax: mylist.3 in a template becomes mylist[3] in python. See http://docs.djangoproject.com/en/dev/ref/templates/api/#rendering-a-context for more information.
This is one way of doing it, although very ugly :
{% for note in note_sets|slice:"forloop.counter0"|first %}
When I define a Django form class similar to this:
def class MyForm(forms.Form):
check = forms.BooleanField(required=True, label="Check this")
It expands to HTML that looks like this:
<form action="." id="form" method=POST>
<p><label for="check">Check this:</label> <input type="checkbox" name="check" id="check" /></p>
<p><input type=submit value="Submit"></p>
</form>
I would like the checkbox input element to have a label that follows the checkbox, not the other way around. Is there a way to convince Django to do that?
[Edit]
Thanks for the answer from Jonas - still, while it fixes the issue I asked about (checkbox labels are rendered to the right of the checkbox) it introduces a new problem (all widget labels are rendered to the right of their widgets...)
I'd like to avoid overriding _html_output() since it's obviously not designed for it. The design I would come up with would be to implement a field html output method in the Field classes, override the one for the Boolean field and use that method in _html_output(). Sadly, the Django developers chose to go a different way, and I would like to work within the existing framework as much as possible.
CSS sounds like a decent approach, except that I don't know enough CSS to pull this off or even to decide whether I like this approach or not. Besides, I prefer markup that still resembles the final output, at least in rendering order.
Furthermore, since it can be reasonable to have more than one style sheet for any particular markup, doing this in CSS could mean having to do it multiple times for multiple styles, which pretty much makes CSS the wrong answer.
[Edit]
Seems like I'm answering my own question below. If anyone has a better idea how to do this, don't be shy.
Here's a solution I've come up with (Django v1.1):
{% load myfilters %}
[...]
{% for field in form %}
[...]
{% if field.field.widget|is_checkbox %}
{{ field }}{{ field.label_tag }}
{% else %}
{{ field.label_tag }}{{ field }}
{% endif %}
[...]
{% endfor %}
You'll need to create a custom template tag (in this example in a "myfilters.py" file) containing something like this:
from django import template
from django.forms.fields import CheckboxInput
register = template.Library()
#register.filter(name='is_checkbox')
def is_checkbox(value):
return isinstance(value, CheckboxInput)
More info on custom template tags available here.
Edit: in the spirit of asker's own answer:
Advantages:
No futzing with CSS.
The markup ends up looking the way it's supposed to.
I didn't hack Django internals. (but had to look at quite a bunch)
The template is nice, compact and idiomatic.
The filter code plays nice regardless of the exact values of the labels and input field names.
Disadvantages:
There's probably something somewhere out there that does it better and faster.
Unlikely that the client will be willing to pay for all the time spent on this just to move the label to the right...
I took the answer from romkyns and made it a little more general
def field_type(field, ftype):
try:
t = field.field.widget.__class__.__name__
return t.lower() == ftype
except:
pass
return False
This way you can check the widget type directly with a string
{% if field|field_type:'checkboxinput' %}
<label>{{ field }} {{ field.label }}</label>
{% else %}
<label> {{ field.label }} </label> {{ field }}
{% endif %}
All presented solutions involve template modifications, which are in general rather inefficient concerning performance. Here's a custom widget that does the job:
from django import forms
from django.forms.fields import BooleanField
from django.forms.util import flatatt
from django.utils.encoding import force_text
from django.utils.html import format_html
from django.utils.translation import ugettext as _
class PrettyCheckboxWidget(forms.widgets.CheckboxInput):
def render(self, name, value, attrs=None):
final_attrs = self.build_attrs(attrs, type='checkbox', name=name)
if self.check_test(value):
final_attrs['checked'] = 'checked'
if not (value is True or value is False or value is None or value == ''):
final_attrs['value'] = force_text(value)
if 'prettycheckbox-label' in final_attrs:
label = _(final_attrs.pop('prettycheckbox-label'))
else:
label = ''
return format_html('<label for="{0}"><input{1} /> {2}</label>', attrs['id'], flatatt(final_attrs), label)
class PrettyCheckboxField(BooleanField):
widget = PrettyCheckboxWidget
def __init__(self, *args, **kwargs):
if kwargs['label']:
kwargs['widget'].attrs['prettycheckbox-label'] = kwargs['label']
kwargs['label'] = ''
super(PrettyCheckboxField, self).__init__(*args, **kwargs)
# usage in form
class MyForm(forms.Form):
my_boolean = PrettyCheckboxField(label=_('Some label'), widget=PrettyCheckboxWidget())
I have PrettyCheckboxWidget and PrettyCheckboxField in an extra file, so they may be imported where needed. If you don't need translations, you can remove the ugettext parts. This code works on Django 1.5 and is untested for lower versions.
Advantages:
Highly performant, needs no template modifications
Easy to use as a custom widget
Disadvantages:
"as_table" renders the checkbox + label inside the second column
{{ field.label }} inside the template is empty. The label is instead bound to {{ field }}
More work than I planned on doing on a Saturday ;-)
I know that the user excluded CSS, but considering the top answers take about half hour of work to do such a small thing, but knowing that details like these are important on a website, I'd settle for the CSS solution.
checkbox.css
input[type="checkbox"] {
float: left;
margin-right: 10px;
margin-top: 4px;
}
forms.py
class MyForm(forms.ModelForm):
# ...
class Media:
css = {
'all': 'checkbox.css',
}
template.html
{{ form.media }}
{{ form.as_p }}
Advantages:
fast!
no futzing with template tags (just form.as_p)
no new damned widgets
the CSS file is automatically included in every form
Disadvantages:
the HTML doesn't reflect the presentation (but is good enough!)
your frontendist could complain
Here's what I ended up doing. I wrote a custom template stringfilter to switch the tags around. Now, my template code looks like this:
{% load pretty_forms %}
<form action="." method="POST">
{{ form.as_p|pretty_checkbox }}
<p><input type="submit" value="Submit"></p>
</form>
The only difference from a plain Django template is the addition of the {% load %} template tag and the pretty_checkbox filter.
Here's a functional but ugly implementation of pretty_checkbox - this code doesn't have any error handling, it assumes that the Django generated attributes are formatted in a very specific way, and it would be a bad idea to use anything like this in your code:
from django import template
from django.template.defaultfilters import stringfilter
import logging
register=template.Library()
#register.filter(name='pretty_checkbox')
#stringfilter
def pretty_checkbox(value):
# Iterate over the HTML fragment, extract <label> and <input> tags, and
# switch the order of the pairs where the input type is "checkbox".
scratch = value
output = ''
try:
while True:
ls = scratch.find('<label')
if ls > -1:
le = scratch.find('</label>')
ins = scratch.find('<input')
ine = scratch.find('/>', ins)
# Check whether we're dealing with a checkbox:
if scratch[ins:ine+2].find(' type="checkbox" ')>-1:
# Switch the tags
output += scratch[:ls]
output += scratch[ins:ine+2]
output += scratch[ls:le-1]+scratch[le:le+8]
else:
output += scratch[:ine+2]
scratch = scratch[ine+2:]
else:
output += scratch
break
except:
logging.error("pretty_checkbox caught an exception")
return output
pretty_checkbox scans its string argument, finds pairs of <label> and <input> tags, and switches them around if the <input> tag's type is "checkbox". It also strips the last character of the label, which happens to be the ':' character.
Advantages:
No futzing with CSS.
The markup ends up looking the way it's supposed to.
I didn't hack Django internals.
The template is nice, compact and idiomatic.
Disadvantages:
The filter code needs to be tested for exciting values of the labels and input field names.
There's probably something somewhere out there that does it better and faster.
More work than I planned on doing on a Saturday.
Changing checkbox position in Django admin can be quite tricky, but luckily there is a simple solution using custom widget:
from django.forms.widgets import Widget, CheckboxInput, boolean_check
class RightCheckbox(Widget):
render = CheckboxInput().render
def __init__(self, attrs=None, check_test=None):
super(RightCheckbox, self).__init__(attrs)
self.check_test = boolean_check if check_test is None else check_test
Django uses left position only when widget is subclass of CheckboxInput
Order of inputs and labels is provided via normal_row parameter of the form and there are no different row patterns for checkboxes. So there are two ways to do this (in 0.96 version exactly):
1. override _html_output of the form
2. use CSS to change position of the label and checkbox