Simple vote button in Django - django

I'm an absolute beginner to django (and programming in general), I've tried the django Polls tutorial and all went well, however I am trying to get a minimal voting button to work. Basically I have set up a database with two columns, my models.py looks like this
from django.db import models
class Imgurl(models.Model):
urlvar = models.URLField(max_length=200)# a URL linking to an image
urlvote = models.IntegerField(default=0)# my intended vote count
def __unicode__(self):
return self.urlvar
I have made an input box where I can copy and paste an image url, this image then displays on a separate page(this works fine). What I want is to have a voting button next to each displayed image, where a user can click on the button (I am trying to use a submit button) and the number of votes will increase in the db(no redirecting to a new page, or anything fancy).
I think this is a trivial question and I am trying to learn the basics of POST and database handling in django (also, I have read the relevant chapters in the djangobook... maybe I'm just a little slow?)
my views looks like this
def urlvotes(request):
if request.method=='POST':
if 'voteup' in request.POST:
v=Imgurl(forloop.counter)
v.urlvote +=1
else:
pass
votetotal=v.urlvote # attempt to give the template some kind of context
return render_to_response('display.html', {'votetotal':votetotal}, context_instance=RequestContext(request))
and my template looks like this:
{% extends "base.html" %}
{% block head %}Image display{% endblock %}
{% block content1 %}
Home
{% if links %}
<ul>
{% for link in links %}
<li><img src="{{ link }}"></li>
<li><form action="{% url urlvotes %}" method="post">
{% csrf_token %}
<input type="submit" name="voteup" value='vote'/></p>
<p>{{votetotal}}</p>
</form></li>
{% endfor %}
</ul>
{% else %}
<p>No uploads.</p>
{% endif %}
{% endblock %}
when I run this, as is, I get a csrf verification failed error
Any help would be greatly appreciated

Try adding the #csrf_protect to your view
#csrf_protect
def urlvotes(request):
if request.method=='POST':
if 'voteup' in request.POST:
v=Imgurl(forloop.counter)
v.urlvote +=1
else:
pass
votetotal=v.urlvote # attempt to give the template some kind of context
return render_to_response('display.html', {'votetotal':votetotal}, context_instance=RequestContext(request))

Related

Trouble paginating results from a 3rd party API in Django

I'm making a portfolio project where I'm using the Google Books API to do a books search, and the Django Paginator class to paginate the results. I've been able to get search results using a CBV FormView and a GET request, but I can't seem to figure out how to get pagination working for the API response.
The solution I can think of is to append &page=1 to the url of the first search, then pull that param on every GET request and use that to paginate. The problem is, I can't figure out how to append that param on the first search, and I don't know how I'd increment that param value when clicking the pagination buttons.
Here's what I've got now:
Form:
class SearchForm(forms.Form):
search = forms.CharField(label='Search:', max_length=150)
View:
class HomeView(FormView):
template_name = "home.html"
form_class = SearchForm
pageIndex = 0
def get(self, request, *args, **kwargs):
# get submitted results in view and display them on results page. This will be swapped out for an AJAX call eventually
if "search" in request.GET:
# getting search from URL params
search = request.GET["search"]
kwargs["search"] = search
context = super().get_context_data(**kwargs)
# Rest API request
response = requests.get(
f'https://www.googleapis.com/books/v1/volumes?q={search}&startIndex={self.pageIndex}&key={env("BOOKS_API_KEY")}'
)
response = response.json()
items = response.get("items")
# pagination...needs work
paginator = Paginator(items, 2)
page_obj = paginator.get_page(1)
context["results"] = page_obj
return self.render_to_response(context)
else:
return self.render_to_response(self.get_context_data())
Template:
{% extends "base.html" %}
{% block content %}
<form action="/">
{{ form }}
<input type="submit" value="Submit">
</form>
<h1>Books</h1>
<ul>
{% for result in results %}
<li>{{ result.volumeInfo.title }} : {{result.volumeInfo.authors.0}}</li>
{% empty %}
<li>Search to see results</li>
{% endfor %}
</ul>
{% if results %}
<div class="pagination">
<span class="step-links">
{% if results.has_previous %}
« first
previous
{% endif %}
<span class="current">
Page {{ results.number }} of {{ results.paginator.num_pages }}
</span>
{% if results.has_next %}
next
last »
{% endif %}
</span>
</div>
{% endif %}
{% endblock content %}
I also looked at Django REST Framework for this, but the Google Books API response doesn't contain any info on next page, previous page, etc. I've done this kind of pagination in React and it's not difficult, I'm just having trouble adjusting my mental model for how to do this to Django. If anyone could offer some advice on how to make this work, I'd be very grateful.

Why I can't save the edit of a Django ModelForm blog?

I was doing the Python Crash Course Ex. 19-1: Blogs, and I'm now stuck at saving the edit of any blog. I tried plugging in the .errors code in the blog.html (for showing each blog) but it shows nothing, so I guess my templates has no field errors (?)
Here're some codes I believe crucial for solving the not-saving-the-edit problem. The new_blog function in views.py works fine so I'll skip it.
The edit_blog function in views.py:
def edit_blog(request, blog_id):
idk = BlogPost.objects.get(id = blog_id)
if request.method != "POST":
form = BlogForm(instance = idk)
else:
form = BlogForm(instance = idk, data = request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('blogs:blogs'))
content = {"editing_blog": form, "psst": idk}
return render(request, 'blogs/edit_blog.html', content)
new_blog.html:
{% extends "blogs/all.html" %}
{% block content %}
<p>Write a new blog:</p>
<form action="{% url 'blogs:new_blog' %}" method='post'>
{% csrf_token %}
<table border="1">
{{ new_blog }}
</table>
<p></p>
<button name="submit">Submit</button>
</form>
{% endblock content %}
edit_blog.html:
{% extends "blogs/all.html" %}
{% block content %}
<p>Edit the blog:</p>
<form action="{% url 'blogs:blog' psst.id %}" method='post'>
{% csrf_token %}
<table border="1">
{{ editing_blog }}
</table>
<p></p>
<button name="submit">Save changes</button>
</form>
{% endblock content %}
Btw, the urlpattern is here:
from django.urls import path, include
from . import views
app_name = 'blogs'
urlpatterns = [
# Home page
path('', views.homepage, name = 'homepage'),
# Show all blogs.
path('blogs/', views.blogs, name = 'blogs'),
# Show the detail of a blog.
path('blogs/<int:blog_id>', views.blog, name = 'blog'),
# Page for adding a new blog.
path('new_blog/', views.new_blog, name = 'new_blog'),
# Page for editing a blog.
path('edit_blog/<int:blog_id>', views.edit_blog, name = 'edit_blog'),
]
No matter how I change the title, or content, or both of the blog I don't see the changes saved. Is it:
A) My form action in edit_blog.html goes wrong, as wakandan mentioned?
B) I need to adjust something in edit_blog view function, like Bibhas said?
Many thanks. Also tell me if I need to add more codes for understanding.
Your form action is currently set to {% url 'blogs:blog' psst.id %}, which means you're posting to your views.blog view, which is just a detail view. You need to change the action to {% url 'blogs:edit_blog' psst.id %} so that the form is posted to your edit view.
It's not clear from the code you have posted where the editing_blog context variable is coming from - you will need to make sure that this is an instance of the same form that your edit view is looking for, otherwise you'll run into other problems.
Finally also note that you are not currently handling the case where the form has errors - i.e., there is no else condition specified for form.is_valid().

