Navigation in Django, using url.patterns - python

I learn Python, doing test project.
Case: I need to use class="active" for <li> based on page url. Now I use django.core.context_processors.request to take url from path.
How it looks now:
<li role="presentation" {% if request.path == '/' %}class="active"{% endif %}>Home</li>
<li role="presentation" {% if '/groups' in request.path %}class="active"{% endif %}>Groups</li>
So, if I open main page - first <li> is active, if url contains "/groups" - secon <li> is active. And it works, but I want to do it more complex, comparing url with url.patterns in urls.py:
url(r'^$', 'students.views.students_list', name='home'),
url(r'^groups/$', 'students.views.groups_list', name='groups')
Question: how to do it? I can't just use {% url 'groups' %} in if-statement because of syntax error. Need some advice.

You can assign the result of url tag to the variable.
{% url 'groups' as groups_url %}
# use groups_url variable somewhere

Related

request.path does not match expected dynamic url

I'm trying to highlight specific buttons in the navigation bar when I'm on a specific page and hyperlink to it when i'm not. The url for this page is a dynamic url however and using the {% url ... as var %} syntax is not making the specific buttons highlighted when they should be.
The issue here is that the button does not even show up at all. I narrowed it down to the request.path not matching with the dynamic url path. Normal urls (without the /str:something) seem to match perfectly.
in navbar.html i've tried to define the comparison_url (before all other code) as follows, but nothing seems to work so far:
{% url 'comparison' as comp_url %}
{% url 'comparison' sub_name as comp_url %}
{% url 'comparison' suburb_name as comp_url %}
{% url 'comparison' sub_name=suburb_name as comp_url %}
{% url 'comparison' vergelijking.suburb as comp_url %}
urls are defined as follows in urls.py:
urlpatterns = [
path("vergelijking/<str:sub_name>", views.ComparisonView.as_view(), name="comparison"),
path("signalen/<str:sub_name>", views.RadarView.as_view(), name="signals"),
path("oorzaken/<str:sub_name>", views.CausationsView.as_view(), name="causation"),
]
in navbar.html, where the actual problem lies:
{% url 'comparison' sub_name as comp_url %}
{% url 'signals' sub_name as sig_url %}
{% url 'causation' sub_name as comp_url %}
{% if request.path == comp_url %}
<li class="nav-item mx-0 mx-lg-1"><a class="nav-link py-3 px-0 px-lg-3 rounded active">Vergelijking</a></li>
{% elif request.path == sig_url or request.path == caus_url %}
<li class="nav-item mx-0 mx-lg-1"><a class="nav-link py-3 px-0 px-lg-3 rounded" href="{% url 'comparison' sub_name=suburb_name%}">Vergelijking</a></li>
{% endif %}
my base.html includes the navbar.html and the comparison.html extends base.html. There is no view for the navbar.html or base.html. My comparisonview looks like this (municipality details are loaded into the context):
class ComparisonView(LoginRequiredMixin, TemplateView):
"""map of municipality and suburbs with tooltip info"""
template_name = 'comparison.html'
def get_context_data(self, **kwargs):
context = super(ComparisonView, self).get_context_data(**kwargs)
context['suburb_name'] = self.kwargs['sub_name']
municipality_name = Suburb.objects.get(name=self.kwargs['sub_name']).municipality.name
context['municipality_name'] = municipality_name
suburb_list = Suburb.objects.filter(municipality__name=municipality_name)
suburbs_signals_dict = {}
for s in suburb_list:
suburbs_signals_dict[s.name] = s.get_signals()
context['suburb_signals'] = dumps(suburbs_signals_dict)
return context
I think the mistake is in the way i define my comp_url but i'm not exactly sure. Does anyone know what I'm doing wrong?
the view was missing a request as a parameter (whenever i was calling on request.path, it was equating the to be matched link with null as request.path did not exist). I solved this by adding the path to the context:
context['path'] = quote(self.request.path)
The quote was in my case necessary since self.request.path can have spaces in a url whereas comp_url has '%20' for spaces. After I could call on path like so:
{% if path == comp_url %}
And the problem was solved

How do i show an active link in a django navigation bar dropdown list?

