Django Get Root Path From Current URL - python

I am developing a Django website using the Wagtail CMS. I have a navbar at the top of the page where using template tags, it loops through pages in the navigation variable.
{% for item in navigation.menu_items.all %}
<a class="nav-link {% if request.get_full_path == item.link %}active{% endif %}" href="{{ item.link }}" {% if item.open_in_new_tab %} target="_blank"{% endif %}>{{ item.title }}</a>
{% endfor %}
Say that the URL is http://localhost:8000/blog/ and the page URL is the same, then the active class is applied to that iteration.
The problem arises when I am on a page with the URL such as http://localhost:8000/blog/example-blog-post/, this does not match with http://localhost:8000/blog/ and the active class is not applied, even though I am in the blog.
Is there a way to strip the URL and only keeping the root path, so http://localhost:8000/blog/example-blog-post/ becomes http://localhost:8000/blog/ so that the active class can be applied to subpages in the directory?

You can use slice filter
{% if request.path|slice:":5" == item.link %} active{% endif %}
OR
You can use in operator.
So instead {% if request.get_full_path == item.link %} do {% if item.link in request.get_full_path %} or to catch homepage {% if request.get_full_path in item.link and request.get_full_path != '/' or request.get_full_path == item.link %}

This probably isn't the most efficient way, but at the moment for me, it's the only way.
I created a custom template tag which takes in the context and the menu item object, then returns the active class name if the current URL matches the URL of the nav-item (item)
In the HTML, as each nav-item is iterated, the item is passed to the get_active method (a custom template tag that I made)
{% load menu_tags %}
{% get_menu "MAIN" as navigation %}
{% for item in navigation.menu_items.all %}
{% get_active item as active_class %}
<a class="nav-link {{ active_class }}" href="{{ item.link }}" {% if item.open_in_new_tab %} target="_blank"{% endif %}>{{ item.title }}</a>
{% endfor %}
Template tag:
#register.simple_tag(takes_context=True)
def get_active(context, item):
request = context['request']
currentURL = request.path
linkURL = item.link
currentURLStripped = str(currentURL).replace("/", "")
linkURLStripped = str(linkURL).replace("/", "")
if linkURLStripped=="" and currentURLStripped=="":
return "active"
elif linkURLStripped in currentURLStripped and linkURLStripped!="":
return "active"
else:
return ""
The code above simply takes the URL of the page the user is currently on, for example, if the user is on http://localhost:8000/blog then currentURL will be /blog/. The linkURL is the URL property of the item object, for the item object which links to the contact me page, its URL property will be /contact-me/ and thus the linkURL will be the same.
The method simply strips the "/" from the URL strings. If the URL is for the homepage (i.e. it's /) then the variable will be empty. if linkURLStripped=="" and currentURLStripped=="": catches the homepage.
elif linkURLStripped in currentURLStripped and linkURLStripped!="": catches the other pages and ignores the homepage.

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

dynamic pagination with django

Okay so this is first time using pagination with Django and I am trying to prevent it from reloading my view on each page turn.
I'm handling the pagination in the view like this:
page = request.GET.get('page', 1)
print page
paginator = Paginator(list(od.iteritems())[:24], 12)
try:
data = paginator.page(page)
except PageNotAnInteger:
data = paginator.page(1)
except EmptyPage:
data = paginator.page(paginator.num_pages)
print data
save_query_form = SaveQueryForm(request.POST or None)
#if request.method == 'POST':
if save_query_form.is_valid():
profile = save_query_form.save(commit=False)
profile.user = request.user
profile.save()
context = {
"title":"Search",
'data': data,#list(od.iteritems()),
'tools': od_tools.iteritems(),
'methods': od_methods.iteritems(),
'data4': od_data.iteritems(),
'search_phrase': " ".join(instanceValuesString),
'json_dump': js_data,
'form': save_query_form,
}
return render(request, 'results.html', context)
and the pagination is handled in the html:
{% if data.has_other_pages %}
<div id='page-slide'>
<ul class="pagination" start='$offset'>
{% if data.has_previous %}
<li>«</li>
{% else %}
<li class="disabled"><span>«</span></li>
{% endif %}
{% for i in data.paginator.page_range %}
{% if data.number == i %}
<li class="active"><span>{{ i }} <span class="sr-only">(current)</span></span></li>
{% else %}
<li>{{ i }}</li>
{% endif %}
{% endfor %}
{% if data.has_next %}
<li>»</li>
{% else %}
<li class="disabled"><span>»</span></li>
{% endif %}
</ul>
</div>
{% endif %}
The issue that I am having is that whenever I switch to another page my entire view will run again and the data will does not reflect the original search query and instead defaults to an empty query.
I was wondering if there is a simple way to either handle pagination dynamically or prevent the page reload when toggling between pages?
Any help is appreciated, thanks.
Update Search Form:
<form action="{% url 'results-view' %}" method="POST" class="autocomplete-me ui-widget" id="myform" >
{% csrf_token %}
<div class="ui-widget" style="text-align:center;">
<input type="text" id="id_q" name="q" placeholder="{{ search_phrase }}">
<br></br>
<div style="text-align:center;" id='adjust-button'>
<input type='submit' class='btn btn-secondary btn-lg' id ='search-btn' value='Search'/>
<a class='btn btn-secondary btn-lg' id ='clear-btn' href="{% url 'inital' %}">Clear</a>
</div>
</div>
</form>
You noted in a comment that you get your search value with instanceValuesString = request.POST.get(u"q").encode('utf-8').strip(). As one commenter correctly pointed out, this means that when you click your "next page" links (making a GET request), your view doesn't receive the information it needs to return search results.
One way to fix this would be to get your instanceValuesString from a GET request instead of a POST request. For instance, perhaps your list view is at
http://example.com/StuffList
You could look for URLs that provide a search querystring:
http://example.com/StuffList?search=goodstuff
And then grab that in your view:
instanceValuesString = request.GET.get('search', None)
if instanceValuesString is not None:
#you have detected a search query; filter results, process request, etc.
One side effect here is that the way you currently construct your next/previous page URLs will break. Consider the example search URL; your current template would construct a link for page 2 like so:
http://example.com/StuffList?search=goodstuff?page=2
This won't work; it should be &page=2. Fortunately there's an easy fix; check out the second answer to this question: Altering one query parameter in a url (Django). Using that url_replace instead of constructing those links with the basic url template tag will solve this part of the issue.
This is very much simplified with below package
http://django-simple-pagination.readthedocs.io/en/latest/

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.

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>

Highlighting link of currently opened category tab in css

I am trying to highlight a link of a currently opened category tab, here is what i have already done:
globs.py
def globs(request):
cats = Category.objects.all()
return {'cats': cats}
views.py
def news_by_category(request, slug):
c = Category.objects.get(slug=slug)
news = News.objects.filter(category=c, status='p').order_by('-id')
#news = c.news_set.all().order_by('-id')
return object_list(
request,
news,
paginate_by = 5,
extra_context = {'c':c},
template_name = 'news_by_category.html')
base.html #bodyclass
<body class="{% block bodyclass %}{% endblock %}">
news_by_category.html
{% block bodyclass %}{{c|cut:" "}}{% endblock %}
base.html
<li><h4>Categories:</h4></li>
{% for i in cats %}
<li class="{{i.name|safe|cut:" "}}_li">
{{ i.name }}
</li> {% endfor %}
What i need to do now is to create style for every category, in category list, I could achieve this easily by styling inside a html file, but I'm not sure wether that would be proper (Would it?). I came up with some css styling,
{% for i in cats %}
body.{{ i|safe|cut:" "}} li.{{i|safe|cut:" "}}_li {
color: red;
}
but as I can't use django template tags inside my .css file, this wont work.
My questions:
1) How could i make this css file work for me. Any chance for a little step by step?
2) If I failed step1, how improper would it be to style those few li elements inside html file?
EDIT: /trying another way
I tried using:
base.html
{% for i in cats %}
<li class="{% ifequal 'request.get_full_path' '/k/{{ i.slug }}/' %}active{% endifequal %}">
{{ i.name }}
</li> {% endfor %}
.css
.active {{color:red;}
When i compared {{ request.get_full_path }} and /k/{{i.slug}}/ both returned same thing... but if its inside ifequal it doesnt seem to work.
You can create a simple class named "active" or something along those lines and add it to the current tab. Then, in your CSS you apply the active styles to that class. So you just append the active class and it'll automatically take the active style.
If you have a url:
{% url app:home i.slug as home %}
<li {% ifequal request.get_full_path home %}class="active"{% endifequal %}>

Categories

Resources