Flask Admin Custom View

I am pretty new to Flask/Flask-Admin.
I have followed the tutorial on flask admin and managed to get the admin panel working but slightly lost on how to get the below things implemented.
https://github.com/flask-admin/flask-admin/tree/master/examples/auth
When logged in as a normal user I can only see "home" page.
How can I expose other views to "normal user" and restrict actions such as read only etc.
I have created a "baseview" which is not associated with any other models as below:
class SitesView(MyBaseView):
#expose('/')
def index(self):
return self.render('views/testviews.html')
admin.add_view(SitesView(name='Test views', endpoint='test views'))
and html as below:
{% extends 'admin/master.html' %}
{% block body %}
{{ super() }}
{% if current_user.has_role('view1') %}
Site1
{% endif %}
{% if current_user.has_role('view2') %}
<a>Site2</a>
{% endif %}
{% if current_user.has_role('view3') %}
<a>Site3</a>
{% endif %}
{% if current_user.has_role('view4') %}
<a>Site4</a>
{% endif %}
{% endblock %}
This gives me a new tab with different views with works as expected.
What I am trying to achieve here is when user click the Site1 link they go to Site1 page within flask-admin interface but I am not sure how to do that. I could create a new route for this but the problem is I can't(don't know how to) extend flask admin template.
For example this works but it redirect the page outside flask-admin template:
#app.route('/views/')
def views():
return render_template('views/views1.html')
and modified the templates>admin>index.html page with below:
<ul class="lead text-center list-group">
{% if current_user.has_role('view1') %}
<li class="list-group-item">View1</li>
{% endif %}
{% if current_user.has_role('view2') %}
<li class="list-group-item">View2</li>
{% endif %}
{% if current_user.has_role('view3') %}
<li class="list-group-item">View3</li>
{% endif %}
{% if current_user.has_role('view4') %}
<li class="list-group-item">View4</li>
{% endif %}
</ul
I want to build the whole web site using flask admin so that I can keep user experience consistence. Am I doing this the wrong way?
Thanks for your time.
Please do let me know if you want me to provide more information on this issue.
Kind Regards.
So after going through documentations and tutorials I have found the solution to my issue.
For my first question:
When logged in as a normal user I can only see "home" page. How can I
expose other views to "normal user" and restrict actions such as read
only etc.
We can do this by overwriting our view functions is_accessible method as below:
def is_accessible(self):
if not current_user.is_active or not current_user.is_authenticated:
return False
if current_user.has_role('superuser') or current_user.has_role('user') or current_user.has_role('view1'):
return True
return False
For my second question we just need to give the endpoint as for our BaseView as below:
class MyView(BaseView):
#expose('/')
def index(self):
return self.render('views.html')
admin.add_view(MyView(name='Custom Views', endpoint='customviews'))
And then in your jinja template you need to call it:
href="{{ url_for('customviews.index') }}
Just one thing to note, doing this:
current_user.has_role('superuser') or current_user.has_role('user') or current_user.has_role('view1')
could get quite messy if we have so many roles, not sure how we would approach this but hoping this will help someone.
Thanks all.
I know this is an old question, but for the following code
current_user.has_role('superuser') or current_user.has_role('user') or current_user.has_role('view1')
What I like to do is having a hybrid_property (available on both Peewee and SQLAlchemy) inside my User class that consolidates these properties. So it'd look something like this:
#hybrid_property
def user_has_administrative_rights(self):
return self.has_role('superuser') or self.has_role('user')