I have a navbar menu with a list of links which i want to show the active link when the user is on the page, so far i have managed to do this with links that dont have dropdowns like this.
But I cannot seem to get it right with the dropdown links in such a way that if the user is on a page on the dropdown link the parent link on the navbar gets highlighted.like shown below
Any help would be greatly appreciated.
If you define your URLs with names like this:
url('', 'home_view', name='home'),
url('posts/', 'posts_view', name='blog'),
url('contact/', 'contact_view', name='contact'),
You can use these names in the template to make the if statements work:
{% with request.resolver_match.url_name as url_name %}
<ul id="menu">
<li class="{% if url_name == 'home' %}active{% endif %}">Home</li>
<li class="{% if url_name == 'blog' %}active{% endif %}">Posts</li>
<li class="{% if url_name == 'contact' %}active{% endif %}">Contact</li>
</ul>
{% endwith %}
This saves you from problems with duplication in url paths.
You can do this by passing a variable in context dictionary views.
Example:
context['faculties']=True
and then in html
{% if faculties %}active{% endif %}
For every view function you can set a variable to which you want to make active.
There are two ways: either manage your CSS to highlight the parent item if any of its children are "active" or (at the expense of some maintenance overhead and loss of DRY) test for membership in the template:
<li class="dropdown {% if url_name in "neo_app:business,neo_app:IT,neo_app:hospitality" %}
active
{% endif %}>Faculties</li>
You can do this all on the front end very easily without passing anything from the backend based on the URL.
If your url for example is "/faculties", you can do:
{% if '/faculties' in url %} active {% endif %}

How to pass urlname as a variable to url tag in django

I have a url defined in urls.py of an application
urlpatterns = [
url(r'^group/create', create_group, name='create_group'),
url(r'^account/create', create_account, name='create_account'),
]
context contains
{'buttons': {'create_account': {'btn_text': 'Create Account',
'secondary': 'Add a new accounting ledger',
'url': 'create_account'}}
How should I use url in the template.
{% for button in buttons %}
<li class="collection-item">
<div>
{% with btn_url=button.url %}
<a class="btn" href="{% url btn_url %}">{{ button.btn_text }}</a>
{% endwith %}
<span class="black-text">{{ button.secondary }}</span>
</div>
</li>
{% endfor %}
The above code in the template throws
Reverse for '' with arguments '()' and keyword arguments '{}' not found. 0 pattern(s) tried: []
How should I pass the url name as a variable into the url template tag?
Or if should I some how generate the whole url in my view itself, how should the url be generated?
I personally don't think it's possible. I suggest you using reverse in your views.py to interpret the url first, then pass the interpreted result into template:
from django.core.urlresolvers import reverse
url = reverse('create_account')
# add url to your context
According to django docs, reverse would have the same result as you use url template tag in the template.
In your context, buttons is a dictionary, so looping through {% for button in buttons %} will only loop through the keys of the dictionary, i.e. ['btn_text',]
You might want to loop through the items or values instead:
{% for key, value in buttons.items %}
<a class="btn" href="{% url value.btn_url %}">{{ value.btn_text }}</a>
{% endfor %}
or, if you don't need the keys, you can loop through the values
{% for value in button.values %}
<a class="btn" href="{% url value.btn_url %}">{{ value.btn_text }}</a>
{% endfor %}
Note that dictionaries are not ordered. If you are worried about the order of the items in the dictionary, then use a different data structure like a list or ordered dict.

Django apps that add content to the same page

I have written a Django website with a simple home page containing a list of the following menu items:
Home
Users
Contact
I am now working on an optional Django app for this website. When installed, I would like it to add an extra menu item to the home page called "Extras".
What is the best way to achieve this sort of thing in Django?
I have considered adding something like this to my settings.py file:
MENU_ITEMS = [
'Home',
'Users',
'Contact',
]
Then inside my app's __init__.py, I would do something like this:
from django.conf import settings
settings.MENU_ITEMS.append('Extras')
And the views.py file would then pass this MENU_ITEMS list to the home page template. Is this the correct approach or is there a better way?
A tip i would give you from experience: don't mess with settings in runtime if possible, those variables are global and changing them may cause some nasty bugs. I'm not sure but with that code you may end up with something like:
MENU_ITEMS = [
'Home',
'Users',
'Contact',
'Extras',
'Extras',
'Extras',
'Extras',
]
Again, I'm not sure of that but you don't need to mess with your settings to achieve your purpose.
A way more clean approach is let your view manage your view. If all you want is a menu, create a base template and put the menu there and then extend from it. If you want to add anything in the future you will need to change that piece only (You can add conditions there if you want to render some parts depending on user or something).
For more details refer to the django docs.
For example:
<div class="header navbar">
<div class="container">
<a class="navbar-brand" href="/">{% endraw %}{{ cookiecutter.project_name }}{% raw %}</a>
<ul class="nav navbar-nav">
<li class="active">Home</li>
<li>About</li>
{% if request.user.is_authenticated %}
<li>{% trans "My Profile" %}</li>
<li>{% trans "Logout" %}</li>
{% else %}
<li>{% trans "Sign Up" %}</li>
<li>{% trans "Log In" %}</li>
{% endif %}
</ul>
</div>
</div>
{% block content %}
{% endblock content %}
If this is defined in your base template, the pages that extend it will show it by default, overriding only the section where the block content is located. If you want to add another link to your navbar just do something like:
<li class="active">My new item</li>
You define 'my_other_url' as usual in your urls.py.
This is the right approach to do what i think you are trying to do. You let the templates handle how your site looks, don't mess views (as in mvc) with your controller.
If you want to make the extra item optional you just have to send a flag or something to the template and use:
{% if flag %}
<li class="active">My new item</li>
{% endif %}

Changing the active class of a link with the twitter bootstrap css in python/flask

I got the following html snippet from my page template.html.
<ul class='nav'>
<li class="active"><a href='/'>Home</a></li>
<li><a href='/lorem'>Lorem</a></li>
{% if session['logged_in'] %}
<li>Account</li>
<li>Projects
<li>Logout</li>
{% endif %}
{% if not session['logged_in'] %}
<li>Login</li>
<li>Register</li>
{% endif %}
</ul>
As you can see on line 2, there's the class active. This highlights the active tab with the twitter bootstrap css file. Now, this will work fine if I would visit www.page.com/ but not when I would visit www.page.com/login for example. It would still highlight the home link as the active tab.
Of course, I could easily do this with Javascript/jQuery but I'd rather not use that in this situation.
There's already a working solution for ruby on rails but I don't know how to convert that into python/jinja (I'm rather new to jinja/flask, never worked with ruby at all)
Have you looked at this ? https://jinja.palletsprojects.com/en/3.0.x/tricks/#highlighting-active-menu-items
Highlighting Active Menu Items
Often you want to have a navigation bar with an active navigation item. This is really simple to achieve. Because assignments outside of blocks in child templates are global and executed before the layout template is evaluated it’s possible to define the active menu item in the child template:
{% extends "layout.html" %}
{% set active_page = "index" %}
The layout template can then access active_page. Additionally it makes sense to define a default for that variable:
{% set navigation_bar = [
('/', 'index', 'Index'),
('/downloads/', 'downloads', 'Downloads'),
('/about/', 'about', 'About')
] -%}
{% set active_page = active_page|default('index') -%}
...
<ul id="navigation">
{% for href, id, caption in navigation_bar %}
<li{% if id == active_page %} class="active"{% endif
%}>{{ caption|e }}
</li>
{% endfor %}
</ul>
Here is another simpler way if you have menus distributed all over the page. This way uses inline if statements to print out the class active.
<ul>
<li class="{{ 'active' if active_page == 'menu1' else '' }}">
Link 1
</li>
<li class="{{ 'active' if active_page == 'menu2' else '' }}">
Link 2
</li>
</ul>
Class active is for highlighting
You still need to set the variable on every page to mark them
{% extends "master.html" %}
{% set active_page = "menu1" %}
or
{% set active_page = "menu2" %}
For jinja/flask/bootstrap users:
If you define your nav like they did in the blog example http://getbootstrap.com/examples/blog/
simply assign ids to your links that match your url_for arguments and you just need to modify the layout-template, the rest just works #magic.
<nav class="blog-nav">
<a id="allposts" class="blog-nav-item" href="{{ url_for('allposts')}}">All Posts</a>
<a id="index" class="blog-nav-item" href="{{ url_for('index')}}">Index</a>
<a id="favorites" class="blog-nav-item" href="{{ url_for('favorites')}}">Favorites</a>
</nav>
At the bottom of your base/layout template just add this
<script>
$(document).ready(function () {
$("#{{request.endpoint}}").addClass("active"); })
</script>
and the right elements will be set active.
EDIT:
If you have a layout with elements in a list, like this:
<nav class="blog-nav">
<ul class="nav navbar-nav">
<li>
<a id="allposts" class="blog-nav-item" href="{{ url_for('allposts')}}">All Posts</a>
</li>
<li>
<a id="index" class="blog-nav-item" href="{{ url_for('index')}}">Index</a>
</li>
<li>
<a id="favorites" class="blog-nav-item" href="{{ url_for('favorites')}}">Favorites</a>
</li>
</ul>
</nav>
use the parent() function to get the li element instead of the link.
<script>
$(document).ready(function () {
$("#{{request.endpoint}}").parent().addClass("active"); })
</script>
we can make class active by using jinja if statements
<ul class="nav navbar-nav">
<li class="{% if request.endpoint=='home' %}active{%endif %}">home</li>
<li class="{% if request.endpoint=='add_client' %}active{%endif %}">Add Report</li>
</li>
</ul>
I liked #philmaweb's approach, but there's really no reason to require duplicating the endpoint in the id of each element.
base.js:
$(document).ready(function () {
var scriptElement = $('#baseScript')[0];
var path = scriptElement.getAttribute('data-path');
$('a[href="'+path+'"]').addClass("active");
});
base.html
<script id="baseScript" src="{{ url_for('static', filename='js/base.js') }}"
data-path="{{ request.path }}"></script>
Why not just put this script inline? You could, of course, but allowing inline JS is a security nightmare. You should be using a CSP on your site (e.g. Flask-Talisman) which will not allow inline JS. With data-* attributes, it's not hard to do this in a secure way.
NB: If you have multiple links leading to the same, current page and you want only ONE of them to be marked "active"—then this approach may not work for you.
I tried different solution for this for the solution 1st by Codegeek didn't work as I have multiple Ul and li under it so I just include my navbar in template.html
{% include 'sidebar.html' %}
then in Navbar file in the li class you can set active with help of "request.endpoint" but then again it will return you entire route instead use split and take last route name and set active if same for exmaple
<li class="{% if request.endpoint.split('.')[1] == 'index' %} active {% else %} {% endif %}">
request.endpoint.split('.')[1] will return the route eg localhost/example. You will get example which you can compare and use. If you won't split and use request.endpoint than you will get 'file.example' (entire route).
Add the following CSS somewhere on your page:
a[href $= {{ page_name|default("'/'"|safe) }}]{ [INSERT YOUR ACTIVE STYLING HERE] }
Now, on each template define page_name, for example:
{% extends "template.html" %}
{% set page_name = "gallery" %}
This seems much simpler and easier to build on, than other options.
EDIT:
Almost 1 year later I'm returning to make this a much simpler fix, because setting the page name on every page is pretty inefficient.
Instead create a function like so:
#app.context_processor
def context_processor():
out = {}
out['request'] = request # Make sure you import request from flask
return out
This will allow you to pass variables implicitly to jinja, in this case we are passing the request for access to request.url_rule which contains the route the user is accessing. In the previous version, we just change {{ page_name|default("'/'"|safe) }} to "{{ request.url_rule|safe }}". Much cleaner.
I did not want to have to define the ID in the child pages, as many of the links I have do not have a specific child template.
Using the request.base_url and if it matches the _external url_for the route, then render that nav item as active.
{% set nav_items = [
("public.home", "Home"),
("public.downloads", "Downloads"),
("public.about", "About")
("account.login", "Login"),
]
-%}
...
<ul class="navbar-nav mr-auto">
{% for route, display_text in nav_items %}
<li class={% if request.base_url == url_for(route, _external=True) %}"nav-item active"{% else %}"nav-item"{% endif %}>
<a class="nav-link" href="{{ url_for(route) }}">{{ display_text }}
{% if request.base_url == url_for(route, _external=True) %}<span class="sr-only">(current)</span>{% endif %}
</a>
</li>
{% endfor %}
</ul>

Categories

Resources