Delayed display of message from within an inclusion tag

The following problem is occurring in a large django project. I've been able to replicate the issue in a small mock-up project (code below).
I am trying to use the django messaging framework within an inclusion tag to display a message when a POST'ed form returns is_valid(). This approach has also been used in an another answer here (see 'final update' section).
The problem is that the message is not immediately displayed when the page is rendered after the POST. Instead the message appears the next time you navigate elsewhere or refresh the page after the POST response is received.
I am not receiving any errors. Everything appears to be operating normally, except for the delayed message display.
The reason for this approach is because I'm reusing multiple small forms across multiple apps and I need to use DRY principals for the GET and POST logic. This approach works perfectly - except for the issue with the delayed 'success' message display!
Really appreciate any feedback or assistance!
EDIT: To be clear the line which sets the message is in 'my_template.py':
messages.add_message(context['request'], messages.SUCCESS, "Successfully added entry")
The Demo Project:
settings.py:
...
TEMPLATE_CONTEXT_PROCESSORS = (
"django.core.context_processors.request",
"django.core.context_processors.media",
"django.contrib.messages.context_processors.messages"
)
...
base_layout.html:
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
</head>
<body>
{% for message in messages %}<div class="alert{% if message.tags %} alert-{{ message.tags }}{% endif %}" role="alert">{{ message }}</div>{% endfor %}
{% block content %}{% endblock %}
</body>
</html>
my_template.html:
<form action="" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit" />
</form>
forms.py:
from django.forms.models import ModelForm
from app.models import ContactMessage
class ContactForm(ModelForm):
class Meta:
model = ContactMessage
fields = ['name']
index.html:
{% extends "app/base_layout.html" %}
{% load my_template %}
{% block content %}
{% my_template %}
{% endblock %}
my_template.py:
from django import template
from django.contrib import messages
from app.forms import ContactForm
register = template.Library()
#register.inclusion_tag('app/my_template.html', takes_context=True)
def my_template(context):
if context['request'].method=='GET':
return { 'form':ContactForm() }
if context['request'].method=='POST':
form = ContactForm(context['request'].POST)
if not form.is_valid():
return { 'form': form }
form.save()
messages.add_message(context['request'], messages.SUCCESS, "Successfully added entry")
return { 'form':ContactForm() }
The messaging framework works through middleware, what you need is some way of informing the posting in the same request/response cycle. You have the context variable at hand, so why not add a value to it:
if form.is_valid():
context['success']=True
else:
context['success']=False
Then in your template:
{%if success %}<div>whoohoo!</div>{%endif%}
According to Django, the messages are queued for rendering until it is cleared by renderer. (reference)
In your case, you are adding messages after {{ message }} tags in base.html has been rendered. So your message is stored until your next view when {{ message }} in base.html is rendered again.
To solve this, you can move your {{ message }} tags behind {% endblock %} of content. Another possible solution is to use javascript to append {{ message }} tags either from my_template.html or from end of base.html.

How to implement breadcrumbs in a Django template?

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